Commit 121c7b21205be023b3d6dc75e9b99367ad8518a1

Authored by sanchez
1 parent 148a99f9

first commit of the high level analysis script : analyse, Ebin and UL

pyext/ctools/__init__.py
1 from ctools import * 1 from ctools import *
2 import obsutils 2 import obsutils
  3 +import analysisutils
  4 +import plotutils
3 5
pyext/ctools/analysisutils.py 0 → 100644
  1 +# ==========================================================================
  2 +# This script provides a number of functions that are useful for handling
  3 +# CTA observations.
  4 +#
  5 +# Copyright (C) 2011-2014 David Sanchez Michael Mayer Rolf Buehler 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 ctools as ct
  22 +import gammalib as gl
  23 +from math import log10,floor
  24 +import sys
  25 +
  26 +class base(object):
  27 + def __init__(self):
  28 + self.classname = self.__class__.__name__
  29 + self.errorcolor = "\033[31m"#red
  30 + self.infocolor = "\033[34m"#blue
  31 + self.warningcolor = "\033[33m"#yellow
  32 + self.successcolor = "\033[32m"#green
  33 + self.endcolor = "\033[0m"#reset
  34 + self.prependfunction = True
  35 + self.basemembers = ["classname","errorcolor","infocolor","warningcolor","successcolor","endcolor","prependfunction"]
  36 +
  37 + def error(self,message, functionname = ""):
  38 + printstring = ""
  39 + if functionname == "":
  40 + printstring = "\n"+self.errorcolor+"*** Error ["+self.classname+"]: "+message+" ***\n"+self.endcolor
  41 + else:
  42 + printstring = "\n"+self.errorcolor+"*** Error ["+self.classname+"::"+functionname+"]: "+message+" ***\n"+self.endcolor
  43 + sys.exit(printstring)
  44 +
  45 + def info(self,message,newline=True):
  46 + printstring = self.infocolor+"["+self.classname+"]: "+message+self.endcolor
  47 + if newline:
  48 + print printstring
  49 + else:
  50 + print self.infocolor+message+self.endcolor,
  51 + sys.stdout.flush()
  52 +
  53 + def warning(self,message,functionname = ""):
  54 + printstring = ""
  55 + if functionname == "":
  56 + printstring = self.warningcolor+"["+self.classname+"] Warning: "+message+self.endcolor
  57 + else:
  58 + printstring = self.warningcolor+"["+self.classname+"::"+functionname+"] Warning: "+message+self.endcolor
  59 + print printstring
  60 +
  61 + def success(self,message):
  62 + printstring = self.successcolor+"["+self.classname+"]: "+message+self.endcolor
  63 + print printstring
  64 +
  65 + def progress(self,message="."):
  66 + string = self.infocolor+message+self.endcolor
  67 + print string
  68 +
  69 +
  70 +class ResultsSorage(dict,base):
  71 + """class to store the results in the good format"""
  72 + def __init__(self,*arg,**kw):
  73 + super(ResultsSorage, self).__init__(*arg, **kw)
  74 + base.__init__(self)
  75 + self["dnde"] = {}
  76 + self["ulim_dnde"] = []
  77 + self["ener"] = {}
  78 + self["TS"] = []
  79 + self["Iflux"] = {}
  80 +
  81 + self["ener"]["value"] = []
  82 + self["ener"]["ed_value"] = []
  83 + self["ener"]["eu_value"] = []
  84 + self["ener"]["unit"] = "MeV"
  85 +
  86 + self["dnde"]["value"] = []
  87 + self["dnde"]["ed_value"] = []
  88 + self["dnde"]["eu_value"] = []
  89 + self["dnde"]["unit"] = "ph/cm2/s/MeV"
  90 +
  91 + self["Iflux"]["value"] = []
  92 + self["Iflux"]["ed_value"] = []
  93 + self["Iflux"]["eu_value"] = []
  94 + self["Iflux"]["unit"] = "ph/cm2/s"
  95 +
  96 + def Print(self):
  97 + self.success("Results are :")
  98 + for k in self.iterkeys():
  99 + self.info("Key name : "+k)
  100 + try :
  101 + for ki in self[k].iterkeys():
  102 + print "\t",ki,"\t",self[k][ki]
  103 + except:
  104 + print self[k]
  105 + print
  106 +
  107 +class Analyser(base):
  108 + def __init__(self):
  109 + super(Analyser,self).__init__()
  110 + self.info("Creating "+self.classname+" object")
  111 + self.m_obs = None
  112 + self.like = None
  113 +
  114 + self.m_evtfile = "events_selected.fits"
  115 + self.m_raw_evt = "events.fits"
  116 + self.m_cntfile = "cntmap.fits"
  117 + self.m_ebinalg = "LOG"
  118 + self.m_eunit = "TeV"
  119 + self.m_ra = 83.63
  120 + self.m_dec = 22.01
  121 + self.m_roi = 2.
  122 + self.m_tmin = 0.
  123 + self.m_tmax = 0.
  124 + self.m_emin = 0.1#emin
  125 + self.m_emax = 10.#emax
  126 + self.m_enumbins = 10#nbins
  127 + self.m_usepnt = True # Use pointing for map centre
  128 + self.m_nxpix = 200#npix
  129 + self.m_nypix = 200#npix
  130 + self.m_binsz = 0.05#binsz
  131 + self.m_coordsys = "GAL"#coord
  132 + self.m_proj = "TAN"#proj
  133 + self.m_binned = False
  134 +
  135 + self.m_stat = "POISSON"#stat
  136 + self.m_caldb = "dummy"
  137 + self.m_irf = "cta_dummy_irf"
  138 + self.m_edisp = False
  139 + self.m_xml = "$PWD/crab.xml"
  140 + self.m_xml_out = "$PWD/crab_results.xml"
  141 +
  142 +
  143 + def set_obs(self,obs):
  144 + self.m_obs = obs #set the GObservation container
  145 +
  146 + def set_energy_boundary(self,emin,emax):
  147 + self.m_emin = emin
  148 + self.m_emax = emax
  149 + ebounds = gl.GEbounds(gl.GEnergy(emin, self.m_eunit), \
  150 + gl.GEnergy(emax, self.m_eunit))
  151 +
  152 + if self.m_obs:
  153 + for obs in self.m_obs:
  154 + obs.events().ebounds(ebounds)
  155 +
  156 + def ctselect(self):
  157 + # ctselect application and set parameters
  158 + self.info("Running ctselect to cut on events")
  159 + if self.m_obs:
  160 + filter = ct.ctselect(self.m_obs)
  161 + else:
  162 + filter = ct.ctselect()
  163 +
  164 + filter["infile"] = self.m_raw_evt
  165 + filter["outfile"] = self.m_evtfile
  166 + filter["usepnt"].boolean(self.m_usepnt) # Use pointing for map centre
  167 + filter["ra"] = self.m_ra
  168 + filter["dec"] = self.m_dec
  169 + filter["rad"] = self.m_roi
  170 + filter["tmin"] = self.m_tmin
  171 + filter["tmax"] = self.m_tmax
  172 + filter["emin"].real(self.m_emin)
  173 + filter["emax"].real(self.m_emax)
  174 + filter["expr"] = ""
  175 + filter.logFileOpen()
  176 +
  177 + filter.run()
  178 + if not(self.m_obs):
  179 + filter.save()
  180 +
  181 + if self.m_obs:
  182 + # Make a deep copy of the observation that will be returned
  183 + # (the ctbin object will go out of scope one the function is
  184 + # left)
  185 + self.m_obs = filter.obs().copy()
  186 +
  187 + def ctbin(self,log=False,debug=False):
  188 + # ctbin application and set parameters
  189 + self.info("Running ctbin to create count map")
  190 + if self.m_obs:
  191 + bin = ct.ctbin(self.m_obs)
  192 + else:
  193 + bin = ct.ctbin()
  194 + bin["evfile"] = self.m_evtfile
  195 + bin["outfile"] = self.m_cntfile
  196 + bin["ebinalg"].string(self.m_ebinalg)
  197 + bin["emin"].real(self.m_emin)
  198 + bin["emax"].real(self.m_emax)
  199 + Nbdecade = log10(self.m_emax)-log10(self.m_emin)#Compute the number of decade
  200 + bin["enumbins"].integer(int(self.m_enumbins*Nbdecade))
  201 + bin["usepnt"].boolean(self.m_usepnt) # Use pointing for map centre
  202 + bin["nxpix"].integer(self.m_nxpix)
  203 + bin["nypix"].integer(self.m_nypix)
  204 + bin["binsz"].real(self.m_binsz)
  205 + bin["coordsys"].string(self.m_coordsys)
  206 + bin["proj"].string(self.m_proj)
  207 +
  208 + # Optionally open the log file
  209 + if log:
  210 + bin.logFileOpen()
  211 +
  212 + # Optionally switch-on debugging model
  213 + if debug:
  214 + bin["debug"].boolean(True)
  215 +
  216 + # Run ctbin application. This will loop over all observations in
  217 + # the container and bin the events in counts maps
  218 + bin.excute()
  219 + if self.m_obs:
  220 + # Make a deep copy of the observation that will be returned
  221 + # (the ctbin object will go out of scope one the function is
  222 + # left)
  223 + self.m_obs = bin.obs().copy()
  224 +
  225 + def create_fit(self,log=False,debug=False):
  226 + # create ctlike instance with given parameters
  227 + self.info("Fitting Data using ctlike")
  228 + if self.m_obs:
  229 + self.like = ct.ctlike(self.m_obs)
  230 + else:
  231 + self.like = ct.ctlike()
  232 + if self.m_binned:
  233 + self.like["infile"] = self.m_cntfile
  234 + else:
  235 + self.like["infile"] = self.m_evtfile
  236 +
  237 + self.like["stat"].string(self.m_stat)
  238 + self.like["caldb"].string(self.m_caldb)
  239 + self.like["irf"].string(self.m_irf)
  240 + self.like["srcmdl"] = self.m_xml
  241 + self.like["edisp"].boolean(self.m_edisp)
  242 + self.like["outmdl"] = self.m_xml_out
  243 +
  244 + # Optionally open the log file
  245 + if log:
  246 + self.like.logFileOpen()
  247 + # Optionally switch-on debugging model
  248 + if debug:
  249 + self.like["debug"].boolean(True)
  250 +
  251 + def fit(self,log=False,debug=False):
  252 + if not(self.like):
  253 + self.warning("ctlike object not created, creating now")
  254 + self.create_fit(log,debug)
  255 +
  256 + # Run ctlike application.
  257 + self.like.run()
  258 + # Save the results in XML
  259 + self.like.save()
  260 + self.success("Fit performed")
  261 +
  262 + def PrintResults(self,srcname = ""):
  263 + self.info("Results of the Fit")
  264 + for m in self.like.obs().models():
  265 + if srcname == m.name() or srcname=="":
  266 + print "Model : "+m.name()
  267 + print m
  268 +
  269 + def GetSrcResuls(self,Res,srcname,E0=1,factor = 1e6):
  270 +
  271 + results = self.GetSrcParams(srcname)
  272 +
  273 + Res["ener"]["value"].append(E0*factor)
  274 + Res["ener"]["ed_value"].append((E0-self.m_emin)*factor)
  275 + Res["ener"]["eu_value"].append((self.m_emax-E0)*factor)
  276 + Res["ener"]["unit"] = "MeV"
  277 +
  278 + Res["Iflux"]["value"].append(results["flux"])
  279 + Res["Iflux"]["eu_value"].append(results["eflux"])
  280 + Res["Iflux"]["ed_value"].append(results["eflux"])
  281 + # Res["Iflux"]["unit"] = "unit"
  282 +
  283 + m = self.GetModel(srcname)
  284 + Res["dnde"]["value"].append(m.spectral()[0].value())
  285 + Res["dnde"]["eu_value"].append(m.spectral()[0].error())
  286 + Res["dnde"]["ed_value"].append(m.spectral()[0].error())
  287 + # Res["dnde"]["value"].append(m.spectral().eval(gl.GEnergy(E0,self.m_eunit),gl.GTime(0)))
  288 + # m.spectral().eval_gradients(gl.GEnergy(E0,self.m_eunit),gl.GTime(0))
  289 + # Assume that par 0 is the norm
  290 + # Res["dnde"]["eu_value"].append(m.spectral()[0].factor_gradient())
  291 + # Res["dnde"]["ed_value"].append(m.spectral()[0].factor_gradient())
  292 + # Res["dnde"]["unit"] = "unit"
  293 +
  294 + Res["TS"].append(100)
  295 + # Res["TS"].append(self.like.obs().models()[srcname].ts())
  296 +
  297 + UL = UpperLimitComputer(self,srcname)
  298 + UL.run()
  299 + Res["ulim_dnde"].append(0)
  300 + # del UL
  301 + return Res
  302 +
  303 + def GetSrcParams(self,srcname):
  304 + results = {}
  305 + found = False
  306 + m = self.GetModel(srcname)
  307 + results["flux"] = m.spectral().flux(gl.GEnergy(self.m_emin,self.m_eunit),gl.GEnergy(self.m_emax,self.m_eunit))
  308 + results["eflux"] = m.spectral().eflux(gl.GEnergy(self.m_emin,self.m_eunit),gl.GEnergy(self.m_emax,self.m_eunit))
  309 + for par in m.spectral():
  310 + if par.is_free():
  311 + results[par.name()] = par.value()
  312 + results["e"+par.name()] = par.error()
  313 +
  314 + return results
  315 +
  316 + def GetModel(self,srcname):
  317 + found = False
  318 + for m in self.like.obs().models():
  319 + if m.name() == srcname:
  320 + return m
  321 + self.error("source "+srcname+" not in the source list")
  322 +
  323 +
  324 +def MakeEbin(analyse,nbins,srcname,binned = False):
  325 + #Store the results
  326 + Res = ResultsSorage()
  327 +
  328 + #check the source name, exit is the srcname is false
  329 + analyse.GetModel(srcname)
  330 +
  331 + #compute the conversion factor to have energy in MeV
  332 + Energy_list = {"MeV":1,"GeV":1e3,"TeV":1e6} #TODO
  333 + factor = Energy_list[analyse.m_eunit]
  334 +
  335 + for i in xrange(nbins):
  336 + obs_copy = analyse.like.obs().copy()
  337 + emin = pow(10,log10(analyse.m_emin) + (log10(analyse.m_emax)-log10(analyse.m_emin))/(nbins)*i)
  338 + emax = pow(10,log10(analyse.m_emin) + (log10(analyse.m_emax)-log10(analyse.m_emin))/(nbins)*(i+1))
  339 + E0 = pow(10, (log10(emin) + log10(emax)) / 2)
  340 + print "Making energy bin between %2.1e"%(emin)+analyse.m_eunit+" and %2.1e"%(emax)+analyse.m_eunit
  341 +
  342 + # change the PivotEnergy to E0, froze all spectral parameters but the prefactor
  343 + m = obs_copy.models()[srcname]
  344 + for par in m.spectral():
  345 + if par.name() == "PivotEnergy":
  346 + par.value(E0*factor)
  347 + # if not(par.name() == "Prefactor") and par.is_free(): # assume that all model have a prefactor
  348 + # par.fix()
  349 + # if par.name() == "Prefactor":
  350 + # Set the norm to a better guess
  351 + # new_val = m.spectral().eval(gl.GEnergy(E0,analyse.m_eunit),gl.GTime(0))
  352 + # m.spectral()["Prefactor"].value(new_val)
  353 +
  354 + ana_bin = Analyser()
  355 +
  356 + #copy the obs object
  357 + ana_bin.set_obs(obs_copy)
  358 +
  359 + # Set energy boundaries
  360 + ana_bin.set_energy_boundary(emin,emax)
  361 + ana_bin.ctselect()
  362 +
  363 + # if binned:
  364 + # ana_bin.ctbin()
  365 +
  366 + # Create the fit object
  367 + ana_bin.create_fit(log=False,debug=False)
  368 +
  369 + # Run ctlike application.
  370 + ana_bin.like.run()
  371 +
  372 + #Retrive the results in the good format
  373 + Res = ana_bin.GetSrcResuls(Res,srcname,E0,factor)
  374 +
  375 + del ana_bin
  376 + return Res
  377 +
  378 +class UpperLimitComputer(base):
  379 + def __init__(self,analyser,srcname,parname = "Prefactor"):
  380 + super(UpperLimitComputer,self).__init__()
  381 + self.info("Creating "+self.classname+" object")
  382 + # self.like = analyser.like.copy()
  383 + obs = analyser.like.obs().copy()
  384 + self.like = ct.ctlike(obs)
  385 + self.succes = False
  386 +
  387 + # get spectral model
  388 + self.model = self.like.obs().models()[srcname]
  389 + self.spec = self.model.spectral()
  390 + self.parname = parname
  391 + self.bestloglike = analyser.like.opt().value()
  392 + self.dloglike = 3.84/2.0 #95% CL now
  393 +
  394 + def _bisec(self,a,b,tol = 1e-12):
  395 + """iterative function to find the roots"""
  396 + if (self._func(a)==0.):
  397 + return a
  398 + elif (self._func(b)==0):
  399 + return b
  400 + elif (self._func(b)*self._func(a)>0.):
  401 + self.warning("Finding root for upper limit calculation failed")
  402 + return 0
  403 + mid = (a+b)/2
  404 + err = abs(a-b)/2
  405 + fm = self._func(mid)
  406 + if (err<tol or self._func(mid)==0.):
  407 + self.succes = True
  408 + return mid
  409 + elif (fm*self._func(a)<0):
  410 + b = mid
  411 + else :
  412 + a = mid
  413 + return self._bisec(a,b)
  414 +
  415 + def _func(self,value):
  416 + """function to be minimized"""
  417 + # get parmeter value
  418 + val = value*self.spec[self.parname].scale()
  419 +
  420 + # ensure value to be above minimum boundary
  421 + if val <= self.spec[self.parname].min():
  422 + return 0.0-self.self.bestloglike-self.dloglike
  423 +
  424 + # set value
  425 + self.spec[self.parname].value(val)
  426 +
  427 + # Recalculate likelihood
  428 + self.like.run()
  429 + logl = self.like.opt().value()
  430 +
  431 + # Return likelihood difference
  432 + return (logl-self.bestloglike-self.dloglike)
  433 +
  434 + def run(self):
  435 +
  436 + # get spectral parameters and set start value
  437 + value = self.spec[self.parname].value()
  438 + error = self.spec[self.parname].error()
  439 + scale = self.spec[self.parname].scale()
  440 + print value," ",error," ",scale
  441 +
  442 + #Fix all parameters
  443 + for model in self.like.obs().models():
  444 + for par in model:
  445 + par.fix()
  446 +
  447 +
  448 + # set start value to 2 sigma above fit value
  449 + self.startpar = value / scale + 2* error/scale
  450 +
  451 + # set start (a) and (b) depend on the CL asked
  452 + a= value / scale + (self.dloglike/4.)*error/scale
  453 + b=value / scale + (self.dloglike*2)* error/scale
  454 + print a," ",b," ",self._func(a)," ",self._func(b)
  455 + ul = self._bisec(a,b)
  456 +
  457 + self.ulimit = ul*self.spec[self.parname].scale()
  458 + # Test for convergence
  459 + if self.succes:
  460 + self.success("Upper limit of parameter \""+self.parname +"\" determined to "+str(self.ulimit))
pyext/ctools/plotutils.py
@@ -2,7 +2,7 @@ @@ -2,7 +2,7 @@
2 # This script provides a number of functions that are useful for handling 2 # This script provides a number of functions that are useful for handling
3 # CTA observations. 3 # CTA observations.
4 # 4 #
5 -# Copyright (C) 2014 Rolf Buehler 5 +# Copyright (C) 2011-2014 David Sanchez Michael Mayer Rolf Buehler Juergen Knoedlseder
6 # 6 #
7 # This program is free software: you can redistribute it and/or modify 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 8 # it under the terms of the GNU General Public License as published by
@@ -18,54 +18,368 @@ @@ -18,54 +18,368 @@
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. 18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 # 19 #
20 # ========================================================================== 20 # ==========================================================================
21 -import ctools  
22 -import gammalib 21 +import ctools as ct
  22 +import gammalib as gl
  23 +from math import log10,pow,sqrt
  24 +import os
  25 +
23 try: 26 try:
24 import matplotlib.pyplot as plt 27 import matplotlib.pyplot as plt
  28 + import matplotlib.gridspec as gridspec
25 has_matplotlib = True 29 has_matplotlib = True
26 except: 30 except:
27 has_matplotlib = False 31 has_matplotlib = False
  32 +
28 try: 33 try:
29 import aplpy 34 import aplpy
30 has_aplpy = True 35 has_aplpy = True
31 except: 36 except:
32 has_aplpy = False 37 has_aplpy = False
33 38
  39 +class options(object):
  40 + def __init__(self):
  41 + self.fmt = 'o'
  42 + self.color = BLUE
  43 + self.linecolor = 'black'
  44 + self.markercolor = self.color
  45 + self.tslim = 4.0
  46 + self.elinewidth = 2.0
  47 + self.linewidth = 2.0
  48 + self.linestyle = '--'
  49 + self.capsize = 5.0
  50 + self.markersize = 5.0
  51 + self.limlength = 0.4
  52 + self.label = "_nolegend_"
  53 +
  54 +import analysisutils
  55 +
  56 +class SpectralBase(object):
  57 + def __init__(self,sed=True,sed_factor=1.):
  58 + self.m_sed_factor = sed_factor
  59 + self.m_sed = sed
  60 +
  61 + def _convertSED(self,tab,energy):
  62 + """convert the a table storing dN/dE in sed with right units"""
  63 +
  64 + if len(tab) != len(energy):
  65 + self.error("input table and energy table does not match")
  66 + return
  67 + if self.m_sed:
  68 + for i in xrange(len(tab)):
  69 + tab[i] *= energy[i]**2*self.m_sed_factor
  70 + return tab
  71 +
  72 +BLUE="#337EF5"
  73 +GREY='grey'
  74 +GREEN = '#21851D'
  75 +RED = '#BD0B0B'
  76 +YELLOW = '#D4CF44'
  77 +ORANGE = '#D19534'
  78 +
  79 +
  80 +class residuals(analysisutils.base,options,SpectralBase):
  81 + def __init__(self,spectrum,datapoint,sed,sed_factor,eunit="TeV"):
  82 + super(residuals,self).__init__()
  83 + options.__init__(self)
  84 + SpectralBase.__init__(self,sed,sed_factor)
  85 +
  86 + # load the gammalib spectrum object
  87 + self.spectrum = spectrum
  88 + self.SetDataPoint(datapoint)
  89 +
  90 + self.xerrors = True
  91 + self.refUnit = eunit # reference unit for conversion
  92 +
  93 + def SetDataPoint(self,datapoint):
  94 + self.m_point_info = datapoint
  95 + self.Npt = len(self.m_point_info["dnde"]["value"])
  96 +
  97 + def _plt_residuals(self):
  98 + th_val = []
  99 + index = []
  100 + ed_y = []
  101 + eu_y = []
  102 + y = []
  103 +
  104 + Energy_list = {"MeV":1,"GeV":1e-3,"TeV":1e-6} #TODO
  105 + for i in range(self.Npt):
  106 + if self.m_point_info["TS"][i] >= self.tslim:
  107 + th_val.append(self.spectrum.eval(gl.GEnergy(self.m_point_info["ener"]["value"][i],self.m_point_info["ener"]["unit"]),gl.GTime(0)))
  108 + index.append(i)
  109 + else:
  110 + th_val.append(0)
  111 + if self.m_sed:
  112 + self._convertSED(th_val,self.m_point_info["ener"])
  113 + for i in index:
  114 + y.append((self.m_point_info["dnde"]["value"][i] - th_val[i]) / th_val[i])
  115 + ed_y.append(self.m_point_info["dnde"]["ed_value"][i]/th_val[i])
  116 + eu_y.append(self.m_point_info["dnde"]["eu_value"][i]/th_val[i])
  117 +
  118 + if self.xerrors:
  119 + plt.errorbar(self.m_point_info["ener"]["value"], y, xerr=[self.m_point_info["ener"]["ed_value"],self.m_point_info["ener"]["ed_value"]],yerr=[ed_y,eu_y],fmt=self.fmt,color=self.markercolor,elinewidth=self.elinewidth)
  120 + else:
  121 + plt.errorbar(self.m_point_info["ener"]["value"], y, yerr=[ed_y,eu_y],fmt=self.fmt,color=self.markercolor,elinewidth=self.elinewidth)
  122 +
  123 + plt.axhline(0.0,color=self.linecolor,lw=self.linewidth,ls=self.linestyle)
  124 +
  125 +
  126 +class ulimgraph(analysisutils.base,options,SpectralBase):
  127 + def __init__(self,datapoint,sed,sed_factor,eunit='TeV'):
  128 + super(ulimgraph,self).__init__()
  129 + #~ super(ulimgraph,self).__init__()
  130 + options.__init__(self)
  131 + SpectralBase.__init__(self,sed,sed_factor)
  132 +
  133 + self.xerrors=True
  134 + self.nlims = -1
  135 + self.refUnit = eunit
  136 + self.SetDataPoint(datapoint)
  137 +
  138 + def SetDataPoint(self,datapoint):
  139 + self.m_point_info = datapoint
  140 + self.Npt = len(self.m_point_info["dnde"]["value"])
  141 +
  142 + def _plt_points(self):
  143 + results = analysisutils.ResultsSorage()
  144 +
  145 + Energy_list = {"MeV":1,"GeV":1e-3,"TeV":1e-6} #TODO
  146 + for i in range(self.Npt):
  147 +
  148 + self.m_point_info["ener"]["value"][i]*=Energy_list[self.refUnit]
  149 + self.m_point_info["ener"]["ed_value"][i]*=Energy_list[self.refUnit]
  150 + self.m_point_info["ener"]["eu_value"][i]*=Energy_list[self.refUnit]
  151 + self.m_point_info["ener"]["unit"] = self.refUnit
  152 +
  153 + if self.m_point_info["TS"][i] >= self.tslim:
  154 +
  155 + if self.m_point_info["dnde"]["ed_value"][i]>self.m_point_info["dnde"]["value"][i]:
  156 + self.warning("\tflux error: "+str(self.m_point_info["dnde"]["ed_value"][i])+" is larger than flux value "+str(self.m_point_info["dnde"]["ed_value"][i]))
  157 + self.warning("\tTS value is however TS="+str(self.m_point_info["TS"][i])+", weird!")
  158 + else :
  159 + self.m_point_info["dnde"]["ed_value"][i] = 0
  160 + self.m_point_info["dnde"]["eu_value"][i] = 0
  161 +
  162 + if self.xerrors:
  163 + plt.errorbar(self.m_point_info["ener"]["value"], self.m_point_info["dnde"]["value"], xerr=[self.m_point_info["ener"]["ed_value"], self.m_point_info["ener"]["eu_value"]],yerr=[self.m_point_info["dnde"]["ed_value"], self.m_point_info["dnde"]["eu_value"]],fmt=self.fmt,color=self.markercolor,elinewidth=self.elinewidth)
  164 + else:
  165 + plt.errorbar(self.m_point_info["ener"]["value"], self.m_point_info["dnde"]["value"],yerr=[results["dnde"]["ed_value"],results["dnde"]["eu_value"]],fmt=self.fmt,color=self.markercolor,elinewidth=self.elinewidth)
  166 +
  167 + def _plt_limits(self):
  168 + ed_flux = []
  169 + eu_flux = []
  170 + flux = []
  171 + ener = []
  172 +
  173 + drawn_lims = 0
  174 + Energy_list = {"MeV":1,"GeV":1e-3,"TeV":1e-6} #TODO
  175 + for i in range(self.Npt):
  176 + if self.m_point_info["TS"][i] < self.tslim:
  177 + self.info("Plot an upper limit for E = "+self.m_point_info["ener"]["value"][i]+" with a TS = "+self.m_point_info["TS"][i])
  178 + if drawn_lims >= self.nlims and self.nlims!=-1:
  179 + break
  180 +
  181 + ed_flux.append(self.limlength*self.m_point_info["ulim_dnde"][i])
  182 + eu_flux.append(0.0)
  183 + flux.append(self.m_point_info["ulim_dnde"][i])
  184 + drawn_lims+=1
  185 +
  186 + # if self.xerrors:
  187 + # plt.errorbar(self.m_point_info["ener"]["value"], self.m_point_info["dnde"]["value"], xerr=[self.m_point_info["ener"]["ed_value"], self.m_point_info["ener"]["eu_value"]],yerr=[self.m_point_info["dnde"]["ed_value"],self.m_point_info["dnde"]["eu_value"]],fmt=self.fmt,markersize=0.0,elinewidth=self.elinewidth,lolims=True,capsize=self.capsize,label='_nolegend_',mfc = self.color,ecolor=self.color,ms =self.markersize)
  188 + # else:
  189 + plt.errorbar(ener, flux, yerr=[ed_flux, eu_flux],fmt=self.fmt,markersize=0.0,elinewidth=self.elinewidth,lolims=True,capsize=self.capsize,label='_nolegend_',mfc = self.color,ecolor=self.color,ms =self.markersize)
  190 +
  191 +
  192 +class SpectrumPlotter(analysisutils.base,SpectralBase):
  193 + """ Class to compute and plot the spectra (i.e best fit model and butterfly).
  194 + if data points are provided, only change applied is energy unit convertion to match the butterfly one
  195 + (i.e. no dN/dE to SED conversion).
  196 + It is currently assumed that the unit is MeV for the energy of the data point. this will be change
  197 + once the I/O container format will be defined."""
  198 + def __init__(self,srcname,like,datapoint,emin=0.1,emax=100,energy = "TeV",SED = True ,npt = 50):
  199 + super(SpectrumPlotter,self).__init__()
  200 +
  201 + self.m_ebound = [emin,emax]
  202 + self.m_name = srcname
  203 + self.m_like = like
  204 + self.m_spectral = like.obs().models()[srcname].spectral()
  205 + self.m_energy = []
  206 + self.m_flux = []
  207 + self.m_error = []
  208 + self.m_but = []
  209 + self.m_enebut = []
  210 + self.m_npt = npt
  211 + self.m_covar = gl.GMatrix(1,1)
  212 + self.covar()
  213 + #~ this part take care of the style : SED or differential flux
  214 + #~ in the case of SED, the y-unit is erg/cm2/s
  215 + self.m_sed = SED
  216 + self.eunit = energy
  217 + self.m_sed_factor = 1.
  218 + self._validateUnits()
  219 + SpectralBase.__init__(self,self.m_sed,self.m_sed_factor)
  220 +
  221 + #Assume MeV for the energy #TODO
  222 + self.points = ulimgraph(datapoint,self.m_sed,gl.MeV2erg,energy)
  223 + self.residuals = residuals(self.m_spectral,datapoint,self.m_sed,gl.MeV2erg,energy)
  224 +
  225 +
  226 + def _validateUnits(self):
  227 + Energy_list = {"MeV":1,"GeV":1e3,"TeV":1e6}
  228 + if not(Energy_list.has_key(self.eunit)):
  229 + self.warning("Change energy unit to TeV")
  230 + self.eunit = "TeV"
  231 +
  232 + if self.m_sed:
  233 + self.m_sed_factor = gl.MeV2erg*Energy_list[self.eunit]**2
  234 +
  235 + def covar(self):
  236 + # Get covariance matrix if fit has converged
  237 + try:
  238 + fullcovar = self.m_like.obs().function().curvature().invert()
  239 + except:
  240 + self.warning("Covariance matrix not determined successfully")
  241 + return
  242 + #~ get the matrix element of the source of interest
  243 + #~ 1) get the right element indices
  244 + idx = 0
  245 + par_index_map = []
  246 + for m in self.m_like.obs().models() :
  247 + if m.name()!= self.m_name:
  248 + idx += m.size()
  249 + continue
  250 + idx += m.spatial().size()
  251 + for par in m.spectral():
  252 + if par.is_free():
  253 + par_index_map.append(idx)
  254 + idx += 1
  255 +
  256 + #~ 2) get the elemets and store them in a matrix
  257 + self.m_covar = gl.GMatrix(len(par_index_map),len(par_index_map))
  258 + i = 0
  259 + for xpar in par_index_map:
  260 + j = 0
  261 + for ypar in par_index_map:
  262 + self.m_covar[i,j] = fullcovar[xpar,ypar]
  263 + j += 1
  264 + i += 1
  265 + self.success("Covariance Matrix succesfuly computed")
  266 +
  267 + def _makespectrum(self):
  268 + for i in xrange(self.m_npt):
  269 + #Compute the energy
  270 + lene = log10(self.m_ebound[0])+log10(self.m_ebound[1])*i/(self.m_npt-1)
  271 + self.m_energy.append(pow(10,lene))
  272 + #Compute the flux
  273 + self.m_flux.append(self.m_spectral.eval(gl.GEnergy(pow(10,lene),self.eunit),gl.GTime(0)))
  274 +
  275 + #Compute the gradients
  276 + if self.m_covar.size() != 1:
  277 + self.m_spectral.eval_gradients(gl.GEnergy(pow(10,lene), self.eunit),gl.GTime(0))
  278 + #store them into a GVector
  279 + Derivative = gl.GVector(self.m_covar.size()/2)
  280 + j = 0
  281 + for par in self.m_spectral:
  282 + if par.is_free():
  283 + Derivative[j] = par.factor_gradient()
  284 + j += 1
  285 + #~ computed the error
  286 + self.m_error.append(sqrt(Derivative * (self.m_covar*Derivative)))
  287 +
  288 + #~ convert the flux in sed if asked
  289 + self.m_flux = self._convertSED(self.m_flux,self.m_energy)
  290 + if self.m_covar.size() != 1:
  291 + self.m_error = self._convertSED(self.m_error,self.m_energy)
  292 + self._makebutterfly() # make the butterfly
  293 + self.success("Spectrum computed")
  294 +
  295 +
  296 + def _makebutterfly(self):
  297 + """make the butterfly by appending element in a table"""
  298 + for i in xrange(self.m_npt):
  299 + self.m_but.append(self.m_flux[i]+self.m_error[i])
  300 + self.m_enebut.append(self.m_energy[i])
  301 + for i in xrange(self.m_npt):
  302 + idx = self.m_npt-i-1
  303 + self.m_but.append(self.m_flux[idx]-self.m_error[idx])
  304 + self.m_enebut.append(self.m_energy[idx])
  305 + self.m_but.append(self.m_but[0])
  306 + self.m_enebut.append(self.m_enebut[0])
  307 +
  308 + def write(self):
  309 + self.warning("write function not yet implemented")
  310 +
  311 + def draw(self):
  312 + """plot the results, provide a file named ptfile for the flux points"""
  313 + gs = gridspec.GridSpec(2, 1,height_ratios=[3,1])
  314 + self._makespectrum()
  315 + self.write()
  316 +
  317 + if not(has_matplotlib):
  318 + self.warning("matplotlib module not found, can draw")
  319 + return
  320 +
  321 + plt.figure("results",figsize=(12,7),edgecolor="w")
  322 +
  323 + sb1 = plt.subplot(gs[0])
  324 + plt.loglog()
  325 + plt.xlabel("Energy ["+ self.eunit+"]",fontsize=15)
  326 +
  327 + if self.m_sed:
  328 + plt.ylabel("E$^{2}$ dN/dE [erg cm$^{2}$ s$^{-1}$]",fontsize=15)
  329 + else:
  330 + plt.ylabel("dN/dE [cm$^{-2}$ s$^{-1}$ "+ self.eunit+"$^{-1}$]",fontsize=15)
  331 +
  332 + plt.plot(self.m_energy,self.m_flux)
  333 + plt.plot(self.m_enebut,self.m_but)
  334 +
  335 + #~ plot the data points on top of the buterfly
  336 + self.points._plt_points()
  337 + self.points._plt_limits()
  338 +
  339 + #~ plot the residual points
  340 + sb2 = plt.subplot(gs[1])
  341 + plt.xscale('log')
  342 + self.residuals._plt_residuals()
  343 +
  344 + plt.show()
  345 +
34 346
35 # ============= # 347 # ============= #
36 # Plot skymaps # 348 # Plot skymaps #
37 # ============= # 349 # ============= #
38 -def plotskymaps(modelmap, countmap):  
39 - """  
40 - Plot the model map.  
41 -  
42 - Parameters:  
43 - modname - model input name  
44 - evtname - events input name  
45 - Keywords:  
46 - None  
47 - """  
48 - # Load model map  
49 - fig = plt.figure()  
50 - f1 = aplpy.FITSFigure(countmap, figure=fig, subplot=[0.1,0.1,0.35,0.8])  
51 - f1.set_tick_labels_font(size='small')  
52 - f1.set_axis_labels_font(size='small')  
53 - f1.show_colorscale()  
54 -  
55 - f1.tick_labels.set_yformat('dd:mm:ss')  
56 - f1.tick_labels.set_xformat('hh:mm:ss')  
57 - f1.axis_labels.set_xtext('Right Ascension (J2000)')  
58 - f1.axis_labels.set_ytext('Declination (J2000)')  
59 - f1.ticks.set_length(10.5, minor_factor=0.5)  
60 -  
61 - f2 = aplpy.FITSFigure(modelmap, figure=fig, subplot=[0.5,0.1,0.35,0.8])  
62 - f2.set_tick_labels_font(size='small')  
63 - f2.set_axis_labels_font(size='small')  
64 - f2.show_colorscale()  
65 -  
66 - f2.hide_yaxis_label()  
67 - f2.hide_ytick_labels()  
68 -  
69 - fig.canvas.draw()  
70 - fig.show()  
71 - 350 +class MapPlotter(analysisutils.base):
  351 + def __init__(self,modelmap, countmap):
  352 + super(MapPlotter,self).__init__()
  353 + self.modelmap = modelmap
  354 + self.countmap = countmap
  355 +
  356 + if not(has_aplpy) :
  357 + self.error("Module aplypy needed")
  358 + if not(has_matplotlib) :
  359 + self.error("Module matplotlib needed")
  360 +
  361 + def draw(self):
  362 + """ Plot the model map. """
  363 + # Load model map
  364 + fig = plt.figure()
  365 + f1 = aplpy.FITSFigure(self.countmap, figure=fig, subplot=[0.1,0.1,0.35,0.8])
  366 + f1.set_tick_labels_font(size='small')
  367 + f1.set_axis_labels_font(size='small')
  368 + f1.show_colorscale()
  369 +
  370 + f1.tick_labels.set_yformat('dd:mm:ss')
  371 + f1.tick_labels.set_xformat('hh:mm:ss')
  372 + f1.axis_labels.set_xtext('Right Ascension (J2000)')
  373 + f1.axis_labels.set_ytext('Declination (J2000)')
  374 + f1.ticks.set_length(10.5, minor_factor=0.5)
  375 +
  376 + f2 = aplpy.FITSFigure(self.modelmap, figure=fig, subplot=[0.5,0.1,0.35,0.8])
  377 + f2.set_tick_labels_font(size='small')
  378 + f2.set_axis_labels_font(size='small')
  379 + f2.show_colorscale()
  380 +
  381 + f2.hide_yaxis_label()
  382 + f2.hide_ytick_labels()
  383 +
  384 + fig.canvas.draw()
  385 + fig.show()
pyext/setup.py.in
@@ -121,7 +121,7 @@ setup(name=&#39;@PACKAGE_NAME@&#39;, @@ -121,7 +121,7 @@ setup(name=&#39;@PACKAGE_NAME@&#39;,
121 author='Juergen Knoedlseder', 121 author='Juergen Knoedlseder',
122 author_email='@PACKAGE_BUGREPORT@', 122 author_email='@PACKAGE_BUGREPORT@',
123 url='http://gammalib.sourceforge.net/', 123 url='http://gammalib.sourceforge.net/',
124 - py_modules=['ctools.ctools', 'ctools.obsutils'], 124 + py_modules=['ctools.ctools', 'ctools.obsutils','ctools.plotutils','ctools.analysisutils'],
125 ext_modules=[Extension('ctools._ctools', ['ctools/ctools_wrap.cpp'], 125 ext_modules=[Extension('ctools._ctools', ['ctools/ctools_wrap.cpp'],
126 include_dirs=include_dirs, 126 include_dirs=include_dirs,
127 library_dirs=library_dirs, 127 library_dirs=library_dirs,