diff --git a/pyext/ctools/__init__.py b/pyext/ctools/__init__.py index 72ca4a2..e226be6 100644 --- a/pyext/ctools/__init__.py +++ b/pyext/ctools/__init__.py @@ -1,6 +1,8 @@ # Import sub modules from .tools import * import obsutils +import analysisutils +import plotutils # Remove swig register stuff bad_entries = [entry for entry in list(locals()) @@ -12,4 +14,4 @@ for entry in bad_entries: del tools del gammalib del bad_entries -del entry \ No newline at end of file +del entry diff --git a/pyext/ctools/analysisutils.py b/pyext/ctools/analysisutils.py new file mode 100644 index 0000000..fac88b3 --- /dev/null +++ b/pyext/ctools/analysisutils.py @@ -0,0 +1,575 @@ +# ========================================================================== +# This script provides a number of functions that are useful for CTA data +# analysis. +# +# Copyright (C) 2014 David Sanchez +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# ========================================================================== +import ctools as ct +import gammalib as gl +from math import sqrt,log10,floor +import sys + +class base(object): + def __init__(self): + self.classname = self.__class__.__name__ + self.errorcolor = "\033[31m"#red + self.infocolor = "\033[34m"#blue + self.warningcolor = "\033[33m"#yellow + self.successcolor = "\033[32m"#green + self.endcolor = "\033[0m"#reset + self.prependfunction = True + self.basemembers = ["classname","errorcolor","infocolor","warningcolor","successcolor","endcolor","prependfunction"] + + def error(self,message, functionname = ""): + printstring = "" + if functionname == "": + printstring = "\n"+self.errorcolor+"*** Error ["+self.classname+"]: "+message+" ***\n"+self.endcolor + else: + printstring = "\n"+self.errorcolor+"*** Error ["+self.classname+"::"+functionname+"]: "+message+" ***\n"+self.endcolor + sys.exit(printstring) + + def info(self,message,newline=True): + printstring = self.infocolor+"["+self.classname+"]: "+message+self.endcolor + if newline: + print printstring + else: + print self.infocolor+message+self.endcolor, + sys.stdout.flush() + + def warning(self,message,functionname = ""): + printstring = "" + if functionname == "": + printstring = self.warningcolor+"["+self.classname+"] Warning: "+message+self.endcolor + else: + printstring = self.warningcolor+"["+self.classname+"::"+functionname+"] Warning: "+message+self.endcolor + print printstring + + def success(self,message): + printstring = self.successcolor+"["+self.classname+"]: "+message+self.endcolor + print printstring + + def progress(self,message="."): + string = self.infocolor+message+self.endcolor + print string + + +class ResultsSorage(dict,base): + """class to store the results in the good format""" + def __init__(self,*arg,**kw): + super(ResultsSorage, self).__init__(*arg, **kw) + base.__init__(self) + self["dnde"] = {} + self["ulim_dnde"] = [] + self["time"] = {} + self["ener"] = {} + self["TS"] = [] + self["Iflux"] = {} + + self["time"]["tmin_value"] = [] + self["time"]["tmax_value"] = [] + self["time"]["unit"] = "sec" + + self["ener"]["value"] = [] + self["ener"]["ed_value"] = [] + self["ener"]["eu_value"] = [] + self["ener"]["unit"] = "MeV" + + self["dnde"]["value"] = [] + self["dnde"]["ed_value"] = [] + self["dnde"]["eu_value"] = [] + self["dnde"]["unit"] = "ph/cm2/s/MeV" + + self["Iflux"]["value"] = [] + self["Iflux"]["ed_value"] = [] + self["Iflux"]["eu_value"] = [] + self["Iflux"]["unit"] = "ph/cm2/s" + + def Print(self): + self.success("Results are :") + for k in self.iterkeys(): + self.info("Key name : "+k) + try : + for ki in self[k].iterkeys(): + print "\t",ki,"\t",self[k][ki] + except: + print self[k] + print + +class Analyser(base): + def __init__(self): + super(Analyser,self).__init__() + self.info("Creating "+self.classname+" object") + self.m_obs = None + self.like = None + + self.m_ebinalg = "LOG" + self.m_eunit = "TeV" + self.m_enumbins = 10#nbins + self.m_usepnt = True # Use pointing for map centre + self.m_coordsys = "CEL"#coord + self.m_proj = "TAN"#proj + self.m_binned = False + self.m_edisp = False + self.m_stat = "POISSON"#stat + + self.m_source_set = False + self.m_files_set = False + self.m_time_set = False + self.m_energy_set = False + self.m_irfs_set = False + self.m_roi_set = False + self.m_xml_set = False + + def set_xml(self,xml): + self.m_xml = xml + ind = xml.find(".xml") + if not ind==-1: + self.m_xml_out = xml[:xml.find(".xml")]+"_results"+xml[xml.find(".xml"):] + self.m_xml_set = True + + def set_roi(self,roi): + self.m_roi = float(roi) + self.m_nxpix = int(self.m_roi*100) #nxpix + self.m_nypix = int(self.m_roi*100) #nypix + self.m_binsz = 0.05 #binsz + self.m_roi_set = True + + def set_irfs(self,caldb,irf): + self.m_caldb = caldb + self.m_irf = irf + self.m_irfs_set = True + + def set_Fitsfiles(self,evtfile,tag="CTA_analysis"): + self.m_evtfile = "event_selected_"+tag+".fits" + self.m_raw_evt = evtfile + self.m_cntfile = "countmap_"+tag+".fits" + self.m_files_set = True + + def set_source(self,name,ra,dec): + self.m_ra = ra + self.m_dec = dec + self.m_name = name + self.m_source_set = True + + def validate(self): + if not self.m_time_set : + self.error("Time range of the observations not set") + + if not self.m_files_set : + self.error("FITS files of the observations not set") + + if not self.m_source_set : + self.error("Source not set") + + if not self.m_energy_set : + self.error("Energy range of the observations not set") + + if not self.m_irfs_set : + self.error("IRFs to use not set") + + if not self.m_roi_set : + self.error("ROI to use not set") + + if not self.m_xml_set : + self.error("XML files for the sky model not set") + + def copy(self,analyse,tag="CTA_analysis_copy"): + self.set_xml(analyse.m_xml) + self.set_irfs(analyse.m_caldb,analyse.m_irf) + self.set_roi(analyse.m_roi) + self.set_time_boundary(analyse.m_tmin,analyse.m_tmax) + self.set_energy_boundary(analyse.m_emin,analyse.m_emax) + self.set_source(analyse.m_name,analyse.m_ra,analyse.m_dec) + self.set_Fitsfiles(analyse.m_evtfile,tag) + + + def set_obs(self,obs): + self.m_obs = obs #set the GObservation container + + def set_time_boundary(self,tmin,tmax): + """Set the start and stop time of the observation""" + self.m_tmin = float(tmin) + self.m_tmax = float(tmax) + self.m_time_set = True + + def set_energy_boundary(self,emin,emax): + """Set the energy bounds of the observation""" + self.m_emin = emin + self.m_emax = emax + ebounds = gl.GEbounds(gl.GEnergy(emin, self.m_eunit), \ + gl.GEnergy(emax, self.m_eunit)) + + if self.m_obs: + for obs in self.m_obs: + obs.events().ebounds(ebounds) + self.m_energy_set = True + + def ctselect(self): + # ctselect application and set parameters + self.info("Running ctselect to cut on events") + self.validate() + if self.m_obs: + filter = ct.ctselect(self.m_obs) + else: + filter = ct.ctselect() + + filter["infile"] = self.m_raw_evt + filter["outfile"] = self.m_evtfile + filter["usepnt"].boolean(self.m_usepnt) # Use pointing for map centre + filter["ra"] = self.m_ra + filter["dec"] = self.m_dec + filter["rad"] = self.m_roi + filter["tmin"] = self.m_tmin + filter["tmax"] = self.m_tmax + filter["emin"].real(self.m_emin) + filter["emax"].real(self.m_emax) + filter["expr"] = "" + filter.logFileOpen() + + filter.run() + if not(self.m_obs): + filter.save() + + if self.m_obs: + # Make a deep copy of the observation that will be returned + # (the ctbin object will go out of scope one the function is + # left) + self.m_obs = filter.obs().copy() + + def ctbin(self,log=False,debug=False): + # ctbin application and set parameters + self.validate() + self.info("Running ctbin to create count map") + if self.m_obs: + bin = ct.ctbin(self.m_obs) + else: + bin = ct.ctbin() + bin["evfile"] = self.m_evtfile + bin["outfile"] = self.m_cntfile + bin["ebinalg"].string(self.m_ebinalg) + bin["emin"].real(self.m_emin) + bin["emax"].real(self.m_emax) + Nbdecade = log10(self.m_emax)-log10(self.m_emin)#Compute the number of decade + bin["enumbins"].integer(int(self.m_enumbins*Nbdecade)) + bin["usepnt"].boolean(self.m_usepnt) # Use pointing for map centre + bin["nxpix"].integer(self.m_nxpix) + bin["nypix"].integer(self.m_nypix) + bin["binsz"].real(self.m_binsz) + bin["coordsys"].string(self.m_coordsys) + bin["proj"].string(self.m_proj) + + # Optionally open the log file + if log: + bin.logFileOpen() + + # Optionally switch-on debugging model + if debug: + bin["debug"].boolean(True) + + # Run ctbin application. This will loop over all observations in + # the container and bin the events in counts maps + bin.execute() + if self.m_obs: + # Make a deep copy of the observation that will be returned + # (the ctbin object will go out of scope one the function is + # left) + self.m_obs = bin.obs().copy() + + def create_fit(self,log=False,debug=False): + self.validate() + # create ctlike instance with given parameters + self.info("Fitting Data using ctlike") + if self.m_obs: + self.like = ct.ctlike(self.m_obs) + else: + self.like = ct.ctlike() + if self.m_binned: + self.like["infile"] = self.m_cntfile + else: + self.like["infile"] = self.m_evtfile + + self.like["stat"].string(self.m_stat) + self.like["caldb"].string(self.m_caldb) + self.like["irf"].string(self.m_irf) + self.like["srcmdl"] = self.m_xml + self.like["edisp"].boolean(self.m_edisp) + self.like["outmdl"] = self.m_xml_out + + # Optionally open the log file + if log: + self.like.logFileOpen() + # Optionally switch-on debugging model + if debug: + self.like["debug"].boolean(True) + + def fit(self,log=False,debug=False): + self.validate() + if not(self.like): + self.warning("ctlike object not created, creating now") + self.create_fit(log,debug) + + # Run ctlike application. + self.like.run() + # Save the results in XML + self.like.save() + self.success("Fit performed") + + def PrintResults(self,srcname = ""): + self.info("Results of the Fit") + for m in self.like.obs().models(): + if srcname == m.name() or srcname=="": + print "Model : "+m.name() + print m + + def GetSrcResuls(self,Res,srcname,E0=None,factor = 1e6,tmin=None,tmax=None): + """function to get and store the results of a fit""" + results = self.GetSrcParams(srcname) + + if 1: + # #Time + # if (tmin is not None) or (tmax is not None) : + # Res["time"]["tmin_value"].append(tmin) + # Res["time"]["tmax_value"].append(tmax) + # Res["time"]["unit"] = "sec" + # else: + Res["time"]["tmin_value"].append(self.m_tmin) + Res["time"]["tmax_value"].append(self.m_tmax) + Res["time"]["unit"] = "sec" + + #Energy + if (E0 is not None): + Res["ener"]["value"].append(E0*factor) + Res["ener"]["ed_value"].append((E0-self.m_emin)*factor) + Res["ener"]["eu_value"].append((self.m_emax-E0)*factor) + Res["ener"]["unit"] = "MeV" + else: + Res["ener"]["value"].append(sqrt(self.m_emin*self.m_emax)) + Res["ener"]["ed_value"].append(-self.m_emin+sqrt(self.m_emin*self.m_emax)) + Res["ener"]["eu_value"].append(self.m_emax-sqrt(self.m_emin*self.m_emax)) + Res["ener"]["unit"] = self.m_eunit + + Res["Iflux"]["value"].append(results["flux"]) + Res["Iflux"]["eu_value"].append(results["eflux"]) + Res["Iflux"]["ed_value"].append(results["eflux"]) + # Res["Iflux"]["unit"] = "unit" + + m = self.GetModel(srcname) + Res["dnde"]["value"].append(m.spectral()[0].value()) + Res["dnde"]["eu_value"].append(m.spectral()[0].error()) + Res["dnde"]["ed_value"].append(m.spectral()[0].error()) + + Res["TS"].append(100) + + UL = UpperLimitComputer(self,srcname) + UL.run() + Res["ulim_dnde"].append(0) + # del UL + return Res + + def GetSrcParams(self,srcname): + results = {} + found = False + m = self.GetModel(srcname) + results["flux"] = m.spectral().flux(gl.GEnergy(self.m_emin,self.m_eunit),gl.GEnergy(self.m_emax,self.m_eunit)) + results["eflux"] = m.spectral().eflux(gl.GEnergy(self.m_emin,self.m_eunit),gl.GEnergy(self.m_emax,self.m_eunit)) + for par in m.spectral(): + if par.is_free(): + results[par.name()] = par.value() + results["e"+par.name()] = par.error() + + return results + + def GetModel(self,srcname): + found = False + for m in self.like.obs().models(): + if m.name() == srcname: + return m + self.error("source "+srcname+" not in the source list") + + +def MakeEbin(analyse,nbins,srcname,binned = False): + #Store the results + Res = ResultsSorage() + + #check the source name, exit is the srcname is false + analyse.GetModel(srcname) + + #compute the conversion factor to have energy in MeV + Energy_list = {"MeV":1,"GeV":1e3,"TeV":1e6} #TODO + factor = Energy_list[analyse.m_eunit] + + for i in xrange(nbins): + obs_copy = analyse.like.obs().copy() + emin = pow(10,log10(analyse.m_emin) + (log10(analyse.m_emax)-log10(analyse.m_emin))/(nbins)*i) + emax = pow(10,log10(analyse.m_emin) + (log10(analyse.m_emax)-log10(analyse.m_emin))/(nbins)*(i+1)) + E0 = pow(10, (log10(emin) + log10(emax)) / 2) + print "Making energy bin between %2.1e"%(emin)+analyse.m_eunit+" and %2.1e"%(emax)+analyse.m_eunit + + # change the PivotEnergy to E0 + m = obs_copy.models()[srcname] + for par in m.spectral(): + if par.name() == "PivotEnergy": + par.value(E0*factor) + + ana_bin = Analyser() + + #copy the obs object + ana_bin.set_obs(obs_copy) + + #copy the analyser parameters + ana_bin.copy(analyse) + + # Set energy boundaries + ana_bin.set_energy_boundary(emin,emax) + ana_bin.ctselect() + + # if binned: + # ana_bin.ctbin() + + # Create the fit object + ana_bin.create_fit(log=False,debug=False) + + # Run ctlike application. + ana_bin.like.run() + + #Retrive the results in the good format + Res = ana_bin.GetSrcResuls(Res,srcname,E0,factor) + + del ana_bin + return Res + +def MakeLC(analyse,nbins,srcname,binned = False): + #Store the results + Res = ResultsSorage() + + #check the source name, exit is the srcname is false + analyse.GetModel(srcname) + + for i in xrange(nbins): + obs_copy = analyse.like.obs().copy() + tmin = analyse.m_tmin + (analyse.m_tmax-analyse.m_tmin)/(nbins)*i + tmax = analyse.m_tmin + (analyse.m_tmax-analyse.m_tmin)/(nbins)*(i+1) + print "Making time bin between %2.1e"%(tmin)+" and %2.1e"%(tmax) + + ana_bin = Analyser() + + #copy the obs object + ana_bin.set_obs(obs_copy) + + #copy the analyser parameters + ana_bin.copy(analyse) + + # Set energy boundaries + ana_bin.set_time_boundary(tmin,tmax) + ana_bin.ctselect() + + # if binned: + # ana_bin.ctbin() + + # Create the fit object + ana_bin.create_fit(log=False,debug=False) + + # Run ctlike application. + ana_bin.like.run() + + #Retrive the results in the good format + Res = ana_bin.GetSrcResuls(Res,srcname,tmin=tmin,tmax=tmax) + + del ana_bin + return Res + +class UpperLimitComputer(base): + def __init__(self,analyser,srcname,parname = "Prefactor"): + super(UpperLimitComputer,self).__init__() + self.info("Creating "+self.classname+" object") + # self.like = analyser.like.copy() + obs = analyser.like.obs().copy() + self.like = ct.ctlike(obs) + self.succes = False + + # get spectral model + self.model = self.like.obs().models()[srcname] + self.spec = self.model.spectral() + self.parname = parname + self.bestloglike = analyser.like.opt().value() + self.dloglike = 3.84/2.0 #95% CL now + + def _bisec(self,a,b,tol = 1e-12): + """iterative function to find the roots""" + if (self._func(a)==0.): + return a + elif (self._func(b)==0): + return b + elif (self._func(b)*self._func(a)>0.): + self.warning("Finding root for upper limit calculation failed") + return 0 + mid = (a+b)/2 + err = abs(a-b)/2 + fm = self._func(mid) + if (err. # # ========================================================================== -import ctools -import gammalib +import ctools as ct +import gammalib as gl +from math import log10,pow,sqrt +import os + try: import matplotlib.pyplot as plt + import matplotlib.gridspec as gridspec has_matplotlib = True except: has_matplotlib = False + try: import aplpy has_aplpy = True except: has_aplpy = False +class options(object): + def __init__(self): + self.fmt = 'o' + self.color = BLUE + self.linecolor = 'black' + self.markercolor = self.color + self.tslim = 4.0 + self.elinewidth = 2.0 + self.linewidth = 2.0 + self.linestyle = '--' + self.capsize = 5.0 + self.markersize = 5.0 + self.limlength = 0.4 + self.label = "_nolegend_" + +import analysisutils + +class SpectralBase(object): + def __init__(self,sed=True,sed_factor=1.): + self.m_sed_factor = sed_factor + self.m_sed = sed + + def _convertSED(self,tab,energy): + """convert the a table storing dN/dE in sed with right units""" + + if len(tab) != len(energy): + self.error("input table and energy table does not match") + return + if self.m_sed: + for i in xrange(len(tab)): + tab[i] *= energy[i]**2*self.m_sed_factor + return tab + +BLUE="#337EF5" +GREY='grey' +GREEN = '#21851D' +RED = '#BD0B0B' +YELLOW = '#D4CF44' +ORANGE = '#D19534' + + +class residuals(analysisutils.base,options,SpectralBase): + def __init__(self,spectrum,datapoint,sed,sed_factor,eunit="TeV"): + super(residuals,self).__init__() + options.__init__(self) + SpectralBase.__init__(self,sed,sed_factor) + + # load the gammalib spectrum object + self.spectrum = spectrum + self.SetDataPoint(datapoint) + + self.xerrors = True + self.refUnit = eunit # reference unit for conversion + + def SetDataPoint(self,datapoint): + self.m_point_info = datapoint + self.Npt = len(self.m_point_info["dnde"]["value"]) + + def _plt_residuals(self): + th_val = [] + index = [] + ed_y = [] + eu_y = [] + y = [] + + Energy_list = {"MeV":1,"GeV":1e-3,"TeV":1e-6} #TODO + for i in range(self.Npt): + if self.m_point_info["TS"][i] >= self.tslim: + th_val.append(self.spectrum.eval(gl.GEnergy(self.m_point_info["ener"]["value"][i],self.m_point_info["ener"]["unit"]),gl.GTime(0))) + index.append(i) + else: + th_val.append(0) + if self.m_sed: + self._convertSED(th_val,self.m_point_info["ener"]) + for i in index: + y.append((self.m_point_info["dnde"]["value"][i] - th_val[i]) / th_val[i]) + ed_y.append(self.m_point_info["dnde"]["ed_value"][i]/th_val[i]) + eu_y.append(self.m_point_info["dnde"]["eu_value"][i]/th_val[i]) + + if self.xerrors: + 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) + else: + plt.errorbar(self.m_point_info["ener"]["value"], y, yerr=[ed_y,eu_y],fmt=self.fmt,color=self.markercolor,elinewidth=self.elinewidth) + + plt.axhline(0.0,color=self.linecolor,lw=self.linewidth,ls=self.linestyle) + + +class ulimgraph(analysisutils.base,options,SpectralBase): + def __init__(self,datapoint,sed,sed_factor,eunit='TeV'): + super(ulimgraph,self).__init__() + #~ super(ulimgraph,self).__init__() + options.__init__(self) + SpectralBase.__init__(self,sed,sed_factor) + + self.xerrors=True + self.nlims = -1 + self.refUnit = eunit + self.SetDataPoint(datapoint) + + def SetDataPoint(self,datapoint): + self.m_point_info = datapoint + self.Npt = len(self.m_point_info["dnde"]["value"]) + + def _plt_points(self): + results = analysisutils.ResultsSorage() + + Energy_list = {"MeV":1,"GeV":1e-3,"TeV":1e-6} #TODO + for i in range(self.Npt): + + self.m_point_info["ener"]["value"][i]*=Energy_list[self.refUnit] + self.m_point_info["ener"]["ed_value"][i]*=Energy_list[self.refUnit] + self.m_point_info["ener"]["eu_value"][i]*=Energy_list[self.refUnit] + self.m_point_info["ener"]["unit"] = self.refUnit + + if self.m_point_info["TS"][i] >= self.tslim: + + if self.m_point_info["dnde"]["ed_value"][i]>self.m_point_info["dnde"]["value"][i]: + 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])) + self.warning("\tTS value is however TS="+str(self.m_point_info["TS"][i])+", weird!") + else : + self.m_point_info["dnde"]["ed_value"][i] = 0 + self.m_point_info["dnde"]["eu_value"][i] = 0 + + if self.xerrors: + 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) + else: + 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) + + def _plt_limits(self): + ed_flux = [] + eu_flux = [] + flux = [] + ener = [] + + drawn_lims = 0 + Energy_list = {"MeV":1,"GeV":1e-3,"TeV":1e-6} #TODO + for i in range(self.Npt): + if self.m_point_info["TS"][i] < self.tslim: + self.info("Plot an upper limit for E = "+self.m_point_info["ener"]["value"][i]+" with a TS = "+self.m_point_info["TS"][i]) + if drawn_lims >= self.nlims and self.nlims!=-1: + break + + ed_flux.append(self.limlength*self.m_point_info["ulim_dnde"][i]) + eu_flux.append(0.0) + flux.append(self.m_point_info["ulim_dnde"][i]) + drawn_lims+=1 + + # if self.xerrors: + # 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) + # else: + 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) + + +class LightCurvePlotter(analysisutils.base,options): + """ Class to plot the Light curve""" + def __init__(self,srcname,datapoint): + super(LightCurvePlotter,self).__init__() + options.__init__(self) + self.m_name = srcname + self.m_data = datapoint #data point in a Results Storage class + + def draw(self): + if not(has_matplotlib): + self.warning("matplotlib module not found, can draw") + return + + time = [] + dtime = [] + flux = [] + dflux_ed = [] + dflux_eu = [] + for i in range(len(self.m_data["time"]["tmin_value"])): + time.append((self.m_data["time"]["tmin_value"][i]+self.m_data["time"]["tmax_value"][i])/2) + dtime.append((self.m_data["time"]["tmax_value"][i]-self.m_data["time"]["tmin_value"][i])/2) + flux.append(self.m_data["Iflux"]["value"][i]) + dflux_eu.append(self.m_data["Iflux"]["eu_value"][i]) + dflux_ed.append(self.m_data["Iflux"]["ed_value"][i]) + + plt.figure("results",figsize=(12,7),edgecolor="w") + unit = self.m_data["time"]["unit"] + plt.xlabel("Time ["+ unit+"]",fontsize=15) + unit = self.m_data["Iflux"]["unit"] + plt.ylabel("E$^{2}$ dN/dE ["+unit+"]",fontsize=15) + + print time + print dtime + print flux + print dflux_ed + print dflux_eu + plt.errorbar(time,flux,xerr=[dtime,dtime],yerr=[dflux_ed,dflux_eu],fmt=self.fmt,color=self.markercolor,elinewidth=self.elinewidth) + plt.show() + +class SpectrumPlotter(analysisutils.base,SpectralBase): + """ Class to compute and plot the spectra (i.e best fit model and butterfly). + if data points are provided, only change applied is energy unit convertion to match the butterfly one + (i.e. no dN/dE to SED conversion). + It is currently assumed that the unit is MeV for the energy of the data point. this will be change + once the I/O container format will be defined.""" + def __init__(self,srcname,like,datapoint,emin=0.1,emax=100,energy = "TeV",SED = True ,npt = 50): + super(SpectrumPlotter,self).__init__() + + self.m_ebound = [emin,emax] + self.m_name = srcname + self.m_like = like + self.m_spectral = like.obs().models()[srcname].spectral() + self.m_energy = [] + self.m_flux = [] + self.m_error = [] + self.m_but = [] + self.m_enebut = [] + self.m_npt = npt + self.m_covar = gl.GMatrix(1,1) + self.covar() + #~ this part take care of the style : SED or differential flux + #~ in the case of SED, the y-unit is erg/cm2/s + self.m_sed = SED + self.eunit = energy + self.m_sed_factor = 1. + self._validateUnits() + SpectralBase.__init__(self,self.m_sed,self.m_sed_factor) + + #Assume MeV for the energy #TODO + self.points = ulimgraph(datapoint,self.m_sed,gl.MeV2erg,energy) + self.residuals = residuals(self.m_spectral,datapoint,self.m_sed,gl.MeV2erg,energy) + + def _validateUnits(self): + Energy_list = {"MeV":1,"GeV":1e3,"TeV":1e6} + if not(Energy_list.has_key(self.eunit)): + self.warning("Change energy unit to TeV") + self.eunit = "TeV" + + if self.m_sed: + self.m_sed_factor = gl.MeV2erg*Energy_list[self.eunit]**2 + + def covar(self): + # Get covariance matrix if fit has converged + try: + fullcovar = self.m_like.obs().function().curvature().invert() + except: + self.warning("Covariance matrix not determined successfully") + return + #~ get the matrix element of the source of interest + #~ 1) get the right element indices + idx = 0 + par_index_map = [] + for m in self.m_like.obs().models() : + if m.name()!= self.m_name: + idx += m.size() + continue + idx += m.spatial().size() + for par in m.spectral(): + if par.is_free(): + par_index_map.append(idx) + idx += 1 + + #~ 2) get the elemets and store them in a matrix + self.m_covar = gl.GMatrix(len(par_index_map),len(par_index_map)) + i = 0 + for xpar in par_index_map: + j = 0 + for ypar in par_index_map: + self.m_covar[i,j] = fullcovar[xpar,ypar] + j += 1 + i += 1 + self.success("Covariance Matrix succesfuly computed") + + def _makespectrum(self): + for i in xrange(self.m_npt): + #Compute the energy + lene = log10(self.m_ebound[0])+log10(self.m_ebound[1])*i/(self.m_npt-1) + self.m_energy.append(pow(10,lene)) + #Compute the flux + self.m_flux.append(self.m_spectral.eval(gl.GEnergy(pow(10,lene),self.eunit),gl.GTime(0))) + + #Compute the gradients + if self.m_covar.size() != 1: + self.m_spectral.eval_gradients(gl.GEnergy(pow(10,lene), self.eunit),gl.GTime(0)) + #store them into a GVector + Derivative = gl.GVector(self.m_covar.size()/2) + j = 0 + for par in self.m_spectral: + if par.is_free(): + Derivative[j] = par.factor_gradient() + j += 1 + #~ computed the error + self.m_error.append(sqrt(Derivative * (self.m_covar*Derivative))) + + #~ convert the flux in sed if asked + self.m_flux = self._convertSED(self.m_flux,self.m_energy) + if self.m_covar.size() != 1: + self.m_error = self._convertSED(self.m_error,self.m_energy) + self._makebutterfly() # make the butterfly + self.success("Spectrum computed") + + def _makebutterfly(self): + """make the butterfly by appending element in a table""" + for i in xrange(self.m_npt): + self.m_but.append(self.m_flux[i]+self.m_error[i]) + self.m_enebut.append(self.m_energy[i]) + for i in xrange(self.m_npt): + idx = self.m_npt-i-1 + self.m_but.append(self.m_flux[idx]-self.m_error[idx]) + self.m_enebut.append(self.m_energy[idx]) + self.m_but.append(self.m_but[0]) + self.m_enebut.append(self.m_enebut[0]) + + def write(self): + self.warning("write function not yet implemented") + + def draw(self): + """plot the results, provide a file named ptfile for the flux points""" + gs = gridspec.GridSpec(2, 1,height_ratios=[3,1]) + self._makespectrum() + self.write() + + if not(has_matplotlib): + self.warning("matplotlib module not found, can draw") + return + + plt.figure("results",figsize=(12,7),edgecolor="w") + + sb1 = plt.subplot(gs[0]) + plt.loglog() + plt.xlabel("Energy ["+ self.eunit+"]",fontsize=15) + + if self.m_sed: + plt.ylabel("E$^{2}$ dN/dE [erg cm$^{2}$ s$^{-1}$]",fontsize=15) + else: + plt.ylabel("dN/dE [cm$^{-2}$ s$^{-1}$ "+ self.eunit+"$^{-1}$]",fontsize=15) + + plt.plot(self.m_energy,self.m_flux) + plt.plot(self.m_enebut,self.m_but) + + #~ plot the data points on top of the buterfly + self.points._plt_points() + self.points._plt_limits() + + #~ plot the residual points + sb2 = plt.subplot(gs[1]) + plt.xscale('log') + self.residuals._plt_residuals() + + plt.show() + # ============= # # Plot skymaps # # ============= # -def plotskymaps(modelmap, countmap): - """ - Plot the model map. - - Parameters: - modname - model input name - evtname - events input name - Keywords: - None - """ - # Load model map - fig = plt.figure() - f1 = aplpy.FITSFigure(countmap, figure=fig, subplot=[0.1,0.1,0.35,0.8]) - f1.set_tick_labels_font(size='small') - f1.set_axis_labels_font(size='small') - f1.show_colorscale() - - f1.tick_labels.set_yformat('dd:mm:ss') - f1.tick_labels.set_xformat('hh:mm:ss') - f1.axis_labels.set_xtext('Right Ascension (J2000)') - f1.axis_labels.set_ytext('Declination (J2000)') - f1.ticks.set_length(10.5, minor_factor=0.5) - - f2 = aplpy.FITSFigure(modelmap, figure=fig, subplot=[0.5,0.1,0.35,0.8]) - f2.set_tick_labels_font(size='small') - f2.set_axis_labels_font(size='small') - f2.show_colorscale() - - f2.hide_yaxis_label() - f2.hide_ytick_labels() - - fig.canvas.draw() - fig.show() - +class MapPlotter(analysisutils.base): + def __init__(self,modelmap, countmap): + super(MapPlotter,self).__init__() + self.modelmap = modelmap + self.countmap = countmap + + if not(has_aplpy) : + self.error("Module aplypy needed") + if not(has_matplotlib) : + self.error("Module matplotlib needed") + + def draw(self): + """ Plot the model map. """ + # Load model map + fig = plt.figure() + f1 = aplpy.FITSFigure(self.countmap, figure=fig, subplot=[0.1,0.1,0.35,0.8]) + f1.set_tick_labels_font(size='small') + f1.set_axis_labels_font(size='small') + f1.show_colorscale() + + f1.tick_labels.set_yformat('dd:mm:ss') + f1.tick_labels.set_xformat('hh:mm:ss') + f1.axis_labels.set_xtext('Right Ascension (J2000)') + f1.axis_labels.set_ytext('Declination (J2000)') + f1.ticks.set_length(10.5, minor_factor=0.5) + + f2 = aplpy.FITSFigure(self.modelmap, figure=fig, subplot=[0.5,0.1,0.35,0.8]) + f2.set_tick_labels_font(size='small') + f2.set_axis_labels_font(size='small') + f2.show_colorscale() + + f2.hide_yaxis_label() + f2.hide_ytick_labels() + + fig.canvas.draw() + fig.show() diff --git a/pyext/setup.py.in b/pyext/setup.py.in index e743ec1..18e250e 100644 --- a/pyext/setup.py.in +++ b/pyext/setup.py.in @@ -121,7 +121,8 @@ setup(name='@PACKAGE_NAME@', author='Juergen Knoedlseder', author_email='@PACKAGE_BUGREPORT@', url='http://gammalib.sourceforge.net/', - py_modules=['ctools.tools', 'ctools.obsutils'], + py_modules=['ctools.tools', 'ctools.obsutils', 'ctools.analysisutils', + 'ctools.plotutils'], ext_modules=[Extension('ctools._tools', ['ctools/tools_wrap.cpp'], include_dirs=include_dirs, library_dirs=library_dirs,