diff --git a/ChangeLog b/ChangeLog index 520a344..b4ef0c8 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,8 +1,9 @@ -2016-11-22 +2016-11-23 * Version 1.2.0 released ======================== + Add csobsselect script (#1881) Add csmodelselect script (#1879) Add startindex parameter to ctobssim (#1878) Fix energy boundary handline in cssens (#1873) diff --git a/NEWS b/NEWS index a592f0d..2fcb748 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,6 @@ New Features and Important Changes in ctools 1.2.0 -22 November 2016 +23 November 2016 Introduction @@ -222,6 +222,9 @@ refactoring (#1873). Added csmodelselect script that allows selecting models from a model definition XML file (#1879). +Added csobsselect script that allows selecting observations from an observation +definition XML file (#1881). + Examples -------- diff --git a/cscripts/Makefile.am b/cscripts/Makefile.am index 4be21d4..52ce856 100644 --- a/cscripts/Makefile.am +++ b/cscripts/Makefile.am @@ -30,6 +30,7 @@ pythonscripts = $(builddir)/__init__.py \ $(srcdir)/csobs2caldb.py \ $(srcdir)/csobsdef.py \ $(srcdir)/csobsinfo.py \ + $(srcdir)/csobsselect.py \ $(srcdir)/cspull.py \ $(srcdir)/csresmap.py \ $(srcdir)/csroot2caldb.py \ @@ -58,6 +59,7 @@ parfiles = $(srcdir)/cscaldb.par \ $(srcdir)/csobs2caldb.par \ $(srcdir)/csobsdef.par \ $(srcdir)/csobsinfo.par \ + $(srcdir)/csobsselect.par \ $(srcdir)/cspull.par \ $(srcdir)/csresmap.par \ $(srcdir)/csroot2caldb.par \ @@ -83,6 +85,7 @@ linkscripts = cscaldb \ csobs2caldb \ csobsdef \ csobsinfo \ + csobsselect \ cspull \ csresmap \ csroot2caldb \ diff --git a/cscripts/__init__.py.in b/cscripts/__init__.py.in index 864149b..9332118 100644 --- a/cscripts/__init__.py.in +++ b/cscripts/__init__.py.in @@ -33,6 +33,7 @@ __all__ = [ "csobs2caldb", "csobsdef", "csobsinfo", + "csobsselect", "cspull", "csresmap", "csroot2caldb", @@ -64,6 +65,7 @@ from cscripts.csmodelselect import csmodelselect from cscripts.csobs2caldb import csobs2caldb from cscripts.csobsdef import csobsdef from cscripts.csobsinfo import csobsinfo +from cscripts.csobsselect import csobsselect from cscripts.cspull import cspull from cscripts.csresmap import csresmap from cscripts.csroot2caldb import csroot2caldb diff --git a/cscripts/csmodelselect.py b/cscripts/csmodelselect.py index 0e8fe93..2f39643 100755 --- a/cscripts/csmodelselect.py +++ b/cscripts/csmodelselect.py @@ -28,7 +28,7 @@ import ctools # =================== # class csmodelselect(ctools.cscript): """ - Selects model from a model container + Selects model from a model definition XML file """ # Constructor @@ -97,7 +97,7 @@ class csmodelselect(ctools.cscript): ---------- model : `~gammalib.GModel` Model - obs : `~gammalib.GObservation` + obs : `~gammalib.GObservations` Observation container Returns @@ -237,7 +237,7 @@ class csmodelselect(ctools.cscript): models = gammalib.GModels() # Write input observation container into logger - self._log_observations(gammalib.NORMAL, self._obs, 'Input observation'); + self._log_observations(gammalib.NORMAL, self._obs, 'Input observation') # Write input models into logger self._log_models(gammalib.VERBOSE, self._models, 'Input model') diff --git a/cscripts/csobsselect.par b/cscripts/csobsselect.par new file mode 100644 index 0000000..76f566e --- /dev/null +++ b/cscripts/csobsselect.par @@ -0,0 +1,42 @@ +############################################################################# +# # +# CTA Science Analysis Tools # +# # +# ------------------------------------------------------------------------- # +# # +# File: csobsselect.par # +# # +# Author: Juergen Knoedlseder # +# jknodlseder@irap.omp.eu # +# IRAP, Toulouse, France # +# # +# Purpose: Parameter file for the csobsselect script # +# # +############################################################################# +# +# Input and output parameters +#============================ +inobs, f, a, obs.xml,,, "Input event list or observation definition XML file" +outobs, f, a, outobs.xml,,, "Output observation definition XML file" + +# +# Script parameters +#================== +pntselect, s, a, CIRCLE,CIRCLE|BOX,, "Pointing selection region shape" +coordsys, s, a, CEL,CEL|GAL,, "Coordinate system (CEL - celestial, GAL - galactic)" +ra, r, a, 83.63,0,360, "Right Ascension of selection centre (deg)" +dec, r, a, 22.01,-90,90, "Declination of selection centre (deg)" +glon, r, a, 184.56,0,360, "Galactic longitude of selection centre (deg)" +glat, r, a, -5.79,-90,90, "Galactic latitude of selection centre (deg)" +rad, r, a, 5.0,0,180, "Radius of selection circle (deg)" +width, r, a, 10.0,0,360, "Width of selection box (deg)" +height, r, a, 10.0,0,180, "Height of selection box (deg)" + +# +# Standard parameters +#==================== +chatter, i, h, 2,0,4, "Chattiness of output" +clobber, b, h, yes,,, "Overwrite existing output files with new output files?" +debug, b, h, no,,, "Debugging mode activated" +mode, s, h, ql,,, "Mode of automatic parameters" +logfile, f, h, csobsselect.log,,, "Log filename" diff --git a/cscripts/csobsselect.py b/cscripts/csobsselect.py new file mode 100755 index 0000000..a444184 --- /dev/null +++ b/cscripts/csobsselect.py @@ -0,0 +1,359 @@ +#!/usr/bin/env python +# ========================================================================== +# Selects observations from an observation definition XML file +# +# Copyright (C) 2016 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 sys +import gammalib +import ctools + + +# ================= # +# csobsselect class # +# ================= # +class csobsselect(ctools.cscript): + """ + Selects observations from an observation definition XML file + """ + + # Constructor + def __init__(self, *argv): + """ + Constructor + """ + # Set name + self._name = 'csobsselect' + self._version = '1.2.0' + + # Initialise observation container from constructor arguments + self._obs, argv = self._set_input_obs(argv) + + # Initialise application by calling the appropriate class constructor + self._init_cscript(argv) + + # Return + return + + + # Private methods + def _get_parameters(self): + """ + Get parameters from parfile and setup observations + """ + # Query input parameters + self['inobs'].filename() + + # Query relevant pointing selection parameters + pntselect = self['pntselect'].string() + coordsys = self['coordsys'].string() + if coordsys == 'CEL': + self['ra'].real() + self['dec'].real() + else: + self['glon'].real() + self['glat'].real() + if pntselect == 'CIRCLE': + self['rad'].real() + else: + self['width'].real() + self['height'].real() + + # Query ahead output model filename + if self._read_ahead(): + self['outobs'].filename() + + # If there are no observations in container then get them from the + # parameter file + if self._obs.size() == 0: + self._obs = self._get_observations(False) + + # Write input parameters into logger + self._log_parameters(gammalib.TERSE) + + # Return + return + + def _select_observation(self, obs): + """ + Select observation + + Parameters + ---------- + obs : `~gammalib.GObservation` + Observation + + Returns + ------- + select : bool + Observation selection flag + """ + # Initialise selection flag + select = False + + # Continue only if observation is a CTA observation + if obs.classname() == 'GCTAObservation': + + # Get selection type + pntselect = self['pntselect'].string() + + # Dispatch according to selection type + if pntselect == 'CIRCLE': + select = self._select_circle(obs) + else: + select = self._select_box(obs) + + # Return selection flag + return select + + def _select_circle(self, obs): + """ + Select observation in a pointing circle + + Parameters + ---------- + obs : `~gammalib.GCTAObservation` + CTA observation + + Returns + ------- + select : bool + Observation selection flag + """ + # Get coordinate system of selection circle + coordsys = self['coordsys'].string() + + # Get pointing direction + pnt = obs.pointing().dir() + + # Set selection circle centre + centre = gammalib.GSkyDir() + if coordsys == 'CEL': + centre.radec_deg(self['ra'].real(), self['dec'].real()) + else: + centre.lb_deg(self['glon'].real(), self['glat'].real()) + + # Set selection flag according to distance + if centre.dist_deg(pnt) <= self['rad'].real(): + select = True + msg = 'inside selection circle' + else: + select = False + msg = 'outside selection circle' + + # Set observation name + if len(obs.name()) > 0: + name = obs.name() + else: + name = 'Observation' + + # Prepend obervation ID + if len(obs.id()) > 0: + msg = obs.id()+' '+msg + + # Log observation selection + self._log_value(gammalib.NORMAL, name, msg) + + # Return selection flag + return select + + def _select_box(self, obs): + """ + Select observation in a pointing box + + Parameters + ---------- + obs : `~gammalib.GCTAObservation` + CTA observation + + Returns + ------- + select : bool + Observation selection flag + """ + # Initialise selection flag + select = False + msg = 'outside selection box' + + # Get coordinate system of selection circle + coordsys = self['coordsys'].string() + + # Get pointing direction + pnt = obs.pointing().dir() + + # Get selection box width and height + width = self['width'].real() + height = self['height'].real() + + # Make selection for celestial coordinates ... + if coordsys == 'CEL': + + # Determine box boundaries + ra = self['ra'].real() + dec = self['dec'].real() + ra_min = ra - 0.5 * width + ra_max = ra + 0.5 * width + dec_min = dec - 0.5 * height + dec_max = dec + 0.5 * height + + # Check if pointing lies within boundaries + ra = pnt.ra_deg() + ra_plus = ra + 360.0 + ra_minus = ra - 360.0 + dec = pnt.dec_deg() + if dec >= dec_min and dec <= dec_max: + if ra >= ra_min and ra <= ra_max or \ + ra_plus >= ra_min and ra_plus <= ra_max or \ + ra_minus >= ra_min and ra_minus <= ra_max: + select = True + msg = 'inside selection box' + + # ... or galactic coordinates + else: + + # Determine box boundaries + glon = self['glon'].real() + glat = self['glat'].real() + glon_min = glon - 0.5 * width + glon_max = glon + 0.5 * width + glat_min = glat - 0.5 * height + glat_max = glat + 0.5 * height + + # Check if pointing lies within boundaries + glon = pnt.l_deg() + glon_plus = glon + 360.0 + glon_minus = glon - 360.0 + glat = pnt.b_deg() + if glat >= glat_min and glat <= glat_max: + if glon >= glon_min and glon <= glon_max or \ + glon_plus >= glon_min and glon_plus <= glon_max or \ + glon_minus >= glon_min and glon_minus <= glon_max: + select = True + msg = 'inside selection box' + + # Set observation name + if len(obs.name()) > 0: + name = obs.name() + else: + name = 'Observation' + + # Prepend obervation ID + if len(obs.id()) > 0: + msg = obs.id()+' '+msg + + # Log observation selection + self._log_value(gammalib.NORMAL, name, msg) + + # Return selection flag + return select + + + # Public methods + def run(self): + """ + Run the script + """ + # Switch screen logging on in debug mode + if self._logDebug(): + self._log.cout(True) + + # Get parameters + self._get_parameters() + + # Initialise empty observation container for selected observations + selected_obs = gammalib.GObservations() + + # Write input observation container into logger + self._log_observations(gammalib.NORMAL, self._obs, 'Input observation') + + # Write header + self._log_header1(gammalib.NORMAL, 'Observation selection') + + # Loop over observations + for obs in self._obs: + + # If observation is selected then append observation + if self._select_observation(obs): + selected_obs.append(obs) + + # Copy selected observations into observation + self._obs = selected_obs + + # Write input observation container into logger + self._log_observations(gammalib.NORMAL, self._obs, 'Selected observation') + + # Return + return + + def save(self): + """ + Save observation definition XML file + """ + # Write header + self._log_header1(gammalib.TERSE, 'Save observations') + + # Get output filename + outobs = self['outobs'].filename() + + # If file exists and clobber flag is false then raise an exception + if outobs.exists() and not self._clobber: + msg = ('Cannot save "'+outobs.url()+'": File already exists. ' + 'Use parameter clobber=yes to allow overwriting of files.') + raise RuntimeError(msg) + + # Otherwise log filename and save file + else: + # Log filename + self._log_value(gammalib.NORMAL, 'Observation definition XML file', + outobs.url()) + + # Save observations + self._obs.save(outobs) + + # Return + return + + def execute(self): + """ + Execute the script + """ + # Open logfile + self.logFileOpen() + + # Read ahead output parameters + self._read_ahead(True) + + # Run the script + self.run() + + # Save observation definition file + self.save() + + # Return + return + + +# ======================== # +# Main routine entry point # +# ======================== # +if __name__ == '__main__': + + # Create instance of application + app = csobsselect(sys.argv) + + # Execute application + app.execute() diff --git a/doc/source/users/reference_manual/csmodelselect.rst b/doc/source/users/reference_manual/csmodelselect.rst index 722f637..6a58f6c 100644 --- a/doc/source/users/reference_manual/csmodelselect.rst +++ b/doc/source/users/reference_manual/csmodelselect.rst @@ -30,10 +30,10 @@ General parameters Output model definition XML file ``(roilimit = 4.5) [real]`` - Maximum RoI radius (deg) + Maximum RoI radius (degrees) ``(roimargin = 0.1) [real]`` - Radial margin to be added to RoIs (deg) + Radial margin to be added to RoIs (degrees) ``(ethres = 0.1) [real]`` Energy threshold for source flux selection (TeV) @@ -83,4 +83,4 @@ Standard parameters Related tools or scripts ------------------------ -None +:doc:`csobsselect` diff --git a/doc/source/users/reference_manual/csobsselect.rst b/doc/source/users/reference_manual/csobsselect.rst new file mode 100644 index 0000000..997515b --- /dev/null +++ b/doc/source/users/reference_manual/csobsselect.rst @@ -0,0 +1,88 @@ +.. _csobsselect: + +csobsselect +=========== + +Selects observations from an observation definition XML file. + + +Synopsis +-------- + +This script selects all observations from an observation definiton XML file that +have pointings within a specified selection region and writes these observations +into a new observation definition XML file. Possible selection regions are a +circle and a box, either in Galactic or Celestial coordinates. + + +General parameters +------------------ + +``inobs [file]`` + Input event list or observation definition XML file + +``outobs [file]`` + Output observation definition XML file + +``pntselect [string]`` + Pointing selection region shape + +``coordsys [string]`` + Coordinate system (CEL - celestial, GAL - galactic) + +``ra [real]`` + Right Ascension of pointing selection centre (J2000, in degrees) + +``dec [real]`` + Declination of pointing selection centre (J2000, in degrees) + +``glon [real]`` + Galactic longitude of pointing selection centre (degrees) + +``glat [real]`` + Galactic latitude of pointing selection centre (degrees) + +``rad [real]`` + Radius of selection circle (degrees) + +``width [real]`` + Width of selection box (degrees) + +``height [real]`` + Height of selection box (degrees) + + +Standard parameters +------------------- + +``(chatter = 2) [integer]`` + Verbosity of the executable: + chatter = 0: no information will be logged + + chatter = 1: only errors will be logged + + chatter = 2: errors and actions will be logged + + chatter = 3: report about the task execution + + chatter = 4: detailed report about the task execution + +``(clobber = yes) [boolean]`` + Specifies whether an existing output model file should be overwritten + +``(debug = no) [boolean]`` + Enables debug mode. In debug mode the executable will dump any log file + output to the console + +``(mode = ql) [string]`` + Mode of automatic parameters (default is "ql", i.e. "query and learn") + +``(logfile = csobsselect.log) [filename]`` + Log filename + + +Related tools or scripts +------------------------ + +:doc:`csmodelselect` + diff --git a/doc/source/users/reference_manual/index.rst b/doc/source/users/reference_manual/index.rst index 073351e..8ad2850 100644 --- a/doc/source/users/reference_manual/index.rst +++ b/doc/source/users/reference_manual/index.rst @@ -47,6 +47,7 @@ cscripts csmodelselect --- Select models from model definition file csobsdef --- Generates observation definition file csobsinfo --- Shows observation container content + csobsselect --- Select observations from observation definition file cspull --- Generates pull distribution csresmap --- Generates residual map cssens --- Computes CTA sensitivity diff --git a/test/test_csobsselect.py b/test/test_csobsselect.py new file mode 100755 index 0000000..0cb47f7 --- /dev/null +++ b/test/test_csobsselect.py @@ -0,0 +1,206 @@ +#! /usr/bin/env python +# ========================================================================== +# This scripts performs unit tests for the csobsselect script +# +# Copyright (C) 2016 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 os +import gammalib +import cscripts +from testing import test + + +# ================================= # +# Test class for csobsselect script # +# ================================= # +class Test(test): + """ + Test class for csobsselect script + + This test class makes unit tests for the csobsselect script by using it + from the command line and from Python. + """ + + # Constructor + def __init__(self): + """ + Constructor. + """ + # Call base class constructor + test.__init__(self) + + # Return + return + + # Set test functions + def set(self): + """ + Set all test functions. + """ + # Set test name + self.name('csobsselect') + + # Append tests + self.append(self._test_cmd, 'Test csobsselect on command line') + self.append(self._test_python, 'Test csobsselect from Python') + + # Return + return + + # Test csobsselect on command line + def _test_cmd(self): + """ + Test csobsselect on the command line + """ + # Set script name + csobsselect = self._script('csobsselect') + + # Setup csobsselect command + cmd = csobsselect+' inobs="'+self._events+'"'+ \ + ' outobs="csobsselect_cmd1.xml"'+ \ + ' pntselect="CIRCLE" coordsys="CEL" ra=83.63'+ \ + ' dec=22.01 rad=5.0'+ \ + ' logfile="csobsselect_cmd1.log" chatter=1' + + # Check if execution of wrong command fails + self.test_assert(self._execute('command_that_does_not_exist') != 0, + 'Self test of test script') + + # Check if execution was successful + self.test_assert(self._execute(cmd) == 0, + 'Check successful execution from command line') + + # Check observation definition file + self._check_observation_file('csobsselect_cmd1.xml', 1) + + # Setup csobsselect command + cmd = csobsselect+' inobs="observation_that_does_not_exist.xml"'+ \ + ' outobs="csobsselect_cmd2.xml"'+ \ + ' pntselect="CIRCLE" coordsys="CEL" ra=83.63'+ \ + ' dec=22.01 rad=5.0'+ \ + ' logfile="csobsselect_cmd2.log" chatter=2' + + # Check if execution failed + self.test_assert(self._execute(cmd) != 0, + 'Check invalid input file when executed from command line') + + # Return + return + + # Test csobsselect from Python + def _test_python(self): + """ + Test csobsselect from Python + """ + # Allocate empty csobsselect script + obsselect = cscripts.csobsselect() + + # Check that saving does not nothing + obsselect['outobs'] = 'csobsselect_py0.xml' + obsselect['logfile'] = 'csobsselect_py0.log' + obsselect.logFileOpen() + obsselect.save() + + # Check than an empty observation definition file has been created + self._check_observation_file('csobsselect_py0.xml', 0) + + # Check that clearing does not lead to an exception or segfault + obsselect.clear() + + # Now set csobsselect parameters + obsselect['inobs'] = self._events + obsselect['outobs'] = 'csobsselect_py1.xml' + obsselect['pntselect'] = 'CIRCLE' + obsselect['coordsys'] = 'CEL' + obsselect['ra'] = 83.63 + obsselect['dec'] = 22.01 + obsselect['rad'] = 5.0 + obsselect['logfile'] = 'csobsselect_py1.log' + obsselect['chatter'] = 2 + + # Run csobsselect script and save observations + obsselect.logFileOpen() # Make sure we get a log file + obsselect.run() + obsselect.save() + + # Check model file + self._check_observation_file('csobsselect_py1.xml', 1) + + # Execute csobsselect script again, now in Galactic coordinates + obsselect['outobs'] = 'csobsselect_py2.xml' + obsselect['pntselect'] = 'CIRCLE' + obsselect['coordsys'] = 'GAL' + obsselect['glon'] = 184.56 + obsselect['glat'] = -5.79 + obsselect['rad'] = 5.0 + obsselect['logfile'] = 'csobsselect_py2.log' + obsselect['chatter'] = 3 + obsselect.logFileOpen() # Needed to get a new log file + obsselect.execute() + + # Check model file + self._check_observation_file('csobsselect_py2.xml', 1) + + # Execute csobsselect script again, now a box in Galactic coordinates + obsselect['outobs'] = 'csobsselect_py3.xml' + obsselect['pntselect'] = 'BOX' + obsselect['coordsys'] = 'GAL' + obsselect['glon'] = 184.56 + obsselect['glat'] = -5.79 + obsselect['width'] = 5.0 + obsselect['height'] = 5.0 + obsselect['logfile'] = 'csobsselect_py3.log' + obsselect['chatter'] = 4 + obsselect.logFileOpen() # Needed to get a new log file + obsselect.execute() + + # Check model file + self._check_observation_file('csobsselect_py3.xml', 1) + + # Execute csobsselect script again, now a box in celestial coordinates + obsselect['outobs'] = 'csobsselect_py4.xml' + obsselect['pntselect'] = 'BOX' + obsselect['coordsys'] = 'CEL' + obsselect['ra'] = 83.63 + obsselect['dec'] = 22.01 + obsselect['width'] = 5.0 + obsselect['height'] = 5.0 + obsselect['logfile'] = 'csobsselect_py4.log' + obsselect['chatter'] = 4 + obsselect.logFileOpen() # Needed to get a new log file + obsselect.execute() + + # Check model file + self._check_observation_file('csobsselect_py4.xml', 1) + + # Return + return + + # Check observation definition file + def _check_observation_file(self, filename, number): + """ + Check observation definition file + """ + # Open observations + obs = gammalib.GObservations(filename) + + # Check number of models + self.test_value(obs.size(), number, 'Check for '+str(number)+ + ' observations in XML file') + + # Return + return diff --git a/test/test_python_cscripts.py b/test/test_python_cscripts.py index b0baaad..1dfebed 100755 --- a/test/test_python_cscripts.py +++ b/test/test_python_cscripts.py @@ -34,6 +34,7 @@ import test_csmodelselect import test_csobs2caldb import test_csobsdef import test_csobsinfo +import test_csobsselect import test_cspull import test_csresmap import test_csroot2caldb @@ -119,6 +120,7 @@ def test(installed=False, debug=False): test_csobs2caldb.Test(), test_csobsdef.Test(), test_csobsinfo.Test(), + test_csobsselect.Test(), test_cspull.Test(), test_csresmap.Test(), test_csroot2caldb.Test(),