26 from cscripts
import mputils
34 Generate PHA, ARF and RMF files for classical IACT spectral analysis
43 self._init_csobservation(self.__class__.__name__, ctools.__version__, argv)
69 Extend ctools.csobservation getstate method to include some members
77 state = {
'base' : ctools.csobservation.__getstate__(self),
100 Extend ctools.csobservation setstate method to include some members
107 ctools.csobservation.__setstate__(self, state[
'base'])
119 self.
_rad = state[
'rad']
131 Set up the source direction parameter
137 coordsys = self[
'coordsys'].string()
140 if coordsys ==
'CEL':
141 ra = self[
'ra'].real()
142 dec = self[
'dec'].real()
143 self._src_dir.radec_deg(ra, dec)
147 elif coordsys ==
'GAL':
148 glon = self[
'glon'].real()
149 glat = self[
'glat'].real()
150 self._src_dir.lb_deg(glon, glat)
157 Compute the difference in position angle wrt the pointing in degrees
161 pnt_dir : `~gammalib.GSkyDir`
163 a : `~gammalib.GSkyDir`
165 a : `~gammalib.GSkyDir`
171 Position angle (degrees)
174 posang_a = pnt_dir.posang_deg(a) % 360
175 posang_b = pnt_dir.posang_deg(b) % 360
178 posang = abs(posang_a - posang_b)
185 Get regions from DS9 file or FITS file
189 filename : `~gammalib.GFilename`
194 regs : `~gammalib.GSkyRegions`
199 if filename.is_fits():
200 map = gammalib.GSkyRegionMap(filename)
201 regs = gammalib.GSkyRegions()
206 regs = gammalib.GSkyRegions(filename)
213 Get parameters to define source/On region
217 self.
_srcshape = self[
'srcshape'].string()
226 self.
_rad = self[
'rad'].real()
227 self._src_reg.append(gammalib.GSkyRegionCircle(self.
_src_dir, self.
_rad))
237 self._src_reg.append(gammalib.GSkyRegionRectangle(self.
_src_dir,
247 Get parameters for REFLECTED background method
255 self[
'bkgregmin'].integer()
256 self[
'bkgregskip'].integer()
263 Get parameters for CUSTOM background method
268 Only one On region is allowed
271 filename = self[
'srcregfile'].filename()
276 raise RuntimeError(
'Only one On region is allowed')
279 if self._models.is_empty():
280 if isinstance(self.
_src_reg[0], gammalib.GSkyRegionCircle):
289 for obs
in self.obs():
290 if obs.classname() ==
'GCTAObservation':
291 if obs.off_regions().is_empty():
292 filename = self[
'bkgregfile'].filename()
294 obs.off_regions(regions)
301 Get parameters for OFF background method
306 On and Off observations must have same size
308 Off observations must be event lists
316 filename = self[
'inobsoff'].filename()
320 if gammalib.GFilename(filename).is_fits():
321 self._obs_off.append(gammalib.GCTAObservation(filename))
325 self._obs_off.load(filename)
329 if self.obs().size() != self.
obs_off().size():
330 raise RuntimeError(
'On and Off observations must have the same size')
336 if obs.eventtype() !=
"EventList":
337 raise RuntimeError(
'Off observations must be event lists')
340 if obs.has_response() ==
False:
343 database = self[
"caldb"].string()
344 irf = self[
"irf"].string()
347 parameter =
"parameter name=\"Calibration\"" +\
348 " database=\"" + database +
"\"" +\
349 " response=\"" + irf +
"\""
350 xml = gammalib.GXmlElement()
351 xml.append(parameter)
354 response = gammalib.GCTAResponseIrf(xml)
357 obs.response(response)
360 for model
in self.
obs_off().models():
361 self._models.append(model)
371 Get background method parameters
374 bkgmethod = self[
'bkgmethod'].string()
377 if bkgmethod ==
'REFLECTED':
379 elif bkgmethod ==
'CUSTOM':
381 elif bkgmethod ==
'OFF':
385 self[
'maxoffset'].real()
386 self[
'use_model_bkg'].boolean()
393 Get parameters from parfile and setup observations
400 self._setup_observations(self.obs(),
True,
True,
False)
405 if self.obs().models().size() > 0:
406 self.
_models = self.obs().models().clone()
407 self.
_srcname = self[
'srcname'].string()
408 elif self[
'inmodel'].is_valid():
409 inmodel = self[
'inmodel'].filename()
410 self.
_models = gammalib.GModels(inmodel)
411 self.
_srcname = self[
'srcname'].string()
414 self.
_ebounds = self._create_ebounds()
417 self.
_src_reg = gammalib.GSkyRegions()
420 if (self.
_excl_reg is not None)
and (self._excl_reg.map().npix() > 0):
423 elif self[
'inexclusion'].is_valid():
424 inexclusion = self[
'inexclusion'].filename()
427 if not inexclusion.has_extname()\
428 and not inexclusion.has_extno()\
429 and gammalib.GFits(inexclusion).contains(
'EXCLUSION'):
431 extname = inexclusion.url() +
'[EXCLUSION]'
432 inexclusion = gammalib.GFilename(extname)
434 self.
_excl_reg = gammalib.GSkyRegionMap(inexclusion)
444 if self.obs().size() > 1:
445 self[
'stack'].boolean()
448 if (self._read_ahead()):
449 self[
'outobs'].filename()
450 self[
'outmodel'].filename()
451 self[
'prefix'].string()
454 self._log_parameters(gammalib.TERSE)
460 if self._models.is_empty():
461 spatial = gammalib.GModelSpatialPointSource(self.
_src_dir)
462 spectral = gammalib.GModelSpectralPlaw(1.0e-18, -2.0,
463 gammalib.GEnergy(1.0,
'TeV'))
464 model = gammalib.GModelSky(spatial, spectral)
466 self._models.append(model)
468 self[
'use_model_bkg'].boolean(
False)
475 Compute the separation angle for reflected off regions in radians
480 Separation angle of two off regions (radians)
486 offset = pnt_dir.dist_deg(self.
_src_dir)
491 separation = 2.0 * self.
_rad / offset
501 cs = [self.
_src_reg[0].corner(icorner)
for icorner
in range(4)]
504 combinations = [[0,1], [0,2], [0,3], [1,2], [1,3], [2,3]]
506 for i,j
in combinations]
509 separation = max(angles) * gammalib.deg2rad
516 Calculate list of reflected regions for a single observation (pointing)
520 obs : `~gammalib.GCTAObservation()`
525 regions : `~gammalib.GSkyRegions`
526 List of reflected regions
529 regions = gammalib.GSkyRegions()
532 pnt_dir = obs.pointing().dir()
533 offset = pnt_dir.dist_deg(self.
_src_dir)
536 if self._src_reg.contains(pnt_dir):
537 msg =
' Skip because observation is pointed at %.3f deg from source'\
540 msg +=
' (circle rad=%.3f).' % (self.
_rad)
541 self._log_string(gammalib.NORMAL, msg)
544 posang = pnt_dir.posang_deg(self.
_src_dir)
548 N_skip = self[
'bkgregskip'].integer()
549 N_lim = 1 + 2 * N_skip
558 N = int(2.0 * math.pi / alpha)
562 if N < self[
'bkgregmin'].integer() + N_lim:
563 msg =
' Skip because the number %d of reflected regions '\
564 'for background estimation is smaller than '\
565 '"bkgregmin"=%d.' % (N-N_lim, self[
'bkgregmin'].integer())
566 self._log_string(gammalib.NORMAL, msg)
574 dphi_max = 360.0 - alpha * (1 + N_skip)
575 dphi = alpha * (1 + N_skip)
576 while dphi <= dphi_max:
577 ctr_dir = pnt_dir.clone()
578 ctr_dir.rotate_deg(posang + dphi, offset)
580 region = gammalib.GSkyRegionCircle(ctr_dir, self.
_rad)
583 region = gammalib.GSkyRegionRectangle(ctr_dir,
588 if self._excl_reg.overlaps(region):
591 msg =
' Reflected region overlaps with '\
593 self._log_string(gammalib.EXPLICIT, msg)
600 regions.append(region)
603 regions.append(region)
608 if regions.size() >= self[
'bkgregmin'].integer():
610 msg =
' Use %d reflected regions.' % (regions.size())
611 self._log_string(gammalib.NORMAL, msg)
614 msg =
' Skip because the number %d of regions' \
615 'for background estimation not overlapping ' \
616 'with the exclusion region is smaller than ' \
617 '"bkgregmin"=%d.' % \
618 (regions.size(), self[
'bkgregmin'].integer())
619 self._log_string(gammalib.NORMAL, msg)
620 regions = gammalib.GSkyRegions()
627 Compute background regions for Off observation
629 Calculate background region in Off observation that corresponds to the
630 source region in the On observation in instrument coordinates
634 obs : `~gammalib.GCTAObservation()`
636 obs_off : `~gammalib.GCTAObservation()`
641 regions : `~gammalib.GSkyRegions`
642 Container with background region
645 regions = gammalib.GSkyRegions()
648 instdir = obs.pointing().instdir(self.
_src_dir)
651 off_dir = obs_off.pointing().skydir(instdir)
656 region = gammalib.GSkyRegionCircle(off_dir, self.
_rad)
662 region = gammalib.GSkyRegionRectangle(off_dir,
670 if self._excl_reg.overlaps(region):
672 msg =
' Background region overlaps with exclusion region.'
673 self._log_string(gammalib.EXPLICIT, msg)
678 regions.append(region)
685 Set models for On/Off fitting
687 The method does the following
688 - append "OnOff" to the instrument name of all background models
689 - fix all spatial and temporal parameters
693 results : list of dict
698 models : `~gammalib.GModels()`
702 self._log_header1(gammalib.NORMAL,
'Set models')
705 models = gammalib.GModels()
708 has_stacked_model =
False
719 if 'GCTA' in model.classname():
722 if not self[
'use_model_bkg'].boolean():
723 self._log_string(gammalib.NORMAL,
' Skip "%s" model "%s" (%s)' % \
724 (model.instruments(), model.name(), model.ids()))
729 for result
in results:
730 if model.is_valid(result[
'instrument'], result[
'id']):
731 if result[
'bkg_reg'].size() > 0:
737 if self[
'stack'].boolean():
741 if has_stacked_model:
742 msg =
' Skip "%s" model "%s" (%s). There is already ' \
743 'a model for stacked analysis.' % \
744 (model.instruments(), model.name(), model.ids())
745 self._log_string(gammalib.NORMAL, msg)
750 has_stacked_model =
True
755 model.instruments(model.instruments()+
'OnOff')
766 self._log_string(gammalib.NORMAL,
' Use "%s" model "%s" (%s)' % \
767 (model.instruments(), model.name(), model.ids()))
774 self._log_string(gammalib.NORMAL,
' Skip "%s" model "%s" (%s)' % \
775 (model.instruments(), model.name(), model.ids()))
782 Set statistic for observation
784 If the "use_model_bkg" is true then set statistic to "cstat",
785 otherwise set it to "wstat"
789 obs : `~gammalib.GObservation()`
794 obs : `~gammalib.GObservation()`
798 if self[
'use_model_bkg'].boolean():
799 obs.statistic(
'cstat')
801 obs.statistic(
'wstat')
808 Set true energy boundaries
812 ebounds : `~gammalib.GEbounds()`
813 True energy boundaries
816 emin = self._ebounds.emin() * 0.5
817 emax = self._ebounds.emax() * 1.2
818 if emin.TeV() < self[
'etruemin'].real():
819 emin = gammalib.GEnergy(self[
'etruemin'].real(),
'TeV')
820 if emax.TeV() > self[
'etruemax'].real():
821 emax = gammalib.GEnergy(self[
'etruemax'].real(),
'TeV')
824 n_decades = (emax.log10TeV() - emin.log10TeV())
825 n_bins = int(n_decades * float(self[
'etruebins'].integer()) + 0.5)
830 ebounds = gammalib.GEbounds(n_bins, emin, emax)
833 self._log_header1(gammalib.TERSE,
'True energy binning')
836 for i
in range(ebounds.size()):
837 value =
'%s - %s' % (str(ebounds.emin(i)), str(ebounds.emax(i)))
838 self._log_value(gammalib.TERSE,
'Bin %d' % (i+1), value)
845 Set background regions for an observation
849 obs : `~gammalib.GCTAObservation()`
854 regions : `~gammalib.GSkyRegions()`
858 bkg_reg = gammalib.GSkyRegions()
862 if self[
'bkgmethod'].string() ==
'REFLECTED':
869 elif self[
'bkgmethod'].string() ==
'CUSTOM':
870 bkg_reg = obs.off_regions().copy()
874 if self[
'bkgmethod'].string() ==
'OFF':
882 Generate On/Off spectra for individual observation
892 On/Off spectra, background regions, observation id
900 if not self.
obs_off().is_empty():
904 obs_off = self.obs()[i]
907 self._log_header3(gammalib.NORMAL,
'%s observation "%s"' % \
908 (obs.instrument(), obs.id()))
911 if obs.classname() !=
'GCTAObservation':
912 self._log_string(gammalib.NORMAL,
' Skip because not a "GCTAObservation"')
917 use_model_bkg = self[
'use_model_bkg'].boolean()
919 msg =
' Use background model.'
921 msg =
' Background model not used, assume constant backround rate.'
922 self._log_string(gammalib.NORMAL, msg)
925 pnt_dir = obs.pointing().dir()
926 offset = pnt_dir.dist_deg(self.
_src_dir)
929 if offset >= self[
'maxoffset'].real():
930 msg =
' Skip because observation is pointed at %.3f deg >= ' \
931 '"maxoffset=%.3f" from source.' \
932 % (offset, self[
'maxoffset'].real())
933 self._log_string(gammalib.NORMAL, msg)
942 if bkg_reg.size() >= 0:
945 onoff = gammalib.GCTAOnOffObservation(obs, obs_off,
959 msg =
' Skip because no valid Off regions could be determined'
960 self._log_string(gammalib.NORMAL, msg)
963 result = {
'onoff' : onoff,
965 'instrument': obs.instrument(),
973 Unpack result from calculation of On/Off regions
977 outobs : `~gammalib.GObservations`
978 Observation container
980 On/Off spectra, background regions, observation id
984 outobs : `~gammalib.GObservations`
985 Observation container with result appended
988 if result[
'onoff'] !=
None:
991 if result[
'onoff'].classname() ==
'GCTAOnOffObservation':
1000 self._bkg_regs.append({
'regions': result[
'bkg_reg'],
1001 'id': result[
'id']})
1016 self._log_observations(gammalib.NORMAL, self.obs(),
'Observation')
1017 if not self.
obs_off().is_empty():
1018 self._log_observations(gammalib.NORMAL, self.
_obs_off,
'Off Observation')
1024 self._log_header1(gammalib.TERSE,
'Spectral binning')
1027 for i
in range(self._ebounds.size()):
1028 value =
'%s - %s' % (str(self._ebounds.emin(i)),
1029 str(self._ebounds.emax(i)))
1030 self._log_value(gammalib.TERSE,
'Bin %d' % (i+1), value)
1033 self._log_header1(gammalib.NORMAL,
1034 'Generation of source and background spectra')
1037 outobs = gammalib.GObservations()
1042 if self.
_nthreads > 1
and self.obs().size() > 1:
1045 args = [(self,
'_process_observation', i)
1046 for i
in range(self.obs().size())]
1047 poolresults = mputils.process(self.
_nthreads, mputils.mpfunc, args)
1050 for i
in range(self.obs().size()):
1051 result = poolresults[i][0]
1053 results.append(result)
1054 self._log_string(gammalib.TERSE, poolresults[i][1][
'log'],
False)
1058 for i
in range(self.obs().size()):
1062 results.append(result)
1065 if outobs.size() > 1
and self[
'stack'].boolean():
1068 self._log_header1(gammalib.NORMAL,
'Stacking %d observations' %
1072 stacked_obs = gammalib.GCTAOnOffObservation(outobs)
1078 outobs = gammalib.GObservations()
1079 outobs.append(stacked_obs)
1085 outobs.models(models)
1098 self._log_header1(gammalib.TERSE,
'Save data')
1101 outobs = self[
'outobs'].filename()
1102 outmodel = self[
'outmodel'].filename()
1103 prefix = self[
'prefix'].string()
1104 clobber = self[
'clobber'].boolean()
1107 for obs
in self.obs():
1110 if self[
'stack'].boolean():
1111 onname = prefix +
'_stacked_pha_on.fits'
1112 offname = prefix +
'_stacked_pha_off.fits'
1113 arfname = prefix +
'_stacked_arf.fits'
1114 rmfname = prefix +
'_stacked_rmf.fits'
1115 elif self.obs().size() > 1:
1116 onname = prefix +
'_%s_pha_on.fits' % (obs.id())
1117 offname = prefix +
'_%s_pha_off.fits' % (obs.id())
1118 arfname = prefix +
'_%s_arf.fits' % (obs.id())
1119 rmfname = prefix +
'_%s_rmf.fits' % (obs.id())
1121 onname = prefix +
'_pha_on.fits'
1122 offname = prefix +
'_pha_off.fits'
1123 arfname = prefix +
'_arf.fits'
1124 rmfname = prefix +
'_rmf.fits'
1127 obs.on_spec().backfile(offname)
1128 obs.on_spec().respfile(rmfname)
1129 obs.on_spec().ancrfile(arfname)
1132 obs.on_spec().
save(onname, clobber)
1133 obs.off_spec().
save(offname, clobber)
1134 obs.arf().
save(arfname, clobber)
1135 obs.rmf().
save(rmfname, clobber)
1139 self._stamp(offname)
1140 self._stamp(arfname)
1141 self._stamp(rmfname)
1144 self._log_value(gammalib.NORMAL,
'PHA on file', onname)
1145 self._log_value(gammalib.NORMAL,
'PHA off file', offname)
1146 self._log_value(gammalib.NORMAL,
'ARF file', arfname)
1147 self._log_value(gammalib.NORMAL,
'RMF file', rmfname)
1150 self.obs().
save(outobs)
1153 self.obs().models().
save(outmodel)
1156 self._log_value(gammalib.NORMAL,
'Obs. definition XML file', outobs.url())
1157 self._log_value(gammalib.NORMAL,
'Model definition XML file', outmodel.url())
1160 regname = prefix +
'_on.reg'
1161 self._src_reg.save(regname)
1162 self._log_value(gammalib.NORMAL,
'On region file', regname)
1169 regname = prefix +
'_%s_off.reg' % (bkg_reg[
'id'])
1171 regname = prefix +
'_off.reg'
1174 bkg_reg[
'regions'].
save(regname)
1177 self._log_value(gammalib.NORMAL,
'Off region file', regname)
1184 Return and optionally set the exclusion regions map
1188 object : `~gammalib.GSkyRegion` or `~gammalib.GSkyMap` or `~gammalib.GFilename`
1189 Exclusion regions object
1193 region : `~gammalib.GSkyRegionMap`
1194 Exclusion regions map
1197 if object
is not None:
1198 self.
_excl_reg = gammalib.GSkyRegionMap(object)
1205 Return and optionally set the Off observations
1209 obs : `~gammalib.GCTAObservations`
1210 Off observations container
1214 observation container : `~gammalib.GCTAObservations`
1215 Off observations container
1228 if __name__ ==
'__main__':
def _get_parameters_bkgmethod
def _get_parameters_bkgmethod_off
def _compute_region_separation
def _get_parameters_bkgmethod_custom
def _set_background_regions
def _get_source_parameters
def _get_parameters_bkgmethod_reflected