Commit acab87bd6e374ea91e0826b2fd45fbddd077b671

Authored by Luigi Tibaldo
1 parent f28fae01

Added On/Off analysis

cscripts/csscs.par
@@ -18,14 +18,14 @@ @@ -18,14 +18,14 @@
18 #============================ 18 #============================
19 inobs, f, a, events.fits,,, "Input event list, counts cube, or observation definition XML file" 19 inobs, f, a, events.fits,,, "Input event list, counts cube, or observation definition XML file"
20 inmodel, f, a, $CTOOLS/share/models/crab.xml,,, "Input model definition XML file" 20 inmodel, f, a, $CTOOLS/share/models/crab.xml,,, "Input model definition XML file"
21 -srcnames, s, a, Crab,,, "Semicolon separated list of sources" 21 +srcnames, s, a, Crab,,, "Semicolon-separated list of sources"
22 expcube, f, a, NONE,,, "Input exposure cube file" 22 expcube, f, a, NONE,,, "Input exposure cube file"
23 psfcube, f, a, NONE,,, "Input PSF cube file" 23 psfcube, f, a, NONE,,, "Input PSF cube file"
24 edispcube, f, a, NONE,,, "Input energy dispersion cube file" 24 edispcube, f, a, NONE,,, "Input energy dispersion cube file"
25 bkgcube, f, a, NONE,,, "Input background cube file" 25 bkgcube, f, a, NONE,,, "Input background cube file"
26 caldb, s, a, prod2,,, "Calibration database" 26 caldb, s, a, prod2,,, "Calibration database"
27 irf, s, a, South_0.5h,,, "Instrument response function" 27 irf, s, a, South_0.5h,,, "Instrument response function"
28 -inexclusion, f, h, NONE,,, "Exclusion regions" 28 +inexclusion, f, a, NONE,,, "Exclusion regions TODO: add meaningful default"
29 edisp, b, h, no,,, "Apply energy dispersion?" 29 edisp, b, h, no,,, "Apply energy dispersion?"
30 outfile, f, a, map.fits,,, "Output map" 30 outfile, f, a, map.fits,,, "Output map"
31 31
@@ -39,9 +39,9 @@ yref, r, a, 22.51,-90,90, "Second coordinate of image center in degrees @@ -39,9 +39,9 @@ yref, r, a, 22.51,-90,90, "Second coordinate of image center in degrees
39 nxpix, i, a, 20,,, "Size of the X axis in pixels" 39 nxpix, i, a, 20,,, "Size of the X axis in pixels"
40 nypix, i, a, 20,,, "Size of the Y axis in pixels" 40 nypix, i, a, 20,,, "Size of the Y axis in pixels"
41 binsz, r, a, 0.1,,, "Pixel size (deg/pixel)" 41 binsz, r, a, 0.1,,, "Pixel size (deg/pixel)"
42 -roisz, r, a, 2.,1.42,1000, "Size of the region of interest (numbers of binsz)" 42 +rad, r, a, 0.2,,, "Radius of the region for component separation (deg)"
43 statistic, s, h, DEFAULT,DEFAULT|CSTAT|WSTAT|CHI2,, "Optimization statistic" 43 statistic, s, h, DEFAULT,DEFAULT|CSTAT|WSTAT|CHI2,, "Optimization statistic"
44 -method, s, a, 3D, 3D|ONOFF,, "Analysis method" 44 +method, s, a, UNBINNED, UNBINNED|ONOFF,, "Analysis method"
45 ebinalg, s, a, LOG,FILE|LIN|LOG,, "Algorithm for defining energy bins" 45 ebinalg, s, a, LOG,FILE|LIN|LOG,, "Algorithm for defining energy bins"
46 emin, r, a, 0.1,,, "Lower energy limit (TeV)" 46 emin, r, a, 0.1,,, "Lower energy limit (TeV)"
47 emax, r, a, 100.0,,, "Upper energy limit (TeV)" 47 emax, r, a, 100.0,,, "Upper energy limit (TeV)"
@@ -49,6 +49,7 @@ enumbins, i, a, 20,,, "Number of energy bins" @@ -49,6 +49,7 @@ enumbins, i, a, 20,,, "Number of energy bins"
49 bkgmethod, s, h, REFLECTED,REFLECTED,, "Method for background estimation" 49 bkgmethod, s, h, REFLECTED,REFLECTED,, "Method for background estimation"
50 srcshape, s, h, CIRCLE,CIRCLE,, "Shape of the source region" 50 srcshape, s, h, CIRCLE,CIRCLE,, "Shape of the source region"
51 bkgregmin, i, h, 2,1,1000, "Minimum number of background regions" 51 bkgregmin, i, h, 2,1,1000, "Minimum number of background regions"
  52 +bkgregskip, i, h, 1,0,10, "Number of background regions to skip next to On region"
52 use_model_bkg, b, h, yes,,, "Use background model?" 53 use_model_bkg, b, h, yes,,, "Use background model?"
53 maxoffset, r, h, 4.0,,, "Maximum offset of source from camera center to accept observation" 54 maxoffset, r, h, 4.0,,, "Maximum offset of source from camera center to accept observation"
54 etruemin, r, h, 0.01,,, "Minimum true energy (TeV)" 55 etruemin, r, h, 0.01,,, "Minimum true energy (TeV)"
cscripts/csscs.py
@@ -50,8 +50,8 @@ class csscs(ctools.csobservation): @@ -50,8 +50,8 @@ class csscs(ctools.csobservation):
50 50
51 # Initialise data members 51 # Initialise data members
52 self._nthreads = 0 52 self._nthreads = 0
53 - self._roisz = 0.  
54 self._srcnames = [] 53 self._srcnames = []
  54 + self._method = None
55 self._fits = None 55 self._fits = None
56 self._excl_reg_map = None # Exclusion region map for on/off analysis 56 self._excl_reg_map = None # Exclusion region map for on/off analysis
57 57
@@ -72,7 +72,7 @@ class csscs(ctools.csobservation): @@ -72,7 +72,7 @@ class csscs(ctools.csobservation):
72 # Set pickled dictionary 72 # Set pickled dictionary
73 state = {'base' : ctools.csobservation.__getstate__(self), 73 state = {'base' : ctools.csobservation.__getstate__(self),
74 'nthreads' : self._nthreads, 74 'nthreads' : self._nthreads,
75 - 'roisz' : self._roisz, 75 + 'method' : self._method,
76 'srcnames' : self._srcnames, 76 'srcnames' : self._srcnames,
77 'fits' : self._fits, 77 'fits' : self._fits,
78 'excl_reg_map' : self._excl_reg_map} 78 'excl_reg_map' : self._excl_reg_map}
@@ -92,8 +92,8 @@ class csscs(ctools.csobservation): @@ -92,8 +92,8 @@ class csscs(ctools.csobservation):
92 # Set state 92 # Set state
93 ctools.csobservation.__setstate__(self, state['base']) 93 ctools.csobservation.__setstate__(self, state['base'])
94 self._nthreads = state['nthreads'] 94 self._nthreads = state['nthreads']
95 - self._roisz = state['roisz']  
96 self._srcnames = state['srcnames'] 95 self._srcnames = state['srcnames']
  96 + self._method = state['method']
97 self._fits = state['fits'] 97 self._fits = state['fits']
98 self._excl_reg_map = state['excl_reg_map'] 98 self._excl_reg_map = state['excl_reg_map']
99 99
@@ -102,6 +102,39 @@ class csscs(ctools.csobservation): @@ -102,6 +102,39 @@ class csscs(ctools.csobservation):
102 102
103 103
104 # Private methods 104 # Private methods
  105 + def _get_onoff_parameters(self):
  106 + """
  107 + Get On/Off analysis parameters.
  108 + This is done here rather then in ctools.is_onoff
  109 + because the exclusion map needs to be handled differently
  110 + as an automatic parameter but queried only if not already set
  111 + and we need to verify is not empty.
  112 + Also many parameters are already queried for all methods
  113 + TODO: verify if this can be made more uniform with other scripts
  114 + """
  115 + # Exclusion map
  116 + if (self._excl_reg_map is not None) and (self._excl_reg_map.map().npix() > 0):
  117 + # Exclusion map set and is not empty
  118 + pass
  119 + elif self['inexclusion'].is_valid():
  120 + inexclusion = self['inexclusion'].filename()
  121 + self._excl_reg_map = gammalib.GSkyRegionMap(inexclusion)
  122 + else:
  123 + msg = 'csscs in On/Off mode requires input exclusion region.'
  124 + raise RuntimeError(msg)
  125 +
  126 + # Other csphagen parameters
  127 + self["enumbins"].integer()
  128 + if self["bkgmethod"].string() == "REFLECTED":
  129 + self["bkgregmin"].integer()
  130 + self['bkgregskip'].integer()
  131 + self["maxoffset"].real()
  132 + self["etruemin"].real()
  133 + self["etruemax"].real()
  134 + self["etruebins"].integer()
  135 +
  136 + return
  137 +
105 def _get_parameters(self): 138 def _get_parameters(self):
106 """ 139 """
107 Get parameters from parfile 140 Get parameters from parfile
@@ -111,6 +144,9 @@ class csscs(ctools.csobservation): @@ -111,6 +144,9 @@ class csscs(ctools.csobservation):
111 if self.obs().is_empty(): 144 if self.obs().is_empty():
112 self.obs(self._get_observations()) 145 self.obs(self._get_observations())
113 146
  147 + # Set observation statistic
  148 + self._set_obs_statistic(gammalib.toupper(self['statistic'].string()))
  149 +
114 # Collect number of unbinned, binned and On/Off observations in 150 # Collect number of unbinned, binned and On/Off observations in
115 # observation container 151 # observation container
116 n_unbinned = 0 152 n_unbinned = 0
@@ -124,24 +160,25 @@ class csscs(ctools.csobservation): @@ -124,24 +160,25 @@ class csscs(ctools.csobservation):
124 n_unbinned += 1 160 n_unbinned += 1
125 elif obs.classname() == 'GCTAOnOffObservation': 161 elif obs.classname() == 'GCTAOnOffObservation':
126 n_onoff += 1 162 n_onoff += 1
  163 + n_cta = n_unbinned + n_binned + n_onoff
  164 + n_other = self.obs().size() - n_cta
127 165
128 - # Check that we have at least one unbinned or binned CTA observation  
129 - if n_unbinned == 0 and n_binned == 0:  
130 - msg = 'No unbinned or binned CTA observations found. ' \  
131 - 'Please provide at least one unbinned or binned ' \  
132 - 'CTA observation.' 166 + # Check if there are On/Off or non-IACT observations
  167 + if n_onoff > 0 or n_other > 0:
  168 + msg = 'On/Off or non-CTA observations found. '\
  169 + 'csscs does not support this type of observations.'
  170 + raise RuntimeError(msg)
  171 + # Otherwise check if there is a mix of binned and unbinned
  172 + elif n_unbinned > 0 and n_binned>0:
  173 + msg = 'Mix of unbinned and binned CTA observations ' \
  174 + 'found in observation container. csscs does not ' \
  175 + 'support this mix.'
133 raise RuntimeError(msg) 176 raise RuntimeError(msg)
134 -  
135 - # Set observation statistic  
136 - self._set_obs_statistic(gammalib.toupper(self['statistic'].string()))  
137 177
138 # Set models if there are none in the container 178 # Set models if there are none in the container
139 if self.obs().models().size() == 0: 179 if self.obs().models().size() == 0:
140 self.obs().models(self['inmodel'].filename()) 180 self.obs().models(self['inmodel'].filename())
141 181
142 - # Query exclusion region  
143 - self["inexclusion"].query()  
144 -  
145 # Query the map definition parameters 182 # Query the map definition parameters
146 self['xref'].real() 183 self['xref'].real()
147 self['yref'].real() 184 self['yref'].real()
@@ -149,29 +186,24 @@ class csscs(ctools.csobservation): @@ -149,29 +186,24 @@ class csscs(ctools.csobservation):
149 self['proj'].string() 186 self['proj'].string()
150 self['nxpix'].integer() 187 self['nxpix'].integer()
151 self['nypix'].integer() 188 self['nypix'].integer()
  189 + self['binsz'].real()
152 190
153 - # Compute the size of the ROIs for the analysis in deg  
154 - self._roisz = self['binsz'].real() * self['roisz'].real() 191 + # Radius of ROI for component separation
  192 + self['rad'].real()
155 193
156 - # Query energy boundaries 194 + # Energy boundaries
157 self["emin"].real() 195 self["emin"].real()
158 self["emax"].real() 196 self["emax"].real()
159 197
160 - # If we have unbinned observations query the analysis method 198 + # If we have unbinned observations query analysis method
161 if n_unbinned > 0: 199 if n_unbinned > 0:
162 - # If method is ONOFF query some csphagen parameters  
163 - if self['method'].string() == 'ONOFF':  
164 - pass  
165 - # TODO: implement Onoff analysis  
166 - # self["enumbins"].integer()  
167 - # self['use_model_bkg'].boolean()  
168 - # if self["bkgmethod"].string() == "REFLECTED":  
169 - # self["srcshape"].string()  
170 - # self["bkgregmin"].integer()  
171 - # self["maxoffset"].real()  
172 - # self["etruemin"].real()  
173 - # self["etruemax"].real()  
174 - # self["etruebins"].integer() 200 + self._method = self['method'].string()
  201 + # If method is Onoff query csphagen parameters
  202 + if self._method == 'ONOFF':
  203 + self._get_onoff_parameters()
  204 + # Otherwise set method to binned
  205 + else:
  206 + self._method = 'BINNED'
175 207
176 # Query target source names 208 # Query target source names
177 srcnames = self['srcnames'].string() 209 srcnames = self['srcnames'].string()
@@ -190,7 +222,27 @@ class csscs(ctools.csobservation): @@ -190,7 +222,27 @@ class csscs(ctools.csobservation):
190 msg = 'Not all target sources are present in input model.' 222 msg = 'Not all target sources are present in input model.'
191 raise RuntimeError(msg) 223 raise RuntimeError(msg)
192 224
193 - # Query the hidden parameters, just in case 225 + # For On/Off analysis check the number of gamma-ray sources in the model
  226 + if self._method == 'ONOFF':
  227 + nsources = 0
  228 + # Verify if model is of type GModelSky
  229 + for model in self.obs().models():
  230 + try:
  231 + gammalib.GModelSky(model)
  232 + nsources +=1
  233 + except:
  234 + pass
  235 + # If there are background gamma-ray sources in the model
  236 + # throw runtime error
  237 + if nsources > len(self._srcnames):
  238 + msg = 'Background gamma-ray sources found in the model. ' \
  239 + 'On/Off analysis does not support this feature.'
  240 + raise RuntimeError(msg)
  241 + # Otherwise continue
  242 + else:
  243 + pass
  244 +
  245 + # Query other hidden parameters, just in case
194 self['edisp'].boolean() 246 self['edisp'].boolean()
195 self['calc_ulim'].boolean() 247 self['calc_ulim'].boolean()
196 self['calc_ts'].boolean() 248 self['calc_ts'].boolean()
@@ -243,6 +295,24 @@ class csscs(ctools.csobservation): @@ -243,6 +295,24 @@ class csscs(ctools.csobservation):
243 # Log model name 295 # Log model name
244 self._log_header3(gammalib.EXPLICIT, model.name()) 296 self._log_header3(gammalib.EXPLICIT, model.name())
245 297
  298 + # In On/Off mode we cannot support multiple source morphologies
  299 + # Thus, if we have more than one target we will set them all
  300 + # to isotropic
  301 + if self._method == 'ONOFF' and len(self._srcnames) > 1:
  302 + # Check if it is a source
  303 + try:
  304 + gammalib.GModelSky(model)
  305 + # In this case check if the spatial model is already isotropic
  306 + if model.spatial() == 'DiffuseIsotropic':
  307 + pass
  308 + # Otherwise change it to isotropic
  309 + else:
  310 + msg = ' Setting spatial model to diffuse isotropic'
  311 + self._log_string(gammalib.EXPLICIT, msg)
  312 + model.spatial(gammalib.GModelSpatialDiffuseConst())
  313 + except:
  314 + pass
  315 +
246 # Freeze all parameters except the normalization 316 # Freeze all parameters except the normalization
247 # which is assumed to be the first spectral parameter 317 # which is assumed to be the first spectral parameter
248 normpar = model.spectral()[0] 318 normpar = model.spectral()[0]
@@ -298,26 +368,29 @@ class csscs(ctools.csobservation): @@ -298,26 +368,29 @@ class csscs(ctools.csobservation):
298 368
299 Returns 369 Returns
300 ------- 370 -------
301 - new_obs : `gammalib.GCTAObservation`  
302 - Output observation with masked cube 371 + new_obs : `gammalib.GCTAObservations`
  372 + Output observations with masked cubes
303 """ 373 """
304 374
305 - # Put observation in container  
306 - container = gammalib.GObservations()  
307 - container.append(obs)  
308 -  
309 # Filter cube according to ROI and user energy range 375 # Filter cube according to ROI and user energy range
310 - cubemask = ctools.ctcubemask(container) 376 + cubemask = ctools.ctcubemask(obs)
311 cubemask['ra'] = ra 377 cubemask['ra'] = ra
312 cubemask['dec'] = dec 378 cubemask['dec'] = dec
313 cubemask['rad'] = rad 379 cubemask['rad'] = rad
314 cubemask['regfile'] = 'NONE' 380 cubemask['regfile'] = 'NONE'
315 cubemask['emin'] = self['emin'].real() 381 cubemask['emin'] = self['emin'].real()
316 cubemask['emax'] = self['emax'].real() 382 cubemask['emax'] = self['emax'].real()
  383 +
  384 + # If chatter level is verbose and debugging is requested then
  385 + # switch also on the debug model in ctcubemask
  386 + if self._logVerbose() and self._logDebug():
  387 + cubemask['debug'] = True
  388 +
  389 + # Mask cube
317 cubemask.run() 390 cubemask.run()
318 391
319 - # Get deep copy of filtered observation  
320 - new_obs = cubemask.obs()[0].copy() 392 + # Get deep copy of filtered observations
  393 + new_obs = cubemask.obs().copy()
321 394
322 # Return 395 # Return
323 return new_obs 396 return new_obs
@@ -328,23 +401,19 @@ class csscs(ctools.csobservation): @@ -328,23 +401,19 @@ class csscs(ctools.csobservation):
328 401
329 Parameters 402 Parameters
330 ---------- 403 ----------
331 - obs : `gammalib.GCTAObservation` Input observation of type cube 404 + obs : `gammalib.GCTAObservations` Input observations of type event list
332 ra : `float` R.A. (deg) 405 ra : `float` R.A. (deg)
333 dec : `float` Dec (deg) 406 dec : `float` Dec (deg)
334 rad : `float` radius (deg) 407 rad : `float` radius (deg)
335 408
336 Returns 409 Returns
337 ------- 410 -------
338 - new_obs : `gammalib.GCTAObservation`  
339 - Output observation with masked event list 411 + new_obs : `gammalib.GCTAObservations`
  412 + Output observations with masked event lists
340 """ 413 """
341 414
342 - # Put observation in container  
343 - container = gammalib.GObservations()  
344 - container.append(obs)  
345 -  
346 # Filter event list according to ROI and user energy range 415 # Filter event list according to ROI and user energy range
347 - select = ctools.ctselect(container) 416 + select = ctools.ctselect(obs)
348 select['ra'] = ra 417 select['ra'] = ra
349 select['dec'] = dec 418 select['dec'] = dec
350 select['rad'] = rad 419 select['rad'] = rad
@@ -352,40 +421,21 @@ class csscs(ctools.csobservation): @@ -352,40 +421,21 @@ class csscs(ctools.csobservation):
352 select['emax'] = self['emax'].real() 421 select['emax'] = self['emax'].real()
353 select['tmin'] = 'INDEF' 422 select['tmin'] = 'INDEF'
354 select['tmax'] = 'INDEF' 423 select['tmax'] = 'INDEF'
  424 +
  425 + # If chatter level is verbose and debugging is requested then
  426 + # switch also on the debug model in ctcubemask
  427 + if self._logVerbose() and self._logDebug():
  428 + select['debug'] = True
  429 +
  430 + # Select event list
355 select.run() 431 select.run()
356 432
357 # Get deep copy of filtered observation 433 # Get deep copy of filtered observation
358 - new_obs = select.obs()[0].copy() 434 + new_obs = select.obs().copy()
359 435
360 # Return 436 # Return
361 return new_obs 437 return new_obs
362 438
363 - def _mask_onoff(self,obs,ra,dec,rad):  
364 - """  
365 - Create On/Off observation  
366 - with On region matching the required mask  
367 -  
368 - Parameters  
369 - ----------  
370 - obs : `gammalib.GCTAObservation` Input observation of type cube  
371 - ra : `float` R.A. (deg)  
372 - dec : `float` Dec (deg)  
373 - rad : `float` radius (deg)  
374 -  
375 - Returns  
376 - -------  
377 - new_obs : `gammalib.GCTAOnoffObservation`  
378 - Output On/Off observation  
379 - """  
380 -  
381 - # Put observation in container  
382 - container = gammalib.GObservations()  
383 - container.append(obs)  
384 -  
385 - onoff_obs = obsutils.get_onoff_obs(self, container, ra=ra, dec=dec, rad=rad)  
386 -  
387 - return onoff_obs[0]  
388 -  
389 def _mask_observations(self,ra,dec,rad): 439 def _mask_observations(self,ra,dec,rad):
390 """ 440 """
391 Create observations restricted to circular ROI 441 Create observations restricted to circular ROI
@@ -402,29 +452,16 @@ class csscs(ctools.csobservation): @@ -402,29 +452,16 @@ class csscs(ctools.csobservation):
402 Observations in circular ROI 452 Observations in circular ROI
403 """ 453 """
404 454
405 - # Create output observation container  
406 - new_obs = gammalib.GObservations()  
407 -  
408 - # Loop over input observations  
409 - for obs in self.obs():  
410 - if obs.classname() == 'GCTAObservation':  
411 - if obs.eventtype() == 'CountsCube':  
412 - masked_obs = self._mask_cube(obs,ra,dec,rad)  
413 - else:  
414 - if self['method'].string() == '3D':  
415 - masked_obs = self._mask_evlist(obs,ra,dec,rad)  
416 - elif self['method'].string() == 'ONOFF':  
417 - pass  
418 - pass  
419 - # TODO: implement Onoff analysis  
420 - # masked_obs = self._mask_onoff(obs, ra, dec, rad)  
421 - new_obs.append(masked_obs)  
422 - # Skip On-Off and non-CTA observations  
423 - else:  
424 - pass  
425 -  
426 - # Append models to output observations  
427 - new_obs.models(self.obs().models()) 455 + # Determine type of masking according
  456 + # to observation type and analysis method
  457 + if self._method == 'UNBINNED':
  458 + new_obs = self._mask_evlist(self.obs(), ra, dec, rad)
  459 + elif self._method == 'BINNED':
  460 + new_obs = self._mask_cube(self.obs(), ra, dec, rad)
  461 + elif self._method == 'ONOFF':
  462 + new_obs = obsutils.get_onoff_obs(self,self.obs(),nthreads=1,
  463 + ra = ra, dec = dec,
  464 + srcname = self._srcnames[0])
428 465
429 # Return 466 # Return
430 return new_obs 467 return new_obs
@@ -465,7 +502,7 @@ class csscs(ctools.csobservation): @@ -465,7 +502,7 @@ class csscs(ctools.csobservation):
465 502
466 # Mask observations 503 # Mask observations
467 self._log_header3(gammalib.EXPLICIT, 'Masking observations') 504 self._log_header3(gammalib.EXPLICIT, 'Masking observations')
468 - masked_obs = self._mask_observations(ra,dec,self._roisz) 505 + masked_obs = self._mask_observations(ra,dec,self['rad'].real())
469 506
470 # Set up likelihood analysis 507 # Set up likelihood analysis
471 self._log_header3(gammalib.EXPLICIT, 'Performing fit in energy bin') 508 self._log_header3(gammalib.EXPLICIT, 'Performing fit in energy bin')
@@ -488,7 +525,7 @@ class csscs(ctools.csobservation): @@ -488,7 +525,7 @@ class csscs(ctools.csobservation):
488 # ROI 525 # ROI
489 centre = gammalib.GSkyDir() 526 centre = gammalib.GSkyDir()
490 centre.radec_deg(ra,dec) 527 centre.radec_deg(ra,dec)
491 - roi = gammalib.GSkyRegionCircle(centre,self._roisz) 528 + roi = gammalib.GSkyRegionCircle(centre,self['rad'].real())
492 # Energy boundaries 529 # Energy boundaries
493 emin = gammalib.GEnergy(self['emin'].real(),'TeV') 530 emin = gammalib.GEnergy(self['emin'].real(),'TeV')
494 emax = gammalib.GEnergy(self['emax'].real(), 'TeV') 531 emax = gammalib.GEnergy(self['emax'].real(), 'TeV')
@@ -508,7 +545,7 @@ class csscs(ctools.csobservation): @@ -508,7 +545,7 @@ class csscs(ctools.csobservation):
508 # Multiply by flux from spatial component in ROI 545 # Multiply by flux from spatial component in ROI
509 flux *= source.spatial().flux(roi) 546 flux *= source.spatial().flux(roi)
510 # Divide by solid angle of ROI 547 # Divide by solid angle of ROI
511 - flux /= gammalib.twopi * (1 - math.cos(math.radians(self._roisz))) 548 + flux /= gammalib.twopi * (1 - math.cos(math.radians(self['rad'].real())))
512 result[name]['flux'] = flux 549 result[name]['flux'] = flux
513 550
514 # Get flux error 551 # Get flux error
@@ -561,7 +598,7 @@ class csscs(ctools.csobservation): @@ -561,7 +598,7 @@ class csscs(ctools.csobservation):
561 # Prepare some header cards 598 # Prepare some header cards
562 # Flux units 599 # Flux units
563 fcard = gammalib.GFitsHeaderCard('BUNIT', 'ph/cm2/s/sr' , 'Photon flux') 600 fcard = gammalib.GFitsHeaderCard('BUNIT', 'ph/cm2/s/sr' , 'Photon flux')
564 - txt = 'Correlation radius %f deg' %(self._roisz) 601 + txt = 'Correlation radius %f deg' %(self['rad'].real())
565 roiszcard = gammalib.GFitsHeaderCard('COMMENT', '' , txt) 602 roiszcard = gammalib.GFitsHeaderCard('COMMENT', '' , txt)
566 603
567 # Loop over target sources 604 # Loop over target sources
@@ -646,14 +683,6 @@ class csscs(ctools.csobservation): @@ -646,14 +683,6 @@ class csscs(ctools.csobservation):
646 for i in range(npix): 683 for i in range(npix):
647 results.append(self._pixel_analysis(i)) 684 results.append(self._pixel_analysis(i))
648 685
649 - # # Loop over pixels and extract results  
650 - # results = []  
651 - # for inx in range(self._create_map().npix()):  
652 - #  
653 - # # Analyse ROI  
654 - # result = self._pixel_analysis(inx)  
655 - # results.append(result)  
656 -  
657 # Fill output Fits 686 # Fill output Fits
658 self._fits = self._fill_fits(results) 687 self._fits = self._fill_fits(results)
659 688
cscripts/obsutils.py
@@ -745,7 +745,7 @@ def get_stacked_obs(cls, obs, nthreads=0): @@ -745,7 +745,7 @@ def get_stacked_obs(cls, obs, nthreads=0):
745 # ================================ # 745 # ================================ #
746 # Get On/Off observation container # 746 # Get On/Off observation container #
747 # ================================ # 747 # ================================ #
748 -def get_onoff_obs(cls, obs, nthreads=0): 748 +def get_onoff_obs(cls, obs, nthreads=0, ra = None, dec = None, srcname = ''):
749 """ 749 """
750 Create On/Off observations container from given observations 750 Create On/Off observations container from given observations
751 751
@@ -757,6 +757,12 @@ def get_onoff_obs(cls, obs, nthreads=0): @@ -757,6 +757,12 @@ def get_onoff_obs(cls, obs, nthreads=0):
757 Observation container 757 Observation container
758 nthreads : str, optional 758 nthreads : str, optional
759 Number of parallel processes for On/Off spectra computation (0=all available CPUs) 759 Number of parallel processes for On/Off spectra computation (0=all available CPUs)
  760 + ra : float, optional
  761 + R.A. of source region centre
  762 + dec : float, optional
  763 + Dec. of source region centre
  764 + srcname : str, optional
  765 + Source name
760 766
761 Returns 767 Returns
762 ------- 768 -------
@@ -767,9 +773,8 @@ def get_onoff_obs(cls, obs, nthreads=0): @@ -767,9 +773,8 @@ def get_onoff_obs(cls, obs, nthreads=0):
767 if cls._logExplicit(): 773 if cls._logExplicit():
768 cls._log.header3('Creating On/Off observations') 774 cls._log.header3('Creating On/Off observations')
769 775
770 - # Initialise inmodel, srcname, inexclusion and use_model_bkg 776 + # Initialise inmodel, inexclusion and use_model_bkg
771 inmodel = 'NONE' 777 inmodel = 'NONE'
772 - srcname = ''  
773 inexclusion = 'NONE' 778 inexclusion = 'NONE'
774 use_model_bkg = True 779 use_model_bkg = True
775 780
@@ -777,7 +782,7 @@ def get_onoff_obs(cls, obs, nthreads=0): @@ -777,7 +782,7 @@ def get_onoff_obs(cls, obs, nthreads=0):
777 if cls.has_par('inmodel') and obs.models().size() == 0: 782 if cls.has_par('inmodel') and obs.models().size() == 0:
778 if cls['inmodel'].is_valid(): 783 if cls['inmodel'].is_valid():
779 inmodel = cls['inmodel'].value() 784 inmodel = cls['inmodel'].value()
780 - if cls.has_par('srcname'): 785 + if srcname == '' and cls.has_par('srcname'):
781 srcname = cls['srcname'].value() 786 srcname = cls['srcname'].value()
782 if cls.has_par('inexclusion'): 787 if cls.has_par('inexclusion'):
783 if cls['inexclusion'].is_valid(): 788 if cls['inexclusion'].is_valid():
@@ -795,13 +800,20 @@ def get_onoff_obs(cls, obs, nthreads=0): @@ -795,13 +800,20 @@ def get_onoff_obs(cls, obs, nthreads=0):
795 phagen['enumbins'] = cls['enumbins'].integer() 800 phagen['enumbins'] = cls['enumbins'].integer()
796 phagen['ebinalg'] = 'LOG' 801 phagen['ebinalg'] = 'LOG'
797 phagen['srcshape'] = cls['srcshape'].string() 802 phagen['srcshape'] = cls['srcshape'].string()
798 - phagen['coordsys'] = cls['coordsys'].string()  
799 - if cls['coordsys'].string() == 'CEL':  
800 - phagen['ra'] = cls['xref'].real()  
801 - phagen['dec'] = cls['yref'].real()  
802 - elif cls['coordsys'].string() == 'GAL':  
803 - phagen['glon'] = cls['xref'].real()  
804 - phagen['glat'] = cls['yref'].real() 803 + # User has specified custom centre for the source region
  804 + if ra != None and dec != None:
  805 + phagen['coordsys'] = 'CEL'
  806 + phagen['ra'] = ra
  807 + phagen['dec'] = dec
  808 + # Otherwise use default in class
  809 + else:
  810 + phagen['coordsys'] = cls['coordsys'].string()
  811 + if cls['coordsys'].string() == 'CEL':
  812 + phagen['ra'] = cls['xref'].real()
  813 + phagen['dec'] = cls['yref'].real()
  814 + elif cls['coordsys'].string() == 'GAL':
  815 + phagen['glon'] = cls['xref'].real()
  816 + phagen['glat'] = cls['yref'].real()
805 if cls['srcshape'].string() == 'CIRCLE': 817 if cls['srcshape'].string() == 'CIRCLE':
806 phagen['rad'] = cls['rad'].real() 818 phagen['rad'] = cls['rad'].real()
807 phagen['bkgmethod'] = cls['bkgmethod'].string() 819 phagen['bkgmethod'] = cls['bkgmethod'].string()