diff --git a/.gitignore b/.gitignore index adaaee5..78f4ad6 100644 --- a/.gitignore +++ b/.gitignore @@ -44,8 +44,8 @@ doc/man src/ctools-setup pyext/build/ pyext/setup.py -pyext/ctools.py -pyext/ctools_wrap.cpp +pyext/ctools/ctools.py +pyext/ctools/ctools_wrap.cpp src/ctbin/ctbin src/ctlike/ctlike src/ctmodel/ctmodel diff --git a/configure.ac b/configure.ac index f87d7c9..c09d4ec 100644 --- a/configure.ac +++ b/configure.ac @@ -630,7 +630,7 @@ else # Do we have wrappers (ctools_wrap.cpp and ctools.py)? has_wrappers="yes" - AC_CHECK_FILES([pyext/ctools_wrap.cpp pyext/ctools.py],, + AC_CHECK_FILES([pyext/ctools/ctools_wrap.cpp pyext/ctools/ctools.py],, [has_wrappers="no"]) if test "x$has_wrappers" = "xno"; then AC_MSG_WARN([Python wrapper missing. Requires swig for wrapper generation.]) @@ -669,7 +669,7 @@ if test "x$enable_python" = "xyes"; then else if test "x$has_python_wrap" = "xno" -a "x$has_swig" = "xno"; then - AC_MSG_WARN([Neither pyext/ctools_wrap.cpp and pyext/ctools.py nor swig has not been found]) + AC_MSG_WARN([Neither pyext/ctools/ctools_wrap.cpp and pyext/ctools/ctools.py nor swig has not been found]) AC_MSG_WARN([Python binding for ctools cannot be built]) AC_MSG_WARN([Either use a ctools distribution with ctools_wrap.cpp and ctools.py shipped or install swig on your system]) AC_MSG_WARN([swig can be obtained from http://www.swig.org/]) diff --git a/pyext/Makefile.am b/pyext/Makefile.am index 2294fda..9b2d2ce 100755 --- a/pyext/Makefile.am +++ b/pyext/Makefile.am @@ -30,7 +30,7 @@ if WITH_PYTHON # Set SWIG interface filename -SWIG_INTERFACE = ctools.i +SWIG_INTERFACE = ctools/ctools.i # Dependencies. Obtain the list of dependencies by executing ls. DEPFILES = $(shell ls *.Pi 2> /dev/null) @@ -39,7 +39,7 @@ include $(DEPFILES) # Define the targets that should be built before anything else. # These are honored by 'make all', 'make check' and 'make install'. if HAVE_SWIG - BUILT_SOURCES = ctools_wrap.cpp ctools.py + BUILT_SOURCES = ctools/ctools_wrap.cpp ctools/ctools.py endif # Set files to be removed in clean. We remove only the SWIG interface files @@ -59,7 +59,7 @@ all-local: build # of this specific directory. So we build one instance for checking, the other # for installation. If we could get the name built by distutils we could avoid # this. -build: ctools_wrap.cpp ctools.py +build: ctools/ctools_wrap.cpp ctools/ctools.py $(PYTHON) setup.py build install-exec-local: @@ -70,9 +70,9 @@ clean-local: # Rule for building Python wrappers using SWIG. if HAVE_SWIG -ctools.py: ctools_wrap.cpp -ctools_wrap.cpp: $(SWIG_INTERFACE) - if $(SWIGCOMPILE) -MMD -MF "ctools.Tpi" -o ctools_wrap.cpp $<; \ +ctools/ctools.py: ctools/ctools_wrap.cpp +ctools/ctools_wrap.cpp: $(SWIG_INTERFACE) + if $(SWIGCOMPILE) -MMD -MF "ctools.Tpi" -o ctools/ctools_wrap.cpp $<; \ then mv -f "ctools.Tpi" "ctools.Pi"; else rm -f "ctools.Tpi"; exit 1; fi endif # HAVE_SWIG diff --git a/pyext/ctools/__init__.py b/pyext/ctools/__init__.py new file mode 100644 index 0000000..131e5b4 --- /dev/null +++ b/pyext/ctools/__init__.py @@ -0,0 +1,3 @@ +from ctools import * +import obsutils + diff --git a/pyext/ctools.i b/pyext/ctools/ctools.i index a72ed96..a72ed96 100755 --- a/pyext/ctools.i +++ b/pyext/ctools/ctools.i diff --git a/pyext/ctools/obsutils.py b/pyext/ctools/obsutils.py new file mode 100755 index 0000000..96be22d --- /dev/null +++ b/pyext/ctools/obsutils.py @@ -0,0 +1,518 @@ +# ========================================================================== +# This script provides a number of functions that are useful for handling +# CTA observations. +# +# Copyright (C) 2011-2014 Juergen Knoedlseder +# +# 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 +import gammalib + + +# ===================== # +# Simulate observations # +# ===================== # +def sim(obs, log=False, debug=False, edisp=False, seed=0, nbins=0, + binsz=0.05, npix=200, proj="TAN", coord="GAL"): + """ + Simulate events for all observations in the container. + + Parameters: + obs - Observation container + Keywords: + log - Create log file(s) + debug - Create console dump? + edisp - Apply energy dispersion? + seed - Seed value for simulations (default: 0) + nbins - Number of energy bins (default: 0=unbinned) + binsz - Pixel size for binned simulation (deg/pixel) + npix - Number of pixels in X and Y for binned simulation + """ + # Allocate ctobssim application and set parameters + sim = ctools.ctobssim(obs) + sim["seed"].integer(seed) + + # Optionally open the log file + if log: + sim.logFileOpen() + + # Optionally switch-on debugging model + if debug: + sim["debug"].boolean(True) + + # Run ctobssim application. This will loop over all observations in the + # container and simulation the events for each observation. Note that + # events are not added together, they still apply to each observation + # separately. + sim.run() + + # Binned option? + if nbins > 0: + + # Determine common energy boundaries for observations + emin = None + emax = None + for run in sim.obs(): + run_emin = run.events().ebounds().emin().TeV() + run_emax = run.events().ebounds().emax().TeV() + if emin == None: + emin = run_emin + elif run_emin > emin: + emin = run_emin + if emax == None: + emax = run_emax + elif run_emax > emax: + emax = run_emax + + # Allocate ctbin application and set parameters + bin = ctools.ctbin(sim.obs()) + bin["ebinalg"].string("LOG") + bin["emin"].real(emin) + bin["emax"].real(emax) + bin["enumbins"].integer(nbins) + bin["usepnt"].boolean(True) # Use pointing for map centre + bin["nxpix"].integer(npix) + bin["nypix"].integer(npix) + bin["binsz"].real(binsz) + bin["coordsys"].string(coord) + bin["proj"].string(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.run() + + # Make a deep copy of the observation that will be returned + # (the ctbin object will go out of scope one the function is + # left) + obs = bin.obs().copy() + + else: + + # Make a deep copy of the observation that will be returned + # (the ctobssim object will go out of scope one the function is + # left) + obs = sim.obs().copy() + + # Delete the simulation + del sim + + # Return observation container + return obs + + +# ================ # +# Fit observations # +# ================ # +def fit(obs, log=False, debug=False, edisp=False): + """ + Perform maximum likelihood fitting of observations in the container. + + Parameters: + obs - Observation container + Keywords: + log - Create log file(s) + debug - Create screen dump + edisp - Apply energy dispersion? + """ + # Allocate ctlike application + like = ctools.ctlike(obs) + + # Optionally open the log file + if log: + like.logFileOpen() + + # Optionally switch-on debugging model + if debug: + like["debug"].boolean(True) + + # Optionally apply energy dispersion + like["edisp"].boolean(edisp) + + # Run ctlike application. + like.run() + + # Return observations + return like + + +# ================= # +# Create counts map # +# ================= # +def cntmap(obs, proj="TAN", coord="GAL", xval=0.0, yval=0.0, \ + binsz=0.05, nxpix=200, nypix=200, \ + outname="cntmap.fits"): + """ + Creates a counts map by combining the events of all observations. + The counts map will be a summed map over all energies. + + Parameters: + obs - Observation container + Keywords: + proj - Projection type (e.g. TAN, CAR, STG, ...) (default: TAN) + coord - Coordinate type (GAL, CEL) (default: GAL) + xval - Reference longitude value [deg] (default: 0.0) + yval - Reference latitude value [deg] (default: 0.0) + binsz - Pixel size [deg/pixel] (default: 0.05) + nxpix - Number of pixels in X direction (default: 200) + nypix - Number of pixels in Y direction (default: 200) + outname - Counts map FITS filename (default: cntmap.fits) + """ + # Allocate counts map + map = gammalib.GSkymap(proj, coord, xval, yval, -binsz, binsz, nxpix, nypix, 1) + + # Set maximum pixel number + maxpixel = nxpix * nypix + + # Fill all observations + for run in obs: + + # Loop over all events + for event in run.events(): + + # Determine sky pixel + skydir = gammalib.GCTAInstDir(event.dir()).dir() + pixel = map.dir2inx(skydir) + + # Set pixel + if pixel < maxpixel: + map[pixel] += event.counts() + + # Save sky map. The clobber flag is set to True, so any existing FITS + # file will be overwritten. + map.save(outname, True) + + # Return counts map + return map + + +# ================ # +# Create model map # +# ================ # +def modmap(obs, eref=0.1, proj="TAN", coord="GAL", xval=0.0, yval=0.0, \ + binsz=0.05, nxpix=200, nypix=200, \ + outname="modmap.fits"): + """ + Make model map for a given reference energy by combining all observations. + The model map will be evaluated for a given reference energy 'eref' and will + be given in units of [counts/(sr MeV s)]. + + Parameters: + obs - Observation container + Keywords: + eref - Reference energy for which model is created [TeV] (default: 0.1) + proj - Projection type (e.g. TAN, CAR, STG, ...) (default: TAN) + coord - Coordinate type (GAL, CEL) (default: GAL) + xval - Reference longitude value [deg] (default: 0.0) + yval - Reference latitude value [deg] (default: 0.0) + binsz - Pixel size [deg/pixel] (default: 0.05) + nxpix - Number of pixels in X direction (default: 200) + nypix - Number of pixels in Y direction (default: 200) + outname - Model map FITS filename (default: modmap.fits) + """ + # Allocate model map + map = gammalib.GSkymap(proj, coord, xval, yval, -binsz, binsz, nxpix, nypix, 1) + + # Set reference energy, time and direction. The time is not initialised and is + # in fact not used (as the IRF is assumed to be time independent for now). + # The sky direction is set later using the pixel values. + energy = gammalib.GEnergy() + time = gammalib.GTime() + instdir = gammalib.GCTAInstDir() + energy.TeV(eref) + + # Loop over all map pixels + for pixel in range(map.npix()): + + # Get sky direction + skydir = map.inx2dir(pixel) + instdir.dir(skydir) + + # Create event atom for map pixel + atom = gammalib.GCTAEventAtom() + atom.dir(instdir) + atom.energy(energy) + atom.time(time) + + # Initialise model value + value = 0.0 + + # Loop over all observations + for run in obs: + value += obs.models().eval(atom, run) + + # Set map value + map[pixel] = value + + # Save sky map + map.save(outname, True) + + # Return model map + return map + + +# ===================================== # +# Set one CTA observation (old version) # +# ===================================== # +def set(pntdir, tstart=0.0, duration=1800.0, deadc=0.95, \ + emin=0.1, emax=100.0, rad=5.0, \ + irf="cta_dummy_irf", caldb="dummy"): + """ + Obsolete function, use set_obs instead. + """ + # Print warning + print("Warning: obsutils.set is obsolete, use obsutils.set_obs instead.") + + # Call new function + obs_cta = set_obs(pntdir, tstart=tstart, duration=duration, deadc=deadc, \ + emin=emin, emax=emax, rad=rad, \ + irf=irf, caldb=caldb) + + # Return CTA observation + return obs_cta + + +# ======================= # +# Set one CTA observation # +# ======================= # +def set_obs(pntdir, tstart=0.0, duration=1800.0, deadc=0.95, \ + emin=0.1, emax=100.0, rad=5.0, \ + irf="cta_dummy_irf", caldb="dummy", id="000000"): + """ + Returns a single CTA observation containing an empty CTA event list. + By looping over this function you can add CTA observations to the + observation container. + + Parameters: + pntdir - Pointing direction [GSkyDir] + Keywords: + tstart - Start time [seconds] (default: 0.0) + duration - Duration of observation [seconds] (default: 1800.0) + deadc - Deadtime correction factor (default: 0.95) + emin - Minimum event energy [TeV] (default: 0.1) + emax - Maximum event energy [TeV] (default: 100.0) + rad - ROI radius used for analysis [deg] (default: 5.0) + irf - Instrument response function (default: cta_dummy_irf) + caldb - Calibration database path (default: "dummy") + """ + # Allocate CTA observation + obs_cta = gammalib.GCTAObservation() + + # Set calibration database + db = gammalib.GCaldb() + if (gammalib.dir_exists(caldb)): + db.rootdir(caldb) + else: + db.open("cta", caldb) + + # Set pointing direction + pnt = gammalib.GCTAPointing() + pnt.dir(pntdir) + obs_cta.pointing(pnt) + + # Set ROI + roi = gammalib.GCTARoi() + instdir = gammalib.GCTAInstDir() + instdir.dir(pntdir) + roi.centre(instdir) + roi.radius(rad) + + # Set GTI + gti = gammalib.GGti() + gti.append(gammalib.GTime(tstart), gammalib.GTime(tstart+duration)) + + # Set energy boundaries + ebounds = gammalib.GEbounds(gammalib.GEnergy(emin, "TeV"), \ + gammalib.GEnergy(emax, "TeV")) + + # Allocate event list + events = gammalib.GCTAEventList() + events.roi(roi) + events.gti(gti) + events.ebounds(ebounds) + obs_cta.events(events) + + # Set instrument response + obs_cta.response(irf, db) + + # Set ontime, livetime, and deadtime correction factor + obs_cta.ontime(duration) + obs_cta.livetime(duration*deadc) + obs_cta.deadc(deadc) + obs_cta.id(id) + + # Return CTA observation + return obs_cta + + +# ============================ # +# Set list of CTA observations # +# ============================ # +def set_obs_list(obsdeflist, tstart=0.0, duration=1800.0, deadc=0.95, \ + emin=0.1, emax=100.0, rad=5.0, \ + irf="cta_dummy_irf", caldb="dummy"): + """ + Returns an observation container filled with a list of CTA observations. + The list is defined by the obsdeflist parameter which is a dictionnary + containing the mandatory keywords 'ra' and 'dec' that specify the + pointing direction for a given observation. Optional keyword give control + over other observation proposerties, such as duration, deadtime correction, + energy range, etc. If an optional keyword is not specified, the function + keyword is used instead. + + Parameters: + obsdeflist - Observation definition list [{'ra': x.xx, 'dec': x.xx}] + The directory can take the following optional keywords: + - duration: Duration of CTA observation [seconds] + - deadc: Deadtime correction factor + - emin: Minimum event energy [TeV] + - emax: Maximum event energy [TeV] + - rad: ROI radius used for analysis [deg] + - irf: Instrument response function + - caldb: Calibration database path + Optional keywords overwrite keywords specified in the + function call. + Keywords: + tstart - Start time [seconds] (default: 0.0) + duration - Duration of one CTA observation [seconds] (default: 1800.0) + deadc - Deadtime correction factor (default: 0.95) + emin - Minimum event energy [TeV] (default: 0.1) + emax - Maximum event energy [TeV] (default: 100.0) + rad - ROI radius used for analysis [deg] (default: 5.0) + irf - Instrument response function (default: cta_dummy_irf) + caldb - Calibration database path (default: "dummy") + """ + # Initialise empty observation container + obs = gammalib.GObservations() + + # Initialise first time and identifier + obs_start = tstart + obs_id = 1 + + # Loop over observation definition list + for obsdef in obsdeflist: + + # Set pointing direction + pntdir = gammalib.GSkyDir() + pntdir.radec_deg(obsdef['ra'], obsdef['dec']) + + # Set duration (use default if not found in definition list) + if 'duration' in obsdef: + obs_duration = obsdef['duration'] + else: + obs_duration = duration + + # Set emin (use default if not found in definition list) + if 'emin' in obsdef: + obs_emin = obsdef['emin'] + else: + obs_emin = emin + + # Set emax (use default if not found in definition list) + if 'emax' in obsdef: + obs_emax = obsdef['emax'] + else: + obs_emax = emax + + # Set radius (use default if not found in definition list) + if 'rad' in obsdef: + obs_rad = obsdef['rad'] + else: + obs_rad = rad + + # Set deadc (use default if not found in definition list) + if 'deadc' in obsdef: + obs_deadc = obsdef['deadc'] + else: + obs_deadc = deadc + + # Set caldb (use default if not found in definition list) + if 'caldb' in obsdef: + obs_caldb = obsdef['caldb'] + else: + obs_caldb = caldb + + # Set irf (use default if not found in definition list) + if 'irf' in obsdef: + obs_irf = obsdef['irf'] + else: + obs_irf = irf + + # Generate identifier string + id = "%6.6d" % obs_id + + # Set CTA observation + obs_cta = set_obs(pntdir, tstart=obs_start, duration=obs_duration, \ + deadc=obs_deadc, emin=obs_emin, emax=obs_emax, \ + rad=obs_rad, irf=obs_irf, caldb=obs_caldb, id=id) + + # Append to container + obs.append(obs_cta) + + # Update start time and identifier + obs_start += obs_duration + obs_id += 1 + + # Return observation container + return obs + + +# ============================ # +# Set CTA observation patterns # +# ============================ # +def set_obs_patterns(pattern, ra=83.6331, dec=22.0145, offset=1.5): + """ + Sets a number of standard patterns. + + Parameters: + pattern - Observation pattern. Possible options are: + - "single": single pointing + - "four": four pointings 'offset' around pattern centre + + Keywords: + ra - Right Ascension of pattern centre [deg] (default: 83.6331) + dec - Declination of pattern centre [deg] (default: 22.0145) + offset - Offset from pattern centre [deg] (default: 1.5) + """ + # Initialise observation definition list + obsdeflist = [] + + # Add patterns + if pattern == "single": + obsdef = {'ra': ra, 'dec': dec} + obsdeflist.append(obsdef) + elif pattern == "four": + # Set pattern centre + centre = gammalib.GSkyDir() + centre.radec_deg(ra, dec) + + # Append pointings + for phi in [0.0, 90.0, 180.0, 270.0]: + pntdir = centre.copy() + pntdir.rotate_deg(phi, offset) + obsdeflist.append({'ra': pntdir.ra_deg(), 'dec': pntdir.dec_deg()}) + else: + print("Warning: Observation pattern '"+str(pattern)+"' not recognized.") + + # Return observation definition list + return obsdeflist diff --git a/pyext/setup.py.in b/pyext/setup.py.in index c8bddda..6361d04 100644 --- a/pyext/setup.py.in +++ b/pyext/setup.py.in @@ -121,8 +121,8 @@ setup(name='@PACKAGE_NAME@', author='Juergen Knoedlseder', author_email='@PACKAGE_BUGREPORT@', url='http://gammalib.sourceforge.net/', - py_modules=['ctools'], - ext_modules=[Extension('_ctools', ['ctools_wrap.cpp'], + py_modules=['ctools.ctools', 'ctools.obsutils'], + ext_modules=[Extension('ctools._ctools', ['ctools/ctools_wrap.cpp'], include_dirs=include_dirs, library_dirs=library_dirs, libraries=libraries, diff --git a/scripts/Makefile.am b/scripts/Makefile.am index c23790b..d4c352a 100644 --- a/scripts/Makefile.am +++ b/scripts/Makefile.am @@ -5,8 +5,8 @@ binscripts = cta_kb_bkgrates.py cta_root2irf.py cta_make_nodes.py # Scripts to be distributed in pythondir pythonscripts = cspull.py cssens.py cstsdist.py cscaldb.py \ - obsutils.py \ - lsmodel.py + lsmodel.py \ + obsutils.py # Scripts for which symbolic links will be created in bindir linkscripts = cspull cssens cstsdist cscaldb \ diff --git a/scripts/cssens.py b/scripts/cssens.py index 0bb8858..6798198 100755 --- a/scripts/cssens.py +++ b/scripts/cssens.py @@ -35,7 +35,6 @@ # ========================================================================== from ctools import * from gammalib import * -import obsutils import sys import csv import math diff --git a/scripts/cstsdist.py b/scripts/cstsdist.py index 95ec225..34d1ad2 100755 --- a/scripts/cstsdist.py +++ b/scripts/cstsdist.py @@ -21,7 +21,6 @@ # ========================================================================== from ctools import * from gammalib import * -import obsutils import sys import csv import math diff --git a/scripts/obsutils.py b/scripts/obsutils.py old mode 100755 new mode 100644 index 96be22d..c8acd13 --- a/scripts/obsutils.py +++ b/scripts/obsutils.py @@ -1,518 +1,5 @@ -# ========================================================================== -# This script provides a number of functions that are useful for handling -# CTA observations. -# -# Copyright (C) 2011-2014 Juergen Knoedlseder -# -# 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 -import gammalib +print "The obsutils are moved under the ctools module." +print "Please update your script to import from ctools, i.e:" +print "from ctools import obsutils" - -# ===================== # -# Simulate observations # -# ===================== # -def sim(obs, log=False, debug=False, edisp=False, seed=0, nbins=0, - binsz=0.05, npix=200, proj="TAN", coord="GAL"): - """ - Simulate events for all observations in the container. - - Parameters: - obs - Observation container - Keywords: - log - Create log file(s) - debug - Create console dump? - edisp - Apply energy dispersion? - seed - Seed value for simulations (default: 0) - nbins - Number of energy bins (default: 0=unbinned) - binsz - Pixel size for binned simulation (deg/pixel) - npix - Number of pixels in X and Y for binned simulation - """ - # Allocate ctobssim application and set parameters - sim = ctools.ctobssim(obs) - sim["seed"].integer(seed) - - # Optionally open the log file - if log: - sim.logFileOpen() - - # Optionally switch-on debugging model - if debug: - sim["debug"].boolean(True) - - # Run ctobssim application. This will loop over all observations in the - # container and simulation the events for each observation. Note that - # events are not added together, they still apply to each observation - # separately. - sim.run() - - # Binned option? - if nbins > 0: - - # Determine common energy boundaries for observations - emin = None - emax = None - for run in sim.obs(): - run_emin = run.events().ebounds().emin().TeV() - run_emax = run.events().ebounds().emax().TeV() - if emin == None: - emin = run_emin - elif run_emin > emin: - emin = run_emin - if emax == None: - emax = run_emax - elif run_emax > emax: - emax = run_emax - - # Allocate ctbin application and set parameters - bin = ctools.ctbin(sim.obs()) - bin["ebinalg"].string("LOG") - bin["emin"].real(emin) - bin["emax"].real(emax) - bin["enumbins"].integer(nbins) - bin["usepnt"].boolean(True) # Use pointing for map centre - bin["nxpix"].integer(npix) - bin["nypix"].integer(npix) - bin["binsz"].real(binsz) - bin["coordsys"].string(coord) - bin["proj"].string(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.run() - - # Make a deep copy of the observation that will be returned - # (the ctbin object will go out of scope one the function is - # left) - obs = bin.obs().copy() - - else: - - # Make a deep copy of the observation that will be returned - # (the ctobssim object will go out of scope one the function is - # left) - obs = sim.obs().copy() - - # Delete the simulation - del sim - - # Return observation container - return obs - - -# ================ # -# Fit observations # -# ================ # -def fit(obs, log=False, debug=False, edisp=False): - """ - Perform maximum likelihood fitting of observations in the container. - - Parameters: - obs - Observation container - Keywords: - log - Create log file(s) - debug - Create screen dump - edisp - Apply energy dispersion? - """ - # Allocate ctlike application - like = ctools.ctlike(obs) - - # Optionally open the log file - if log: - like.logFileOpen() - - # Optionally switch-on debugging model - if debug: - like["debug"].boolean(True) - - # Optionally apply energy dispersion - like["edisp"].boolean(edisp) - - # Run ctlike application. - like.run() - - # Return observations - return like - - -# ================= # -# Create counts map # -# ================= # -def cntmap(obs, proj="TAN", coord="GAL", xval=0.0, yval=0.0, \ - binsz=0.05, nxpix=200, nypix=200, \ - outname="cntmap.fits"): - """ - Creates a counts map by combining the events of all observations. - The counts map will be a summed map over all energies. - - Parameters: - obs - Observation container - Keywords: - proj - Projection type (e.g. TAN, CAR, STG, ...) (default: TAN) - coord - Coordinate type (GAL, CEL) (default: GAL) - xval - Reference longitude value [deg] (default: 0.0) - yval - Reference latitude value [deg] (default: 0.0) - binsz - Pixel size [deg/pixel] (default: 0.05) - nxpix - Number of pixels in X direction (default: 200) - nypix - Number of pixels in Y direction (default: 200) - outname - Counts map FITS filename (default: cntmap.fits) - """ - # Allocate counts map - map = gammalib.GSkymap(proj, coord, xval, yval, -binsz, binsz, nxpix, nypix, 1) - - # Set maximum pixel number - maxpixel = nxpix * nypix - - # Fill all observations - for run in obs: - - # Loop over all events - for event in run.events(): - - # Determine sky pixel - skydir = gammalib.GCTAInstDir(event.dir()).dir() - pixel = map.dir2inx(skydir) - - # Set pixel - if pixel < maxpixel: - map[pixel] += event.counts() - - # Save sky map. The clobber flag is set to True, so any existing FITS - # file will be overwritten. - map.save(outname, True) - - # Return counts map - return map - - -# ================ # -# Create model map # -# ================ # -def modmap(obs, eref=0.1, proj="TAN", coord="GAL", xval=0.0, yval=0.0, \ - binsz=0.05, nxpix=200, nypix=200, \ - outname="modmap.fits"): - """ - Make model map for a given reference energy by combining all observations. - The model map will be evaluated for a given reference energy 'eref' and will - be given in units of [counts/(sr MeV s)]. - - Parameters: - obs - Observation container - Keywords: - eref - Reference energy for which model is created [TeV] (default: 0.1) - proj - Projection type (e.g. TAN, CAR, STG, ...) (default: TAN) - coord - Coordinate type (GAL, CEL) (default: GAL) - xval - Reference longitude value [deg] (default: 0.0) - yval - Reference latitude value [deg] (default: 0.0) - binsz - Pixel size [deg/pixel] (default: 0.05) - nxpix - Number of pixels in X direction (default: 200) - nypix - Number of pixels in Y direction (default: 200) - outname - Model map FITS filename (default: modmap.fits) - """ - # Allocate model map - map = gammalib.GSkymap(proj, coord, xval, yval, -binsz, binsz, nxpix, nypix, 1) - - # Set reference energy, time and direction. The time is not initialised and is - # in fact not used (as the IRF is assumed to be time independent for now). - # The sky direction is set later using the pixel values. - energy = gammalib.GEnergy() - time = gammalib.GTime() - instdir = gammalib.GCTAInstDir() - energy.TeV(eref) - - # Loop over all map pixels - for pixel in range(map.npix()): - - # Get sky direction - skydir = map.inx2dir(pixel) - instdir.dir(skydir) - - # Create event atom for map pixel - atom = gammalib.GCTAEventAtom() - atom.dir(instdir) - atom.energy(energy) - atom.time(time) - - # Initialise model value - value = 0.0 - - # Loop over all observations - for run in obs: - value += obs.models().eval(atom, run) - - # Set map value - map[pixel] = value - - # Save sky map - map.save(outname, True) - - # Return model map - return map - - -# ===================================== # -# Set one CTA observation (old version) # -# ===================================== # -def set(pntdir, tstart=0.0, duration=1800.0, deadc=0.95, \ - emin=0.1, emax=100.0, rad=5.0, \ - irf="cta_dummy_irf", caldb="dummy"): - """ - Obsolete function, use set_obs instead. - """ - # Print warning - print("Warning: obsutils.set is obsolete, use obsutils.set_obs instead.") - - # Call new function - obs_cta = set_obs(pntdir, tstart=tstart, duration=duration, deadc=deadc, \ - emin=emin, emax=emax, rad=rad, \ - irf=irf, caldb=caldb) - - # Return CTA observation - return obs_cta - - -# ======================= # -# Set one CTA observation # -# ======================= # -def set_obs(pntdir, tstart=0.0, duration=1800.0, deadc=0.95, \ - emin=0.1, emax=100.0, rad=5.0, \ - irf="cta_dummy_irf", caldb="dummy", id="000000"): - """ - Returns a single CTA observation containing an empty CTA event list. - By looping over this function you can add CTA observations to the - observation container. - - Parameters: - pntdir - Pointing direction [GSkyDir] - Keywords: - tstart - Start time [seconds] (default: 0.0) - duration - Duration of observation [seconds] (default: 1800.0) - deadc - Deadtime correction factor (default: 0.95) - emin - Minimum event energy [TeV] (default: 0.1) - emax - Maximum event energy [TeV] (default: 100.0) - rad - ROI radius used for analysis [deg] (default: 5.0) - irf - Instrument response function (default: cta_dummy_irf) - caldb - Calibration database path (default: "dummy") - """ - # Allocate CTA observation - obs_cta = gammalib.GCTAObservation() - - # Set calibration database - db = gammalib.GCaldb() - if (gammalib.dir_exists(caldb)): - db.rootdir(caldb) - else: - db.open("cta", caldb) - - # Set pointing direction - pnt = gammalib.GCTAPointing() - pnt.dir(pntdir) - obs_cta.pointing(pnt) - - # Set ROI - roi = gammalib.GCTARoi() - instdir = gammalib.GCTAInstDir() - instdir.dir(pntdir) - roi.centre(instdir) - roi.radius(rad) - - # Set GTI - gti = gammalib.GGti() - gti.append(gammalib.GTime(tstart), gammalib.GTime(tstart+duration)) - - # Set energy boundaries - ebounds = gammalib.GEbounds(gammalib.GEnergy(emin, "TeV"), \ - gammalib.GEnergy(emax, "TeV")) - - # Allocate event list - events = gammalib.GCTAEventList() - events.roi(roi) - events.gti(gti) - events.ebounds(ebounds) - obs_cta.events(events) - - # Set instrument response - obs_cta.response(irf, db) - - # Set ontime, livetime, and deadtime correction factor - obs_cta.ontime(duration) - obs_cta.livetime(duration*deadc) - obs_cta.deadc(deadc) - obs_cta.id(id) - - # Return CTA observation - return obs_cta - - -# ============================ # -# Set list of CTA observations # -# ============================ # -def set_obs_list(obsdeflist, tstart=0.0, duration=1800.0, deadc=0.95, \ - emin=0.1, emax=100.0, rad=5.0, \ - irf="cta_dummy_irf", caldb="dummy"): - """ - Returns an observation container filled with a list of CTA observations. - The list is defined by the obsdeflist parameter which is a dictionnary - containing the mandatory keywords 'ra' and 'dec' that specify the - pointing direction for a given observation. Optional keyword give control - over other observation proposerties, such as duration, deadtime correction, - energy range, etc. If an optional keyword is not specified, the function - keyword is used instead. - - Parameters: - obsdeflist - Observation definition list [{'ra': x.xx, 'dec': x.xx}] - The directory can take the following optional keywords: - - duration: Duration of CTA observation [seconds] - - deadc: Deadtime correction factor - - emin: Minimum event energy [TeV] - - emax: Maximum event energy [TeV] - - rad: ROI radius used for analysis [deg] - - irf: Instrument response function - - caldb: Calibration database path - Optional keywords overwrite keywords specified in the - function call. - Keywords: - tstart - Start time [seconds] (default: 0.0) - duration - Duration of one CTA observation [seconds] (default: 1800.0) - deadc - Deadtime correction factor (default: 0.95) - emin - Minimum event energy [TeV] (default: 0.1) - emax - Maximum event energy [TeV] (default: 100.0) - rad - ROI radius used for analysis [deg] (default: 5.0) - irf - Instrument response function (default: cta_dummy_irf) - caldb - Calibration database path (default: "dummy") - """ - # Initialise empty observation container - obs = gammalib.GObservations() - - # Initialise first time and identifier - obs_start = tstart - obs_id = 1 - - # Loop over observation definition list - for obsdef in obsdeflist: - - # Set pointing direction - pntdir = gammalib.GSkyDir() - pntdir.radec_deg(obsdef['ra'], obsdef['dec']) - - # Set duration (use default if not found in definition list) - if 'duration' in obsdef: - obs_duration = obsdef['duration'] - else: - obs_duration = duration - - # Set emin (use default if not found in definition list) - if 'emin' in obsdef: - obs_emin = obsdef['emin'] - else: - obs_emin = emin - - # Set emax (use default if not found in definition list) - if 'emax' in obsdef: - obs_emax = obsdef['emax'] - else: - obs_emax = emax - - # Set radius (use default if not found in definition list) - if 'rad' in obsdef: - obs_rad = obsdef['rad'] - else: - obs_rad = rad - - # Set deadc (use default if not found in definition list) - if 'deadc' in obsdef: - obs_deadc = obsdef['deadc'] - else: - obs_deadc = deadc - - # Set caldb (use default if not found in definition list) - if 'caldb' in obsdef: - obs_caldb = obsdef['caldb'] - else: - obs_caldb = caldb - - # Set irf (use default if not found in definition list) - if 'irf' in obsdef: - obs_irf = obsdef['irf'] - else: - obs_irf = irf - - # Generate identifier string - id = "%6.6d" % obs_id - - # Set CTA observation - obs_cta = set_obs(pntdir, tstart=obs_start, duration=obs_duration, \ - deadc=obs_deadc, emin=obs_emin, emax=obs_emax, \ - rad=obs_rad, irf=obs_irf, caldb=obs_caldb, id=id) - - # Append to container - obs.append(obs_cta) - - # Update start time and identifier - obs_start += obs_duration - obs_id += 1 - - # Return observation container - return obs - - -# ============================ # -# Set CTA observation patterns # -# ============================ # -def set_obs_patterns(pattern, ra=83.6331, dec=22.0145, offset=1.5): - """ - Sets a number of standard patterns. - - Parameters: - pattern - Observation pattern. Possible options are: - - "single": single pointing - - "four": four pointings 'offset' around pattern centre - - Keywords: - ra - Right Ascension of pattern centre [deg] (default: 83.6331) - dec - Declination of pattern centre [deg] (default: 22.0145) - offset - Offset from pattern centre [deg] (default: 1.5) - """ - # Initialise observation definition list - obsdeflist = [] - - # Add patterns - if pattern == "single": - obsdef = {'ra': ra, 'dec': dec} - obsdeflist.append(obsdef) - elif pattern == "four": - # Set pattern centre - centre = gammalib.GSkyDir() - centre.radec_deg(ra, dec) - - # Append pointings - for phi in [0.0, 90.0, 180.0, 270.0]: - pntdir = centre.copy() - pntdir.rotate_deg(phi, offset) - obsdeflist.append({'ra': pntdir.ra_deg(), 'dec': pntdir.dec_deg()}) - else: - print("Warning: Observation pattern '"+str(pattern)+"' not recognized.") - - # Return observation definition list - return obsdeflist +from ctools.obsutils import * diff --git a/test/Makefile.am b/test/Makefile.am index f94addb..6207e4c 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -8,7 +8,7 @@ MACOSX_DEPLOYMENT_TARGET=@CONFIGURE_MACOSX_DEPLOYMENT_TARGET@ if WITH_PYTHON TEST_CSCRIPTS = test_cscripts.sh TEST_PYTHON_SCRIPT = test_python.py - TEST_PYTHON_ENV = PYTHONPATH=$(top_builddir)/pyext:$(top_builddir)/pyext/build/ctools:$(top_builddir)/scripts:$(pythondir):$(pyexecdir):$(PYTHONPATH) + TEST_PYTHON_ENV = PYTHONPATH=$(top_builddir)/pyext:$(top_builddir)/pyext/build/ctools/ctools:$(top_builddir)/scripts:$(pythondir):$(pyexecdir):$(PYTHONPATH) endif # Set test environment