ctools 2.1.0
Loading...
Searching...
No Matches
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# ==========================================================================
21import sys
22import gammalib
23import ctools
24
25
26# =============== #
27# csobsinfo class #
28# =============== #
29class 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# ======================== #
399if __name__ == '__main__':
400
401 # Create instance of application
402 app = csobsinfo(sys.argv)
403
404 # Execute application
405 app.execute()