ctools 2.1.0
Loading...
Searching...
No Matches
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# ==========================================================================
21import sys
22import math
23import gammalib
24import ctools
25from cscripts import mputils
26from cscripts import obsutils
27
28
29# =========== #
30# csscs class #
31# =========== #
32class 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
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':
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# ======================== #
991if __name__ == '__main__':
992
993 # Create instance of application
994 app = csscs(sys.argv)
995
996 # Execute application
997 app.execute()
exclusion_map(self, object=None)
Definition csscs.py:860
ts(self, name)
Definition csscs.py:933
_mask_evlist(self, obs, ra, dec, rad)
Definition csscs.py:410
__setstate__(self, state)
Definition csscs.py:84
_get_parameters(self)
Definition csscs.py:141
_fill_fits(self, results)
Definition csscs.py:679
_pixel_analysis(self, inx)
Definition csscs.py:487
_get_onoff_parameters(self)
Definition csscs.py:106
_write_hdu_keywords(self, hdu, is_flux=False)
Definition csscs.py:638
_adjust_model_pars(self)
Definition csscs.py:289
flux_error(self, name)
Definition csscs.py:913
flux(self, name)
Definition csscs.py:893
_mask_observations(self, ra, dec, rad)
Definition csscs.py:455
_get_skymap(self, name, quantity)
Definition csscs.py:739
ulimit(self, name)
Definition csscs.py:960
__init__(self, *argv)
Definition csscs.py:38
_mask_cube(self, obs, ra, dec, rad)
Definition csscs.py:367