ctools  2.0.0
 All Classes Namespaces Files Functions Variables Macros Pages
csobsdef.py
Go to the documentation of this file.
1 #! /usr/bin/env python
2 # ==========================================================================
3 # Generation of an observation definition file
4 #
5 # Copyright (C) 2015-2022 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 sys
22 import gammalib
23 import ctools
24 
25 
26 # ============== #
27 # csobsdef class #
28 # ============== #
29 class csobsdef(ctools.cscript):
30  """
31  Creates an observation definition file from a pointing list
32 
33  The pointing list is a comma-separated value ASCII file with header
34  keywords in the first row followed by a list of pointings (one
35  pointing per row). The following header keywords are supported (case
36  sensitive, column order irrelevant):
37 
38  name - Observation name string
39  id - Unique observation identifier string
40  ra - Right Ascension of pointing (deg)
41  dec - Declination of pointing (deg)
42  lon - Galactic longitude of pointing (deg)
43  lat - Galactic latitude of pointing (deg)
44  tmin - Start of pointing (seconds)
45  duration - Duration of pointing (seconds)
46  emin - Lower energy limit (TeV)
47  emax - Upper energy limit (TeV)
48  rad - Radius of region of interest (deg)
49  deadc - Deadtime correction factor [0-1]
50  instrument - Name of Cherenkov telescope
51  caldb - Calibration database
52  irf - Response function name
53 
54  Only the pairs (ra,dec) or (lon,lat) are mandatory header keywords.
55  All other keywords are optional and can be specified when calling
56  csobsdef as user parameters. The only exception is the "duration"
57  keyword that will automatically be queried.
58 
59  Examples:
60 
61  ./csobsdef
62  Creates minimal observation definition file.
63 
64  ./csobsdef emin=0.1 emax=100.0
65  Creates observation definition file with an energy range
66  100 GeV - 100 TeV.
67 
68  ./csobsdef rad=5
69  Creates observation definition file with a ROI radius of
70  5 degrees.
71 
72  ./csobsdef caldb=prod2 irf=South_50h
73  Creates observation definition file using the "South_50h"
74  IRF in the "prod2" calibration database of CTA.
75  """
76 
77  # Constructor
78  def __init__(self, *argv):
79  """
80  Constructor
81 
82  Parameters
83  ----------
84  argv : list of str
85  List of IRAF command line parameter strings of the form
86  ``parameter=3``.
87  """
88  # Initialise application by calling the base class constructor
89  self._init_cscript(self.__class__.__name__, ctools.__version__, argv)
90 
91  # Initialise class members
92  self._obs = gammalib.GObservations()
93  self._pntdef = gammalib.GCsv()
94  self._tmin = 0.0
95 
96  # Return
97  return
98 
99 
100  # Private methods
101  def _get_parameters(self):
102  """
103  Get parameters from parfile
104  """
105  # Query input filename if necessary
106  if self._pntdef.size() == 0:
107  self['inpnt'].filename()
108 
109  # Read ahead parameters
110  if self._read_ahead():
111  self['outobs'].query()
112 
113  # Write input parameters into logger
114  self._log_parameters(gammalib.TERSE)
115 
116  # Return
117  return
118 
119  def _set_irf(self, obs, caldb, irf):
120  """
121  Set response for an observation
122 
123  The method creates an XML element so that that the response XML
124  writer will write the database and response name into the
125  observation definition file.
126 
127  Parameters
128  ----------
129  obs : `~gammalib.GCTAObservation`
130  CTA observation
131  caldb : str
132  Calibration database
133  irf : str
134  Instrument response function
135 
136  Returns
137  -------
138  obs : `~gammalib.GCTAObservation`
139  CTA observation with response attached
140  """
141  # Create XML element
142  xml = gammalib.GXmlElement()
143 
144  # Append parameter
145  parameter = 'parameter name="Calibration" database="'+caldb+\
146  '" response="'+irf+'"'
147  xml.append(gammalib.GXmlElement(parameter))
148 
149  # Create CTA response
150  response = gammalib.GCTAResponseIrf()
151  response.read(xml)
152 
153  # Attach response to observation
154  obs.response(response)
155 
156  # Return observation
157  return obs
158 
159 
160  # Public methods
161  def process(self):
162  """
163  Process the script
164 
165  Raises
166  ------
167  RuntimeError
168  Invalid pointing definition file format
169  """
170  # Get parameters
171  self._get_parameters()
172 
173  # Write header into logger
174  self._log_header1(gammalib.TERSE,
175  'Creating observation definition XML file')
176 
177  # Load pointing definition file if it is not already set. Extract
178  # the number of columns and pointings
179  if self._pntdef.size() == 0:
180  self._pntdef = gammalib.GCsv(self['inpnt'].filename(), ',')
181  ncols = self._pntdef.ncols()
182  npnt = self._pntdef.nrows()-1
183 
184  # Raise an exception if there is no header information
185  if self._pntdef.nrows() < 1:
186  raise RuntimeError('No header found in pointing definition file.')
187 
188  # Clear observation container
189  self._obs.clear()
190 
191  # Initialise observation identifier counter
192  identifier = 1
193 
194  # Extract header columns from pointing definition file and put them
195  # into a list
196  header = []
197  for col in range(ncols):
198  header.append(self._pntdef[0,col])
199 
200  # Loop over all pointings
201  for pnt in range(npnt):
202 
203  # Set pointing definition CSV file row index
204  row = pnt + 1
205 
206  # Create empty CTA observation
207  obs = gammalib.GCTAObservation()
208 
209  # Set instrument
210  if 'instrument' in header:
211  instrument = self._pntdef[row, header.index('instrument')]
212  else:
213  instrument = self['instrument'].string()
214  instrument = gammalib.toupper(instrument)
215  obs.instrument(instrument)
216 
217  # Set observation name. If no observation name was given then
218  # use "None".
219  if 'name' in header:
220  name = self._pntdef[row, header.index('name')]
221  else:
222  name = self['name'].string()
223  obs.name(name)
224 
225  # Set observation identifier. If no observation identified was
226  # given the use the internal counter.
227  if 'id' in header:
228  obsid = self._pntdef[row, header.index('id')]
229  else:
230  obsid = '%6.6d' % identifier
231  identifier += 1
232  obs.id(obsid)
233 
234  # Set pointing. Either use "ra" and "dec" or "lon" and "lat".
235  # If none of these pairs are given then raise an exception.
236  if 'ra' in header and 'dec' in header:
237  ra = float(self._pntdef[row, header.index('ra')])
238  dec = float(self._pntdef[row, header.index('dec')])
239  pntdir = gammalib.GSkyDir()
240  pntdir.radec_deg(ra,dec)
241  elif 'lon' in header and 'lat' in header:
242  lon = float(self._pntdef[row, header.index('lon')])
243  lat = float(self._pntdef[row, header.index('lat')])
244  pntdir = gammalib.GSkyDir()
245  pntdir.lb_deg(lon,lat)
246  else:
247  raise RuntimeError('No (ra,dec) or (lon,lat) columns '
248  'found in pointing definition file.')
249  obs.pointing(gammalib.GCTAPointing(pntdir))
250 
251  # Set response function. If no "caldb" or "irf" information is
252  # provided then use the user parameter values.
253  if 'caldb' in header:
254  caldb = self._pntdef[row, header.index('caldb')]
255  else:
256  caldb = self['caldb'].string()
257  if 'irf' in header:
258  irf = self._pntdef[row, header.index('irf')]
259  else:
260  irf = self['irf'].string()
261  if caldb != '' and irf != '':
262  obs = self._set_irf(obs, caldb, irf)
263 
264  # Set deadtime correction factor. If no information is provided
265  # then use the user parameter value "deadc".
266  if 'deadc' in header:
267  deadc = float(self._pntdef[row, header.index('deadc')])
268  else:
269  deadc = self['deadc'].real()
270  obs.deadc(deadc)
271 
272  # Set Good Time Interval. If no information is provided then use
273  # the user parameter values "tmin" and "duration".
274  if 'tmin' in header:
275  self._tmin = float(self._pntdef[row, header.index('tmin')])
276  if 'duration' in header:
277  duration = float(self._pntdef[row, header.index('duration')])
278  else:
279  duration = self['duration'].real()
280  tref = gammalib.GTimeReference(self['mjdref'].real(),'s')
281  tmin = self._tmin
282  tmax = self._tmin + duration
283  gti = gammalib.GGti(tref)
284  tstart = gammalib.GTime(tmin, tref)
285  tstop = gammalib.GTime(tmax, tref)
286  self._tmin = tmax
287  gti.append(tstart, tstop)
288  obs.ontime(gti.ontime())
289  obs.livetime(gti.ontime()*deadc)
290 
291  # Set Energy Boundaries. If no "emin" or "emax" information is
292  # provided then use the user parameter values in case they are
293  # valid.
294  has_emin = False
295  has_emax = False
296  if 'emin' in header:
297  emin = float(self._pntdef[row, header.index('emin')])
298  has_emin = True
299  else:
300  if self['emin'].is_valid():
301  emin = self['emin'].real()
302  has_emin = True
303  if 'emax' in header:
304  emax = float(self._pntdef[row, header.index('emax')])
305  has_emax = True
306  else:
307  if self['emax'].is_valid():
308  emax = self['emax'].real()
309  has_emax = True
310  has_ebounds = has_emin and has_emax
311  if has_ebounds:
312  ebounds = gammalib.GEbounds(gammalib.GEnergy(emin, 'TeV'),
313  gammalib.GEnergy(emax, 'TeV'))
314 
315  # Set ROI. If no ROI radius is provided then use the user
316  # parameters "rad".
317  has_roi = False
318  if 'rad' in header:
319  rad = float(self._pntdef[row, header.index('rad')])
320  has_roi = True
321  else:
322  if self['rad'].is_valid():
323  rad = self['rad'].real()
324  has_roi = True
325  if has_roi:
326  roi = gammalib.GCTARoi(gammalib.GCTAInstDir(pntdir), rad)
327 
328  # Create an empty event list
329  event_list = gammalib.GCTAEventList()
330  event_list.gti(gti)
331 
332  # If available, set the energy boundaries and the ROI
333  if has_ebounds:
334  event_list.ebounds(ebounds)
335  if has_roi:
336  event_list.roi(roi)
337 
338  # Attach event list to CTA observation
339  obs.events(event_list)
340 
341  # Write observation into logger
342  name = obs.instrument()+' observation'
343  value = 'Name="%s" ID="%s"' % (obs.name(), obs.id())
344  self._log_value(gammalib.NORMAL, name, value)
345  self._log_string(gammalib.EXPLICIT, str(obs)+'\n')
346 
347  # Append observation
348  self._obs.append(obs)
349 
350  # Return
351  return
352 
353  def save(self):
354  """
355  Save observation definition XML file.
356  """
357  # Write header and filename into logger
358  self._log_header1(gammalib.TERSE, 'Save observation definition XML file')
359 
360  # Check if observation definition XML file is valid
361  if self['outobs'].is_valid():
362 
363  # Get output filename in case it was not read ahead
364  outobs = self['outobs'].filename()
365 
366  # Log filename
367  self._log_value(gammalib.NORMAL, 'Observation XML file', outobs.url())
368 
369  # Save observation definition XML file
370  self._obs.save(outobs)
371 
372  # Return
373  return
374 
375  def obs(self):
376  """
377  Returns observation container
378  -----
379  Returns
380  obs : `~gammalib.GObservations`
381  Observation container
382  """
383  # Return container
384  return self._obs
385 
386  def pntdef(self, csv):
387  """
388  Set pointing definition from a CSV table
389 
390  Parameters
391  ----------
392  csv : `~gammalib.GCsv`
393  Comma-separated values table
394  """
395  # Set pointing definition
396  self._pntdef = csv
397 
398  # Return
399  return
400 
401 
402 # ======================== #
403 # Main routine entry point #
404 # ======================== #
405 if __name__ == '__main__':
406 
407  # Create instance of application
408  app = csobsdef(sys.argv)
409 
410  # Execute application
411  app.execute()