ctools  2.0.0
 All Classes Namespaces Files Functions Variables Macros Pages
obsutils.py
Go to the documentation of this file.
1 # ==========================================================================
2 # Utility functions for observation handling
3 #
4 # Copyright (C) 2011-2020 Juergen Knoedlseder
5 #
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
18 #
19 # ==========================================================================
20 import math
21 import gammalib
22 import ctools
23 import cscripts
24 
25 
26 # ===================== #
27 # Simulate observations #
28 # ===================== #
29 def sim(obs, log=False, debug=False, chatter=2, edisp=False, seed=0,
30  emin=None, emax=None, nbins=0, onsrc=None, onrad=0.2, addbounds=False,
31  binsz=0.05, npix=200, proj='TAN', coord='GAL', nthreads=0):
32  """
33  Simulate events for all observations in the container
34 
35  Simulate events for all observations using ctobssim. If the number of
36  energy bins is positive, the events are binned into a counts cube using
37  ctbin. If multiple observations are simulated, the counts cube is a
38  stacked cube and the corresponding response cubes are computed using
39  ctexpcube, ctpsfcube, ctbkgcube and optionally ctedispcube. The response
40  cubes are attached to the first observation in the container, which
41  normally is the observation with the counts cube.
42 
43  Parameters
44  ----------
45  obs : `~gammalib.GObservations`
46  Observation container without events
47  log : bool, optional
48  Create log file(s)
49  debug : bool, optional
50  Create console dump?
51  chatter : int, optional
52  Chatter level
53  edisp : bool, optional
54  Apply energy dispersion?
55  seed : int, optional
56  Seed value for simulations
57  emin : float, optional
58  Minimum energy of counts cube for binned (TeV)
59  emax : float, optional
60  Maximum energy of counts cube for binned (TeV)
61  nbins : int, optional
62  Number of energy bins (0=unbinned)
63  onsrc : str, optional
64  Name of source for On region (None if no On/Off obs. is used)
65  onrad : float, optional
66  Radius for On region (deg)
67  addbounds : bool, optional
68  Add boundaries at observation energies
69  binsz : float, optional
70  Pixel size for binned simulation (deg/pixel)
71  npix : int, optional
72  Number of pixels in X and Y for binned simulation
73  proj : str, optional
74  Projection for binned simulation
75  coord : str, optional
76  Coordinate system for binned simulation
77  nthreads : str, optional
78  Number of parallel processes for On/Off spectra computation (0=all available CPUs)
79 
80  Returns
81  -------
82  obs : `~gammalib.GObservations`
83  Observation container filled with simulated events
84  """
85  # Allocate ctobssim application and set parameters
86  obssim = ctools.ctobssim(obs)
87  obssim['seed'] = seed
88  obssim['edisp'] = edisp
89  obssim['nthreads'] = nthreads
90  obssim['chatter'] = chatter
91  obssim['debug'] = debug
92 
93  # Optionally open the log file
94  if log:
95  obssim.logFileOpen()
96 
97  # Run ctobssim application. This will loop over all observations in the
98  # container and simulation the events for each observation. Note that
99  # events are not added together, they still apply to each observation
100  # separately.
101  obssim.run()
102 
103  # Binned option?
104  if nbins > 0:
105 
106  # If energy boundaries are not given then determine the minimum and
107  # the maximum energies from all observations and use these values
108  # as energy boundaries. The energy boundaries are given in TeV.
109  if emin == None or emax == None:
110  emin = 1.0e30
111  emax = 0.0
112  for run in obssim.obs():
113  emin = min(run.events().ebounds().emin().TeV(), emin)
114  emax = max(run.events().ebounds().emax().TeV(), emax)
115 
116  # If a On source is specified then create On/Off observations
117  if onsrc != None:
118 
119  # Extract source position from model
120  model = obssim.obs().models()[onsrc]
121  ra = model['RA'].value()
122  dec = model['DEC'].value()
123 
124  # Allocate csphagen application and set parameters
125  phagen = cscripts.csphagen(obssim.obs())
126  phagen['inmodel'] = 'NONE'
127  phagen['srcname'] = onsrc
128  phagen['ebinalg'] = 'LOG'
129  phagen['emin'] = emin
130  phagen['emax'] = emax
131  phagen['enumbins'] = nbins
132  phagen['srcshape'] = 'CIRCLE'
133  phagen['coordsys'] = 'CEL'
134  phagen['ra'] = ra
135  phagen['dec'] = dec
136  phagen['rad'] = onrad
137  phagen['stack'] = False
138  phagen['inexclusion'] = 'NONE'
139  phagen['bkgmethod'] = 'REFLECTED'
140  phagen['nthreads'] = nthreads
141 
142  # Optionally open the log file
143  if log:
144  phagen.logFileOpen()
145 
146  # Run csphagen application
147  phagen.run()
148 
149  # Make a deep copy of the observation that will be returned
150  # (the csphagen object will go out of scope one the function is
151  # left)
152  obs = phagen.obs().copy()
153 
154  # ... otherwise use binned observations
155  else:
156 
157  # Allocate ctbin application and set parameters
158  binning = ctools.ctbin(obssim.obs())
159  binning['ebinalg'] = 'LOG'
160  binning['emin'] = emin
161  binning['emax'] = emax
162  binning['enumbins'] = nbins
163  binning['usepnt'] = True # Use pointing for map centre
164  binning['nxpix'] = npix
165  binning['nypix'] = npix
166  binning['binsz'] = binsz
167  binning['coordsys'] = coord
168  binning['proj'] = proj
169  binning['nthreads'] = nthreads
170  binning['chatter'] = chatter
171  binning['debug'] = debug
172 
173  # Optionally open the log file
174  if log:
175  binning.logFileOpen()
176 
177  # Run ctbin application. This will loop over all observations in
178  # the container and bin the events in counts maps
179  binning.run()
180 
181  # If we have multiple input observations then create stacked response
182  # cubes and append them to the observation
183  if len(obssim.obs()) > 1:
184 
185  # Get counts cube. The counts cube is needed to obtained a
186  # proper background cube.
187  cntcube = binning.cube()
188 
189  # Get stacked response
190  response = get_stacked_response(obssim.obs(), cntcube,
191  edisp=edisp,
192  addbounds=addbounds,
193  log=log, debug=debug,
194  chatter=chatter)
195 
196  # Set stacked response
197  if edisp:
198  binning.obs()[0].response(response['expcube'],
199  response['psfcube'],
200  response['edispcube'],
201  response['bkgcube'])
202  else:
203  binning.obs()[0].response(response['expcube'],
204  response['psfcube'],
205  response['bkgcube'])
206 
207  # Set new models
208  binning.obs().models(response['models'])
209 
210  # Make a deep copy of the observation that will be returned
211  # (the ctbin object will go out of scope one the function is
212  # left)
213  obs = binning.obs().copy()
214 
215  else:
216 
217  # Make a deep copy of the observation that will be returned
218  # (the ctobssim object will go out of scope one the function is
219  # left)
220  obs = obssim.obs().copy()
221 
222  # Delete the simulation
223  del obssim
224 
225  # Return observation container
226  return obs
227 
228 
229 # ======================= #
230 # Set one CTA observation #
231 # ======================= #
232 def set_obs(pntdir, tstart=0.0, duration=1800.0, deadc=0.98, \
233  emin=0.1, emax=100.0, rad=5.0, \
234  instrument='CTA', irf='South_50h', caldb='prod2', \
235  obsid='000000', mjdref=51544.5):
236  """
237  Set a single CTA observation
238 
239  The function sets a single CTA observation containing an empty CTA
240  event list. By looping over this function CTA observations can be
241  added to the observation container.
242 
243  Parameters
244  ----------
245  pntdir : `~gammalib.GSkyDir`
246  Pointing direction
247  tstart : float, optional
248  Start time (s)
249  duration : float, optional
250  Duration of observation (s)
251  deadc : float, optional
252  Deadtime correction factor
253  emin : float, optional
254  Minimum event energy (TeV)
255  emax : float, optional
256  Maximum event energy (TeV)
257  rad : float, optional
258  ROI radius used for analysis (deg)
259  instrument : str, optional
260  Name of Cherenkov Telescope
261  irf : str, optional
262  Instrument response function
263  caldb : str, optional
264  Calibration database path
265  obsid : str, optional
266  Observation identifier
267 
268  Returns
269  -------
270  obs : `~gammalib.GCTAObservation`
271  CTA observation
272  """
273  # Allocate CTA observation
274  obs = gammalib.GCTAObservation()
275 
276  # Set mission
277  mission = gammalib.toupper(instrument)
278  obs.instrument(mission)
279 
280  # Set CTA calibration database
281  db = gammalib.GCaldb()
282  if (gammalib.dir_exists(caldb)):
283  db.rootdir(caldb)
284  else:
285  db.open(mission, caldb)
286 
287  # Set pointing direction for CTA observation
288  pnt = gammalib.GCTAPointing()
289  pnt.dir(pntdir)
290  obs.pointing(pnt)
291 
292  # Set ROI
293  roi = gammalib.GCTARoi()
294  instdir = gammalib.GCTAInstDir()
295  instdir.dir(pntdir)
296  roi.centre(instdir)
297  roi.radius(rad)
298 
299  # Set GTI
300  ref = gammalib.GTimeReference(mjdref,'s','TT','LOCAL')
301  gti = gammalib.GGti(ref)
302  gti.append(gammalib.GTime(tstart, ref),
303  gammalib.GTime(tstart+duration, ref))
304 
305  # Set energy boundaries
306  ebounds = gammalib.GEbounds(gammalib.GEnergy(emin, 'TeV'),
307  gammalib.GEnergy(emax, 'TeV'))
308 
309  # Allocate event list
310  events = gammalib.GCTAEventList()
311 
312  # Set ROI, GTI and energy boundaries for event list
313  events.roi(roi)
314  events.gti(gti)
315  events.ebounds(ebounds)
316 
317  # Set the event list as the events for CTA observation
318  obs.events(events)
319 
320  # Set instrument response for CTA observation
321  obs.response(irf, db)
322 
323  # Set ontime, livetime, and deadtime correction factor for CTA observation
324  obs.ontime(duration)
325  obs.livetime(duration*deadc)
326  obs.deadc(deadc)
327  obs.id(obsid)
328 
329  # Return CTA observation
330  return obs
331 
332 
333 # ============================ #
334 # Set list of CTA observations #
335 # ============================ #
336 def set_obs_list(obsdeflist, tstart=0.0, duration=1800.0, deadc=0.98, \
337  emin=0.1, emax=100.0, rad=5.0, \
338  irf='South_50h', caldb='prod2'):
339  """
340  Returns an observation container filled with a list of CTA observations
341 
342  The list is defined by the obsdeflist parameter which is a dictionnary
343  containing the mandatory keywords 'ra' and 'dec' that specify the
344  pointing direction for a given observation. Optional keyword give control
345  over other observation proposerties, such as duration, deadtime correction,
346  energy range, etc. If an optional keyword is not specified, the function
347  keyword is used instead.
348 
349  Parameters
350  ----------
351  obsdeflist : list of dict
352  Observation definition list
353  tstart : float, optional
354  Start time (s)
355  duration : float, optional
356  Duration of observation (s)
357  deadc : float, optional
358  Deadtime correction factor
359  emin : float, optional
360  Minimum event energy (TeV)
361  emax : float, optional
362  Maximum event energy (TeV)
363  rad : float, optional
364  ROI radius used for analysis (deg)
365  irf : str, optional
366  Instrument response function
367  caldb : str, optional
368  Calibration database path
369 
370  Returns
371  -------
372  obs : `~gammalib.GObservations`
373  Observation container filled with CTA observation
374  """
375  # Initialise empty observation container
376  obs = gammalib.GObservations()
377 
378  # Initialise first time and identifier
379  obs_start = tstart
380  obs_id = 1
381 
382  # Loop over observation definition list
383  for obsdef in obsdeflist:
384 
385  # Set pointing direction
386  pntdir = gammalib.GSkyDir()
387  pntdir.radec_deg(obsdef['ra'], obsdef['dec'])
388 
389  # Generate identifier string
390  obsid = '%6.6d' % obs_id
391 
392  # Set one CTA observation
393  obs_cta = set_obs(pntdir,
394  tstart=obs_start,
395  duration=obsdef.setdefault('duration', duration),
396  deadc=obsdef.setdefault('deadc', deadc),
397  emin=obsdef.setdefault('emin', emin),
398  emax=obsdef.setdefault('emax', emax),
399  rad=obsdef.setdefault('rad', rad),
400  irf=obsdef.setdefault('irf', irf),
401  caldb=obsdef.setdefault('caldb', caldb),
402  obsid=obsid)
403 
404  # Append to container
405  obs.append(obs_cta)
406 
407  # Update start time and identifier
408  obs_start += duration
409  obs_id += 1
410 
411  # Return observation container
412  return obs
413 
414 
415 # ============================ #
416 # Set CTA observation patterns #
417 # ============================ #
418 def set_obs_patterns(pattern, ra=83.6331, dec=22.0145, offset=1.5):
419  """
420  Sets a number of standard patterns
421 
422  Parameters
423  ----------
424  pattern : str
425  Observation pattern ("single", "four")
426  ra : float, optional
427  Right Ascension of pattern centre (deg)
428  dec : float, optional
429  Declination of pattern centre (deg)
430  offset : float, optional
431  Offset from pattern centre (deg)
432 
433  Returns
434  -------
435  obsdeflist : list
436  Observation definition list
437  """
438  # Initialise observation definition list
439  obsdeflist = []
440 
441  # If the pattern is a single observation then append the Right Ascension
442  # and Declination to the observation definition list
443  if pattern == 'single':
444  obsdef = {'ra': ra, 'dec': dec}
445  obsdeflist.append(obsdef)
446 
447  # ... otherwise, if the pattern is four observations then append four
448  # observations offset by a certain amount from the pattern centre to the
449  # observation definition list
450  elif pattern == 'four':
451 
452  # Set pattern centre
453  centre = gammalib.GSkyDir()
454  centre.radec_deg(ra, dec)
455 
456  # Append pointings
457  for phi in [0.0, 90.0, 180.0, 270.0]:
458  pntdir = centre.copy()
459  pntdir.rotate_deg(phi, offset)
460  obsdeflist.append({'ra': pntdir.ra_deg(), 'dec': pntdir.dec_deg()})
461 
462  # ... otherwise raise an exception since we have an unknown pattern
463  else:
464  msg = 'Observation pattern "%s" not recognized.' % (pattern)
465  raise RuntimeError(msg)
466 
467  # Return observation definition list
468  return obsdeflist
469 
470 
471 # ====================================================== #
472 # Set observation container filled with CTA observations #
473 # ====================================================== #
474 def set_observations(ra, dec, rad, tstart, duration, emin, emax, irf, caldb,
475  deadc=0.98, pattern='single', offset=1.5):
476  """
477  Set an observation container filled with CTA observations
478 
479  Parameters
480  ----------
481  ra : float
482  Right Ascension of pattern centre (deg)
483  dec : float
484  Declination of pattern centre (deg)
485  rad : float
486  ROI radius used for analysis (deg)
487  tstart : float
488  Start time of observation (s)
489  duration : float
490  Duration of each observation (s)
491  emin : float, optional
492  Minimum event energy (TeV)
493  emax : float, optional
494  Maximum event energy (TeV)
495  irf : str
496  Instrument response function
497  caldb : str
498  Calibration database path
499  deadc : float, optional
500  Deadtime correction factor
501  pattern : str, optional
502  Observation pattern ("single", "four")
503  offset : float, optional
504  Offset from pattern centre (deg)
505 
506  Returns
507  -------
508  obs : `~gammalib.GObservations()
509  Observation container
510  """
511  # Setup observation definition list
512  obsdeflist = set_obs_patterns(pattern, offset=offset, ra=ra, dec=dec)
513 
514  # Create list of observations
515  obs = set_obs_list(obsdeflist, tstart=tstart, duration=duration,
516  emin=emin, emax=emax, rad=rad, irf=irf, caldb=caldb,
517  deadc=deadc)
518 
519  # Return observation container
520  return obs
521 
522 
523 # ==================== #
524 # Get stacked response #
525 # ==================== #
526 def get_stacked_response(obs, cntcube, edisp=False, addbounds=False,
527  log=False, debug=False, chatter=2):
528  """
529  Get stacked response cubes
530 
531  The number of energies bins are set to at least 30 bins per decade, unless
532  the counts cube has more energy bins per decade.
533 
534  Parameters
535  ----------
536  obs : `~gammalib.GObservations`
537  Observation container
538  cntcube : `~gammalib.GCTAEventCube`
539  Counts cube
540  edisp : bool, optional
541  Apply energy dispersion?
542  addbounds : bool, optional
543  Add boundaries at observation energies
544  log : bool, optional
545  Create log file(s)
546  debug : bool, optional
547  Create console dump?
548  chatter : int, optional
549  Chatter level
550 
551  Returns
552  -------
553  result : dict
554  Dictionary of response cubes
555  """
556  # Derive binning parameters from counts cube
557  xref = cntcube.counts().projection().crval(0)
558  yref = cntcube.counts().projection().crval(1)
559  binsz = cntcube.counts().projection().cdelt(1)
560  coordsys = cntcube.counts().projection().coordsys()
561  proj = cntcube.counts().projection().code()
562  nxpix = cntcube.nx()
563  nypix = cntcube.ny()
564  emin = cntcube.emin().TeV()
565  emax = cntcube.emax().TeV()
566  ebins = cntcube.ebins()
567 
568  # Translate coordinate system
569  if coordsys == 'EQU':
570  coordsys = 'CEL'
571 
572  # Set energy limits, with larger etrue limits for the case that energy
573  # dispersion is requested
574  if edisp:
575  emin *= 0.5
576  emax *= 1.5
577 
578  # Set number of energy bins to at least 30 per energy decade
579  enumbins = int((math.log10(emax) - math.log10(emin)) * 30.0)
580  if ebins > enumbins:
581  enumbins = ebins
582 
583  # Compute spatial binning for point spread function and energy dispersion
584  # cubes. The spatial binning is 10 times coarser than the spatial binning
585  # of the exposure and background cubes. At least 2 spatial are required.
586  psf_binsz = 10.0 * binsz
587  psf_nxpix = max(nxpix // 10, 2) # Make sure result is int
588  psf_nypix = max(nypix // 10, 2) # Make sure result is int
589 
590  # Create exposure cube
591  expcube = ctools.ctexpcube(obs)
592  expcube['incube'] = 'NONE'
593  expcube['ebinalg'] = 'LOG'
594  expcube['xref'] = xref
595  expcube['yref'] = yref
596  expcube['binsz'] = binsz
597  expcube['nxpix'] = nxpix
598  expcube['nypix'] = nypix
599  expcube['enumbins'] = enumbins
600  expcube['emin'] = emin
601  expcube['emax'] = emax
602  expcube['coordsys'] = coordsys
603  expcube['proj'] = proj
604  expcube['addbounds'] = addbounds
605  expcube['debug'] = debug
606  expcube['chatter'] = chatter
607  if log:
608  expcube.logFileOpen()
609  expcube.run()
610 
611  # Create point spread function cube
612  psfcube = ctools.ctpsfcube(obs)
613  psfcube['incube'] = 'NONE'
614  psfcube['ebinalg'] = 'LOG'
615  psfcube['xref'] = xref
616  psfcube['yref'] = yref
617  psfcube['binsz'] = psf_binsz
618  psfcube['nxpix'] = psf_nxpix
619  psfcube['nypix'] = psf_nypix
620  psfcube['enumbins'] = enumbins
621  psfcube['emin'] = emin
622  psfcube['emax'] = emax
623  psfcube['coordsys'] = coordsys
624  psfcube['proj'] = proj
625  psfcube['addbounds'] = addbounds
626  psfcube['debug'] = debug
627  psfcube['chatter'] = chatter
628  if log:
629  psfcube.logFileOpen()
630  psfcube.run()
631 
632  # Create background cube. Note that we use the same energy binning as
633  # for the counts cube since no interpolation should be actually done
634  # for the background cube.
635  bkgcube = ctools.ctbkgcube(obs)
636  bkgcube.cntcube(cntcube)
637  bkgcube['debug'] = debug
638  bkgcube['chatter'] = chatter
639  if log:
640  bkgcube.logFileOpen()
641  bkgcube.run()
642 
643  # If energy dispersion is requested then create energy dispersion cube
644  if edisp:
645  edispcube = ctools.ctedispcube(obs)
646  edispcube['incube'] = 'NONE'
647  edispcube['ebinalg'] = 'LOG'
648  edispcube['xref'] = xref
649  edispcube['yref'] = yref
650  edispcube['binsz'] = psf_binsz
651  edispcube['nxpix'] = psf_nxpix
652  edispcube['nypix'] = psf_nypix
653  edispcube['enumbins'] = enumbins
654  edispcube['emin'] = emin
655  edispcube['emax'] = emax
656  edispcube['coordsys'] = coordsys
657  edispcube['proj'] = proj
658  edispcube['addbounds'] = addbounds
659  edispcube['debug'] = debug
660  edispcube['chatter'] = chatter
661  if log:
662  edispcube.logFileOpen()
663  edispcube.run()
664 
665  # Build response dictionary
666  response = {}
667  response['expcube'] = expcube.expcube().copy()
668  response['psfcube'] = psfcube.psfcube().copy()
669  response['bkgcube'] = bkgcube.bkgcube().copy()
670  response['models'] = bkgcube.models().copy()
671  if edisp:
672  response['edispcube'] = edispcube.edispcube().copy()
673 
674  # Return response cubes
675  return response
676 
677 
678 # ================================= #
679 # Get stacked observation container #
680 # ================================= #
681 def get_stacked_obs(cls, obs, nthreads=0):
682  """
683  Bin an observation and return an observation container with a single
684  binned observation
685 
686  Parameters
687  ----------
688  cls : `~ctools.cscript`
689  cscript class
690  obs : `~gammalib.GObservations`
691  Observation container
692  nthreads : str, optional
693  Number of parallel processes for On/Off spectra computation (0=all available CPUs)
694 
695  Returns
696  -------
697  obs : `~gammalib.GObservations`
698  Observation container where the first observation is a binned observation
699  """
700  # Write header
701  if cls._logExplicit():
702  cls._log.header3('Binning events')
703 
704  # Bin events
705  cntcube = ctools.ctbin(obs)
706  cntcube['usepnt'] = False
707  cntcube['ebinalg'] = 'LOG'
708  cntcube['xref'] = cls['xref'].real()
709  cntcube['yref'] = cls['yref'].real()
710  cntcube['binsz'] = cls['binsz'].real()
711  cntcube['nxpix'] = cls['nxpix'].integer()
712  cntcube['nypix'] = cls['nypix'].integer()
713  cntcube['enumbins'] = cls['enumbins'].integer()
714  cntcube['emin'] = cls['emin'].real()
715  cntcube['emax'] = cls['emax'].real()
716  cntcube['coordsys'] = cls['coordsys'].string()
717  cntcube['proj'] = cls['proj'].string()
718  cntcube['nthreads'] = nthreads
719  cntcube.run()
720 
721  # Store counts cube so that we can use it to build the background
722  # cube
723  cube = cntcube.cube()
724 
725  # Write header
726  if cls._logExplicit():
727  cls._log.header3('Creating stacked response')
728 
729  # Get stacked response
730  response = get_stacked_response(obs, cube, edisp=cls['edisp'].boolean())
731 
732  # Retrieve a new oberservation container
733  new_obs = cntcube.obs().copy()
734 
735  # Set stacked response
736  if cls['edisp'].boolean():
737  new_obs[0].response(response['expcube'], response['psfcube'],
738  response['edispcube'], response['bkgcube'])
739  else:
740  new_obs[0].response(response['expcube'], response['psfcube'],
741  response['bkgcube'])
742 
743  # Get new models
744  models = response['models']
745 
746  # Set models for new oberservation container
747  new_obs.models(models)
748 
749  # Return new oberservation container
750  return new_obs
751 
752 
753 # ================================ #
754 # Get On/Off observation container #
755 # ================================ #
756 def get_onoff_obs(cls, obs, nthreads=0, ra = None, dec = None, srcname = ''):
757  """
758  Create On/Off observations container from given observations
759 
760  Parameters
761  ----------
762  cls : `~ctools.cscript`
763  cscript class
764  obs : `~gammalib.GObservations`
765  Observation container
766  nthreads : str, optional
767  Number of parallel processes for On/Off spectra computation (0=all available CPUs)
768  ra : float, optional
769  R.A. of source region centre
770  dec : float, optional
771  Dec. of source region centre
772  srcname : str, optional
773  Source name
774 
775  Returns
776  -------
777  onoff_obs : `~gammalib.GObservations`
778  Observation container with On/Off observations
779  """
780  # Write header
781  if cls._logExplicit():
782  cls._log.header3('Creating On/Off observations')
783 
784  # Initialise inmodel and use_model_bkg
785  inmodel = 'NONE'
786  use_model_bkg = True
787 
788  # Set inmodel, srcname, and use_model_bkg if they are available
789  if cls.has_par('inmodel') and obs.models().size() == 0:
790  if cls['inmodel'].is_valid():
791  inmodel = cls['inmodel'].value()
792  if srcname == '' and cls.has_par('srcname'):
793  srcname = cls['srcname'].value()
794  if cls.has_par('use_model_bkg'):
795  use_model_bkg = cls['use_model_bkg'].boolean()
796 
797  # Create On/Off observations
798  phagen = cscripts.csphagen(obs)
799  phagen['inmodel'] = inmodel
800  phagen['srcname'] = srcname
801  phagen['emin'] = cls['emin'].real()
802  phagen['emax'] = cls['emax'].real()
803  phagen['enumbins'] = cls['enumbins'].integer()
804  phagen['ebinalg'] = 'LOG'
805  phagen['srcshape'] = cls['srcshape'].string()
806  # User has specified custom centre for the source region
807  if ra != None and dec != None:
808  phagen['coordsys'] = 'CEL'
809  phagen['ra'] = ra
810  phagen['dec'] = dec
811  # Otherwise use default in class
812  else:
813  phagen['coordsys'] = cls['coordsys'].string()
814  if cls['coordsys'].string() == 'CEL':
815  phagen['ra'] = cls['xref'].real()
816  phagen['dec'] = cls['yref'].real()
817  elif cls['coordsys'].string() == 'GAL':
818  phagen['glon'] = cls['xref'].real()
819  phagen['glat'] = cls['yref'].real()
820  if cls['srcshape'].string() == 'CIRCLE':
821  phagen['rad'] = cls['rad'].real()
822  elif cls['srcshape'].string() == 'RECT':
823  phagen['width'] = cls['width'].real()
824  phagen['height'] = cls['height'].real()
825  phagen['posang'] = cls['posang'].real()
826  phagen['bkgmethod'] = cls['bkgmethod'].string()
827  if cls['bkgmethod'].string() == 'REFLECTED':
828  phagen['bkgregmin'] = cls['bkgregmin'].integer()
829  phagen['use_model_bkg'] = use_model_bkg
830  phagen['maxoffset'] = cls['maxoffset'].real()
831  phagen['stack'] = True
832  phagen['etruemin'] = cls['etruemin'].real()
833  phagen['etruemax'] = cls['etruemax'].real()
834  phagen['etruebins'] = cls['etruebins'].integer()
835  phagen['chatter'] = cls['chatter'].integer()
836  phagen['clobber'] = cls['clobber'].boolean()
837  phagen['debug'] = cls['debug'].boolean()
838  phagen['nthreads'] = nthreads
839 
840  # Set exclusion map
841  # Initialise exclusion map flag to False
842  use_excl_map = False
843  # Initialise inexclusion par flag to False
844  use_inexclusion = False
845  # Do we have valid exclusion map in memory?
846  if hasattr(cls, 'exclusion_map'):
847  exclusion_map = cls.exclusion_map()
848  if exclusion_map is not None:
849  # Set use exclusion map flag to True
850  use_excl_map = True
851  # Set exclusion map
852  phagen.exclusion_map(exclusion_map)
853  # If we do not have valid exclusion map in memory ...
854  if not use_excl_map:
855  # ... do we have an inxeclusion parameter?
856  if cls.has_par('inexclusion'):
857  # If the inexclusion parameter is valid
858  if cls['inexclusion'].is_valid():
859  # Set use inexclusion flag to True
860  use_inexclusion = True
861  # Set inexclusion parameter
862  inexclusion = cls['inexclusion'].value()
863  phagen['inexclusion'] = inexclusion
864  # If there is no valid exclusion map in memory
865  # nor valid inexclusion parameter
866  if not use_excl_map and not use_inexclusion:
867  # Set inexclusion for csphagen to None
868  phagen['inexclusion'] = 'NONE'
869 
870  # Run csphagen
871  phagen.run()
872 
873  # Clone resulting observation container
874  onoff_obs = phagen.obs().copy()
875 
876  # On/Off observations are created with CSTAT as default statistic
877  # We will change this to the user choice
878  if cls['statistic'].string() != 'DEFAULT':
879  for observation in onoff_obs:
880  observation.statistic(cls['statistic'].string())
881 
882  # Return On/Off oberservation container
883  return onoff_obs
884 
885 
886 # ========================================= #
887 # Calculate residuals from counts and model #
888 # ========================================= #
889 def residuals(cls, counts, model):
890  """
891  Calculate residuals given counts and models, according to algorithm
892  specified by user.
893 
894  Can handle GSkyMap or GNdarray objects
895 
896 
897  Parameters
898  ----------
899  cls : `~ctools.cscript`
900  cscript class
901  counts : `~gammalib.GSkyMap/~gammalib.GNdarray'
902  Data counts
903  model : `~gammalib.GSkyMap/~gammalib.GNdarray'
904  Model counts
905 
906  Returns
907  -------
908  residuals : `~gammalib.GSkyMap/~gammalib.GNdarray'
909  Residuals
910  """
911  # Find type of objects we are manipulating and set size to iterate later
912  if counts.classname() == 'GNdarray':
913  nelem = counts.size()
914  elif counts.classname() == 'GSkyMap':
915  nelem = counts.npix()
916  else:
917  msg = 'cscripts.obsutils.residuals only handles ' \
918  + 'gammalib.GNdarray or gammalib.GSkyMap objects.\n'
919  raise RuntimeError(msg)
920 
921  # Copy counts to initialise residuals
922  residuals = counts.copy()
923 
924  # Get residual map algorithm type
925  algorithm = cls['algorithm'].string()
926 
927  # Subtract
928  if algorithm == 'SUB':
929  residuals -= model
930 
931  # Subtract and divide by model
932  elif algorithm == 'SUBDIV':
933  residuals -= model
934  residuals /= model
935 
936  # Subtract and divide by sqrt of model
937  elif algorithm == 'SUBDIVSQRT':
938  residuals -= model
939  residuals /= model.sqrt()
940 
941  # Calculate significance from Li&Ma
942  elif algorithm == 'SIGNIFICANCE':
943 
944  # Compute sign
945  sign = (residuals - model).sign()
946 
947  # Loop over every bin
948  for i in range(nelem):
949 
950  # If the model value > 0.0 do the computation as normal ...
951  model_val = model[i]
952  if model_val > 0.0:
953 
954  # If the data value is also > 0 then compute the
955  # significance^2 and save it ...
956  data_val = residuals[i]
957  if data_val > 0.0:
958  log_val = math.log(data_val / model_val)
959  residuals[i] = (data_val * log_val) + model_val - data_val
960  if residuals[i] < 0.0: # See glitch issue #2765
961  residuals[i] = 0.0
962 
963  # ... otherwise compute the reduced value of the above
964  # expression. This is necessary to avoid computing log(0).
965  else:
966  residuals[i] = model_val
967 
968  # ... otherwise hard-code the significance to 0
969  else:
970  residuals[i] = 0.0
971 
972  # Compute significance map
973  residuals *= 2.0
974  residuals = residuals.sqrt()
975  residuals *= sign
976 
977  # Raise exception if algorithm is unknown
978  else:
979  raise TypeError('Algorithm "' + algorithm + '" not known')
980 
981  # Return
982  return residuals
983 
984 
985 # ================= #
986 # Create counts map #
987 # ================= #
988 def create_counts_cube(cls, obs):
989  """
990  Create counts cube from observations
991 
992  Parameters
993  ----------
994  cls : `~ctools.cscript`
995  cscript class
996  obs : `~gammalib.GObservations`
997  Observation container
998 
999  Returns
1000  -------
1001  cntcube : `~gammalib.GCTAEventCube`
1002  Counts cube
1003  """
1004  # Initialise ctbin
1005  ctbin = ctools.ctbin(obs)
1006 
1007  # Set parameters
1008  ctbin['xref'] = cls['xref'].real()
1009  ctbin['yref'] = cls['yref'].real()
1010  ctbin['proj'] = cls['proj'].string()
1011  ctbin['coordsys'] = cls['coordsys'].string()
1012  ctbin['ebinalg'] = cls['ebinalg'].string()
1013  ctbin['nxpix'] = cls['nxpix'].integer()
1014  ctbin['nypix'] = cls['nypix'].integer()
1015  ctbin['binsz'] = cls['binsz'].real()
1016  if cls['ebinalg'].string() == 'FILE':
1017  ctbin['ebinfile'] = cls['ebinfile'].filename().file()
1018  else:
1019  ctbin['enumbins'] = cls['enumbins'].integer()
1020  ctbin['emin'] = cls['emin'].real()
1021  ctbin['emax'] = cls['emax'].real()
1022  if cls['ebinalg'].string() == 'POW':
1023  ctbin['ebingamma'] = cls['ebingamma'].real()
1024  ctbin['chatter'] = cls['chatter'].integer()
1025  ctbin['clobber'] = cls['clobber'].boolean()
1026  ctbin['debug'] = cls['debug'].boolean()
1027 
1028  # Run ctbin
1029  ctbin.run()
1030 
1031  # Retrieve counts cube
1032  cntcube = ctbin.cube().copy()
1033 
1034  # Return counts cube
1035  return cntcube
def create_counts_cube
Definition: obsutils.py:988
virtual void run(void)
Run ctool.
Definition: ctool.cpp:257
const GCTAEventCube & cube(const int &index=0) const
Return event cube at index.
Definition: ctbin.cpp:460
def get_stacked_response
Definition: obsutils.py:527