ctools  2.0.0
 All Classes Namespaces Files Functions Variables Macros Pages
csscs.py
Go to the documentation of this file.
1 #! /usr/bin/env python
2 # ==========================================================================
3 # Spectral component separation script
4 #
5 # Copyright (C) 2020-2022 Luigi Tibaldo
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 math
23 import gammalib
24 import ctools
25 from cscripts import mputils
26 from cscripts import obsutils
27 
28 
29 # =========== #
30 # csscs class #
31 # =========== #
32 class csscs(ctools.csobservation):
33  """
34  Spectral component separation script
35  """
36 
37  # Constructor
38  def __init__(self, *argv):
39  """
40  Constructor
41 
42  Parameters
43  ----------
44  argv : list of str
45  List of IRAF command line parameter strings of the form
46  ``parameter=3``.
47  """
48  # Initialise application by calling the base class constructor
49  self._init_csobservation(self.__class__.__name__, ctools.__version__, argv)
50 
51  # Initialise data members
52  self._nthreads = 0
53  self._srcnames = []
54  self._method = None
55  self._fits = None
56  self._excl_reg_map = None # Exclusion region map for on/off analysis
57  self._excl_reg_name = None
58 
59  # Return
60  return
61 
62  # State methods for pickling
63  def __getstate__(self):
64  """
65  Extend ctools.csobservation __getstate__ method
66 
67  Returns
68  -------
69  state : dict
70  Pickled instance
71  """
72  # Set pickled dictionary
73  state = {'base': ctools.csobservation.__getstate__(self),
74  'nthreads': self._nthreads,
75  'srcnames': self._srcnames,
76  'method': self._method,
77  'fits': self._fits,
78  'excl_reg_map': self._excl_reg_map,
79  'excl_reg_name': self._excl_reg_name}
80 
81  # Return pickled dictionary
82  return state
83 
84  def __setstate__(self, state):
85  """
86  Extend ctools.csobservation __setstate__ method
87 
88  Parameters
89  ----------
90  state : dict
91  Pickled instance
92  """
93  # Set state
94  ctools.csobservation.__setstate__(self, state['base'])
95  self._nthreads = state['nthreads']
96  self._srcnames = state['srcnames']
97  self._method = state['method']
98  self._fits = state['fits']
99  self._excl_reg_map = state['excl_reg_map']
100  self._excl_reg_name = state['excl_reg_name']
101 
102  # Return
103  return
104 
105  # Private methods
107  """
108  Get On/Off analysis parameters.
109 
110  This is done here rather than in ctools.is_onoff because the exclusion
111  map needs to be handled differently as an automatic parameter but
112  queried only if not already set and we need to verify is not empty.
113  Also many parameters are already queried for all methods
114 
115  TODO: verify if this can be made more uniform with other scripts
116  """
117  # Exclusion map
118  if (self._excl_reg_map is not None) and (self._excl_reg_map.map().npix() > 0):
119  # Exclusion map set and is not empty
120  pass
121  elif self['inexclusion'].is_valid():
122  self._excl_reg_name = self['inexclusion'].filename()
123  self._excl_reg_map = gammalib.GSkyRegionMap(self._excl_reg_name)
124  else:
125  msg = 'csscs in On/Off mode requires input exclusion region.'
126  raise RuntimeError(msg)
127 
128  # Other csphagen parameters
129  self['enumbins'].integer()
130  if self['bkgmethod'].string() == 'REFLECTED':
131  self['bkgregmin'].integer()
132  self['bkgregskip'].integer()
133  self['maxoffset'].real()
134  self['etruemin'].real()
135  self['etruemax'].real()
136  self['etruebins'].integer()
137 
138  # Return
139  return
140 
141  def _get_parameters(self):
142  """
143  Get parameters from parfile
144  """
145  # Set observation if not done before
146  if self.obs().is_empty():
147  self.obs(self._get_observations())
148 
149  # Set observation statistic
150  self._set_obs_statistic(gammalib.toupper(self['statistic'].string()))
151 
152  # Collect number of unbinned, binned and On/Off observations in
153  # observation container
154  n_unbinned = 0
155  n_binned = 0
156  n_onoff = 0
157  for obs in self.obs():
158  if obs.classname() == 'GCTAObservation':
159  if obs.eventtype() == 'CountsCube':
160  n_binned += 1
161  else:
162  n_unbinned += 1
163  elif obs.classname() == 'GCTAOnOffObservation':
164  n_onoff += 1
165  n_cta = n_unbinned + n_binned + n_onoff
166  n_other = self.obs().size() - n_cta
167 
168  # Check if there are On/Off or non-IACT observations
169  if n_onoff > 0 or n_other > 0:
170  msg = 'On/Off or non-CTA observations found. ' \
171  'csscs does not support this type of observations.'
172  raise RuntimeError(msg)
173 
174  # ... otherwise check if there is a mix of binned and unbinned
175  elif n_unbinned > 0 and n_binned > 0:
176  msg = 'Mix of unbinned and binned CTA observations ' \
177  'found in observation container. csscs does not ' \
178  'support this mix.'
179  raise RuntimeError(msg)
180 
181  # Set models if there are none in the container
182  if self.obs().models().size() == 0:
183  self.obs().models(self['inmodel'].filename())
184 
185  # Query the map definition parameters
186  self['xref'].real()
187  self['yref'].real()
188  self['coordsys'].string()
189  self['proj'].string()
190  self['nxpix'].integer()
191  self['nypix'].integer()
192  self['binsz'].real()
193 
194  # Query radius of ROI for component separation
195  self['rad'].real()
196 
197  # Query energy boundaries
198  self['emin'].real()
199  self['emax'].real()
200 
201  # If we have unbinned observations query analysis method
202  if n_unbinned > 0:
203  self._method = self['method'].string()
204  # If method is Onoff query csphagen parameters
205  if self._method == 'ONOFF':
206  self._get_onoff_parameters()
207  # Otherwise set method to binned
208  else:
209  self._method = 'BINNED'
210 
211  # Query target source names
212  srcnames = self['srcnames'].string()
213 
214  # Fashion target sources into Python list. Strip leading and trailing
215  # spaces from names.
216  self._srcnames = srcnames.split(';')
217  for s, name in enumerate(self._srcnames):
218  self._srcnames[s] = name.strip()
219 
220  # Verify that the target sources are in the model
221  src_in_model = True
222  for name in self._srcnames:
223  src_in_model = src_in_model and self.obs().models().contains(name)
224  if src_in_model == False:
225  msg = 'Not all target sources are present in input model.'
226  raise RuntimeError(msg)
227 
228  # For On/Off analysis check the number of gamma-ray sources in the model
229  if self._method == 'ONOFF':
230 
231  # Initialise number of sources
232  nsources = 0
233 
234  # Loop over models and verify if model is of type GModelSky
235  for model in self.obs().models():
236  if model.classname() == 'GModelSky':
237  nsources += 1
238 
239  # If there are background gamma-ray sources in the model
240  # throw runtime error
241  if nsources > len(self._srcnames):
242  msg = 'Background gamma-ray sources found in the model. ' \
243  'On/Off analysis does not support this feature.'
244  raise RuntimeError(msg)
245 
246  # ... otherwise continue
247  else:
248  pass
249 
250  # Query other hidden parameters, just in case
251  self['edisp'].boolean()
252  self['calc_ulim'].boolean()
253  self['calc_ts'].boolean()
254  self['fix_bkg'].boolean()
255  self['fix_srcs'].boolean()
256 
257  # Read ahead output parameters
258  if self._read_ahead():
259  self['outfile'].query()
260 
261  # Write input parameters into logger
262  self._log_parameters(gammalib.TERSE)
263 
264  # Set number of processes for multiprocessing
265  self._nthreads = mputils.nthreads(self)
266 
267  # Return
268  return
269 
270  def _create_map(self):
271  """
272  Create a gammalib.GSkyMap object to store the output
273 
274  Returns
275  -------
276  skymap : `~gammalib.GSkyMap`
277  Sky map
278  """
279  # Create sky map
280  skymap = gammalib.GSkyMap(self['proj'].string(), self['coordsys'].string(),
281  self['xref'].real(), self['yref'].real(),
282  -self['binsz'].real(), self['binsz'].real(),
283  self['nxpix'].integer(), self['nypix'].integer(),
284  1)
285 
286  # Return sky map
287  return skymap
288 
290  """
291  Adjust model parameters
292  """
293  # Write header
294  self._log_header1(gammalib.TERSE, 'Adjust model parameters')
295 
296  # Adjust model parameters based on input user parameters
297  for model in self.obs().models():
298 
299  # Initialise TS flag for all models to false
300  model.tscalc(False)
301 
302  # Log model name
303  self._log_header3(gammalib.EXPLICIT, model.name())
304 
305  # In On/Off mode we cannot support multiple source morphologies
306  # Thus, if we have more than one target we will set them all
307  # to isotropic
308  if self._method == 'ONOFF' and len(self._srcnames) > 1:
309 
310  # Check if it is a source
311  try:
312  gammalib.GModelSky(model)
313  # In this case check if the spatial model is already isotropic
314  if model.spatial() == 'DiffuseIsotropic':
315  pass
316 
317  # ... otherwise change it to isotropic
318  else:
319  msg = ' Setting spatial model to diffuse isotropic'
320  self._log_string(gammalib.EXPLICIT, msg)
321  model.spatial(gammalib.GModelSpatialDiffuseConst())
322  except:
323  pass
324 
325  # Freeze all parameters except the normalization
326  # which is assumed to be the first spectral parameter
327  normpar = model.spectral()[0]
328  for par in model:
329  if par.name() == normpar.name():
330  pass
331  else:
332  if par.is_free():
333  self._log_string(gammalib.EXPLICIT, ' Fixing "' + par.name() + '"')
334  par.fix()
335 
336  # Deal with the target sources
337  if model.name() in self._srcnames:
338 
339  # Free the normalisation parameter which is assumed to be
340  # the first spectral parameter
341  if normpar.is_fixed():
342  self._log_string(gammalib.EXPLICIT, ' Freeing "' + normpar.name() + '"')
343  normpar.free()
344 
345  # Optionally compute Test Statistic value
346  if self['calc_ts'].boolean():
347  model.tscalc(True)
348 
349  # Deal with background models
350  elif self['fix_bkg'].boolean() and not model.classname() == 'GModelSky':
351 
352  if normpar.is_free():
353  self._log_string(gammalib.EXPLICIT, ' Fixing "' + normpar.name() + '"')
354  normpar.fix()
355 
356 
357  # Deal with background sources
358  elif self['fix_srcs'].boolean() and model.classname() == 'GModelSky':
359 
360  if normpar.is_free():
361  self._log_string(gammalib.EXPLICIT, ' Fixing "' + normpar.name() + '"')
362  normpar.fix()
363 
364  # Return
365  return
366 
367  def _mask_cube(self, obs, ra, dec, rad):
368  """
369  Mask cube observation
370 
371  Parameters
372  ----------
373  obs : `gammalib.GCTAObservation`
374  Input observation of type cube
375  ra : float
376  Right Ascension (deg)
377  dec : float
378  Declination (deg)
379  rad : float
380  Radius (deg)
381 
382  Returns
383  -------
384  new_obs : `gammalib.GCTAObservations`
385  Output observations with masked cubes
386  """
387  # Filter cube according to ROI and user energy range
388  cubemask = ctools.ctcubemask(obs)
389  cubemask['ra'] = ra
390  cubemask['dec'] = dec
391  cubemask['rad'] = rad
392  cubemask['regfile'] = 'NONE'
393  cubemask['emin'] = self['emin'].real()
394  cubemask['emax'] = self['emax'].real()
395 
396  # If chatter level is verbose and debugging is requested then
397  # switch also on the debug model in ctcubemask
398  if self._logVerbose() and self._logDebug():
399  cubemask['debug'] = True
400 
401  # Mask cube
402  cubemask.run()
403 
404  # Get deep copy of filtered observations
405  new_obs = cubemask.obs().copy()
406 
407  # Return
408  return new_obs
409 
410  def _mask_evlist(self, obs, ra, dec, rad):
411  """
412  Mask event list
413 
414  Parameters
415  ----------
416  obs : `gammalib.GCTAObservations`
417  Input observations of type event list
418  ra : float
419  Right Ascension (deg)
420  dec : float
421  Declination (deg)
422  rad : float
423  Radius (deg)
424 
425  Returns
426  -------
427  new_obs : `gammalib.GCTAObservations`
428  Output observations with masked event lists
429  """
430  # Filter event list according to ROI and user energy range
431  select = ctools.ctselect(obs)
432  select['usepnt'] = False
433  select['ra'] = ra
434  select['dec'] = dec
435  select['rad'] = rad
436  select['emin'] = self['emin'].real()
437  select['emax'] = self['emax'].real()
438  select['tmin'] = 'INDEF'
439  select['tmax'] = 'INDEF'
440 
441  # If chatter level is verbose and debugging is requested then
442  # switch also on the debug model in ctcubemask
443  if self._logVerbose() and self._logDebug():
444  select['debug'] = True
445 
446  # Select event list
447  select.run()
448 
449  # Get deep copy of filtered observation
450  new_obs = select.obs().copy()
451 
452  # Return
453  return new_obs
454 
455  def _mask_observations(self, ra, dec, rad):
456  """
457  Create observations restricted to circular ROI
458 
459  Parameters
460  ----------
461  ra : float
462  Right Ascension (deg)
463  dec : float
464  Declination (deg)
465  rad : float
466  Radius (deg)
467 
468  Returns
469  -------
470  new_obs : `~gammalib.GObservations`
471  Observations in circular ROI
472  """
473  # Determine type of masking according to observation type and analysis
474  # method
475  if self._method == 'UNBINNED':
476  new_obs = self._mask_evlist(self.obs(), ra, dec, rad)
477  elif self._method == 'BINNED':
478  new_obs = self._mask_cube(self.obs(), ra, dec, rad)
479  elif self._method == 'ONOFF':
480  new_obs = obsutils.get_onoff_obs(self, self.obs(), nthreads=1,
481  ra=ra, dec=dec,
482  srcname=self._srcnames[0])
483 
484  # Return
485  return new_obs
486 
487  def _pixel_analysis(self, inx):
488  """
489  Performs analysis over the region of interest corresponding to a
490  single pixel of output map
491 
492  Parameters
493  ----------
494  inx : int
495  Pixel index
496 
497  Returns
498  -------
499  result : dict
500  Results
501  """
502 
503  # Create template for output map to extract pixel coordinates
504  outmap = self._create_map()
505 
506  # Determine pixel centre coordinates
507  pixdir = outmap.inx2dir(inx)
508  ra = pixdir.ra_deg()
509  dec = pixdir.dec_deg()
510 
511  # Write header for spatial bin
512  msg = 'Spatial bin (%d) centred on (R.A.,Dec) = (%f,%f) deg' % (inx, ra, dec)
513  self._log_header2(gammalib.TERSE, msg)
514 
515  # Initialize results
516  result = {}
517  for name in self._srcnames:
518  result[name] = {'flux': 0.0,
519  'flux_err': 0.0,
520  'TS': 0.0,
521  'ulimit': -1.0}
522 
523  # Mask observations
524  self._log_header3(gammalib.NORMAL, 'Masking observations')
525  masked_obs = self._mask_observations(ra, dec, self['rad'].real())
526 
527  # Continue only if we have at least one observation
528  if masked_obs.size() > 0:
529 
530  # Set up likelihood analysis
531  self._log_header3(gammalib.NORMAL, 'Performing fit in region')
532  like = ctools.ctlike(masked_obs)
533  like['edisp'] = self['edisp'].boolean()
534  like['nthreads'] = 1 # Avoids OpenMP conflict
535 
536  # If chatter level is verbose and debugging is requested then
537  # switch also on the debug model in ctlike
538  if self._logVerbose() and self._logDebug():
539  like['debug'] = True
540 
541  # Perform maximum likelihood fit
542  like.run()
543 
544  # Write model results for explicit chatter level
545  self._log_string(gammalib.EXPLICIT, str(like.obs().models()))
546 
547  # Continue only if log-likelihood is non-zero
548  if like.obs().logL() != 0.0:
549 
550  # Prepare objects for flux extraction
551  # ROI
552  centre = gammalib.GSkyDir()
553  centre.radec_deg(ra, dec)
554  roi = gammalib.GSkyRegionCircle(centre, self['rad'].real())
555 
556  # Energy boundaries
557  emin = gammalib.GEnergy(self['emin'].real(), 'TeV')
558  emax = gammalib.GEnergy(self['emax'].real(), 'TeV')
559 
560  # Loop over target sources
561  for name in self._srcnames:
562 
563  # Get source
564  source = like.obs().models()[name]
565 
566  # Get flux
567  # Integrate spectral model between emin and emax
568  flux = source.spectral().flux(emin, emax)
569 
570  # Calculate correction factor
571  # Spatial model flux over ROI divided by ROI solid angle
572  corr_factor = source.spatial().flux(roi)
573  corr_factor /= gammalib.twopi * (1.0 - math.cos(math.radians(self['rad'].real())))
574 
575  # Multiply flux by correction factor
576  flux *= corr_factor
577  result[name]['flux'] = flux
578 
579  # Get flux error
580  # Normalization parameter
581  normpar = source.spectral()[0]
582 
583  # Only normalization parameter free
584  # Relative error on flux is same as on normalization
585  # Avoid zero division error
586  if normpar.value() > 0.:
587  flux_error = flux * normpar.error() / normpar.value()
588  else:
589  flux_error = 0.
590  result[name]['flux_error'] = flux_error
591 
592  # If requested get TS
593  if self['calc_ts'].boolean():
594  result[name]['TS'] = source.ts()
595 
596  # If requested compute upper flux limit
597  if self['calc_ulim'].boolean():
598 
599  # Logging information
600  self._log_header3(gammalib.NORMAL,
601  'Computing upper limit for source ' + name)
602 
603  # Create upper limit object
604  ulimit = ctools.ctulimit(like.obs())
605  ulimit['srcname'] = name
606  ulimit['emin'] = self['emin'].real()
607  ulimit['emax'] = self['emax'].real()
608 
609  # If chatter level is verbose and debugging is requested
610  # then switch also on the debug model in ctulimit
611  if self._logVerbose() and self._logDebug():
612  ulimit['debug'] = True
613 
614  # Try to run upper limit and catch exceptions
615  try:
616  ulimit.run()
617  ulimit_value = ulimit.flux_ulimit()
618  # Multiply by correction factor to get flux per solid angle in ROI
619  ulimit_value *= corr_factor
620  result[name]['ulimit'] = ulimit_value
621  except:
622  self._log_string(gammalib.NORMAL, 'Upper limit '
623  'calculation failed.')
624 
625  # if likelihood is zero
626  else:
627  value = 'Likelihood is zero. Bin is skipped.'
628  self._log_value(gammalib.TERSE, '(R.A.,Dec) = (%f,%f) deg' % (ra, dec), value)
629 
630  # if observation size = 0
631  else:
632  value = 'Size of masked observations is zero. Bin is skipped.'
633  self._log_value(gammalib.TERSE, '(R.A.,Dec) = (%f,%f) deg' % (ra, dec), value)
634 
635  # Return result
636  return result
637 
638  def _write_hdu_keywords(self, hdu, is_flux=False):
639  """
640  Append cards to HDU
641 
642  Parameters
643  ----------
644  hdu : `~gammalib.GFitsHDU`
645  HDU
646  """
647  # Flux units
648  if is_flux:
649  hdu.card('BUNIT', 'ph/cm2/s/sr', 'Photon flux')
650 
651  # Minimum and maximum energy
652  hdu.card('E_MIN', self['emin'].real(), '[TeV] Lower energy boundary')
653  hdu.card('E_MAX', self['emax'].real(), '[TeV] Upper energy boundary')
654  hdu.card('EUNIT', 'TeV', 'Units for E_MIN and E_MAX')
655 
656  # ROI size
657  hdu.card('ROISZ', self['rad'].real(),
658  '[deg] Size of ROI used for component separation')
659 
660  # Analysis method
661  hdu.card('METHOD', self._method, 'Analysis method')
662 
663  # For On/Off log information
664  if self._method == 'ONOFF':
665 
666  # Exclusion map
667  if self._excl_reg_name is not None:
668  hdu.card('EXCLMAP', self._excl_reg_name.url(), 'Exclusion map name')
669 
670  # Model background?
671  hdu.card('BKGMODEL', self['use_model_bkg'].boolean(), 'Use background model?')
672 
673  # Fit statistic
674  hdu.card('STAT', self['statistic'].string(), 'Fit statistic')
675 
676  # Return
677  return
678 
679  def _fill_fits(self, results):
680  """
681  Fill FITS object to store the results
682 
683  Parameters
684  ----------
685  result : `dict`
686  Results
687 
688  Returns
689  -------
690  fits : `~gammalib.GFits`
691  FITS object
692  """
693  # Create new Fits object
694  fits = gammalib.GFits()
695 
696  # Append empty primary HDU
697  fits.append(gammalib.GFitsImageDouble())
698 
699  # Loop over target sources
700  for s, name in enumerate(self._srcnames):
701 
702  # Create skymaps to store results
703  # Maps for TS and upper limits created and filled to avoid if statements
704  # Will not be saved in the end if not requested
705  fmap = self._create_map()
706  errmap = self._create_map()
707  tsmap = self._create_map()
708  ulmap = self._create_map()
709 
710  # Loop over pixels and fill maps
711  for inx in range(fmap.npix()):
712  fmap[inx, 0] = results[inx][name]['flux']
713  errmap[inx, 0] = results[inx][name]['flux_error']
714  tsmap[inx, 0] = results[inx][name]['TS']
715  ulmap[inx, 0] = results[inx][name]['ulimit']
716 
717  # Uppercase name to adhere to gammalib convention
718  Name = gammalib.toupper(name)
719 
720  # Write maps to Fits
721  fhdu = fmap.write(fits, Name + ' FLUX')
722  self._write_hdu_keywords(fhdu, is_flux=True)
723  errhdu = errmap.write(fits, Name + ' FLUX ERROR')
724  self._write_hdu_keywords(errhdu, is_flux=True)
725 
726  # If requested write TS map to fits
727  if self['calc_ts'].boolean():
728  tshdu = tsmap.write(fits, Name + ' TS')
729  self._write_hdu_keywords(tshdu)
730 
731  # If requested write upper limit map to fits
732  if self['calc_ulim'].boolean():
733  ulhdu = ulmap.write(fits, Name + ' FLUX UPPER LIMIT')
734  self._write_hdu_keywords(ulhdu, is_flux=True)
735 
736  # Return
737  return fits
738 
739  def _get_skymap(self, name, quantity):
740  """
741  Fetches skymap from FITS container
742 
743  Parameters
744  ----------
745  name : str
746  Source name
747  quantity : str
748  Quantity
749 
750  Returns
751  -------
752  skymap : `~gammalib.GSkyMap`
753  Skymap
754 
755  Raises
756  ------
757  NameError
758  """
759 
760  # Initialise skymap object
761  skymap = None
762 
763  # Proceed only if fits has been filled
764  if self._fits != None:
765 
766  # Check that the name is in the list of target sources
767  if name in self._srcnames:
768 
769  # Uppercase name
770  Name = gammalib.toupper(name)
771 
772  # Assemble HDU name
773  hduname = Name + ' ' + quantity
774 
775  # Try to fetch HDU
776  try:
777  hdu = self._fits[hduname]
778  # Convert to GSkyMap
779  skymap = gammalib.GSkyMap(hdu)
780  # Catch exception if HDU does not exist
781  except:
782  msg = 'HDU "' + hduname + '" not found in FITS container.'
783  raise RuntimeError(msg)
784 
785  # ... otherwise throw name error
786  else:
787  print('ERROR: Source "' + name + '" not found in list of target sources.')
788  raise NameError(name)
789 
790  # Return
791  return skymap
792 
793  # Public methods
794  def process(self):
795  """
796  Process the script
797  """
798  # Get parameters
799  self._get_parameters()
800 
801  # Write input observation container into logger
802  self._log_observations(gammalib.NORMAL, self.obs(), 'Input observation')
803 
804  # Adjust model parameters based on input user parameters
805  self._adjust_model_pars()
806 
807  # Compute number of pixels of output map
808  npix = self._create_map().npix()
809 
810  # If more than a single thread is requested then use multiprocessing
811  if self._nthreads > 1:
812 
813  # Compute values in pixels
814  args = [(self, '_pixel_analysis', i)
815  for i in range(npix)]
816  poolresults = mputils.process(self._nthreads, mputils.mpfunc, args)
817 
818  # Construct results
819  results = []
820  for i in range(npix):
821  results.append(poolresults[i][0])
822  self._log_string(gammalib.TERSE, poolresults[i][1]['log'], False)
823 
824  # Otherwise loop over pixels and run the pixel analysis
825  else:
826  results = []
827  for i in range(npix):
828  results.append(self._pixel_analysis(i))
829 
830  # Fill output Fits
831  self._fits = self._fill_fits(results)
832 
833  # Stamp FITS file
834  self._stamp(self._fits)
835 
836  # Return
837  return
838 
839  def save(self):
840  """
841  Save maps to Fits flie
842  """
843  # Write header
844  self._log_header1(gammalib.TERSE, 'Save maps')
845 
846  # Continue only if FITS file is valid
847  if self._fits != None:
848  # Get outmap parameter
849  outfile = self['outfile'].filename()
850 
851  # Log file name
852  self._log_value(gammalib.NORMAL, 'Output file', outfile.url())
853 
854  # Save spectrum
855  self._fits.saveto(outfile, self['clobber'].boolean())
856 
857  # Return
858  return
859 
860  def exclusion_map(self, object=None):
861  """
862  Return and optionally set the exclusion regions map
863 
864  Parameters
865  ----------
866  object : `~gammalib.GSkyRegion` or `~gammalib.GSkyMap` or `~gammalib.GFilename`
867  Exclusion regions object
868 
869  Returns
870  -------
871  region : `~gammalib.GSkyRegionMap`
872  Exclusion regions map
873  """
874  # If a regions object is provided then set the regions
875  if object is not None:
876  self._excl_reg_map = gammalib.GSkyRegionMap(object)
877 
878  # Return
879  return self._excl_reg_map
880 
881  def fits(self):
882  """
883  Return copy of the fits container
884 
885  Returns
886  -------
887  fits : `~gammalib.GFits`
888  FITS file containing the maps
889  """
890  # Return
891  return self._fits.copy()
892 
893  def flux(self, name):
894  """
895  Return flux skymap
896 
897  Parameters
898  ----------
899  name : str
900  Source name
901 
902  Returns
903  -------
904  skymap : `~gammalib.GSkyMap`
905  Flux skymap
906  """
907  # Get flux skymap
908  skymap = self._get_skymap(name, 'FLUX')
909 
910  # Return flux sky map
911  return skymap
912 
913  def flux_error(self, name):
914  """
915  Return flux error skymap
916 
917  Parameters
918  ----------
919  name : str
920  Source name
921 
922  Returns
923  -------
924  skymap : `~gammalib.GSkyMap`
925  Flux error skymap
926  """
927  # Get flux error sky map
928  skymap = self._get_skymap(name, 'FLUX ERROR')
929 
930  # Return flux error sky map
931  return skymap
932 
933  def ts(self, name):
934  """
935  Return TS skymap
936 
937  Parameters
938  ----------
939  name : str
940  Source name
941 
942  Returns
943  -------
944  skymap : `~gammalib.GSkyMap`
945  TS skymap
946  """
947  # Check that TS computation is requested
948  if self['calc_ts'].boolean():
949  skymap = self._get_skymap(name, 'TS')
950 
951  # ... otherwise raise error
952  else:
953  msg = 'TS computation not requested. Change user parameter ' \
954  '"calc_ts" to True to obtain TS.'
955  raise RuntimeError(msg)
956 
957  # Return TS map
958  return skymap
959 
960  def ulimit(self, name):
961  """
962  Return flux upper limit skymap
963 
964  Parameters
965  ----------
966  name : str
967  Source name
968 
969  Returns
970  -------
971  skymap : `~gammalib.GSkyMap`
972  Flux upper limit skymap
973  """
974  # Check that upper limit computation is requested
975  if self['calc_ulim'].boolean():
976  skymap = self._get_skymap(name, 'FLUX UPPER LIMIT')
977 
978  # ... otherwise raise error
979  else:
980  msg = 'Upper limit computation not requested. Change user parameter ' \
981  '"calc_ulimit" to True to obtain upper limit.'
982  raise RuntimeError(msg)
983 
984  # Return upper limit map
985  return skymap
986 
987 
988 # ======================== #
989 # Main routine entry point #
990 # ======================== #
991 if __name__ == '__main__':
992 
993  # Create instance of application
994  app = csscs(sys.argv)
995 
996  # Execute application
997  app.execute()
def _adjust_model_pars
Definition: csscs.py:289
def _write_hdu_keywords
Definition: csscs.py:638
def _get_onoff_parameters
Definition: csscs.py:106
def _mask_observations
Definition: csscs.py:455