ctools  2.0.0
 All Classes Namespaces Files Functions Variables Macros Pages
csobsinfo.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 # ==========================================================================
3 # Dump information about observation into log file
4 #
5 # Copyright (C) 2015-2022 Michael Mayer
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 # csobsinfo class #
28 # =============== #
29 class csobsinfo(ctools.csobservation):
30  """
31  Shows the content of an observation container
32  """
33 
34  # Constructor
35  def __init__(self, *argv):
36  """
37  Constructor.
38  """
39  # Initialise application by calling the appropriate class constructor
40  self._init_csobservation(self.__class__.__name__, ctools.__version__, argv)
41 
42  # Initialise class members
43  self._obj_dir = None
44  self._compute_offset = False
45  self._offsets = []
46  self._zeniths = []
47  self._azimuths = []
48  self._pnt_ra = []
49  self._pnt_dec = []
50  self._ebounds = gammalib.GEbounds()
51  self._gti = gammalib.GGti()
52 
53  # Return
54  return
55 
56 
57  # Private methods
58  def _get_parameters(self):
59  """
60  Get parameters from parfile and setup the observation
61  """
62  # Get parameters
63  if self.obs().size() == 0:
64  self._require_inobs('csobsinfo::get_parameters')
65  self.obs(self._get_observations(False))
66 
67  # Initialise object position
68  self._obj_dir = gammalib.GSkyDir()
69 
70  # Get (optional) offset parameters
71  self._compute_offset = self['offset'].boolean()
72  if self._compute_offset:
73  ra = self['ra'].real()
74  dec = self['dec'].real()
75  self._obj_dir.radec_deg(ra,dec)
76 
77  # Read ahead DS9 filename
78  if self._read_ahead():
79  self['outds9file'].query()
80 
81  # Write input parameters into logger
82  self._log_parameters(gammalib.TERSE)
83 
84  # Return
85  return
86 
87 
88  # Public methods
89  def process(self):
90  """
91  Process the script
92  """
93  # Get parameters
94  self._get_parameters()
95 
96  # Initialise arrays to store certain values for reuse
97  # Todo, think about using a python dictionary
98  self._offsets = []
99  self._zeniths = []
100  self._azimuths = []
101  self._pnt_ra = []
102  self._pnt_dec = []
103  self._ebounds = gammalib.GEbounds()
104  self._gti = gammalib.GGti()
105  obs_names = []
106 
107  # Initialise output to be filled
108  ontime = 0.0
109  livetime = 0.0
110  n_events = 0
111  n_eventbins = 0
112  n_obs_binned = 0
113  n_obs_unbinned = 0
114 
115  # Write header
116  if self._logTerse():
117  self._log('\n')
118  self._log.header1(gammalib.number('Observation', self.obs().size()))
119 
120  # Loop over observations
121  for obs in self.obs():
122 
123  # Skip non-CTA observations
124  if not obs.classname() == 'GCTAObservation':
125  self._log('Skipping '+obs.instrument()+' observation\n')
126  continue
127 
128  # Use observed object as observation name if name is not given
129  obs_name = obs.name()
130  if obs_name == '':
131  obs_name = obs.object()
132 
133  # Logging
134  if self._logExplicit():
135  obs_id = obs.id()
136  if obs_id != '':
137  log_name = obs_name + ' (ID='+obs_id+')'
138  else:
139  log_name = obs_name
140  self._log.header2(log_name)
141 
142  # Retrieve observation name
143  obs_names.append(obs_name)
144 
145  # Retrieve energy boundaries
146  obs_bounds = obs.events().ebounds()
147 
148  # Retrieve time interval
149  obs_gti = obs.events().gti()
150 
151  # Compute mean time and dead time fraction in percent
152  deadfrac = (1.0-obs.deadc())*100.0
153 
154  # Retrieve pointing and store Ra,Dec
155  pnt_dir = obs.pointing().dir()
156  self._pnt_ra.append(pnt_dir.ra_deg())
157  self._pnt_dec.append(pnt_dir.dec_deg())
158 
159  # If avaliable append energy boundaries
160  if obs_bounds.size() > 0 :
161  self._ebounds.append(obs_bounds.emin(),obs_bounds.emax())
162 
163  # Append time interval
164  self._gti.append(obs_gti.tstart(), obs_gti.tstop())
165 
166  # Increment global livetime and ontime
167  ontime += obs.ontime()
168  livetime += obs.livetime()
169 
170  # Bookkeeping
171  if obs.eventtype() == 'CountsCube':
172  n_eventbins += obs.events().size()
173  n_obs_binned += 1
174  is_binned = 'yes'
175  is_what = 'Number of bins'
176  else:
177  n_events += obs.events().size()
178  n_obs_unbinned += 1
179  is_binned = 'no'
180  is_what = 'Number of events'
181  self._log_value(gammalib.EXPLICIT, 'Binned', is_binned)
182  self._log_value(gammalib.EXPLICIT, is_what, obs.events().size())
183 
184  # Retrieve zenith and azimuth and store for later use
185  zenith = obs.pointing().zenith()
186  azimuth = obs.pointing().azimuth()
187  self._zeniths.append(zenith)
188  self._azimuths.append(azimuth)
189 
190  # Optionally compute offset with respect to target direction
191  if self._compute_offset:
192  offset = pnt_dir.dist_deg(self._obj_dir)
193  self._offsets.append(offset)
194 
195  # Optionally log details
196  if self._logExplicit():
197 
198  # Log the observation energy range (if available)
199  self._log.parformat('Energy range')
200  if obs_bounds.size() == 0:
201  self._log('undefined')
202  else:
203  self._log(str(obs_bounds.emin()))
204  self._log(' - ')
205  self._log(str(obs_bounds.emax()))
206  self._log('\n')
207 
208  # Log observation time interval
209  self._log.parformat('Time range (MJD)')
210  if obs_gti.size() == 0:
211  self._log('undefined')
212  else:
213  self._log(str(obs_gti.tstart().mjd()))
214  self._log(' - ')
215  self._log(str(obs_gti.tstop().mjd()))
216  self._log('\n')
217 
218  # Log observation information
219  self._log_value(gammalib.EXPLICIT, 'Ontime', '%.3f s' %
220  obs.ontime())
221  self._log_value(gammalib.EXPLICIT, 'Livetime', '%.3f s' %
222  obs.livetime())
223  self._log_value(gammalib.EXPLICIT, 'Deadtime fraction', '%.3f %%' %
224  deadfrac)
225  self._log_value(gammalib.EXPLICIT, 'Pointing', pnt_dir)
226 
227  # Optionally log offset with respect to target direction
228  if self._compute_offset:
229  self._log_value(gammalib.EXPLICIT,
230  'Offset from target', '%.2f deg' % offset)
231 
232  # Log Zenith and Azimuth angles
233  self._log_value(gammalib.EXPLICIT, 'Zenith angle', '%.2f deg' %
234  zenith)
235  self._log_value(gammalib.EXPLICIT, 'Azimuth angle', '%.2f deg' %
236  azimuth)
237 
238  # Write summary header
239  self._log_header1(gammalib.NORMAL, 'Summary')
240 
241  # Log general summary
242  self._log_header3(gammalib.NORMAL, 'Observations')
243  self._log_value(gammalib.NORMAL, 'Unbinned observations', n_obs_unbinned)
244  self._log_value(gammalib.NORMAL, 'Binned observations', n_obs_binned)
245  self._log_header3(gammalib.NORMAL, 'Events')
246  self._log_value(gammalib.NORMAL, 'Number of events', n_events)
247  self._log_value(gammalib.NORMAL, 'Number of bins', n_eventbins)
248 
249  # Compute mean offset, azimuth and zenith angle
250  if len(self._offsets) > 0:
251  mean_offset = '%.2f deg' % (sum(self._offsets) / len(self._offsets))
252  else:
253  mean_offset = 'Unknown'
254  if len(self._zeniths) > 0:
255  mean_zenith = '%.2f deg' % (sum(self._zeniths) / len(self._zeniths))
256  else:
257  mean_zenith = 'Unknown'
258  if len(self._azimuths) > 0:
259  mean_azimuth = '%.2f deg' % (sum(self._azimuths) / len(self._azimuths))
260  else:
261  mean_azimuth = 'Unknown'
262 
263  # Log mean offset, azimuth and zenith angle
264  self._log_header3(gammalib.NORMAL, 'Pointings')
265  self._log_value(gammalib.NORMAL, 'Mean offset angle', mean_offset)
266  self._log_value(gammalib.NORMAL, 'Mean zenith angle', mean_zenith)
267  self._log_value(gammalib.NORMAL, 'Mean azimuth angle', mean_azimuth)
268 
269  # Optionally log names of observations. Note that the set class is
270  # used to extract all different observation names from the list of
271  # observation names, and the set class is only available from
272  # Python 2.4 on.
273  if sys.version_info >= (2,4):
274  obs_set = set(obs_names)
275  for name in obs_set:
276  self._log_value(gammalib.EXPLICIT,'"'+name+'"',
277  obs_names.count(name))
278 
279  # Get energy boundary information
280  if self._ebounds.size() == 0:
281  min_value = 'undefined'
282  max_value = 'undefined'
283  else:
284  min_value = str(self._ebounds.emin())
285  max_value = str(self._ebounds.emax())
286 
287  # Log energy range
288  self._log_header3(gammalib.NORMAL, 'Energy range')
289  self._log_value(gammalib.NORMAL, 'Minimum energy', min_value)
290  self._log_value(gammalib.NORMAL, 'Maximum energy', max_value)
291 
292  # Log time range
293  mjd = '%.3f - %.3f' % (self._gti.tstart().mjd(), self._gti.tstop().mjd())
294  utc = '%s - %s' % (self._gti.tstart().utc(), self._gti.tstop().utc())
295  self._log_header3(gammalib.NORMAL, 'Time range')
296  self._log_value(gammalib.NORMAL, 'MJD (days)', mjd)
297  self._log_value(gammalib.NORMAL, 'UTC', utc)
298 
299  # Log ontime and livetime in different units
300  on_time = '%.2f s = %.2f min = %.2f h' % \
301  (ontime, ontime/60., ontime/3600.)
302  live_time = '%.2f s = %.2f min = %.2f h' % \
303  (livetime, livetime/60., livetime/3600.)
304  self._log_value(gammalib.NORMAL, 'Total ontime', on_time)
305  self._log_value(gammalib.NORMAL, 'Total livetime', live_time)
306 
307  # Return
308  return
309 
310  def save(self):
311  """
312  Save pointings into DS9 region file
313 
314  This method saves all pointing directions that are found in the
315  observation container into a DS9 region file. If "NONE" is
316  specified for the "outds9file" parameter the method does nothing.
317  """
318  # Check if DS9 file is valid
319  if self['outds9file'].is_valid():
320 
321  # Get output filename in case it was not read ahead
322  ds9file = self['outds9file'].filename()
323 
324  # Write header
325  self._log_header1(gammalib.TERSE, 'Save pointings in DS9 file')
326 
327  # Log filename
328  self._log_value(gammalib.NORMAL, 'DS9 filename', ds9file.url())
329 
330  # Open file
331  f = open(ds9file.url(),'w')
332 
333  # Write coordinate system
334  f.write('fk5\n')
335 
336  # Loop over pointings
337  for i in range(len(self._pnt_ra)):
338 
339  # Create string
340  line = 'point('
341  line += str(self._pnt_ra[i])+','+str(self._pnt_dec[i])+')'
342  line += ' # point=cross 20 width=3\n'
343 
344  # Write to file
345  f.write(line)
346 
347  # Close file
348  f.close()
349 
350  # Return
351  return
352 
353  def zeniths(self):
354  """
355  Return zenith angles
356  """
357  return self._zeniths
358 
359  def azimuths(self):
360  """
361  Return azimuth angles
362  """
363  return self._azimuths
364 
365  def ras(self):
366  """
367  Return pointings right ascension
368  """
369  return self._pnt_ra
370 
371  def decs(self):
372  """
373  Return pointings declination
374  """
375  return self._pnt_dec
376 
377  def offsets(self):
378  """
379  Return offset angles
380  """
381  return self._offsets
382 
383  def ebounds(self):
384  """
385  Return energy boundaries
386  """
387  return self._ebounds
388 
389  def gti(self):
390  """
391  Return good time intervals
392  """
393  return self._gti
394 
395 
396 # ======================== #
397 # Main routine entry point #
398 # ======================== #
399 if __name__ == '__main__':
400 
401  # Create instance of application
402  app = csobsinfo(sys.argv)
403 
404  # Execute application
405  app.execute()