Commit 0a8e572995a9eb22174ddf61abaf3ed741e44a3e

Authored by Jürgen Knödlseder
1 parent 7c3dedd7

Replace cta_root2caldb.py script by csroot2caldb cscript.

Convert the cta_root2caldb.py script into a cscript and add unit test. The script only works if PyROOT is installed.

Instead of the cta_root2caldb.py script a generate_prod3_irf.py script was added to the example folder.
.gitignore
@@ -72,6 +72,7 @@ src/ctulimit/ctulimit @@ -72,6 +72,7 @@ src/ctulimit/ctulimit
72 src/cterror/cterror 72 src/cterror/cterror
73 test/pfiles/ 73 test/pfiles/
74 test/caldb/ 74 test/caldb/
  75 +test/csroot2caldb_*
75 test/iactdata_* 76 test/iactdata_*
76 test/*.fits 77 test/*.fits
77 test/*.dat 78 test/*.dat
cscripts/Makefile.am
@@ -5,7 +5,7 @@ pythonscripts = __init__.py \ @@ -5,7 +5,7 @@ pythonscripts = __init__.py \
5 cspull.py cssens.py cstsdist.py cscaldb.py csresmap.py \ 5 cspull.py cssens.py cstsdist.py cscaldb.py csresmap.py \
6 csobsdef.py csspec.py cslightcrv.py csobsinfo.py \ 6 csobsdef.py csspec.py cslightcrv.py csobsinfo.py \
7 csinfo.py csmodelinfo.py csmodelmerge.py cstsmapmerge.py \ 7 csinfo.py csmodelinfo.py csmodelmerge.py cstsmapmerge.py \
8 - csobs2caldb.py \ 8 + csroot2caldb.py csobs2caldb.py \
9 csfindobs.py csiactcopy.py csiactdata.py csiactobs.py \ 9 csfindobs.py csiactcopy.py csiactdata.py csiactobs.py \
10 obsutils.py 10 obsutils.py
11 11
@@ -13,14 +13,14 @@ pythonscripts = __init__.py \ @@ -13,14 +13,14 @@ pythonscripts = __init__.py \
13 parfiles = cspull.par cssens.par cstsdist.par cscaldb.par csresmap.par \ 13 parfiles = cspull.par cssens.par cstsdist.par cscaldb.par csresmap.par \
14 csobsdef.par csspec.par cslightcrv.par csobsinfo.par \ 14 csobsdef.par csspec.par cslightcrv.par csobsinfo.par \
15 csmodelinfo.par csmodelmerge.par cstsmapmerge.par \ 15 csmodelinfo.par csmodelmerge.par cstsmapmerge.par \
16 - csobs2caldb.par \ 16 + csroot2caldb.par csobs2caldb.par \
17 csfindobs.par csiactcopy.par csiactdata.par csiactobs.par 17 csfindobs.par csiactcopy.par csiactdata.par csiactobs.par
18 18
19 # Scripts for which symbolic links will be created in bindir 19 # Scripts for which symbolic links will be created in bindir
20 linkscripts = cspull cssens cstsdist cscaldb csresmap \ 20 linkscripts = cspull cssens cstsdist cscaldb csresmap \
21 csobsdef csspec cslightcrv csobsinfo \ 21 csobsdef csspec cslightcrv csobsinfo \
22 csinfo csmodelinfo csmodelmerge cstsmapmerge \ 22 csinfo csmodelinfo csmodelmerge cstsmapmerge \
23 - csobs2caldb \ 23 + csroot2caldb csobs2caldb \
24 csfindobs csiactcopy csiactdata csiactobs 24 csfindobs csiactcopy csiactdata csiactobs
25 25
26 # Test scripts to be distributed in $(pyexecdir)/cscripts/tests 26 # Test scripts to be distributed in $(pyexecdir)/cscripts/tests
@@ -34,6 +34,7 @@ testscripts = tests/__init__.py \ @@ -34,6 +34,7 @@ testscripts = tests/__init__.py \
34 ../test/test_csobsdef.py \ 34 ../test/test_csobsdef.py \
35 ../test/test_csobsinfo.py \ 35 ../test/test_csobsinfo.py \
36 ../test/test_cspull.py \ 36 ../test/test_cspull.py \
  37 + ../test/test_csroot2caldb.py \
37 ../test/test_csfindobs.py \ 38 ../test/test_csfindobs.py \
38 ../test/test_csiactcopy.py \ 39 ../test/test_csiactcopy.py \
39 ../test/test_csiactdata.py \ 40 ../test/test_csiactdata.py \
@@ -53,7 +54,8 @@ testdata = ../test/data/crab.xml \ @@ -53,7 +54,8 @@ testdata = ../test/data/crab.xml \
53 ../test/data/models.txt \ 54 ../test/data/models.txt \
54 ../test/data/model_cube_background.xml \ 55 ../test/data/model_cube_background.xml \
55 ../test/data/model_cube_background2.xml \ 56 ../test/data/model_cube_background2.xml \
56 - ../test/data/crab_bkgcube.xml 57 + ../test/data/crab_bkgcube.xml \
  58 + ../test/data/irf.root
57 59
58 # Test data to be distributed in $(pyexecdir)/cscripts/tests/iactdata 60 # Test data to be distributed in $(pyexecdir)/cscripts/tests/iactdata
59 testiactdata = ../test/iactdata/hdu-index.fits.gz \ 61 testiactdata = ../test/iactdata/hdu-index.fits.gz \
@@ -61,7 +63,7 @@ testiactdata = ../test/iactdata/hdu-index.fits.gz \ @@ -61,7 +63,7 @@ testiactdata = ../test/iactdata/hdu-index.fits.gz \
61 ../test/iactdata/obs-index.fits.gz 63 ../test/iactdata/obs-index.fits.gz
62 64
63 # Test IRFs to be distributed in $(pyexecdir)/cscripts/tests/irf 65 # Test IRFs to be distributed in $(pyexecdir)/cscripts/tests/irf
64 -testirf = ../test/irf/irf_file.fits.gz 66 +testirf = ../test/irf/irf_file.fits.gz
65 67
66 # Distribute scripts and parfiles 68 # Distribute scripts and parfiles
67 EXTRA_DIST = $(binscripts) $(pythonscripts) 69 EXTRA_DIST = $(binscripts) $(pythonscripts)
cscripts/__init__.py.in
@@ -34,6 +34,7 @@ __all__ = [ @@ -34,6 +34,7 @@ __all__ = [
34 "csobsinfo", 34 "csobsinfo",
35 "cspull", 35 "cspull",
36 "csresmap", 36 "csresmap",
  37 + "csroot2caldb",
37 "cssens", 38 "cssens",
38 "csspec", 39 "csspec",
39 "cstsdist", 40 "cstsdist",
@@ -58,6 +59,7 @@ from cscripts.csobsdef import csobsdef @@ -58,6 +59,7 @@ from cscripts.csobsdef import csobsdef
58 from cscripts.csobsinfo import csobsinfo 59 from cscripts.csobsinfo import csobsinfo
59 from cscripts.cspull import cspull 60 from cscripts.cspull import cspull
60 from cscripts.csresmap import csresmap 61 from cscripts.csresmap import csresmap
  62 +from cscripts.csroot2caldb import csroot2caldb
61 from cscripts.cssens import cssens 63 from cscripts.cssens import cssens
62 from cscripts.csspec import csspec 64 from cscripts.csspec import csspec
63 from cscripts.cstsdist import cstsdist 65 from cscripts.cstsdist import cstsdist
cscripts/csroot2caldb.par 0 → 100644
  1 +#############################################################################
  2 +# #
  3 +# CTA Science Analysis Tools #
  4 +# #
  5 +# ------------------------------------------------------------------------- #
  6 +# #
  7 +# File: csroot2caldb.par #
  8 +# Version: 1.0.0 #
  9 +# #
  10 +# Author: Juergen Knoedlseder #
  11 +# Jurgen.Knodlseder@irap.omp.eu #
  12 +# IRAP #
  13 +# #
  14 +# Purpose: Parameter file for the csroot2caldb script #
  15 +# #
  16 +#############################################################################
  17 +#
  18 +# Input parameters
  19 +#=================
  20 +infile, f, a, irf.root,,, "Input ROOT file"
  21 +outdir, s, h, ,,, "Output CALDB directory"
  22 +inst, s, a, prod3,,, "Instrument name"
  23 +id, s, a, South_50h,,, "Instrument Response Function identifier"
  24 +version, s, a, prod3,,, "Instrument Response Function version"
  25 +analysis, s, a, DESY,,, "Instrument Response Function analysis"
  26 +psftype, s, h, Gauss,Gauss|King,, "Point spread function type"
  27 +rebin, b, h, no,,, "Rebin"
  28 +eascale, r, h, 1.0,,, "Scaling factor for effective areas"
  29 +bgdscale, r, h, 1.0,,, "Scaling factor for background rates"
  30 +
  31 +#
  32 +# Standard parameters
  33 +#====================
  34 +chatter, i, h, 2,0,4, "Chattiness of output"
  35 +clobber, b, h, yes,,, "Overwrite existing output files with new output files?"
  36 +debug, b, h, no,,, "Debugging mode activated"
  37 +mode, s, h, ql,,, "Mode of automatic parameters"
  38 +logfile, f, h, csroot2caldb.log,,, "Log filename"
cscripts/csroot2caldb.py 0 → 100755
  1 +#! /usr/bin/env python
  2 +# ==========================================================================
  3 +# Generate IRFs in CALDB format from a ROOT offaxis performance file
  4 +#
  5 +# Copyright (C) 2016 Juergen Knoedlseder
  6 +#
  7 +# This program is free software: you can redistribute it and/or modify
  8 +# it under the terms of the GNU General Public License as published by
  9 +# the Free Software Foundation, either version 3 of the License, or
  10 +# (at your option) any later version.
  11 +#
  12 +# This program is distributed in the hope that it will be useful,
  13 +# but WITHOUT ANY WARRANTY; without even the implied warranty of
  14 +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15 +# GNU General Public License for more details.
  16 +#
  17 +# You should have received a copy of the GNU General Public License
  18 +# along with this program. If not, see <http://www.gnu.org/licenses/>.
  19 +#
  20 +# ==========================================================================
  21 +import os
  22 +import sys
  23 +import math
  24 +import gammalib
  25 +import ctools
  26 +from datetime import datetime
  27 +
  28 +# Optional ROOT import
  29 +try:
  30 + from ROOT import TFile, TH2F, TH3F
  31 + _has_root = True
  32 +except:
  33 + print('ROOT module not present, so csroot2caldb script will not work.')
  34 + _has_root = False
  35 +
  36 +
  37 +# ================== #
  38 +# csroot2caldb class #
  39 +# ================== #
  40 +class csroot2caldb(ctools.cscript):
  41 + """
  42 + Generates IRFs in CALDB from a ROOT offaxis performance file.
  43 + """
  44 +
  45 + # Constructor
  46 + def __init__(self, *argv):
  47 + """
  48 + CALDB constructor.
  49 +
  50 + """
  51 + # Set name
  52 + self._name = "csroot2caldb"
  53 + self._version = "1.0.0"
  54 +
  55 + # Initialise some members
  56 + self._cif = None
  57 + self._irf = None
  58 + self._ea = None
  59 + self._psf = None
  60 + self._edisp = None
  61 + self._bgd = None
  62 + self._hdu_cif = None
  63 +
  64 + # Initialise application by calling the appropriate class constructor
  65 + self._init_cscript(argv)
  66 +
  67 + # Return
  68 + return
  69 +
  70 +
  71 + # Private methods
  72 + def _get_parameters(self):
  73 + """
  74 + Get parameters from parfile.
  75 + """
  76 + # Query parameters
  77 + self["infile"].filename()
  78 + self["outdir"].string()
  79 + self["inst"].string()
  80 + self["id"].string()
  81 + self["analysis"].string()
  82 + self["version"].string()
  83 + self["psftype"].string()
  84 + self["rebin"].boolean()
  85 + self["eascale"].real()
  86 + self["bgdscale"].real()
  87 +
  88 + # Write input parameters into logger
  89 + if self._logTerse():
  90 + self._log_parameters()
  91 + self._log("\n")
  92 +
  93 + # Return
  94 + return
  95 +
  96 + def _initialise(self):
  97 +
  98 + # Set CALDB information
  99 + self._cal_tel = "CTA"
  100 + self._cal_inst = self["inst"].string().upper()
  101 + self._cal_obsid = self["id"].string()
  102 + self._cal_det = "NONE"
  103 + self._cal_flt = "NONE"
  104 + self._cal_class = "BCF"
  105 + self._cal_type = "DATA"
  106 + self._cal_qual = 0 # 0=good, 1=bad, 2=dubious, ...
  107 + self._cal_date = "14/01/30"
  108 + self._val_date = "2014-01-30"
  109 + self._val_time = "00:00:00"
  110 + self._ref_time = 51544.0
  111 + self._cal_name = "NAME("+self["id"].string()+")"
  112 + self._cal_version = "VERSION("+self["version"].string()+")"
  113 + self._cal_cut = "CLASS(BEST)"
  114 + self._cal_analysis = "ANALYSIS("+self["analysis"].string()+")"
  115 + #
  116 + self._ea_name = "EFF_AREA"
  117 + self._ea_doc = "CAL/GEN/92-019"
  118 + self._ea_bounds = [self._cal_name, self._cal_version,
  119 + self._cal_cut, self._cal_analysis]
  120 + self._ea_desc = "CTA effective area"
  121 + #
  122 + self._psf_name = "RPSF"
  123 + self._psf_doc = "CAL/GEN/92-020"
  124 + self._psf_bounds = [self._cal_name, self._cal_version,
  125 + self._cal_cut, self._cal_analysis]
  126 + self._psf_desc = "CTA point spread function"
  127 + #
  128 + self._edisp_name = "EDISP"
  129 + self._edisp_doc = "???"
  130 + self._edisp_bounds = [self._cal_name, self._cal_version,
  131 + self._cal_cut, self._cal_analysis]
  132 + self._edisp_desc = "CTA energy dispersion"
  133 + #
  134 + self._bgd_name = "BGD"
  135 + self._bgd_doc = "???"
  136 + self._bgd_bounds = [self._cal_name, self._cal_version,
  137 + self._cal_cut, self._cal_analysis]
  138 + self._bgd_desc = "CTA background"
  139 +
  140 + # Create directory structure
  141 + self._make_dirs()
  142 +
  143 + # Return
  144 + return
  145 +
  146 + def _make_dirs(self):
  147 + """
  148 + Generate CALDB directory structure for one observation identifier.
  149 + The structure is given by
  150 +
  151 + data/<tel>/<inst>/bcf/<obsid>
  152 +
  153 + where <tel> is "cta" and <inst> is the instrument specified in the
  154 + CALDB constructor (the instrument may be used for different array
  155 + configurations).
  156 + """
  157 + # Set base directory
  158 + base_dir = "data"
  159 + base_dir += "/"+self._cal_tel.lower()
  160 + base_dir += "/"+self._cal_inst.lower()
  161 +
  162 + # Set directory names
  163 + obs_dir = base_dir+"/bcf/"+self._cal_obsid
  164 + self._ea_dir = obs_dir
  165 + self._psf_dir = obs_dir
  166 + self._edisp_dir = obs_dir
  167 + self._bgd_dir = obs_dir
  168 +
  169 + # Optionally prefix path
  170 + outdir = self["outdir"].string()
  171 + if len(outdir) > 0:
  172 + self._base_path = outdir+"/"+base_dir
  173 + self._ea_path = outdir+"/"+self._ea_dir
  174 + self._psf_path = outdir+"/"+self._psf_dir
  175 + self._edisp_path = outdir+"/"+self._edisp_dir
  176 + self._bgd_path = outdir+"/"+self._bgd_dir
  177 + else:
  178 + self._base_path = base_dir
  179 + self._ea_path = self._ea_dir
  180 + self._psf_path = self._psf_dir
  181 + self._edisp_path = self._edisp_dir
  182 + self._bgd_path = self._bgd_dir
  183 +
  184 + # Create directory structure
  185 + if not os.path.isdir(self._ea_path):
  186 + os.makedirs(self._ea_path)
  187 + if not os.path.isdir(self._psf_path):
  188 + os.makedirs(self._psf_path)
  189 + if not os.path.isdir(self._edisp_path):
  190 + os.makedirs(self._edisp_path)
  191 + if not os.path.isdir(self._bgd_path):
  192 + os.makedirs(self._bgd_path)
  193 +
  194 + # Return
  195 + return
  196 +
  197 + def _open(self, version, split=False, clobber=True):
  198 + """
  199 + Open existing or create new calibration. The actual version will put
  200 + all calibrations in the same file, although each part of the response
  201 + function will have its own logical name. We can thus easily modify
  202 + the script to put each calibration information in a separate file.
  203 +
  204 + Parameters:
  205 + version - Filename version
  206 + Keywords:
  207 + split - Split IRF over several files?
  208 + clobber - Overwrite existing files?
  209 + """
  210 + # Set calibrate file names
  211 + if split:
  212 + self._ea_file = "ea_"+version+".fits"
  213 + self._psf_file = "psf_"+version+".fits"
  214 + self._edisp_file = "edisp_"+version+".fits"
  215 + self._bgd_file = "bgd_"+version+".fits"
  216 + else:
  217 + self._ea_file = "irf_"+version+".fits"
  218 + self._psf_file = "irf_"+version+".fits"
  219 + self._edisp_file = "irf_"+version+".fits"
  220 + self._bgd_file = "irf_"+version+".fits"
  221 +
  222 + # Open calibration database index
  223 + self._cif = gammalib.GFits(self._base_path+"/caldb.indx", True)
  224 +
  225 + # If file has no CIF extension than create it now
  226 + try:
  227 + self._hdu_cif = self._cif.table("CIF")
  228 + except:
  229 + self._create_cif()
  230 + self._hdu_cif = self._cif.table("CIF")
  231 +
  232 + # Set filenames
  233 + ea_filename = self._ea_path+"/"+self._ea_file
  234 + psf_filename = self._psf_path+"/"+self._psf_file
  235 + edisp_filename = self._edisp_path+"/"+self._edisp_file
  236 + bgd_filename = self._bgd_path+"/"+self._bgd_file
  237 +
  238 + # Open files
  239 + if split:
  240 + self._ea = gammalib.GFits(ea_filename, True)
  241 + self._psf = gammalib.GFits(psf_filename, True)
  242 + self._edisp = gammalib.GFits(edisp_filename, True)
  243 + self._bgd = gammalib.GFits(bgd_filename, True)
  244 + else:
  245 + self._irf = gammalib.GFits(ea_filename, True)
  246 + self._ea = self._irf
  247 + self._psf = self._irf
  248 + self._edisp = self._irf
  249 + self._bgd = self._irf
  250 +
  251 + # Open HDUs
  252 + self._hdu_ea = self._open_hdu(self._ea, "EFFECTIVE AREA",
  253 + self._ea_name, self._ea_doc)
  254 + self._hdu_psf = self._open_hdu(self._psf, "POINT SPREAD FUNCTION",
  255 + self._psf_name, self._psf_doc)
  256 + self._hdu_edisp = self._open_hdu(self._edisp, "ENERGY DISPERSION",
  257 + self._edisp_name, self._edisp_doc)
  258 + self._hdu_bgd = self._open_hdu(self._bgd, "BACKGROUND",
  259 + self._bgd_name, self._bgd_doc)
  260 +
  261 + # Return
  262 + return
  263 +
  264 + def _close(self):
  265 + """
  266 + Close calibration FITS files.
  267 + """
  268 + # Add information to CIF. We do this now as before we did not
  269 + # necessarily have all the information at hand (in particular
  270 + # about the boundaries)
  271 + self._add_cif_info()
  272 +
  273 + # Close CIF
  274 + self._close_file(self._cif)
  275 +
  276 + # If IRF file exists then close it now. All file pointers will
  277 + # be set to None
  278 + if self._irf != None:
  279 + self._close_file(self._irf)
  280 + self._ea = None
  281 + self._psf = None
  282 + self._edisp = None
  283 + self._bgd = None
  284 +
  285 + # ... otherwise we have split files, so we have to close them all
  286 + else:
  287 + self._close_file(self._ea)
  288 + self._close_file(self._psf)
  289 + self._close_file(self._edisp)
  290 + self._close_file(self._bgd)
  291 +
  292 + # Return
  293 + return
  294 +
  295 + def _close_file(self, file):
  296 + """
  297 + Close one file.
  298 + """
  299 + # If file is open then close it
  300 + if file != None:
  301 + file.save(True)
  302 + file.close()
  303 + file = None
  304 +
  305 + # Return
  306 + return
  307 +
  308 + def _open_hdu(self, fits, extname, name, doc):
  309 + """
  310 + Open HDU
  311 +
  312 + If HDU does not exist then create one.
  313 +
  314 + Parameters:
  315 + fits: FITS file
  316 + extname: Extension name
  317 + name: Name string
  318 + doc: Document string
  319 +
  320 + Returns:
  321 + FITS table
  322 + """
  323 + # Create table if it does not yet exist
  324 + if not fits.contains(extname):
  325 +
  326 + # Create binary table
  327 + table = gammalib.GFitsBinTable()
  328 +
  329 + # Set extension name
  330 + table.extname(extname)
  331 +
  332 + # Set OGIP keywords
  333 + self._set_ogip_keywords(table, doc, ["RESPONSE", name])
  334 +
  335 + # Append table to FITS file
  336 + fits.append(table)
  337 +
  338 + # Return FITS table
  339 + return fits.table(extname)
  340 +
  341 + def _set_ogip_keywords(self, hdu, hdudoc, hduclas):
  342 + """
  343 + Set standard OGIP keywords for extension.
  344 +
  345 + Parameters:
  346 + hdu: Header Data Unit.
  347 + hdudoc: Documentation reference string
  348 + hduclas: Array of HDUCLAS fields
  349 + """
  350 + # Set UTC date of file creation
  351 + utc = datetime.utcnow().isoformat()[:19]
  352 +
  353 + # Set keywords
  354 + hdu.card("ORIGIN", "IRAP", "Name of organization making this file")
  355 + hdu.card("DATE", utc, "File creation date (YYYY-MM-DDThh:mm:ss UTC)")
  356 + hdu.card("TELESCOP", self._cal_tel, "Name of telescope")
  357 + hdu.card("INSTRUME", self._cal_inst, "Name of instrument")
  358 + hdu.card("DETNAM", self._cal_det, "Name of detector")
  359 + hdu.card("HDUCLASS", "OGIP", "HDU class")
  360 + hdu.card("HDUDOC", hdudoc, "HDU documentation")
  361 + for i, item in enumerate(hduclas):
  362 + key = "HDUCLAS%d" % (i+1)
  363 + hdu.card(key, item, "HDU class")
  364 + hdu.card("HDUVERS", "1.0.0", "HDU version")
  365 +
  366 + # Return
  367 + return
  368 +
  369 + def _create_cif(self):
  370 + """
  371 + Create Calibration Index File (CIF) extension in CIF FITS file.
  372 +
  373 + Parameters:
  374 + None
  375 + Keywords:
  376 + None
  377 + """
  378 + # Create binary table
  379 + table = gammalib.GFitsBinTable()
  380 +
  381 + # Set boundary
  382 +
  383 + # Attach columns. Reference: CAL/GEN/92-008
  384 + table.append(gammalib.GFitsTableStringCol("TELESCOP", 0, 10))
  385 + table.append(gammalib.GFitsTableStringCol("INSTRUME", 0, 10))
  386 + table.append(gammalib.GFitsTableStringCol("DETNAM", 0, 20))
  387 + table.append(gammalib.GFitsTableStringCol("FILTER", 0, 10))
  388 + table.append(gammalib.GFitsTableStringCol("CAL_DEV", 0, 20))
  389 + table.append(gammalib.GFitsTableStringCol("CAL_DIR", 0, 70))
  390 + table.append(gammalib.GFitsTableStringCol("CAL_FILE", 0, 40))
  391 + table.append(gammalib.GFitsTableStringCol("CAL_CLAS", 0, 3))
  392 + table.append(gammalib.GFitsTableStringCol("CAL_DTYP", 0, 4))
  393 + table.append(gammalib.GFitsTableStringCol("CAL_CNAM", 0, 20))
  394 + table.append(gammalib.GFitsTableStringCol("CAL_CBD", 0, 70, 9))
  395 + table.append(gammalib.GFitsTableShortCol("CAL_XNO", 0))
  396 + table.append(gammalib.GFitsTableStringCol("CAL_VSD", 0, 10))
  397 + table.append(gammalib.GFitsTableStringCol("CAL_VST", 0, 8))
  398 + table.append(gammalib.GFitsTableDoubleCol("REF_TIME", 0))
  399 + table.append(gammalib.GFitsTableShortCol("CAL_QUAL", 0))
  400 + table.append(gammalib.GFitsTableStringCol("CAL_DATE", 0, 8))
  401 + table.append(gammalib.GFitsTableStringCol("CAL_DESC", 0, 70))
  402 +
  403 + # Set keywords. Reference: CAL/GEN/92-008
  404 + table.extname("CIF")
  405 + table.card("CIFVERSN", "1992a", "Version of CIF format")
  406 +
  407 + # Attach table to FITS file. Note that at this moment the
  408 + # FITS table gets connected to the FITS file. Yet since
  409 + # nothing was yet written to the FITS file, we cannot read
  410 + # anything from it.
  411 + self._cif.append(table)
  412 +
  413 + # Return
  414 + return
  415 +
  416 + def _add_cif_info(self):
  417 + """
  418 + Add information to CIF extension.
  419 + """
  420 + # Append 4 rows to CIF extension
  421 + self._hdu_cif.append_rows(4)
  422 +
  423 + # Add generic information for these 4 rows
  424 + for i in range(4):
  425 +
  426 + # Set row number
  427 + row = i + self._hdu_cif.nrows() - 4
  428 +
  429 + # Set element
  430 + self._hdu_cif["TELESCOP"][row] = self._cal_tel
  431 + self._hdu_cif["INSTRUME"][row] = self._cal_inst
  432 + self._hdu_cif["DETNAM"][row] = self._cal_det
  433 + self._hdu_cif["FILTER"][row] = self._cal_flt
  434 + self._hdu_cif["CAL_DEV"][row] = "ONLINE"
  435 + self._hdu_cif["CAL_CLAS"][row] = self._cal_class
  436 + self._hdu_cif["CAL_DTYP"][row] = self._cal_type
  437 + self._hdu_cif["CAL_VSD"][row] = self._val_date
  438 + self._hdu_cif["CAL_VST"][row] = self._val_time
  439 + self._hdu_cif["REF_TIME"][row] = self._ref_time
  440 + self._hdu_cif["CAL_QUAL"][row] = self._cal_qual
  441 + self._hdu_cif["CAL_DATE"][row] = self._cal_date
  442 +
  443 + # Add effective area information
  444 + row = self._hdu_cif.nrows() - 4
  445 + self._hdu_cif["CAL_DIR"][row] = self._ea_dir
  446 + self._hdu_cif["CAL_FILE"][row] = self._ea_file
  447 + self._hdu_cif["CAL_CNAM"][row] = self._ea_name
  448 + self._hdu_cif["CAL_DESC"][row] = self._ea_desc
  449 + self._hdu_cif["CAL_XNO"][row] = 1
  450 + for n in range(9):
  451 + if n >= len(self._ea_bounds):
  452 + self._hdu_cif["CAL_CBD"][row, n] = "NONE"
  453 + else:
  454 + self._hdu_cif["CAL_CBD"][row, n] = self._ea_bounds[n]
  455 +
  456 + # Add point spread function information
  457 + row = self._hdu_cif.nrows() - 3
  458 + self._hdu_cif["CAL_DIR"][row] = self._psf_dir
  459 + self._hdu_cif["CAL_FILE"][row] = self._psf_file
  460 + self._hdu_cif["CAL_CNAM"][row] = self._psf_name
  461 + self._hdu_cif["CAL_DESC"][row] = self._psf_desc
  462 + self._hdu_cif["CAL_XNO"][row] = 1
  463 + for n in range(9):
  464 + if n >= len(self._psf_bounds):
  465 + self._hdu_cif["CAL_CBD"][row, n] = "NONE"
  466 + else:
  467 + self._hdu_cif["CAL_CBD"][row, n] = self._psf_bounds[n]
  468 +
  469 + # Add energy dispersion information
  470 + row = self._hdu_cif.nrows() - 2
  471 + self._hdu_cif["CAL_DIR"][row] = self._edisp_dir
  472 + self._hdu_cif["CAL_FILE"][row] = self._edisp_file
  473 + self._hdu_cif["CAL_CNAM"][row] = self._edisp_name
  474 + self._hdu_cif["CAL_DESC"][row] = self._edisp_desc
  475 + self._hdu_cif["CAL_XNO"][row] = 1
  476 + for n in range(9):
  477 + if n >= len(self._edisp_bounds):
  478 + self._hdu_cif["CAL_CBD"][row, n] = "NONE"
  479 + else:
  480 + self._hdu_cif["CAL_CBD"][row, n] = self._edisp_bounds[n]
  481 +
  482 + # Add background information
  483 + row = self._hdu_cif.nrows() - 1
  484 + self._hdu_cif["CAL_DIR"][row] = self._bgd_dir
  485 + self._hdu_cif["CAL_FILE"][row] = self._bgd_file
  486 + self._hdu_cif["CAL_CNAM"][row] = self._bgd_name
  487 + self._hdu_cif["CAL_DESC"][row] = self._bgd_desc
  488 + self._hdu_cif["CAL_XNO"][row] = 1
  489 + for n in range(9):
  490 + if n >= len(self._bgd_bounds):
  491 + self._hdu_cif["CAL_CBD"][row, n] = "NONE"
  492 + else:
  493 + self._hdu_cif["CAL_CBD"][row, n] = self._bgd_bounds[n]
  494 +
  495 + # Return
  496 + return
  497 +
  498 + def _set_cif_keywords(self, hdu, name, bounds, desc):
  499 + """
  500 + Set standard CIF keywords for extension.
  501 + """
  502 + # Set standard CIF keywords
  503 + hdu.card("CSYSNAME", "XMA_POL", "")
  504 + hdu.card("CCLS0001", self._cal_class, "Calibration class")
  505 + hdu.card("CDTP0001", self._cal_type, "Calibration type")
  506 + hdu.card("CCNM0001", name, "Calibration name")
  507 +
  508 + # Set boundary keywords
  509 + for n in range(9):
  510 + keyname = "CBD%d0001" % (n+1)
  511 + if n >= len(bounds):
  512 + value = "NONE"
  513 + else:
  514 + value = bounds[n]
  515 + hdu.card(keyname, value, "Calibration boundary")
  516 +
  517 + # Set validity keywords
  518 + hdu.card("CVSD0001", self._val_date, "Calibration validity start date (UTC)")
  519 + hdu.card("CVST0001", self._val_time, "Calibration validity start time (UTC)")
  520 + hdu.card("CDEC0001", desc, "Calibration description")
  521 +
  522 + # Return
  523 + return
  524 +
  525 +
  526 + # ROOT specific private members
  527 + def _root2caldb(self):
  528 + """
  529 + Translate ROOT to CALDB information.
  530 +
  531 + Parameters:
  532 + filename - ROOT 2D performance filename.
  533 + """
  534 + # Open ROOT performance file
  535 + file = TFile(self["infile"].filename().url())
  536 +
  537 + # Create effective area
  538 + self._root2ea(file, rebin=self["rebin"].boolean(),
  539 + eascale=self["eascale"].real())
  540 +
  541 + # Create point spread function
  542 + self._root2psf(file, self["psftype"].string())
  543 +
  544 + # Create energy dispersion
  545 + self._root2edisp(file)
  546 +
  547 + # Create background
  548 + #self._root2bgd(file)
  549 + self._root2bgd3D(file, bgdscale=self["bgdscale"].real())
  550 +
  551 + # Return
  552 + return
  553 +
  554 + def _root2ea(self, file, rebin=False, eascale=1.0):
  555 + """
  556 + Translate ROOT to CALDB effective area extension. The following ROOT
  557 + histograms are used:
  558 +
  559 + EffectiveAreaEtrue_offaxis -> EFFAREA
  560 + EffectiveArea_offaxis -> EFFAREA_RECO
  561 +
  562 + Parameters:
  563 + file: ROOT file.
  564 +
  565 + Keywords:
  566 + rebin: Rebin Etrue histogram (useful for Prod1 IFAE runs)
  567 + eascale: Effective area scaling factor
  568 + """
  569 + # Continue only if effective area HDU has been opened
  570 + if self._hdu_ea != None:
  571 +
  572 + # Allocate ROOT 2D arrays
  573 + etrue = TH2F()
  574 + ereco = TH2F()
  575 + file.GetObject("EffectiveAreaEtrue_offaxis", etrue)
  576 + file.GetObject("EffectiveArea_offaxis", ereco)
  577 +
  578 + # Rebin etrue histogram
  579 + if rebin:
  580 + etrue.RebinX(10)
  581 + neng = etrue.GetXaxis().GetNbins()
  582 + noffset = etrue.GetYaxis().GetNbins()
  583 + for ioff in range(noffset):
  584 + for ieng in range(neng):
  585 + value = etrue.GetBinContent(ieng+1,ioff+1) / 10.0
  586 + etrue.SetBinContent(ieng+1,ioff+1,value)
  587 +
  588 + # Scale histograms (if needed)
  589 + if eascale != 1.0:
  590 + neng = etrue.GetXaxis().GetNbins()
  591 + noffset = etrue.GetYaxis().GetNbins()
  592 + for ioff in range(noffset):
  593 + for ieng in range(neng):
  594 + value = etrue.GetBinContent(ieng+1,ioff+1) * eascale
  595 + etrue.SetBinContent(ieng+1,ioff+1,value)
  596 + neng = ereco.GetXaxis().GetNbins()
  597 + noffset = ereco.GetYaxis().GetNbins()
  598 + for ioff in range(noffset):
  599 + for ieng in range(neng):
  600 + value = ereco.GetBinContent(ieng+1,ioff+1) * eascale
  601 + ereco.SetBinContent(ieng+1,ioff+1,value)
  602 +
  603 + # Write boundaries (use Ereco boundaries)
  604 + bounds = self._make_2D(ereco, self._hdu_ea, None, "m2")
  605 + for b in bounds:
  606 + self._ea_bounds.append(b)
  607 + self._set_cif_keywords(self._hdu_ea, self._ea_name, \
  608 + self._ea_bounds, self._ea_desc)
  609 +
  610 + # EFFAREA
  611 + self._make_2D(etrue, self._hdu_ea, "EFFAREA", "m2")
  612 +
  613 + # EFFAREA_RECO
  614 + self._make_2D(ereco, self._hdu_ea, "EFFAREA_RECO", "m2")
  615 +
  616 + # Return
  617 + return
  618 +
  619 + def _root2psf(self, file, psftype):
  620 + """
  621 + Translate ROOT to CALDB point spread function extension.
  622 + Parameters:
  623 + file - ROOT file.
  624 + psftype - PSF type (Gauss, King)
  625 + Keywords:
  626 + None
  627 + """
  628 + # Continue only if point spread function HDU has been opened
  629 + if self._hdu_psf != None:
  630 +
  631 + # King profile PSF
  632 + if psftype == "King":
  633 + self._root2psf_king(file)
  634 + else:
  635 + self._root2psf_gauss(file)
  636 +
  637 + # Return
  638 + return
  639 +
  640 + def _root2psf_gauss(self, file):
  641 + """
  642 + Translate ROOT to CALDB point spread function extension. The following
  643 + ROOT histograms are used:
  644 +
  645 + 1/(2*pi*SIGMA_1) -> SCALE
  646 + AngRes_offaxis -> SIGMA_1 (scaling: 1/0.8)
  647 + 0.0 -> AMPL_2
  648 + 0.0 -> SIGMA_2
  649 + 0.0 -> AMPL_3
  650 + 0.0 -> SIGMA_3
  651 +
  652 + Parameters:
  653 + file - ROOT file.
  654 + Keywords:
  655 + None
  656 + """
  657 + # Continue only if point spread function HDU has been opened
  658 + if self._hdu_psf != None:
  659 +
  660 + # Allocate ROOT 2D array
  661 + r68 = TH2F()
  662 +
  663 + # Read 68% containment histogram
  664 + file.GetObject("AngRes_offaxis", r68)
  665 + neng = r68.GetXaxis().GetNbins()
  666 + noffset = r68.GetYaxis().GetNbins()
  667 +
  668 + # Converts 68% -> 1 sigma
  669 + r68_to_sigma = 0.6624305
  670 + for ioff in range(noffset):
  671 + for ieng in range(neng):
  672 + sigma = r68.GetBinContent(ieng+1,ioff+1) * r68_to_sigma
  673 + r68.SetBinContent(ieng+1,ioff+1,sigma)
  674 +
  675 + # Compute scale histogram
  676 + scale = r68.Clone()
  677 + for ioff in range(noffset):
  678 + for ieng in range(neng):
  679 + integral = 2.0 * math.pi * r68.GetBinContent(ieng+1,ioff+1)
  680 + if integral > 0.0:
  681 + value = 1.0 / integral
  682 + else:
  683 + value = 0.0
  684 + scale.SetBinContent(ieng+1,ioff+1,value)
  685 +
  686 + # Set zero histogram
  687 + zero = r68.Clone()
  688 + for ioff in range(noffset):
  689 + for ieng in range(neng):
  690 + zero.SetBinContent(ieng+1,ioff+1,0.0)
  691 +
  692 + # Set boundaries
  693 + bounds = self._make_2D(r68, self._hdu_psf, None, "deg")
  694 + for b in bounds:
  695 + self._psf_bounds.append(b)
  696 + self._psf_bounds.append("PSF(GAUSS)")
  697 + self._set_cif_keywords(self._hdu_psf, self._psf_name, \
  698 + self._psf_bounds, self._psf_desc)
  699 +
  700 + # SCALE
  701 + #self._make_2D(scale, self._hdu_psf, "SCALE", "")
  702 +
  703 + # SIGMA_1
  704 + self._make_2D(r68, self._hdu_psf, "SIGMA_1", "deg")
  705 +
  706 + # AMPL_2
  707 + self._make_2D(zero, self._hdu_psf, "AMPL_2", "")
  708 +
  709 + # SIGMA_2
  710 + self._make_2D(zero, self._hdu_psf, "SIGMA_2", "deg")
  711 +
  712 + # AMPL_3
  713 + self._make_2D(zero, self._hdu_psf, "AMPL_3", "")
  714 +
  715 + # SIGMA_3
  716 + self._make_2D(zero, self._hdu_psf, "SIGMA_3", "deg")
  717 +
  718 + # Return
  719 + return
  720 +
  721 + def _root2psf_king(self, file):
  722 + """
  723 + Translate ROOT to CALDB point spread function extension. The following
  724 + ROOT histograms are used:
  725 +
  726 + AngRes_offaxis
  727 + AngRes80_offaxis
  728 +
  729 + Parameters:
  730 + file - ROOT file.
  731 + Keywords:
  732 + None
  733 + """
  734 + # Continue only if point spread function HDU has been opened
  735 + if self._hdu_psf != None:
  736 +
  737 + # Allocate ROOT 2D arrays
  738 + r68 = TH2F()
  739 + r80 = TH2F()
  740 +
  741 + # Read 68% and 80% containment histograms
  742 + file.GetObject("AngRes_offaxis", r68)
  743 + file.GetObject("AngRes80_offaxis", r80)
  744 + neng = r68.GetXaxis().GetNbins()
  745 + noffset = r68.GetYaxis().GetNbins()
  746 +
  747 + # Initialise parameter maps by cloning the r68 2D map
  748 + gamma2D = r68.Clone()
  749 + sigma2D = r68.Clone()
  750 +
  751 + # Compute gamma and sigma values
  752 + for ioff in range(noffset):
  753 +
  754 + # Initialise last results
  755 + last_gamma = 0.0
  756 + last_sigma = 0.0
  757 +
  758 + for ieng in range(neng):
  759 +
  760 + # Extract radii
  761 + r_68 = r68.GetBinContent(ieng+1,ioff+1)
  762 + r_80 = r80.GetBinContent(ieng+1,ioff+1)
  763 +
  764 + # Initialise results
  765 + gamma = 0.0
  766 + sigma = 0.0
  767 +
  768 + # Continue only if both radii are positive
  769 + if r_68 > 0 and r_80 > 0:
  770 +
  771 + # Derive constants for equation to solve
  772 + a = 1.0 - 0.68
  773 + b = 1.0 - 0.80
  774 + c = r_68*r_68/(r_80*r_80)
  775 +
  776 + # Solve equation (a^x-1)/(b^x-1)=c for x using secant
  777 + # method. Stop when we are better than 1e-6.
  778 + x1 = -0.5
  779 + x2 = -1.0
  780 + f1 = (math.pow(a,x1) - 1.0)/(math.pow(b,x1) - 1.0) - c
  781 + f2 = (math.pow(a,x2) - 1.0)/(math.pow(b,x2) - 1.0) - c
  782 + iter = 0
  783 + while True:
  784 + x = x1 - f1 * (x1-x2)/(f1-f2)
  785 + f = (math.pow(a,x) - 1.0)/(math.pow(b,x) - 1.0) - c
  786 + iter += 1
  787 + if abs(f) < 1.0e-6:
  788 + break
  789 + else:
  790 + f2 = f1
  791 + x2 = x1
  792 + f1 = f
  793 + x1 = x
  794 +
  795 + # Compute gamma.
  796 + if x < 0.0:
  797 + gamma = 1.0 - 1.0/x
  798 + else:
  799 + gamma = 1.0
  800 +
  801 + # Compute sigma
  802 + denominator = 2.0 * gamma * (math.pow(a, x) - 1.0)
  803 + if denominator > 0.0:
  804 + sigma = r_68 * math.sqrt(1.0/denominator)
  805 + else:
  806 + denominator = 2.0 * gamma * (math.pow(b, x) - 1.0)
  807 + if denominator > 0.0:
  808 + sigma = r_80 * math.sqrt(1.0/denominator)
  809 + else:
  810 + gamma = 0.0
  811 + sigma = 0.0
  812 +
  813 + # Handle special case that no results were found.
  814 + # This takes care of pixels that are ill defined
  815 + # in the MC file.
  816 + if gamma == 0.0 and sigma == 0.0:
  817 + gamma = last_gamma
  818 + sigma = last_sigma
  819 +
  820 + # Store surrent result as last result
  821 + last_gamma = gamma
  822 + last_sigma = sigma
  823 +
  824 + # Show results on console
  825 + #print(ioff,ieng,gamma,sigma,x,f,iter)
  826 +
  827 + # Set bin contents
  828 + gamma2D.SetBinContent(ieng+1,ioff+1,gamma)
  829 + sigma2D.SetBinContent(ieng+1,ioff+1,sigma)
  830 +
  831 + # Set boundaries
  832 + bounds = self._make_2D(r68, self._hdu_psf, None, "deg")
  833 + for b in bounds:
  834 + self._psf_bounds.append(b)
  835 + self._psf_bounds.append("PSF(KING)")
  836 + self._set_cif_keywords(self._hdu_psf, self._psf_name, \
  837 + self._psf_bounds, self._psf_desc)
  838 +
  839 + # GAMMA
  840 + self._make_2D(gamma2D, self._hdu_psf, "GAMMA", "")
  841 +
  842 + # SIGMA
  843 + self._make_2D(sigma2D, self._hdu_psf, "SIGMA", "deg")
  844 +
  845 + # Return
  846 + return
  847 +
  848 + def _root2edisp(self, file):
  849 + """
  850 + Translate ROOT to CALDB energy dispersion extension. The following ROOT
  851 + histograms are used:
  852 +
  853 + EestOverEtrue_offaxis -> MATRIX
  854 +
  855 + Parameters:
  856 + file - ROOT file.
  857 + Keywords:
  858 + None
  859 + """
  860 + # Continue only if energy dispersion HDU has been opened
  861 + if self._hdu_edisp != None:
  862 +
  863 + # Allocate ROOT 3D array
  864 + matrix = TH3F()
  865 + file.GetObject("EestOverEtrue_offaxis", matrix)
  866 +
  867 + # Set boundaries
  868 + bounds = self._make_3D_migra(matrix, self._hdu_edisp, None, "")
  869 + for b in bounds:
  870 + self._edisp_bounds.append(b)
  871 + self._set_cif_keywords(self._hdu_edisp, self._edisp_name, \
  872 + self._edisp_bounds, self._edisp_desc)
  873 +
  874 + # MATRIX
  875 + self._make_3D_migra(matrix, self._hdu_edisp, "MATRIX", "")
  876 +
  877 + # Return
  878 + return
  879 +
  880 + def _root2bgd(self, file):
  881 + """
  882 + Translate ROOT to CALDB background extension. The following ROOT
  883 + histograms are used:
  884 +
  885 + BGRatePerSqDeg_offaxis -> BGD
  886 + BGRatePerSqDeg_offaxis -> BGD_RECO
  887 +
  888 + Parameters:
  889 + file - ROOT file.
  890 + Keywords:
  891 + None
  892 + """
  893 + # Continue only if background HDU has been opened
  894 + if self._hdu_bgd != None:
  895 +
  896 + # Allocate ROOT 2D array
  897 + array = TH2F()
  898 + file.GetObject("BGRatePerSqDeg_offaxis", array)
  899 +
  900 + # Set boundaries
  901 + bounds = self._make_2D(array, self._hdu_bgd, None, "deg")
  902 + for b in bounds:
  903 + self._bgd_bounds.append(b)
  904 + self._set_cif_keywords(self._hdu_bgd, self._bgd_name, \
  905 + self._bgd_bounds, self._bgd_desc)
  906 +
  907 + # BGD
  908 + self._make_2D(array, self._hdu_bgd, "BGD", "")
  909 +
  910 + # BGD_RECO
  911 + self._make_2D(array, self._hdu_bgd, "BGD_RECO", "")
  912 +
  913 + # Return
  914 + return
  915 +
  916 + def _root2bgd3D(self, file, bgdscale=1.0):
  917 + """
  918 + Translate ROOT to CALDB background extension. The following ROOT
  919 + histograms are used:
  920 +
  921 + BGRatePerSqDeg_offaxis -> BGD
  922 +
  923 + Parameters:
  924 + file - ROOT file.
  925 + Keywords:
  926 + scale - Background rate scaling factor
  927 + """
  928 + # Continue only if background HDU has been opened
  929 + if self._hdu_bgd != None:
  930 +
  931 + # Allocate ROOT 2D array
  932 + array = TH2F()
  933 + file.GetObject("BGRatePerSqDeg_offaxis", array)
  934 +
  935 + # Set boundaries
  936 + bounds = self._make_3D(array, self._hdu_bgd, None, "deg")
  937 + for b in bounds:
  938 + self._bgd_bounds.append(b)
  939 + self._set_cif_keywords(self._hdu_bgd, self._bgd_name, \
  940 + self._bgd_bounds, self._bgd_desc)
  941 +
  942 + # BGD (reconstructed energy)
  943 + self._make_3D(array, self._hdu_bgd, "BGD", "1/s/MeV/sr", scale=bgdscale)
  944 +
  945 + # Return
  946 + return
  947 +
  948 + def _append_column(self, hdu, name, unit, axis, log=False):
  949 + """
  950 + Append column to HDU.
  951 +
  952 + Parameters:
  953 + hdu: HDU
  954 + name: Column name
  955 + unit: Column unit
  956 + axis: ROOT histogram axis
  957 +
  958 + Keywords:
  959 + log: Logarithmic axis
  960 + """
  961 + # Continue only if columns does not yet exist
  962 + if not hdu.contains(name):
  963 +
  964 + # Get number of axis bins
  965 + nbins = axis.GetNbins()
  966 +
  967 + # Append column and set column unit
  968 + hdu.append(gammalib.GFitsTableFloatCol(name, 1, nbins))
  969 + hdu[name].unit(unit)
  970 +
  971 + # Do we have a LO axis? If not we have a HI axis
  972 + if name[-2:] == "LO":
  973 + low = True
  974 + else:
  975 + low = False
  976 +
  977 + # Fill column
  978 + for i in range(nbins):
  979 + if low:
  980 + value = axis.GetBinLowEdge(i+1)
  981 + else:
  982 + value = axis.GetBinUpEdge(i+1)
  983 + if log:
  984 + value = pow(10.0, value)
  985 + hdu[name][0,i] = value
  986 +
  987 + # Return
  988 + return
  989 +
  990 + def _make_2D(self, array, hdu, name, unit, scale=1.0):
  991 + """
  992 + Make 2D matrix as function of energy and offset angle from a
  993 + ROOT 2D histogram. If the HDU has already the energy and
  994 + offset angle columns, this method will simply add another data
  995 + column.
  996 + If name==None, the method will not append any data column.
  997 +
  998 + Parameters:
  999 + array - ROOT 2D histogram.
  1000 + hdu - FITS HDU.
  1001 + name - Data column name.
  1002 + unit - Data unit.
  1003 + Keywords:
  1004 + scale - Scaling factor for histogram values.
  1005 + """
  1006 + # Extract energy and offset angle vectors
  1007 + energies = array.GetXaxis()
  1008 + offsets = array.GetYaxis()
  1009 + neng = energies.GetNbins()
  1010 + noffset = offsets.GetNbins()
  1011 +
  1012 + # Append axis columns to HDU
  1013 + self._append_column(hdu, "ENERG_LO", "TeV", energies, log=True)
  1014 + self._append_column(hdu, "ENERG_HI", "TeV", energies, log=True)
  1015 + self._append_column(hdu, "THETA_LO", "deg", offsets)
  1016 + self._append_column(hdu, "THETA_HI", "deg", offsets)
  1017 +
  1018 + # Append array column
  1019 + if name != None and not hdu.contains(name):
  1020 + hdu.append(gammalib.GFitsTableFloatCol(name, 1, neng*noffset))
  1021 + hdu[name].unit(unit)
  1022 + hdu[name].dim([neng, noffset])
  1023 + for ioff in range(noffset):
  1024 + for ieng in range(neng):
  1025 + index = ieng + ioff * neng
  1026 + value = array.GetBinContent(ieng+1,ioff+1)
  1027 + hdu[name][0,index] = value * scale
  1028 +
  1029 + # Collect boundary information
  1030 + bd_eng = "ENERG(%.4f-%.2f)TeV" % \
  1031 + (pow(10.0, energies.GetBinLowEdge(1)), \
  1032 + pow(10.0, energies.GetBinUpEdge(neng)))
  1033 + bd_off = "THETA(%.2f-%.2f)deg" % \
  1034 + (offsets.GetBinLowEdge(1), \
  1035 + offsets.GetBinUpEdge(noffset))
  1036 + bd_phi = "PHI(0-360)deg"
  1037 + bounds = [bd_eng, bd_off, bd_phi]
  1038 +
  1039 + # Return boundary information
  1040 + return bounds
  1041 +
  1042 + def _make_3D(self, array, hdu, name, unit, scale=1.0):
  1043 + """
  1044 + Make 3D cube as function of DETX, DETY and energy from a
  1045 + ROOT 2D histogram. If the HDU has already the energy and
  1046 + offset angle columns, this method will simply add another data
  1047 + column.
  1048 + If name==None, the method will not append any data column.
  1049 +
  1050 + Parameters:
  1051 + array - ROOT 2D histogram.
  1052 + hdu - FITS HDU.
  1053 + name - Data column name.
  1054 + unit - Data unit.
  1055 + Keywords:
  1056 + scale - Scaling factor for histogram values.
  1057 + """
  1058 + # Set constants
  1059 + deg2sr = 0.01745329*0.01745329
  1060 +
  1061 + # Extract energy and offset angle vectors
  1062 + energies = array.GetXaxis()
  1063 + neng = energies.GetNbins()
  1064 + offsets = array.GetYaxis()
  1065 + noffset = offsets.GetNbins()
  1066 + ewidth = [] # in MeV
  1067 + for ieng in range(neng):
  1068 + ewidth.append(pow(10.0, energies.GetBinUpEdge(ieng+1) +6.0) - \
  1069 + pow(10.0, energies.GetBinLowEdge(ieng+1)+6.0))
  1070 +
  1071 + # Build DETX and DETY axes
  1072 + ndet = array.GetYaxis().GetNbins()
  1073 + ndets = 2*ndet
  1074 + det_max = array.GetYaxis().GetBinUpEdge(ndet)
  1075 + det_bin = det_max/float(ndet)
  1076 + dets_lo = []
  1077 + dets_hi = []
  1078 + dets2 = []
  1079 + for i in range(ndets):
  1080 + det_lo = -det_max + i*det_bin
  1081 + det_hi = det_lo + det_bin
  1082 + det_val = 0.5*(det_lo + det_hi)
  1083 + dets_lo.append(det_lo)
  1084 + dets_hi.append(det_hi)
  1085 + dets2.append(det_val*det_val)
  1086 +
  1087 + # Append DETX_LO column
  1088 + if not hdu.contains("DETX_LO"):
  1089 + hdu.append(gammalib.GFitsTableFloatCol("DETX_LO", 1, ndets))
  1090 + hdu["DETX_LO"].unit("deg")
  1091 + for i in range(ndets):
  1092 + hdu["DETX_LO"][0,i] = dets_lo[i]
  1093 +
  1094 + # Append DETX_HI column
  1095 + if not hdu.contains("DETX_HI"):
  1096 + hdu.append(gammalib.GFitsTableFloatCol("DETX_HI", 1, ndets))
  1097 + hdu["DETX_HI"].unit("deg")
  1098 + for i in range(ndets):
  1099 + hdu["DETX_HI"][0,i] = dets_hi[i]
  1100 +
  1101 + # Append DETY_LO column
  1102 + if not hdu.contains("DETY_LO"):
  1103 + hdu.append(gammalib.GFitsTableFloatCol("DETY_LO", 1, ndets))
  1104 + hdu["DETY_LO"].unit("deg")
  1105 + for i in range(ndets):
  1106 + hdu["DETY_LO"][0,i] = dets_lo[i]
  1107 +
  1108 + # Append DETY_HI column
  1109 + if not hdu.contains("DETY_HI"):
  1110 + hdu.append(gammalib.GFitsTableFloatCol("DETY_HI", 1, ndets))
  1111 + hdu["DETY_HI"].unit("deg")
  1112 + for i in range(ndets):
  1113 + hdu["DETY_HI"][0,i] = dets_hi[i]
  1114 +
  1115 + # Append ENERG_LO and ENERG_HI columns
  1116 + self._append_column(hdu, "ENERG_LO", "TeV", energies, log=True)
  1117 + self._append_column(hdu, "ENERG_HI", "TeV", energies, log=True)
  1118 +
  1119 + # Append array column
  1120 + if name != None and not hdu.contains(name):
  1121 + hdu.append(gammalib.GFitsTableFloatCol(name, 1, ndets*ndets*neng))
  1122 + hdu[name].unit(unit)
  1123 + hdu[name].dim([ndets,ndets,neng])
  1124 + for ix in range(ndets):
  1125 + for iy in range(ndets):
  1126 + for ieng in range(neng):
  1127 + index = ix + (iy + ieng * ndets) * ndets
  1128 + theta = math.sqrt(dets2[ix] + dets2[iy])
  1129 + ioff = offsets.FindBin(theta)
  1130 + if ioff > 0 and ioff <= noffset:
  1131 + binsq = array.GetBinContent(ieng+1,ioff)
  1132 + if binsq >= 0:
  1133 + value = binsq / deg2sr / (ewidth[ieng])
  1134 + hdu[name][0,index] = value * scale
  1135 +
  1136 + # Debugging
  1137 + if False:
  1138 + for ieng in range(neng):
  1139 + sum = 0.0
  1140 + for ioff in range(noffset):
  1141 + index = ieng + ioff * neng
  1142 + binsq = array.GetBinContent(ieng+1,ioff+1)
  1143 + value = binsq / deg2sr / ewidth[ieng]
  1144 + sum += value
  1145 + if ioff == 0:
  1146 + peak = value
  1147 + peaksq = binsq
  1148 + print(pow(10.0, energies.GetBinLowEdge(ieng+1))+ \
  1149 + pow(10.0, energies.GetBinUpEdge(ieng+1)), sum, peak, peaksq)
  1150 +
  1151 + # Collect boundary information
  1152 + bd_detx = "DETX(%.2f-%.2f)deg" % (-det_max, det_max)
  1153 + bd_dety = "DETY(%.2f-%.2f)deg" % (-det_max, det_max)
  1154 + bd_eng = "ENERG(%.4f-%.2f)TeV" % \
  1155 + (pow(10.0, energies.GetBinLowEdge(1)),
  1156 + pow(10.0, energies.GetBinUpEdge(neng)))
  1157 + bounds = [bd_detx, bd_dety, bd_eng]
  1158 +
  1159 + # Return boundary information
  1160 + return bounds
  1161 +
  1162 + def _make_3D_migra(self, array, hdu, name, unit, scale=1.0):
  1163 + """
  1164 + Make 3D cube as function of ETRUE, MIGRA and THETA form ROOT 3D
  1165 + histogram. If the HDU has already the columns, this method will
  1166 + simply add another data column.
  1167 + If name==None, the method will not append any data column.
  1168 +
  1169 + Parameters:
  1170 + array - ROOT 3D histogram.
  1171 + hdu - FITS HDU.
  1172 + name - Data column name.
  1173 + unit - Data unit.
  1174 + Keywords:
  1175 + scale - Scaling factor for histogram values.
  1176 + """
  1177 + # Extract Etrue, Eobs/Etrue and offset angle vectors
  1178 + etrue = array.GetXaxis()
  1179 + netrue = etrue.GetNbins()
  1180 + migra = array.GetYaxis()
  1181 + nmigra = migra.GetNbins()
  1182 + offsets = array.GetZaxis()
  1183 + noffset = offsets.GetNbins()
  1184 + ewidth = [] # in MeV
  1185 + for ieng in range(netrue):
  1186 + ewidth.append(pow(10.0, etrue.GetBinUpEdge(ieng+1) +6.0) - \
  1187 + pow(10.0, etrue.GetBinLowEdge(ieng+1)+6.0))
  1188 +
  1189 + # Append axis columns to HDU
  1190 + self._append_column(hdu, "ETRUE_LO", "TeV", etrue, log=True)
  1191 + self._append_column(hdu, "ETRUE_HI", "TeV", etrue, log=True)
  1192 + self._append_column(hdu, "MIGRA_LO", "", migra)
  1193 + self._append_column(hdu, "MIGRA_HI", "", migra)
  1194 + self._append_column(hdu, "THETA_LO", "deg", offsets)
  1195 + self._append_column(hdu, "THETA_HI", "deg", offsets)
  1196 +
  1197 + # Append migration matrix to HDU
  1198 + if name != None and not hdu.contains(name):
  1199 + hdu.append(gammalib.GFitsTableFloatCol(name, 1, netrue*nmigra*noffset))
  1200 + hdu[name].unit(unit)
  1201 + hdu[name].dim([netrue, nmigra, noffset])
  1202 + for ioff in range(noffset):
  1203 + for imigra in range(nmigra):
  1204 + for ieng in range(netrue):
  1205 + index = ieng + (imigra + ioff * nmigra) * netrue
  1206 + value = array.GetBinContent(ieng+1,imigra+1,ioff+1)
  1207 + hdu[name][0,index] = value * scale
  1208 +
  1209 + # Collect boundary information
  1210 + bd_eng = "ETRUE(%.4f-%.2f)TeV" % \
  1211 + (pow(10.0, etrue.GetBinLowEdge(1)),
  1212 + pow(10.0, etrue.GetBinUpEdge(netrue)))
  1213 + bd_migra = "MIGRA(%.3f-%.3f)" % \
  1214 + (migra.GetBinLowEdge(1),
  1215 + migra.GetBinUpEdge(nmigra))
  1216 + bd_off = "THETA(%.2f-%.2f)deg" % \
  1217 + (offsets.GetBinLowEdge(1),
  1218 + offsets.GetBinUpEdge(noffset))
  1219 + bd_phi = "PHI(0-360)deg"
  1220 + bounds = [bd_eng, bd_migra, bd_off, bd_phi]
  1221 +
  1222 + # Return boundary information
  1223 + return bounds
  1224 +
  1225 +
  1226 + # Public methods
  1227 + def run(self):
  1228 + """
  1229 + Run the script.
  1230 + """
  1231 + # Switch screen logging on in debug mode
  1232 + if self._logDebug():
  1233 + self._log.cout(True)
  1234 +
  1235 + # Get parameters
  1236 + self._get_parameters()
  1237 +
  1238 + # Initialise IRF
  1239 + self._initialise()
  1240 +
  1241 + # Open calibration file
  1242 + self._open("file")
  1243 +
  1244 + # Translate ROOT to CALDB information
  1245 + if _has_root:
  1246 + self._root2caldb()
  1247 +
  1248 + # Close calibration file
  1249 + self._close()
  1250 +
  1251 + # Return
  1252 + return
  1253 +
  1254 + def execute(self):
  1255 + """
  1256 + Execute the script.
  1257 + """
  1258 + # Open logfile
  1259 + self.logFileOpen()
  1260 +
  1261 + # Run the script
  1262 + self.run()
  1263 +
  1264 + # Return
  1265 + return
  1266 +
  1267 +
  1268 +# ======================== #
  1269 +# Main routine entry point #
  1270 +# ======================== #
  1271 +if __name__ == '__main__':
  1272 +
  1273 + # Create instance of application
  1274 + app = csroot2caldb(sys.argv)
  1275 +
  1276 + # Execute application
  1277 + app.execute()
cscripts/cta_root2caldb.py deleted
1 -#! /usr/bin/env python  
2 -# ==========================================================================  
3 -# This script generates IRFs in the CALDB format of HEASARC using ROOT  
4 -# offaxis performance files.  
5 -# -------------------------------------------------------------------------  
6 -# Copyright (C) 2011-2016 Juergen Knoedlseder  
7 -# -------------------------------------------------------------------------  
8 -#  
9 -# This program is free software: you can redistribute it and/or modify  
10 -# it under the terms of the GNU General Public License as published by  
11 -# the Free Software Foundation, either version 3 of the License, or  
12 -# (at your option) any later version.  
13 -#  
14 -# This program is distributed in the hope that it will be useful,  
15 -# but WITHOUT ANY WARRANTY; without even the implied warranty of  
16 -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the  
17 -# GNU General Public License for more details.  
18 -#  
19 -# You should have received a copy of the GNU General Public License  
20 -# along with this program. If not, see <http://www.gnu.org/licenses/>.  
21 -#  
22 -# -------------------------------------------------------------------------  
23 -# Requirements:  
24 -# - gammalib  
25 -# - pyroot  
26 -# ==========================================================================  
27 -#from ROOT import TFile, TH1F, TH2F, TH3F  
28 -from ROOT import TFile, TH2F, TH3F  
29 -import gammalib  
30 -from datetime import datetime  
31 -import math  
32 -#import sys  
33 -#import glob  
34 -import os  
35 -  
36 -  
37 -# =========== #  
38 -# CalDB class #  
39 -# =========== #  
40 -class caldb():  
41 - """  
42 - """  
43 - def __init__(self, inst, obsid, path=""):  
44 - """  
45 - CALDB constructor. The constructor will create one CALDB entry for  
46 - an observation.  
47 -  
48 - Parameters:  
49 - inst - Instrument string.  
50 - obsid - Observation ID string.  
51 - Keywords:  
52 - path - CALDB Root path.  
53 - """  
54 - # Store root path  
55 - self.path = path  
56 -  
57 - # Initialise some members  
58 - self.cif = None  
59 - self.irf = None  
60 - self.ea = None  
61 - self.psf = None  
62 - self.edisp = None  
63 - self.bgd = None  
64 - self.hdu_cif = None  
65 -  
66 - # Store UTC date of job execution  
67 - self.utc = datetime.utcnow().isoformat()[:19]  
68 -  
69 - # Set CALDB information  
70 - self.cal_tel = "CTA"  
71 - self.cal_inst = inst.upper()  
72 - self.cal_obsid = obsid  
73 - self.cal_det = "NONE"  
74 - self.cal_flt = "NONE"  
75 - self.cal_class = "BCF"  
76 - self.cal_type = "DATA"  
77 - self.cal_qual = 0 # 0=good, 1=bad, 2=dubious, ...  
78 - self.cal_date = "14/01/30"  
79 - self.val_date = "2014-01-30"  
80 - self.val_time = "00:00:00"  
81 - self.ref_time = 51544.0  
82 - self.cal_name = "NAME("+obsid+")"  
83 - self.cal_version = "VERSION(Prod2)"  
84 - self.cal_cut = "CLASS(BEST)"  
85 - self.cal_analysis = "ANALYSIS(DESY)"  
86 - #  
87 - self.ea_name = "EFF_AREA"  
88 - self.ea_doc = "CAL/GEN/92-019"  
89 - self.ea_bounds = [self.cal_name, self.cal_version, self.cal_cut, self.cal_analysis]  
90 - self.ea_desc = "CTA effective area"  
91 - #  
92 - self.psf_name = "RPSF"  
93 - self.psf_doc = "CAL/GEN/92-020"  
94 - self.psf_bounds = [self.cal_name, self.cal_version, self.cal_cut, self.cal_analysis]  
95 - self.psf_desc = "CTA point spread function"  
96 - #  
97 - self.edisp_name = "EDISP"  
98 - self.edisp_doc = "???"  
99 - self.edisp_bounds = [self.cal_name, self.cal_version, self.cal_cut, self.cal_analysis]  
100 - self.edisp_desc = "CTA energy dispersion"  
101 - #  
102 - self.bgd_name = "BGD"  
103 - self.bgd_doc = "???"  
104 - self.bgd_bounds = [self.cal_name, self.cal_version, self.cal_cut, self.cal_analysis]  
105 - self.bgd_desc = "CTA background"  
106 -  
107 - # Create directory structure  
108 - self.make_dirs()  
109 -  
110 - # Return  
111 - return  
112 -  
113 - def __del__(self):  
114 - """  
115 - Destructor. Close all FITS files.  
116 - """  
117 - # Close calibration  
118 - self.close()  
119 -  
120 - # Return  
121 - return  
122 -  
123 - def make_dirs(self):  
124 - """  
125 - Generate CALDB directory structure for one observation identifier.  
126 - The structure is given by  
127 -  
128 - data/<tel>/<inst>/bcf/<obsid>  
129 -  
130 - where <tel> is "cta" and <inst> is the instrument specified in the  
131 - CALDB constructor (the instrument may be used for different array  
132 - configurations).  
133 -  
134 - Parameters:  
135 - None  
136 - Keywords:  
137 - None  
138 - """  
139 - # Set base path  
140 - self.base_dir = "data"  
141 - self.base_dir += "/"+self.cal_tel.lower()  
142 - self.base_dir += "/"+self.cal_inst.lower()  
143 -  
144 - # Set directory names  
145 - self.bcf_dir = self.base_dir+"/bcf"  
146 - self.obs_dir = self.bcf_dir+"/"+self.cal_obsid  
147 - self.ea_dir = self.obs_dir  
148 - self.psf_dir = self.obs_dir  
149 - self.edisp_dir = self.obs_dir  
150 - self.bgd_dir = self.obs_dir  
151 -  
152 - # Optionally prefix path  
153 - if len(self.path) > 0:  
154 - self.base_path = self.path+"/"+self.base_dir  
155 - self.bcf_path = self.path+"/"+self.bcf_dir  
156 - self.obs_path = self.path+"/"+self.obs_dir  
157 - self.ea_path = self.path+"/"+self.ea_dir  
158 - self.psf_path = self.path+"/"+self.psf_dir  
159 - self.edisp_path = self.path+"/"+self.edisp_dir  
160 - self.bgd_path = self.path+"/"+self.bgd_dir  
161 - else:  
162 - self.base_path = self.base_dir  
163 - self.bcf_path = self.bcf_dir  
164 - self.obs_path = self.obs_dir  
165 - self.ea_path = self.ea_dir  
166 - self.psf_path = self.psf_dir  
167 - self.edisp_path = self.edisp_dir  
168 - self.bgd_path = self.bgd_dir  
169 -  
170 - # Create directory structure  
171 - if not os.path.isdir(self.ea_path):  
172 - os.makedirs(self.ea_path)  
173 - if not os.path.isdir(self.psf_path):  
174 - os.makedirs(self.psf_path)  
175 - if not os.path.isdir(self.edisp_path):  
176 - os.makedirs(self.edisp_path)  
177 - if not os.path.isdir(self.bgd_path):  
178 - os.makedirs(self.bgd_path)  
179 -  
180 - # Return  
181 - return  
182 -  
183 - def open(self, version, split=False, clobber=True):  
184 - """  
185 - Open existing or create new calibration. The actual version will put  
186 - all calibrations in the same file, although each part of the response  
187 - function will have its own logical name. We can thus easily modify  
188 - the script to put each calibration information in a separate file.  
189 -  
190 - Parameters:  
191 - version - Filename version  
192 - Keywords:  
193 - split - Split IRF over several files?  
194 - clobber - Overwrite existing files?  
195 - """  
196 - # Set calibrate file names  
197 - if split:  
198 - self.ea_file = "ea_"+version+".fits"  
199 - self.psf_file = "psf_"+version+".fits"  
200 - self.edisp_file = "edisp_"+version+".fits"  
201 - self.bgd_file = "bgd_"+version+".fits"  
202 - else:  
203 - self.ea_file = "irf_"+version+".fits"  
204 - self.psf_file = "irf_"+version+".fits"  
205 - self.edisp_file = "irf_"+version+".fits"  
206 - self.bgd_file = "irf_"+version+".fits"  
207 -  
208 - # Open calibration database index  
209 - self.cif = gammalib.GFits(self.base_path+"/caldb.indx", True)  
210 -  
211 - # If file has no CIF extension than create it now  
212 - try:  
213 - self.hdu_cif = self.cif.table("CIF")  
214 - except:  
215 - self.create_cif()  
216 - self.hdu_cif = self.cif.table("CIF")  
217 -  
218 - # Set filenames  
219 - ea_filename = self.ea_path+"/"+self.ea_file  
220 - psf_filename = self.psf_path+"/"+self.psf_file  
221 - edisp_filename = self.edisp_path+"/"+self.edisp_file  
222 - bgd_filename = self.bgd_path+"/"+self.bgd_file  
223 -  
224 - # Open files  
225 - if split:  
226 - self.ea = gammalib.GFits(ea_filename, True)  
227 - self.psf = gammalib.GFits(psf_filename, True)  
228 - self.edisp = gammalib.GFits(edisp_filename, True)  
229 - self.bgd = gammalib.GFits(bgd_filename, True)  
230 - else:  
231 - self.irf = gammalib.GFits(ea_filename, True)  
232 - self.ea = self.irf  
233 - self.psf = self.irf  
234 - self.edisp = self.irf  
235 - self.bgd = self.irf  
236 -  
237 - # Open HDUs  
238 - self.open_ea()  
239 - self.open_psf()  
240 - self.open_edisp()  
241 - self.open_bgd()  
242 -  
243 - # Return  
244 - return  
245 -  
246 - def close(self):  
247 - """  
248 - Close calibration FITS files.  
249 -  
250 - Parameters:  
251 - None  
252 - Keywords:  
253 - None  
254 - """  
255 - # Add information to CIF. We do this now as before we did not  
256 - # necessarily have all the information at hand (in particular  
257 - # about the boundaries)  
258 - self.add_cif_info()  
259 -  
260 - # Close CIF  
261 - if self.cif != None:  
262 - self.cif.save(True)  
263 - self.cif.close()  
264 - self.cif = None  
265 -  
266 - # If IRF file exists then close it now. All file pointers will  
267 - # be set to None  
268 - if self.irf != None:  
269 - self.irf.save(True)  
270 - self.irf.close()  
271 - self.irf = None  
272 - self.ea = None  
273 - self.psf = None  
274 - self.edisp = None  
275 - self.bgd = None  
276 -  
277 - # ... otherwise we have split files, so we have to close them  
278 - # all  
279 - else:  
280 -  
281 - # Close effective area file  
282 - if self.ea != None:  
283 - self.ea.save(True)  
284 - self.ea.close()  
285 - self.ea = None  
286 -  
287 - # Close point spread function file  
288 - if self.psf != None:  
289 - self.psf.save(True)  
290 - self.psf.close()  
291 - self.psf = None  
292 -  
293 - # Close energy dispersion file  
294 - if self.edisp != None:  
295 - self.edisp.save(True)  
296 - self.edisp.close()  
297 - self.edisp = None  
298 -  
299 - # Close background file  
300 - if self.bgd != None:  
301 - self.bgd.save(True)  
302 - self.bgd.close()  
303 - self.bgd = None  
304 -  
305 - # Return  
306 - return  
307 -  
308 - def create_cif(self):  
309 - """  
310 - Create Calibration Index File (CIF) extension in CIF FITS file.  
311 -  
312 - Parameters:  
313 - None  
314 - Keywords:  
315 - None  
316 - """  
317 - # Create binary table  
318 - table = gammalib.GFitsBinTable()  
319 -  
320 - # Set boundary  
321 -  
322 - # Attach columns. Reference: CAL/GEN/92-008  
323 - table.append(gammalib.GFitsTableStringCol("TELESCOP", 0, 10))  
324 - table.append(gammalib.GFitsTableStringCol("INSTRUME", 0, 10))  
325 - table.append(gammalib.GFitsTableStringCol("DETNAM", 0, 20))  
326 - table.append(gammalib.GFitsTableStringCol("FILTER", 0, 10))  
327 - table.append(gammalib.GFitsTableStringCol("CAL_DEV", 0, 20))  
328 - table.append(gammalib.GFitsTableStringCol("CAL_DIR", 0, 70))  
329 - table.append(gammalib.GFitsTableStringCol("CAL_FILE", 0, 40))  
330 - table.append(gammalib.GFitsTableStringCol("CAL_CLAS", 0, 3))  
331 - table.append(gammalib.GFitsTableStringCol("CAL_DTYP", 0, 4))  
332 - table.append(gammalib.GFitsTableStringCol("CAL_CNAM", 0, 20))  
333 - table.append(gammalib.GFitsTableStringCol("CAL_CBD", 0, 70, 9))  
334 - table.append(gammalib.GFitsTableShortCol("CAL_XNO", 0))  
335 - table.append(gammalib.GFitsTableStringCol("CAL_VSD", 0, 10))  
336 - table.append(gammalib.GFitsTableStringCol("CAL_VST", 0, 8))  
337 - table.append(gammalib.GFitsTableDoubleCol("REF_TIME", 0))  
338 - table.append(gammalib.GFitsTableShortCol("CAL_QUAL", 0))  
339 - table.append(gammalib.GFitsTableStringCol("CAL_DATE", 0, 8))  
340 - table.append(gammalib.GFitsTableStringCol("CAL_DESC", 0, 70))  
341 -  
342 - # Set keywords. Reference: CAL/GEN/92-008  
343 - table.extname("CIF")  
344 - table.card("CIFVERSN", "1992a", "Version of CIF format")  
345 -  
346 - # Attach table to FITS file. Note that at this moment the  
347 - # FITS table gets connected to the FITS file. Yet since  
348 - # nothing was yet written to the FITS file, we cannot read  
349 - # anything from it.  
350 - self.cif.append(table)  
351 -  
352 - # Return  
353 - return  
354 -  
355 - def add_cif_info(self):  
356 - """  
357 - Add information to CIF extension.  
358 -  
359 - Parameters:  
360 - None  
361 - Keywords:  
362 - None  
363 - """  
364 - # Append 4 rows to CIF extension  
365 - self.hdu_cif.append_rows(4)  
366 -  
367 - # Add generic information for these 4 rows  
368 - for i in range(4):  
369 -  
370 - # Set row number  
371 - row = i+self.hdu_cif.nrows()-4  
372 -  
373 - # Set element  
374 - self.hdu_cif["TELESCOP"][row] = self.cal_tel  
375 - self.hdu_cif["INSTRUME"][row] = self.cal_inst  
376 - self.hdu_cif["DETNAM"][row] = self.cal_det  
377 - self.hdu_cif["FILTER"][row] = self.cal_flt  
378 - self.hdu_cif["CAL_DEV"][row] = "ONLINE"  
379 - self.hdu_cif["CAL_CLAS"][row] = self.cal_class  
380 - self.hdu_cif["CAL_DTYP"][row] = self.cal_type  
381 - self.hdu_cif["CAL_VSD"][row] = self.val_date  
382 - self.hdu_cif["CAL_VST"][row] = self.val_time  
383 - self.hdu_cif["REF_TIME"][row] = self.ref_time  
384 - self.hdu_cif["CAL_QUAL"][row] = self.cal_qual  
385 - self.hdu_cif["CAL_DATE"][row] = self.cal_date  
386 -  
387 - # Add effective area information  
388 - row = self.hdu_cif.nrows()-4  
389 - self.hdu_cif["CAL_DIR"][row] = self.ea_dir  
390 - self.hdu_cif["CAL_FILE"][row] = self.ea_file  
391 - self.hdu_cif["CAL_CNAM"][row] = self.ea_name  
392 - self.hdu_cif["CAL_DESC"][row] = self.ea_desc  
393 - self.hdu_cif["CAL_XNO"][row] = 1  
394 - for n in range(9):  
395 - if n >= len(self.ea_bounds):  
396 - self.hdu_cif["CAL_CBD"][row, n] = "NONE"  
397 - else:  
398 - self.hdu_cif["CAL_CBD"][row, n] = self.ea_bounds[n]  
399 -  
400 - # Add point spread function information  
401 - row = self.hdu_cif.nrows()-3  
402 - self.hdu_cif["CAL_DIR"][row] = self.psf_dir  
403 - self.hdu_cif["CAL_FILE"][row] = self.psf_file  
404 - self.hdu_cif["CAL_CNAM"][row] = self.psf_name  
405 - self.hdu_cif["CAL_DESC"][row] = self.psf_desc  
406 - self.hdu_cif["CAL_XNO"][row] = 1  
407 - for n in range(9):  
408 - if n >= len(self.psf_bounds):  
409 - self.hdu_cif["CAL_CBD"][row, n] = "NONE"  
410 - else:  
411 - self.hdu_cif["CAL_CBD"][row, n] = self.psf_bounds[n]  
412 -  
413 - # Add energy dispersion information  
414 - row = self.hdu_cif.nrows()-2  
415 - self.hdu_cif["CAL_DIR"][row] = self.edisp_dir  
416 - self.hdu_cif["CAL_FILE"][row] = self.edisp_file  
417 - self.hdu_cif["CAL_CNAM"][row] = self.edisp_name  
418 - self.hdu_cif["CAL_DESC"][row] = self.edisp_desc  
419 - self.hdu_cif["CAL_XNO"][row] = 1  
420 - for n in range(9):  
421 - if n >= len(self.edisp_bounds):  
422 - self.hdu_cif["CAL_CBD"][row, n] = "NONE"  
423 - else:  
424 - self.hdu_cif["CAL_CBD"][row, n] = self.edisp_bounds[n]  
425 -  
426 - # Add background information  
427 - row = self.hdu_cif.nrows()-1  
428 - self.hdu_cif["CAL_DIR"][row] = self.bgd_dir  
429 - self.hdu_cif["CAL_FILE"][row] = self.bgd_file  
430 - self.hdu_cif["CAL_CNAM"][row] = self.bgd_name  
431 - self.hdu_cif["CAL_DESC"][row] = self.bgd_desc  
432 - self.hdu_cif["CAL_XNO"][row] = 1  
433 - for n in range(9):  
434 - if n >= len(self.bgd_bounds):  
435 - self.hdu_cif["CAL_CBD"][row, n] = "NONE"  
436 - else:  
437 - self.hdu_cif["CAL_CBD"][row, n] = self.bgd_bounds[n]  
438 -  
439 - # Return  
440 - return  
441 -  
442 - def open_ea(self):  
443 - """  
444 - Open effective area extension. If no extension was found  
445 - then create one. We do not yet set any data as we don't  
446 - know the dimensions of the table yet.  
447 -  
448 - Parameters:  
449 - None  
450 - Keywords:  
451 - None  
452 - """  
453 - # Get extension. If it does not exist then create it now.  
454 - try:  
455 - self.hdu_ea = self.ea.table("EFFECTIVE AREA")  
456 - except:  
457 - # Create binary table  
458 - table = gammalib.GFitsBinTable()  
459 -  
460 - # Set extension name  
461 - table.extname("EFFECTIVE AREA")  
462 -  
463 - # Set OGIP keywords  
464 - self.set_ogip_keywords(table, self.ea_doc, \  
465 - ["RESPONSE", self.ea_name])  
466 -  
467 - # Append table  
468 - self.ea.append(table)  
469 -  
470 - # Get extension  
471 - self.hdu_ea = self.ea.table("EFFECTIVE AREA")  
472 -  
473 - # Return  
474 - return  
475 -  
476 - def open_psf(self):  
477 - """  
478 - Open point spread function extension. If no extension was  
479 - found then create one. We do not yet set any data as we don't  
480 - know the dimensions of the table yet.  
481 -  
482 - Parameters:  
483 - None  
484 - Keywords:  
485 - None  
486 - """  
487 - # Get extension. If it does not exist then create it now.  
488 - try:  
489 - self.hdu_psf = self.psf.table("POINT SPREAD FUNCTION")  
490 - except:  
491 - # Create binary table  
492 - table = gammalib.GFitsBinTable()  
493 -  
494 - # Set extension name  
495 - table.extname("POINT SPREAD FUNCTION")  
496 -  
497 - # Set OGIP keywords  
498 - self.set_ogip_keywords(table, self.psf_doc, \  
499 - ["RESPONSE", self.psf_name])  
500 -  
501 - # Append table  
502 - self.psf.append(table)  
503 -  
504 - # Get extension  
505 - self.hdu_psf = self.psf.table("POINT SPREAD FUNCTION")  
506 -  
507 - # Return  
508 - return  
509 -  
510 - def open_edisp(self):  
511 - """  
512 - Open energy dispersion extension. If no extension was found  
513 - then create one. We do not yet set any data as we don't  
514 - know the dimensions of the table yet.  
515 -  
516 - Parameters:  
517 - None  
518 - Keywords:  
519 - None  
520 - """  
521 - # Get extension. If it does not exist then create it now.  
522 - try:  
523 - self.hdu_edisp = self.edisp.table("ENERGY DISPERSION")  
524 - except:  
525 - # Create binary table  
526 - table = gammalib.GFitsBinTable()  
527 -  
528 - # Set extension name  
529 - table.extname("ENERGY DISPERSION")  
530 -  
531 - # Set OGIP keywords  
532 - self.set_ogip_keywords(table, self.edisp_doc, \  
533 - ["RESPONSE", self.edisp_name])  
534 -  
535 - # Append table  
536 - self.edisp.append(table)  
537 -  
538 - # Get extension  
539 - self.hdu_edisp = self.edisp.table("ENERGY DISPERSION")  
540 -  
541 - # Return  
542 - return  
543 -  
544 - def open_bgd(self):  
545 - """  
546 - Open background extension. If no extension was found  
547 - then create one. We do not yet set any data as we don't  
548 - know the dimensions of the table yet.  
549 -  
550 - Parameters:  
551 - None  
552 - Keywords:  
553 - None  
554 - """  
555 - # Get extension. If it does not exist then create it now.  
556 - try:  
557 - self.hdu_bgd = self.bgd.table("BACKGROUND")  
558 - except:  
559 - # Create binary table  
560 - table = gammalib.GFitsBinTable()  
561 -  
562 - # Set extension name  
563 - table.extname("BACKGROUND")  
564 -  
565 - # Set OGIP keywords  
566 - self.set_ogip_keywords(table, self.bgd_doc, \  
567 - ["RESPONSE", self.bgd_name])  
568 -  
569 - # Append table  
570 - self.bgd.append(table)  
571 -  
572 - # Get extension  
573 - self.hdu_bgd = self.bgd.table("BACKGROUND")  
574 -  
575 - # Return  
576 - return  
577 -  
578 - def set_ogip_keywords(self, hdu, hdudoc, hduclas):  
579 - """  
580 - Set standard OGIP keywords for extension.  
581 -  
582 - Parameters:  
583 - hdu - Header Data Unit.  
584 - hdudoc - Documentation reference string  
585 - hduclas - Array of HDUCLAS fields  
586 - Keywords:  
587 - None  
588 - """  
589 - # Set keywords  
590 - hdu.card("ORIGIN", "IRAP", "Name of organization making this file")  
591 - hdu.card("DATE", self.utc, "File creation date (YYYY-MM-DDThh:mm:ss UTC)")  
592 - hdu.card("TELESCOP", self.cal_tel, "Name of telescope")  
593 - hdu.card("INSTRUME", self.cal_inst, "Name of instrument")  
594 - hdu.card("DETNAM", self.cal_det, "Name of detector")  
595 - hdu.card("HDUCLASS", "OGIP", "")  
596 - hdu.card("HDUDOC", hdudoc, "")  
597 - for i, item in enumerate(hduclas):  
598 - key = "HDUCLAS%d" % (i+1)  
599 - hdu.card(key, item, "")  
600 - hdu.card("HDUVERS", "1.0.0", "")  
601 -  
602 - # Return  
603 - return  
604 -  
605 - def make_2D(self, array, hdu, name, unit, scale=1.0):  
606 - """  
607 - Make 2D matrix as function of energy and offset angle from a  
608 - ROOT 2D histogram. If the HDU has already the energy and  
609 - offset angle columns, this method will simply add another data  
610 - column.  
611 - If name==None, the method will not append any data column.  
612 -  
613 - Parameters:  
614 - array - ROOT 2D histogram.  
615 - hdu - FITS HDU.  
616 - name - Data column name.  
617 - unit - Data unit.  
618 - Keywords:  
619 - scale - Scaling factor for histogram values.  
620 - """  
621 - # Extract energy and offset angle vectors  
622 - energies = array.GetXaxis()  
623 - offsets = array.GetYaxis()  
624 - neng = energies.GetNbins()  
625 - noffset = offsets.GetNbins()  
626 -  
627 - # Attach columns to HDU. We do this only if they do not yet  
628 - # exist. This allows adding columns to the same HDU in successive  
629 - # calls of this method  
630 - #  
631 - # ENERG_LO  
632 - if not hdu.contains("ENERG_LO"):  
633 - hdu.append(gammalib.GFitsTableFloatCol("ENERG_LO", 1, neng))  
634 - hdu["ENERG_LO"].unit("TeV")  
635 - for ieng in range(neng):  
636 - e_lo = pow(10.0, energies.GetBinLowEdge(ieng+1))  
637 - hdu["ENERG_LO"][0,ieng] = e_lo  
638 - #  
639 - # ENERG_HI  
640 - if not hdu.contains("ENERG_HI"):  
641 - hdu.append(gammalib.GFitsTableFloatCol("ENERG_HI", 1, neng))  
642 - hdu["ENERG_HI"].unit("TeV")  
643 - for ieng in range(neng):  
644 - e_hi = pow(10.0, energies.GetBinUpEdge(ieng+1))  
645 - hdu["ENERG_HI"][0,ieng] = e_hi  
646 - #  
647 - # THETA_LO  
648 - if not hdu.contains("THETA_LO"):  
649 - hdu.append(gammalib.GFitsTableFloatCol("THETA_LO", 1, noffset))  
650 - hdu["THETA_LO"].unit("deg")  
651 - for ioff in range(noffset):  
652 - o_lo = offsets.GetBinLowEdge(ioff+1)  
653 - hdu["THETA_LO"][0,ioff] = o_lo  
654 - #  
655 - # THETA_LO  
656 - if not hdu.contains("THETA_HI"):  
657 - hdu.append(gammalib.GFitsTableFloatCol("THETA_HI", 1, noffset))  
658 - hdu["THETA_HI"].unit("deg")  
659 - for ioff in range(noffset):  
660 - o_hi = offsets.GetBinUpEdge(ioff+1)  
661 - hdu["THETA_HI"][0,ioff] = o_hi  
662 - #  
663 - # "NAME"  
664 - if name != None and not hdu.contains(name):  
665 - hdu.append(gammalib.GFitsTableFloatCol(name, 1, neng*noffset))  
666 - hdu[name].unit(unit)  
667 - hdu[name].dim([neng, noffset])  
668 - for ioff in range(noffset):  
669 - for ieng in range(neng):  
670 - index = ieng + ioff * neng  
671 - value = array.GetBinContent(ieng+1,ioff+1)  
672 - hdu[name][0,index] = value * scale  
673 -  
674 - # Collect boundary information  
675 - bd_eng = "ENERG(%.4f-%.2f)TeV" % \  
676 - (pow(10.0, energies.GetBinLowEdge(1)), \  
677 - pow(10.0, energies.GetBinUpEdge(neng)))  
678 - bd_off = "THETA(%.2f-%.2f)deg" % \  
679 - (offsets.GetBinLowEdge(1), \  
680 - offsets.GetBinUpEdge(noffset))  
681 - bd_phi = "PHI(0-360)deg"  
682 - bounds = [bd_eng, bd_off, bd_phi]  
683 -  
684 - # Return boundary information  
685 - return bounds  
686 -  
687 - def make_3D(self, array, hdu, name, unit, scale=1.0):  
688 - """  
689 - Make 3D cube as function of DETX, DETY and energy from a  
690 - ROOT 2D histogram. If the HDU has already the energy and  
691 - offset angle columns, this method will simply add another data  
692 - column.  
693 - If name==None, the method will not append any data column.  
694 -  
695 - Parameters:  
696 - array - ROOT 2D histogram.  
697 - hdu - FITS HDU.  
698 - name - Data column name.  
699 - unit - Data unit.  
700 - Keywords:  
701 - scale - Scaling factor for histogram values.  
702 - """  
703 - # Set constants  
704 - deg2sr = 0.01745329*0.01745329  
705 -  
706 - # Extract energy and offset angle vectors  
707 - energies = array.GetXaxis()  
708 - neng = energies.GetNbins()  
709 - offsets = array.GetYaxis()  
710 - noffset = offsets.GetNbins()  
711 - ewidth = [] # in MeV  
712 - for ieng in range(neng):  
713 - ewidth.append(pow(10.0, energies.GetBinUpEdge(ieng+1) +6.0) - \  
714 - pow(10.0, energies.GetBinLowEdge(ieng+1)+6.0))  
715 -  
716 - # Build DETX and DETY axes  
717 - ndet = array.GetYaxis().GetNbins()  
718 - ndets = 2*ndet  
719 - det_max = array.GetYaxis().GetBinUpEdge(ndet)  
720 - det_bin = det_max/float(ndet)  
721 - dets_lo = []  
722 - dets_hi = []  
723 - dets2 = []  
724 - for i in range(ndets):  
725 - det_lo = -det_max + i*det_bin  
726 - det_hi = det_lo + det_bin  
727 - det_val = 0.5*(det_lo + det_hi)  
728 - dets_lo.append(det_lo)  
729 - dets_hi.append(det_hi)  
730 - dets2.append(det_val*det_val)  
731 -  
732 - # Attach columns to HDU. We do this only if they do not yet  
733 - # exist. This allows adding columns to the same HDU in successive  
734 - # calls of this method  
735 - #  
736 - # DETX_LO  
737 - if not hdu.contains("DETX_LO"):  
738 - hdu.append(gammalib.GFitsTableFloatCol("DETX_LO", 1, ndets))  
739 - hdu["DETX_LO"].unit("deg")  
740 - for i in range(ndets):  
741 - hdu["DETX_LO"][0,i] = dets_lo[i]  
742 - #  
743 - # DETX_HI  
744 - if not hdu.contains("DETX_HI"):  
745 - hdu.append(gammalib.GFitsTableFloatCol("DETX_HI", 1, ndets))  
746 - hdu["DETX_HI"].unit("deg")  
747 - for i in range(ndets):  
748 - hdu["DETX_HI"][0,i] = dets_hi[i]  
749 - #  
750 - # DETY_LO  
751 - if not hdu.contains("DETY_LO"):  
752 - hdu.append(gammalib.GFitsTableFloatCol("DETY_LO", 1, ndets))  
753 - hdu["DETY_LO"].unit("deg")  
754 - for i in range(ndets):  
755 - hdu["DETY_LO"][0,i] = dets_lo[i]  
756 - #  
757 - # DETY_HI  
758 - if not hdu.contains("DETY_HI"):  
759 - hdu.append(gammalib.GFitsTableFloatCol("DETY_HI", 1, ndets))  
760 - hdu["DETY_HI"].unit("deg")  
761 - for i in range(ndets):  
762 - hdu["DETY_HI"][0,i] = dets_hi[i]  
763 - #  
764 - # ENERG_LO  
765 - if not hdu.contains("ENERG_LO"):  
766 - hdu.append(gammalib.GFitsTableFloatCol("ENERG_LO", 1, neng))  
767 - hdu["ENERG_LO"].unit("TeV")  
768 - for ieng in range(neng):  
769 - e_lo = pow(10.0, energies.GetBinLowEdge(ieng+1))  
770 - hdu["ENERG_LO"][0,ieng] = e_lo  
771 - #  
772 - # ENERG_HI  
773 - if not hdu.contains("ENERG_HI"):  
774 - hdu.append(gammalib.GFitsTableFloatCol("ENERG_HI", 1, neng))  
775 - hdu["ENERG_HI"].unit("TeV")  
776 - for ieng in range(neng):  
777 - e_hi = pow(10.0, energies.GetBinUpEdge(ieng+1))  
778 - hdu["ENERG_HI"][0,ieng] = e_hi  
779 - #  
780 - # "NAME"  
781 - if name != None and not hdu.contains(name):  
782 - hdu.append(gammalib.GFitsTableFloatCol(name, 1, ndets*ndets*neng))  
783 - hdu[name].unit(unit)  
784 - hdu[name].dim([ndets,ndets,neng])  
785 - for ix in range(ndets):  
786 - for iy in range(ndets):  
787 - for ieng in range(neng):  
788 - index = ix + (iy + ieng * ndets) * ndets  
789 - theta = math.sqrt(dets2[ix] + dets2[iy])  
790 - ioff = offsets.FindBin(theta)  
791 - if ioff > 0 and ioff <= noffset:  
792 - binsq = array.GetBinContent(ieng+1,ioff)  
793 - if binsq >= 0:  
794 - value = binsq / deg2sr / (ewidth[ieng])  
795 - hdu[name][0,index] = value * scale  
796 - #  
797 - # Debugging  
798 - if False:  
799 - for ieng in range(neng):  
800 - sum = 0.0  
801 - for ioff in range(noffset):  
802 - index = ieng + ioff * neng  
803 - binsq = array.GetBinContent(ieng+1,ioff+1)  
804 - value = binsq / deg2sr / ewidth[ieng]  
805 - sum += value  
806 - if ioff == 0:  
807 - peak = value  
808 - peaksq = binsq  
809 - print(pow(10.0, energies.GetBinLowEdge(ieng+1))+ \  
810 - pow(10.0, energies.GetBinUpEdge(ieng+1)), sum, peak, peaksq)  
811 -  
812 - # Collect boundary information  
813 - bd_detx = "DETX(%.2f-%.2f)deg" % (-det_max, det_max)  
814 - bd_dety = "DETY(%.2f-%.2f)deg" % (-det_max, det_max)  
815 - bd_eng = "ENERG(%.4f-%.2f)TeV" % \  
816 - (pow(10.0, energies.GetBinLowEdge(1)), \  
817 - pow(10.0, energies.GetBinUpEdge(neng)))  
818 - bounds = [bd_detx, bd_dety, bd_eng]  
819 -  
820 - # Return boundary information  
821 - return bounds  
822 -  
823 - def make_3D_migra(self, array, hdu, name, unit, scale=1.0):  
824 - """  
825 - Make 3D cube as function of ETRUE, MIGRA and THETA form ROOT 3D  
826 - histogram. If the HDU has already the columns, this method will  
827 - simply add another data column.  
828 - If name==None, the method will not append any data column.  
829 -  
830 - Parameters:  
831 - array - ROOT 3D histogram.  
832 - hdu - FITS HDU.  
833 - name - Data column name.  
834 - unit - Data unit.  
835 - Keywords:  
836 - scale - Scaling factor for histogram values.  
837 - """  
838 - # Extract Etrue, Eobs/Etrue and offset angle vectors  
839 - etrue = array.GetXaxis()  
840 - netrue = etrue.GetNbins()  
841 - migra = array.GetYaxis()  
842 - nmigra = migra.GetNbins()  
843 - offsets = array.GetZaxis()  
844 - noffset = offsets.GetNbins()  
845 - ewidth = [] # in MeV  
846 - for ieng in range(netrue):  
847 - ewidth.append(pow(10.0, etrue.GetBinUpEdge(ieng+1) +6.0) - \  
848 - pow(10.0, etrue.GetBinLowEdge(ieng+1)+6.0))  
849 -  
850 - # Attach columns to HDU. We do this only if they do not yet  
851 - # exist. This allows adding columns to the same HDU in successive  
852 - # calls of this method  
853 - #  
854 - # ETRUE_LO  
855 - if not hdu.contains("ETRUE_LO"):  
856 - hdu.append(gammalib.GFitsTableFloatCol("ETRUE_LO", 1, netrue))  
857 - hdu["ETRUE_LO"].unit("TeV")  
858 - for ieng in range(netrue):  
859 - e_lo = pow(10.0, etrue.GetBinLowEdge(ieng+1))  
860 - hdu["ETRUE_LO"][0,ieng] = e_lo  
861 - #  
862 - # ETRUE_HI  
863 - if not hdu.contains("ETRUE_HI"):  
864 - hdu.append(gammalib.GFitsTableFloatCol("ETRUE_HI", 1, netrue))  
865 - hdu["ETRUE_HI"].unit("TeV")  
866 - for ieng in range(netrue):  
867 - e_hi = pow(10.0, etrue.GetBinUpEdge(ieng+1))  
868 - hdu["ETRUE_HI"][0,ieng] = e_hi  
869 - #  
870 - # MIGRA_LO  
871 - if not hdu.contains("MIGRA_LO"):  
872 - hdu.append(gammalib.GFitsTableFloatCol("MIGRA_LO", 1, nmigra))  
873 - hdu["MIGRA_LO"].unit("")  
874 - for imigra in range(nmigra):  
875 - migra_lo = migra.GetBinLowEdge(imigra+1)  
876 - hdu["MIGRA_LO"][0,imigra] = migra_lo  
877 - #  
878 - # MIGRA_HI  
879 - if not hdu.contains("MIGRA_HI"):  
880 - hdu.append(gammalib.GFitsTableFloatCol("MIGRA_HI", 1, nmigra))  
881 - hdu["MIGRA_HI"].unit("")  
882 - for imigra in range(nmigra):  
883 - migra_hi = migra.GetBinUpEdge(imigra+1)  
884 - hdu["MIGRA_HI"][0,imigra] = migra_hi  
885 - #  
886 - # THETA_LO  
887 - if not hdu.contains("THETA_LO"):  
888 - hdu.append(gammalib.GFitsTableFloatCol("THETA_LO", 1, noffset))  
889 - hdu["THETA_LO"].unit("deg")  
890 - for ioff in range(noffset):  
891 - o_lo = offsets.GetBinLowEdge(ioff+1)  
892 - hdu["THETA_LO"][0,ioff] = o_lo  
893 - #  
894 - # THETA_HI  
895 - if not hdu.contains("THETA_HI"):  
896 - hdu.append(gammalib.GFitsTableFloatCol("THETA_HI", 1, noffset))  
897 - hdu["THETA_HI"].unit("deg")  
898 - for ioff in range(noffset):  
899 - o_hi = offsets.GetBinUpEdge(ioff+1)  
900 - hdu["THETA_HI"][0,ioff] = o_hi  
901 - #  
902 - # "NAME"  
903 - if name != None and not hdu.contains(name):  
904 - hdu.append(gammalib.GFitsTableFloatCol(name, 1, netrue*nmigra*noffset))  
905 - hdu[name].unit(unit)  
906 - hdu[name].dim([netrue, nmigra, noffset])  
907 - for ioff in range(noffset):  
908 - for imigra in range(nmigra):  
909 - for ieng in range(netrue):  
910 - index = ieng + (imigra + ioff * nmigra) * netrue  
911 - value = array.GetBinContent(ieng+1,imigra+1,ioff+1)  
912 - hdu[name][0,index] = value * scale  
913 -  
914 - # Collect boundary information  
915 - bd_eng = "ETRUE(%.4f-%.2f)TeV" % \  
916 - (pow(10.0, etrue.GetBinLowEdge(1)), \  
917 - pow(10.0, etrue.GetBinUpEdge(netrue)))  
918 - bd_migra = "MIGRA(%.4f-%.2f)TeV" % \  
919 - (pow(10.0, migra.GetBinLowEdge(1)), \  
920 - pow(10.0, migra.GetBinUpEdge(nmigra)))  
921 - bd_off = "THETA(%.2f-%.2f)deg" % \  
922 - (offsets.GetBinLowEdge(1), \  
923 - offsets.GetBinUpEdge(noffset))  
924 - bd_phi = "PHI(0-360)deg"  
925 - bounds = [bd_eng, bd_migra, bd_off, bd_phi]  
926 -  
927 - # Return boundary information  
928 - return bounds  
929 -  
930 - def root2caldb(self, filename, rebin=False, psftype="Gauss", eascale=1.0, bgdscale=1.0):  
931 - """  
932 - Translate ROOT to CALDB information.  
933 -  
934 - Parameters:  
935 - filename - ROOT 2D performance filename.  
936 - Keywords:  
937 - rebin - Rebin Etrue histogram (useful for Prod1 IFAE runs)  
938 - psftype - PSF type  
939 - eascale - Effective area scaling factor  
940 - bgdscale - Background scaling factor  
941 - """  
942 - # Open ROOT performance file  
943 - file = TFile(filename)  
944 -  
945 - # Create effective area  
946 - self.root2ea(file, rebin=rebin, eascale=eascale)  
947 -  
948 - # Create point spread function  
949 - self.root2psf(file, psftype)  
950 -  
951 - # Create energy dispersion  
952 - self.root2edisp_migra(file)  
953 -  
954 - # Create background  
955 - #self.root2bgd(file)  
956 - self.root2bgd3D(file, bgdscale=bgdscale)  
957 -  
958 - # Return  
959 - return  
960 -  
961 - def root2ea(self, file, rebin=False, eascale=1.0):  
962 - """  
963 - Translate ROOT to CALDB effective area extension. The following ROOT  
964 - histograms are used:  
965 -  
966 - EffectiveAreaEtrue_offaxis -> EFFAREA  
967 - EffectiveArea_offaxis -> EFFAREA_RECO  
968 -  
969 - Parameters:  
970 - file - ROOT file.  
971 - Keywords:  
972 - rebin - Rebin Etrue histogram (useful for Prod1 IFAE runs)  
973 - eascale - Effective area scaling factor  
974 - """  
975 - # Continue only if effective area HDU has been opened  
976 - if self.hdu_ea != None:  
977 -  
978 - # Allocate ROOT 2D arrays  
979 - etrue = TH2F()  
980 - ereco = TH2F()  
981 - file.GetObject("EffectiveAreaEtrue_offaxis", etrue)  
982 - file.GetObject("EffectiveArea_offaxis", ereco)  
983 -  
984 - # Rebin etrue histogram  
985 - if rebin:  
986 - etrue.RebinX(10)  
987 - neng = etrue.GetXaxis().GetNbins()  
988 - noffset = etrue.GetYaxis().GetNbins()  
989 - for ioff in range(noffset):  
990 - for ieng in range(neng):  
991 - value = etrue.GetBinContent(ieng+1,ioff+1) / 10.0  
992 - etrue.SetBinContent(ieng+1,ioff+1,value)  
993 -  
994 - # Scale histograms (if needed)  
995 - if eascale != 1.0:  
996 - neng = etrue.GetXaxis().GetNbins()  
997 - noffset = etrue.GetYaxis().GetNbins()  
998 - for ioff in range(noffset):  
999 - for ieng in range(neng):  
1000 - value = etrue.GetBinContent(ieng+1,ioff+1) * eascale  
1001 - etrue.SetBinContent(ieng+1,ioff+1,value)  
1002 - neng = ereco.GetXaxis().GetNbins()  
1003 - noffset = ereco.GetYaxis().GetNbins()  
1004 - for ioff in range(noffset):  
1005 - for ieng in range(neng):  
1006 - value = ereco.GetBinContent(ieng+1,ioff+1) * eascale  
1007 - ereco.SetBinContent(ieng+1,ioff+1,value)  
1008 -  
1009 - # Write boundaries (use Ereco boundaries)  
1010 - bounds = self.make_2D(ereco, self.hdu_ea, None, "m2")  
1011 - for b in bounds:  
1012 - self.ea_bounds.append(b)  
1013 - self.set_cif_keywords(self.hdu_ea, self.ea_name, \  
1014 - self.ea_bounds, self.ea_desc)  
1015 -  
1016 - # EFFAREA  
1017 - self.make_2D(etrue, self.hdu_ea, "EFFAREA", "m2")  
1018 -  
1019 - # EFFAREA_RECO  
1020 - self.make_2D(ereco, self.hdu_ea, "EFFAREA_RECO", "m2")  
1021 -  
1022 - # Return  
1023 - return  
1024 -  
1025 - def root2psf(self, file, psftype):  
1026 - """  
1027 - Translate ROOT to CALDB point spread function extension.  
1028 - Parameters:  
1029 - file - ROOT file.  
1030 - psftype - PSF type (Gauss, King)  
1031 - Keywords:  
1032 - None  
1033 - """  
1034 - # Continue only if point spread function HDU has been opened  
1035 - if self.hdu_psf != None:  
1036 -  
1037 - # King profile PSF  
1038 - if psftype == "King":  
1039 - self.root2psf_king(file)  
1040 - else:  
1041 - self.root2psf_gauss(file)  
1042 -  
1043 - # Return  
1044 - return  
1045 -  
1046 - def root2psf_gauss(self, file):  
1047 - """  
1048 - Translate ROOT to CALDB point spread function extension. The following  
1049 - ROOT histograms are used:  
1050 -  
1051 - 1/(2*pi*SIGMA_1) -> SCALE  
1052 - AngRes_offaxis -> SIGMA_1 (scaling: 1/0.8)  
1053 - 0.0 -> AMPL_2  
1054 - 0.0 -> SIGMA_2  
1055 - 0.0 -> AMPL_3  
1056 - 0.0 -> SIGMA_3  
1057 -  
1058 - Parameters:  
1059 - file - ROOT file.  
1060 - Keywords:  
1061 - None  
1062 - """  
1063 - # Continue only if point spread function HDU has been opened  
1064 - if self.hdu_psf != None:  
1065 -  
1066 - # Allocate ROOT 2D array  
1067 - r68 = TH2F()  
1068 -  
1069 - # Read 68% containment histogram  
1070 - file.GetObject("AngRes_offaxis", r68)  
1071 - neng = r68.GetXaxis().GetNbins()  
1072 - noffset = r68.GetYaxis().GetNbins()  
1073 -  
1074 - # Converts 68% -> 1 sigma  
1075 - r68_to_sigma = 0.6624305  
1076 - for ioff in range(noffset):  
1077 - for ieng in range(neng):  
1078 - sigma = r68.GetBinContent(ieng+1,ioff+1) * r68_to_sigma  
1079 - r68.SetBinContent(ieng+1,ioff+1,sigma)  
1080 -  
1081 - # Compute scale histogram  
1082 - scale = r68.Clone()  
1083 - for ioff in range(noffset):  
1084 - for ieng in range(neng):  
1085 - integral = 2.0 * math.pi * r68.GetBinContent(ieng+1,ioff+1)  
1086 - if integral > 0.0:  
1087 - value = 1.0 / integral  
1088 - else:  
1089 - value = 0.0  
1090 - scale.SetBinContent(ieng+1,ioff+1,value)  
1091 -  
1092 - # Set zero histogram  
1093 - zero = r68.Clone()  
1094 - for ioff in range(noffset):  
1095 - for ieng in range(neng):  
1096 - zero.SetBinContent(ieng+1,ioff+1,0.0)  
1097 -  
1098 - # Set boundaries  
1099 - bounds = self.make_2D(r68, self.hdu_psf, None, "deg")  
1100 - for b in bounds:  
1101 - self.psf_bounds.append(b)  
1102 - self.psf_bounds.append("PSF(GAUSS)")  
1103 - self.set_cif_keywords(self.hdu_psf, self.psf_name, \  
1104 - self.psf_bounds, self.psf_desc)  
1105 -  
1106 - # SCALE  
1107 - self.make_2D(scale, self.hdu_psf, "SCALE", "")  
1108 -  
1109 - # SIGMA_1  
1110 - self.make_2D(r68, self.hdu_psf, "SIGMA_1", "deg")  
1111 -  
1112 - # AMPL_2  
1113 - self.make_2D(zero, self.hdu_psf, "AMPL_2", "")  
1114 -  
1115 - # SIGMA_2  
1116 - self.make_2D(zero, self.hdu_psf, "SIGMA_2", "deg")  
1117 -  
1118 - # AMPL_3  
1119 - self.make_2D(zero, self.hdu_psf, "AMPL_3", "")  
1120 -  
1121 - # SIGMA_3  
1122 - self.make_2D(zero, self.hdu_psf, "SIGMA_3", "deg")  
1123 -  
1124 - # Return  
1125 - return  
1126 -  
1127 - def root2psf_king(self, file):  
1128 - """  
1129 - Translate ROOT to CALDB point spread function extension. The following  
1130 - ROOT histograms are used:  
1131 -  
1132 - AngRes_offaxis  
1133 - AngRes80_offaxis  
1134 -  
1135 - Parameters:  
1136 - file - ROOT file.  
1137 - Keywords:  
1138 - None  
1139 - """  
1140 - # Continue only if point spread function HDU has been opened  
1141 - if self.hdu_psf != None:  
1142 -  
1143 - # Allocate ROOT 2D arrays  
1144 - r68 = TH2F()  
1145 - r80 = TH2F()  
1146 -  
1147 - # Read 68% and 80% containment histograms  
1148 - file.GetObject("AngRes_offaxis", r68)  
1149 - file.GetObject("AngRes80_offaxis", r80)  
1150 - neng = r68.GetXaxis().GetNbins()  
1151 - noffset = r68.GetYaxis().GetNbins()  
1152 -  
1153 - # Initialise parameter maps by cloning the r68 2D map  
1154 - gamma2D = r68.Clone()  
1155 - sigma2D = r68.Clone()  
1156 -  
1157 - # Compute gamma and sigma values  
1158 - for ioff in range(noffset):  
1159 -  
1160 - # Initialise last results  
1161 - last_gamma = 0.0  
1162 - last_sigma = 0.0  
1163 -  
1164 - for ieng in range(neng):  
1165 -  
1166 - # Extract radii  
1167 - r_68 = r68.GetBinContent(ieng+1,ioff+1)  
1168 - r_80 = r80.GetBinContent(ieng+1,ioff+1)  
1169 -  
1170 - # Initialise results  
1171 - gamma = 0.0  
1172 - sigma = 0.0  
1173 -  
1174 - # Continue only if both radii are positive  
1175 - if r_68 > 0 and r_80 > 0:  
1176 -  
1177 - # Derive constants for equation to solve  
1178 - a = 1.0 - 0.68  
1179 - b = 1.0 - 0.80  
1180 - c = r_68*r_68/(r_80*r_80)  
1181 -  
1182 - # Solve equation (a^x-1)/(b^x-1)=c for x using secant  
1183 - # method. Stop when we are better than 1e-6.  
1184 - x1 = -0.5  
1185 - x2 = -1.0  
1186 - f1 = (math.pow(a,x1) - 1.0)/(math.pow(b,x1) - 1.0) - c  
1187 - f2 = (math.pow(a,x2) - 1.0)/(math.pow(b,x2) - 1.0) - c  
1188 - iter = 0  
1189 - while True:  
1190 - x = x1 - f1 * (x1-x2)/(f1-f2)  
1191 - f = (math.pow(a,x) - 1.0)/(math.pow(b,x) - 1.0) - c  
1192 - iter += 1  
1193 - if abs(f) < 1.0e-6:  
1194 - break  
1195 - else:  
1196 - f2 = f1  
1197 - x2 = x1  
1198 - f1 = f  
1199 - x1 = x  
1200 -  
1201 - # Compute gamma.  
1202 - if x < 0.0:  
1203 - gamma = 1.0 - 1.0/x  
1204 - else:  
1205 - gamma = 1.0  
1206 -  
1207 - # Compute sigma  
1208 - denominator = 2.0 * gamma * (math.pow(a, x) - 1.0)  
1209 - if denominator > 0.0:  
1210 - sigma = r_68 * math.sqrt(1.0/denominator)  
1211 - else:  
1212 - denominator = 2.0 * gamma * (math.pow(b, x) - 1.0)  
1213 - if denominator > 0.0:  
1214 - sigma = r_80 * math.sqrt(1.0/denominator)  
1215 - else:  
1216 - gamma = 0.0  
1217 - sigma = 0.0  
1218 -  
1219 - # Handle special case that no results were found.  
1220 - # This takes care of pixels that are ill defined  
1221 - # in the MC file.  
1222 - if gamma == 0.0 and sigma == 0.0:  
1223 - gamma = last_gamma  
1224 - sigma = last_sigma  
1225 -  
1226 - # Store surrent result as last result  
1227 - last_gamma = gamma  
1228 - last_sigma = sigma  
1229 -  
1230 - # Show results on console  
1231 - #print(ioff,ieng,gamma,sigma,x,f,iter)  
1232 -  
1233 - # Set bin contents  
1234 - gamma2D.SetBinContent(ieng+1,ioff+1,gamma)  
1235 - sigma2D.SetBinContent(ieng+1,ioff+1,sigma)  
1236 -  
1237 - # Set boundaries  
1238 - bounds = self.make_2D(r68, self.hdu_psf, None, "deg")  
1239 - for b in bounds:  
1240 - self.psf_bounds.append(b)  
1241 - self.psf_bounds.append("PSF(KING)")  
1242 - self.set_cif_keywords(self.hdu_psf, self.psf_name, \  
1243 - self.psf_bounds, self.psf_desc)  
1244 -  
1245 - # GAMMA  
1246 - self.make_2D(gamma2D, self.hdu_psf, "GAMMA", "")  
1247 -  
1248 - # SIGMA  
1249 - self.make_2D(sigma2D, self.hdu_psf, "SIGMA", "deg")  
1250 -  
1251 - # Return  
1252 - return  
1253 -  
1254 - def root2edisp_2D(self, file):  
1255 - """  
1256 - Translate ROOT to CALDB energy dispersion extension. The following ROOT  
1257 - histograms are used:  
1258 -  
1259 - ERes_offaxis -> ERES  
1260 - Ebias_offaxis -> EBIAS  
1261 -  
1262 - Parameters:  
1263 - file - ROOT file.  
1264 - Keywords:  
1265 - None  
1266 - """  
1267 - # Continue only if energy dispersion HDU has been opened  
1268 - if self.hdu_edisp != None:  
1269 -  
1270 - # Allocate ROOT 2D array  
1271 - eres = TH2F()  
1272 - ebias = TH2F()  
1273 - file.GetObject("ERes_offaxis", eres)  
1274 - file.GetObject("Ebias_offaxis", ebias)  
1275 -  
1276 - # Set boundaries  
1277 - bounds = self.make_2D(eres, self.hdu_edisp, None, "deg")  
1278 - for b in bounds:  
1279 - self.edisp_bounds.append(b)  
1280 - self.set_cif_keywords(self.hdu_edisp, self.edisp_name, \  
1281 - self.edisp_bounds, self.edisp_desc)  
1282 -  
1283 - # ERES  
1284 - self.make_2D(eres, self.hdu_edisp, "ERES", "Ereco/Etrue")  
1285 -  
1286 - # EBIAS  
1287 - self.make_2D(ebias, self.hdu_edisp, "EBIAS", "Ereco/Etrue")  
1288 -  
1289 - # Return  
1290 - return  
1291 -  
1292 - def root2edisp_migra(self, file):  
1293 - """  
1294 - Translate ROOT to CALDB energy dispersion extension. The following ROOT  
1295 - histograms are used:  
1296 -  
1297 - EestOverEtrue_offaxis -> MATRIX  
1298 -  
1299 - Parameters:  
1300 - file - ROOT file.  
1301 - Keywords:  
1302 - None  
1303 - """  
1304 - # Continue only if energy dispersion HDU has been opened  
1305 - if self.hdu_edisp != None:  
1306 -  
1307 - # Allocate ROOT 3D array  
1308 - matrix = TH3F()  
1309 - file.GetObject("EestOverEtrue_offaxis", matrix)  
1310 -  
1311 - # Set boundaries  
1312 - bounds = self.make_3D_migra(matrix, self.hdu_edisp, None, "")  
1313 - for b in bounds:  
1314 - self.edisp_bounds.append(b)  
1315 - self.set_cif_keywords(self.hdu_edisp, self.edisp_name, \  
1316 - self.edisp_bounds, self.edisp_desc)  
1317 -  
1318 - # MATRIX  
1319 - self.make_3D_migra(matrix, self.hdu_edisp, "MATRIX", "")  
1320 -  
1321 - # Return  
1322 - return  
1323 -  
1324 - def root2bgd(self, file):  
1325 - """  
1326 - Translate ROOT to CALDB background extension. The following ROOT  
1327 - histograms are used:  
1328 -  
1329 - BGRatePerSqDeg_offaxis -> BGD  
1330 - BGRatePerSqDeg_offaxis -> BGD_RECO  
1331 -  
1332 - Parameters:  
1333 - file - ROOT file.  
1334 - Keywords:  
1335 - None  
1336 - """  
1337 - # Continue only if background HDU has been opened  
1338 - if self.hdu_bgd != None:  
1339 -  
1340 - # Allocate ROOT 2D array  
1341 - array = TH2F()  
1342 - file.GetObject("BGRatePerSqDeg_offaxis", array)  
1343 -  
1344 - # Set boundaries  
1345 - bounds = self.make_2D(array, self.hdu_bgd, None, "deg")  
1346 - for b in bounds:  
1347 - self.bgd_bounds.append(b)  
1348 - self.set_cif_keywords(self.hdu_bgd, self.bgd_name, \  
1349 - self.bgd_bounds, self.bgd_desc)  
1350 -  
1351 - # BGD  
1352 - self.make_2D(array, self.hdu_bgd, "BGD", "")  
1353 -  
1354 - # BGD_RECO  
1355 - self.make_2D(array, self.hdu_bgd, "BGD_RECO", "")  
1356 -  
1357 - # Return  
1358 - return  
1359 -  
1360 - def root2bgd3D(self, file, bgdscale=1.0):  
1361 - """  
1362 - Translate ROOT to CALDB background extension. The following ROOT  
1363 - histograms are used:  
1364 -  
1365 - BGRatePerSqDeg_offaxis -> BGD  
1366 -  
1367 - Parameters:  
1368 - file - ROOT file.  
1369 - Keywords:  
1370 - scale - Background rate scaling factor  
1371 - """  
1372 - # Continue only if background HDU has been opened  
1373 - if self.hdu_bgd != None:  
1374 -  
1375 - # Allocate ROOT 2D array  
1376 - array = TH2F()  
1377 - file.GetObject("BGRatePerSqDeg_offaxis", array)  
1378 -  
1379 - # Set boundaries  
1380 - bounds = self.make_3D(array, self.hdu_bgd, None, "deg")  
1381 - for b in bounds:  
1382 - self.bgd_bounds.append(b)  
1383 - self.set_cif_keywords(self.hdu_bgd, self.bgd_name, \  
1384 - self.bgd_bounds, self.bgd_desc)  
1385 -  
1386 - # BGD (reconstructed energy)  
1387 - self.make_3D(array, self.hdu_bgd, "BGD", "MeV^-1 s^-1 sr^-1", scale=bgdscale)  
1388 -  
1389 - # Return  
1390 - return  
1391 -  
1392 - def set_cif_keywords(self, hdu, name, bounds, desc):  
1393 - """  
1394 - Set standard CIF keywords for extension.  
1395 - """  
1396 - # Set standard CIF keywords  
1397 - hdu.card("CSYSNAME", "XMA_POL", "")  
1398 - hdu.card("CCLS0001", self.cal_class, "Calibration class")  
1399 - hdu.card("CDTP0001", self.cal_type, "Calibration type")  
1400 - hdu.card("CCNM0001", name, "Calibration name")  
1401 -  
1402 - # Set boundary keywords  
1403 - for n in range(9):  
1404 - keyname = "CBD%d0001" % (n+1)  
1405 - if n >= len(bounds):  
1406 - value = "NONE"  
1407 - else:  
1408 - value = bounds[n]  
1409 - hdu.card(keyname, value, "Calibration boundary")  
1410 -  
1411 - # Set validity keywords  
1412 - hdu.card("CVSD0001", self.val_date, "Calibration validity start date (UTC)")  
1413 - hdu.card("CVST0001", self.val_time, "Calibration validity start time (UTC)")  
1414 - hdu.card("CDEC0001", desc, "Calibration description")  
1415 -  
1416 - # Return  
1417 - return  
1418 -  
1419 -  
1420 -# ================= #  
1421 -# Set test database #  
1422 -# ================= #  
1423 -def set_test():  
1424 - """  
1425 - Set Prod1 IFAE database.  
1426 - """  
1427 - # Set database attributes  
1428 - path = "/project-data/cta/performance/prod1/IFAEOffaxisPerformanceBEI_May2012"  
1429 - rebin = True  
1430 -  
1431 - # Set database content  
1432 - db = [{'inst': "e", 'id': "IFAE20120510_50h",  
1433 - 'path': path, 'rebin': rebin, 'psftype': "Gauss",  
1434 - 'file': "SubarrayE_IFAE_50hours_20120510_offaxis.root"},  
1435 - {'inst': "e", 'id': "IFAE20120510_50h_King",  
1436 - 'path': path, 'rebin': rebin, 'psftype': "King",  
1437 - 'file': "SubarrayE_IFAE_50hours_20120510_offaxis.root"}]  
1438 -  
1439 - # Return database  
1440 - return db  
1441 -  
1442 -  
1443 -# ======================= #  
1444 -# Set Prod1 IFAE database #  
1445 -# ======================= #  
1446 -def set_prod1_ifae():  
1447 - """  
1448 - Set Prod1 IFAE database.  
1449 - """  
1450 - # Set database attributes  
1451 - path = "/project-data/cta/performance/prod1/IFAEOffaxisPerformanceBEI_May2012"  
1452 - rebin = True  
1453 - psftype = "Gauss"  
1454 -  
1455 - # Set database content  
1456 - db = [{'inst': "b", 'id': "IFAE20120510_50h",  
1457 - 'path': path, 'rebin': rebin, 'psftype': psftype,  
1458 - 'file': "SubarrayB_IFAE_50hours_20120510_offaxis.root"},  
1459 - {'inst': "e", 'id': "IFAE20120510_50h",  
1460 - 'path': path, 'rebin': rebin, 'psftype': psftype,  
1461 - 'file': "SubarrayE_IFAE_50hours_20120510_offaxis.root"},  
1462 - {'inst': "i", 'id': "IFAE20120510_50h",  
1463 - 'path': path, 'rebin': rebin, 'psftype': psftype,  
1464 - 'file': "SubarrayI_IFAE_50hours_20120510_offaxis.root"}]  
1465 -  
1466 - # Return database  
1467 - return db  
1468 -  
1469 -  
1470 -# ======================= #  
1471 -# Set Prod2 DESY database #  
1472 -# ======================= #  
1473 -def set_prod2_desy():  
1474 - """  
1475 - Set Prod2 DESY database.  
1476 - """  
1477 - # Set database attributes  
1478 - path_aar = "/project-data/cta/performance/prod2/Performance_DESY_20140131_Aar"  
1479 - path_ten = "/project-data/cta/performance/prod2/Performance_DESY_20140131_Tenerife_50h"  
1480 - rebin = False  
1481 - psftype = "Gauss"  
1482 - eascale = 1.0 / 0.68 # Prod 2 DESY effective areas are for 68% containment  
1483 -  
1484 - # Set database content  
1485 - db = [{'inst': "aar", 'id': "DESY20140105_50h",  
1486 - 'path': path_aar, 'rebin': rebin, 'psftype': psftype, 'eascale': eascale,  
1487 - 'file': "DESY.d20140105.Erec1.V3.ID0NIM2.prod2-Aar-NS.S.2a.180000s.root"},  
1488 - {'inst': "aar", 'id': "DESY20140105_50h_0deg",  
1489 - 'path': path_aar, 'rebin': rebin, 'psftype': psftype, 'eascale': eascale,  
1490 - 'file': "DESY.d20140105.Erec1.V3.ID0_0degNIM2.prod2-Aar-NS.S.2a.180000s.root"},  
1491 - {'inst': "aar", 'id': "DESY20140105_50h_180deg",  
1492 - 'path': path_aar, 'rebin': rebin, 'psftype': psftype, 'eascale': eascale,  
1493 - 'file': "DESY.d20140105.Erec1.V3.ID0_180degNIM2.prod2-Aar-NS.S.2a.180000s.root"},  
1494 - {'inst': "aar", 'id': "DESY20140105_50h_noLST",  
1495 - 'path': path_aar, 'rebin': rebin, 'psftype': psftype, 'eascale': eascale,  
1496 - 'file': "DESY.d20140105.Erec1.V3.ID0NIM2.prod2-Aar-NS.S.2a-noLST.180000s.root"},  
1497 - {'inst': "aar", 'id': "DESY20140105_50h_0deg_noLST",  
1498 - 'path': path_aar, 'rebin': rebin, 'psftype': psftype, 'eascale': eascale,  
1499 - 'file': "DESY.d20140105.Erec1.V3.ID0_0degNIM2.prod2-Aar-NS.S.2a-noLST.180000s.root"},  
1500 - {'inst': "aar", 'id': "DESY20140105_50h_180deg_noLST",  
1501 - 'path': path_aar, 'rebin': rebin, 'psftype': psftype, 'eascale': eascale,  
1502 - 'file': "DESY.d20140105.Erec1.V3.ID0_180degNIM2.prod2-Aar-NS.S.2a-noLST.180000s.root"},  
1503 -  
1504 - {'inst': "aar500", 'id': "DESY20140105_50h",  
1505 - 'path': path_aar, 'rebin': rebin, 'psftype': psftype, 'eascale': eascale,  
1506 - 'file': "DESY.d20140105.Erec1.V3.ID0NIM2.prod2-Aar-500m-NS.S.2a.180000s.root"},  
1507 - {'inst': "aar500", 'id': "DESY20140105_50h_0deg",  
1508 - 'path': path_aar, 'rebin': rebin, 'psftype': psftype, 'eascale': eascale,  
1509 - 'file': "DESY.d20140105.Erec1.V3.ID0_0degNIM2.prod2-Aar-500m-NS.S.2a.180000s.root"},  
1510 - {'inst': "aar500", 'id': "DESY20140105_50h_180deg",  
1511 - 'path': path_aar, 'rebin': rebin, 'psftype': psftype, 'eascale': eascale,  
1512 - 'file': "DESY.d20140105.Erec1.V3.ID0_180degNIM2.prod2-Aar-500m-NS.S.2a.180000s.root"},  
1513 - {'inst': "aar", 'id': "DESY20140105_50h_noLST",  
1514 - 'path': path_aar, 'rebin': rebin, 'psftype': psftype, 'eascale': eascale,  
1515 - 'file': "DESY.d20140105.Erec1.V3.ID0NIM2.prod2-Aar-500m-NS.S.2a-noLST.180000s.root"},  
1516 - {'inst': "aar", 'id': "DESY20140105_50h_0deg_noLST",  
1517 - 'path': path_aar, 'rebin': rebin, 'psftype': psftype, 'eascale': eascale,  
1518 - 'file': "DESY.d20140105.Erec1.V3.ID0_0degNIM2.prod2-Aar-500m-NS.S.2a-noLST.180000s.root"},  
1519 - {'inst': "aar", 'id': "DESY20140105_50h_180deg_noLST",  
1520 - 'path': path_aar, 'rebin': rebin, 'psftype': psftype, 'eascale': eascale,  
1521 - 'file': "DESY.d20140105.Erec1.V3.ID0_180degNIM2.prod2-Aar-500m-NS.S.2a-noLST.180000s.root"},  
1522 -  
1523 - {'inst': "tenerife", 'id': "DESY20140105_50h",  
1524 - 'path': path_ten, 'rebin': rebin, 'psftype': psftype, 'eascale': eascale,  
1525 - 'file': "DESY.d20140105.Erec1.V3.ID0NIM2.prod2-Tenerife-NS.N.2NN.180000s.root"},  
1526 - {'inst': "tenerife", 'id': "DESY20140105_50h_0deg",  
1527 - 'path': path_ten, 'rebin': rebin, 'psftype': psftype, 'eascale': eascale,  
1528 - 'file': "DESY.d20140105.Erec1.V3.ID0_0degNIM2.prod2-Tenerife-NS.N.2NN.180000s.root"},  
1529 - {'inst': "tenerife", 'id': "DESY20140105_50h_180deg",  
1530 - 'path': path_ten, 'rebin': rebin, 'psftype': psftype, 'eascale': eascale,  
1531 - 'file': "DESY.d20140105.Erec1.V3.ID0_180degNIM2.prod2-Tenerife-NS.N.2NN.180000s.root"},  
1532 - {'inst': "tenerife", 'id': "DESY20140105_50h_noLST",  
1533 - 'path': path_ten, 'rebin': rebin, 'psftype': psftype, 'eascale': eascale,  
1534 - 'file': "DESY.d20140105.Erec1.V3.ID0NIM2.prod2-Tenerife-NS.N.2NN-MST.180000s.root"},  
1535 - {'inst': "tenerife", 'id': "DESY20140105_50h_0deg_noLST",  
1536 - 'path': path_ten, 'rebin': rebin, 'psftype': psftype, 'eascale': eascale,  
1537 - 'file': "DESY.d20140105.Erec1.V3.ID0_0degNIM2.prod2-Tenerife-NS.N.2NN-MST.180000s.root"},  
1538 - {'inst': "tenerife", 'id': "DESY20140105_50h_180deg_noLST",  
1539 - 'path': path_ten, 'rebin': rebin, 'psftype': psftype, 'eascale': eascale,  
1540 - 'file': "DESY.d20140105.Erec1.V3.ID0_180degNIM2.prod2-Tenerife-NS.N.2NN-MST.180000s.root"}  
1541 - ]  
1542 -  
1543 - # Return database  
1544 - return db  
1545 -  
1546 -  
1547 -# ======================== #  
1548 -# Main routine entry point #  
1549 -# ======================== #  
1550 -if __name__ == '__main__':  
1551 - """  
1552 - This script translates a CTA ROOT 2D performance files into a CALDB  
1553 - compliant CTA response. The script will create the response for 3  
1554 - observations to illustrate the run-wise file structure.  
1555 -  
1556 - The CTA ROOT 2D performance files contain the following histograms:  
1557 - - DiffSens_offaxis (2D) - Differential sensitivity  
1558 - - EffectiveArea_offaxis (2D) - Effective area (full containment?, reco energy?)  
1559 - - EffectiveArea80_offaxis (2D) - Effective area for 80% source containment  
1560 - - AngRes_offaxis (2D) - Angular resolution 68% containment  
1561 - - AngRes80_offaxis (2D) - Angular resolution 80% containment  
1562 - - BGRatePerSqDeg_offaxis (2D) - Background rate per square degree [counts/s/deg2]  
1563 - - BGRate_offaxis (2D) - Background rate  
1564 - - EffectiveAreaEtrue_offaxis (2D) - Effective area in true energy (finer bins)  
1565 - - MigMatrix_offaxis (3D) - Migration matrix  
1566 - - EestOverEtrue_offaxis (3D) - Migration matrix  
1567 - - ERes_offaxis (2D) - Energy Resolution  
1568 - - Ebias_offaxis (2D) - Energy bias  
1569 - """  
1570 - # Get database  
1571 - #entries = set_test()  
1572 - #entries = set_prod1_ifae()  
1573 - entries = set_prod2_desy()  
1574 -  
1575 - # Loop over entries  
1576 - for entry in entries:  
1577 -  
1578 - # Get attributes  
1579 - inst = entry['inst']  
1580 - path = entry['path']  
1581 - id = entry['id']  
1582 - rebin = entry['rebin']  
1583 - psftype = entry['psftype']  
1584 - if entry.has_key('eascale'):  
1585 - eascale = entry['eascale']  
1586 - else:  
1587 - eascale = 1.0  
1588 - if entry.has_key('bgdscale'):  
1589 - bgdscale = entry['bgdscale']  
1590 - else:  
1591 - bgdscale = 1.0  
1592 - filename = path+"/"+entry['file']  
1593 -  
1594 - # Allocate caldb entry  
1595 - irf = caldb(inst, id)  
1596 -  
1597 - # Open calibration file  
1598 - irf.open("file")  
1599 -  
1600 - # Translate ROOT to CALDB information  
1601 - irf.root2caldb(filename, rebin=rebin, psftype=psftype, eascale=eascale, bgdscale=bgdscale)  
examples/generate_prod3_irfs.py 0 → 100755
  1 +#! /usr/bin/env python
  2 +# ==========================================================================
  3 +# Generates Prod3 IRFs from ROOT performance files.
  4 +#
  5 +# Copyright (C) 2016 Juergen Knoedlseder
  6 +#
  7 +# This program is free software: you can redistribute it and/or modify
  8 +# it under the terms of the GNU General Public License as published by
  9 +# the Free Software Foundation, either version 3 of the License, or
  10 +# (at your option) any later version.
  11 +#
  12 +# This program is distributed in the hope that it will be useful,
  13 +# but WITHOUT ANY WARRANTY; without even the implied warranty of
  14 +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15 +# GNU General Public License for more details.
  16 +#
  17 +# You should have received a copy of the GNU General Public License
  18 +# along with this program. If not, see <http://www.gnu.org/licenses/>.
  19 +#
  20 +# ==========================================================================
  21 +import gammalib
  22 +import cscripts
  23 +
  24 +
  25 +# ======================= #
  26 +# Set Prod3 DESY database #
  27 +# ======================= #
  28 +def set_prod3_desy():
  29 + """
  30 + Set Prod3 DESY database.
  31 + """
  32 + # Set database attributes
  33 + path = "/project-data/cta/performance/prod3"
  34 + psftype = "Gauss"
  35 +
  36 + # Set database content
  37 + db = [{'inst': "prod3", 'id': "South_50h", 'psftype': psftype, 'path': path,
  38 + 'file': "DESY.d20160304.V5.ID0NIM2.prod3-paranalp05-NN.S.3HB1-ND-3.180000s.root"}
  39 + ]
  40 +
  41 + # Return database
  42 + return db
  43 +
  44 +
  45 +# ======================== #
  46 +# Main routine entry point #
  47 +# ======================== #
  48 +if __name__ == '__main__':
  49 +
  50 + # Get database
  51 + entries = set_prod3_desy()
  52 +
  53 + # Loop over entries
  54 + for entry in entries:
  55 +
  56 + # Set optional attributes
  57 + if entry.has_key('rebin'):
  58 + rebin = entry['rebin']
  59 + else:
  60 + rebin = False
  61 + if entry.has_key('eascale'):
  62 + eascale = entry['eascale']
  63 + else:
  64 + eascale = 1.0
  65 + if entry.has_key('bgdscale'):
  66 + bgdscale = entry['bgdscale']
  67 + else:
  68 + bgdscale = 1.0
  69 +
  70 + # Set filename
  71 + filename = entry['path']+"/"+entry['file']
  72 +
  73 + # Set-up csroot2caldb
  74 + caldb = cscripts.csroot2caldb()
  75 + caldb["infile"] = filename
  76 + caldb["outdir"] = "prod3"
  77 + caldb["inst"] = entry['inst']
  78 + caldb["id"] = entry['id']
  79 + caldb["version"] = entry['inst']
  80 + caldb["analysis"] = "DESY"
  81 + caldb["psftype"] = entry['psftype']
  82 + caldb["rebin"] = rebin
  83 + caldb["eascale"] = eascale
  84 + caldb["bgdscale"] = bgdscale
  85 + caldb["logfile"] = "generate_prod3_irfs.log"
  86 +
  87 + # Add CALDB entry
  88 + caldb.execute()
test/data/irf.root 0 → 100644
No preview for this file type
test/test_csroot2caldb.py 0 → 100644
  1 +#! /usr/bin/env python
  2 +# ==========================================================================
  3 +# This scripts performs unit tests for the csroot2caldb script.
  4 +#
  5 +# Copyright (C) 2016 Juergen Knoedlseder
  6 +#
  7 +# This program is free software: you can redistribute it and/or modify
  8 +# it under the terms of the GNU General Public License as published by
  9 +# the Free Software Foundation, either version 3 of the License, or
  10 +# (at your option) any later version.
  11 +#
  12 +# This program is distributed in the hope that it will be useful,
  13 +# but WITHOUT ANY WARRANTY; without even the implied warranty of
  14 +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15 +# GNU General Public License for more details.
  16 +#
  17 +# You should have received a copy of the GNU General Public License
  18 +# along with this program. If not, see <http://www.gnu.org/licenses/>.
  19 +#
  20 +# ==========================================================================
  21 +import os
  22 +import gammalib
  23 +import cscripts
  24 +
  25 +
  26 +# ================================== #
  27 +# Test class for csroot2caldb script #
  28 +# ================================== #
  29 +class Test(gammalib.GPythonTestSuite):
  30 + """
  31 + Test class for csroot2caldb script.
  32 +
  33 + This test class makes unit tests for the cslightcrv script by using it
  34 + from the command line and from Python.
  35 + """
  36 +
  37 + # Constructor
  38 + def __init__(self):
  39 + """
  40 + Constructor.
  41 + """
  42 + # Call base class constructor
  43 + gammalib.GPythonTestSuite.__init__(self)
  44 +
  45 + # Clean calibration database
  46 + os.system("rm -rf csroot2caldb_* >/dev/null 2>&1")
  47 +
  48 + # Return
  49 + return
  50 +
  51 + # Set test functions
  52 + def set(self):
  53 + """
  54 + Set all test functions.
  55 + """
  56 + # Set test name
  57 + self.name("csroot2caldb")
  58 +
  59 + # Append tests
  60 + self.append(self._test_cmd, "Test csroot2caldb on command line")
  61 + self.append(self._test_python, "Test csroot2caldb from Python")
  62 +
  63 + # Return
  64 + return
  65 +
  66 + # Test csroot2caldb on command line
  67 + def _test_cmd(self):
  68 + """
  69 + Test csroot2caldb on the command line.
  70 + """
  71 + # Kluge to set the command (installed version has no README file)
  72 + if os.path.isfile("README"):
  73 + csroot2caldb = "../cscripts/csroot2caldb.py"
  74 + else:
  75 + csroot2caldb = "csroot2caldb"
  76 +
  77 + # Setup csroot2caldb command
  78 + cmd = csroot2caldb+' infile="data/irf.root"'+ \
  79 + ' outdir="csroot2caldb_cmd1"'+ \
  80 + ' inst=prod3 id=South_50h version=prod3'+ \
  81 + ' analysis=DESY'+ \
  82 + ' logfile="csroot2caldb_cmd1.log" chatter=1'
  83 +
  84 + # Execute csroot2caldb, make sure we catch any exception
  85 + try:
  86 + rc = os.system(cmd+" >/dev/null 2>&1")
  87 + except:
  88 + pass
  89 +
  90 + # Check if execution was successful
  91 + self.test_assert(rc == 0,
  92 + "Successful csroot2caldb execution on command line")
  93 +
  94 + # Return
  95 + return
  96 +
  97 + # Test csroot2caldb from Python
  98 + def _test_python(self):
  99 + """
  100 + Test csroot2caldb from Python.
  101 + """
  102 + # Set-up csroot2caldb
  103 + caldb = cscripts.csroot2caldb()
  104 + caldb["infile"] = "data/irf.root"
  105 + caldb["outdir"] = "csroot2caldb_py1"
  106 + caldb["inst"] = "prod3"
  107 + caldb["id"] = "South_50h"
  108 + caldb["version"] = "prod3"
  109 + caldb["analysis"] = "DESY"
  110 + caldb["logfile"] = "csroot2caldb_py1.log"
  111 + caldb["chatter"] = 2
  112 +
  113 + # Run script
  114 + caldb.logFileOpen() # Make sure we get a log file
  115 + caldb.run()
  116 +
  117 + # Return
  118 + return
test/test_python_cscripts.py
@@ -31,6 +31,7 @@ import test_csobs2caldb @@ -31,6 +31,7 @@ import test_csobs2caldb
31 import test_csobsdef 31 import test_csobsdef
32 import test_csobsinfo 32 import test_csobsinfo
33 import test_cspull 33 import test_cspull
  34 +import test_csroot2caldb
34 import test_csfindobs 35 import test_csfindobs
35 import test_csiactcopy 36 import test_csiactcopy
36 import test_csiactdata 37 import test_csiactdata
@@ -92,6 +93,7 @@ def test(installed=False, debug=False): @@ -92,6 +93,7 @@ def test(installed=False, debug=False):
92 suite_csobsdef = test_csobsdef.Test() 93 suite_csobsdef = test_csobsdef.Test()
93 suite_csobsinfo = test_csobsinfo.Test() 94 suite_csobsinfo = test_csobsinfo.Test()
94 suite_cspull = test_cspull.Test() 95 suite_cspull = test_cspull.Test()
  96 + suite_csroot2caldb = test_csroot2caldb.Test()
95 97
96 # Setup unit tests 98 # Setup unit tests
97 suite_cscaldb.set() 99 suite_cscaldb.set()
@@ -102,6 +104,7 @@ def test(installed=False, debug=False): @@ -102,6 +104,7 @@ def test(installed=False, debug=False):
102 suite_csobsdef.set() 104 suite_csobsdef.set()
103 suite_csobsinfo.set() 105 suite_csobsinfo.set()
104 suite_cspull.set() 106 suite_cspull.set()
  107 + suite_csroot2caldb.set()
105 108
106 # Append tests to container 109 # Append tests to container
107 suites.append(suite_cscaldb) 110 suites.append(suite_cscaldb)
@@ -112,6 +115,7 @@ def test(installed=False, debug=False): @@ -112,6 +115,7 @@ def test(installed=False, debug=False):
112 suites.append(suite_csobsdef) 115 suites.append(suite_csobsdef)
113 suites.append(suite_csobsinfo) 116 suites.append(suite_csobsinfo)
114 suites.append(suite_cspull) 117 suites.append(suite_cspull)
  118 + suites.append(suite_csroot2caldb)
115 119
116 # Append tests for Python 2.6+ (the IACT cscripts depend on the json 120 # Append tests for Python 2.6+ (the IACT cscripts depend on the json
117 # module which is only available since Python 2.6+ 121 # module which is only available since Python 2.6+