Commit 6d2ca44db21d80bfa946e022ca3edeadd959f90d

Authored by Jürgen Knödlseder
1 parent 3b6811f5

Add exposure map computation to ctskymap

src/ctskymap/ctskymap.cpp
... ... @@ -32,9 +32,10 @@
32 32 #include "ctskymap.hpp"
33 33  
34 34 /* __ Method name definitions ____________________________________________ */
35   -#define G_INIT_MAP "ctskymap::init_map(GCTAObservation* obs)"
36   -#define G_BIN_EVENTS "ctskymap::bin_events(GCTAObservation*)"
37   -#define G_BKG_SUBTRACT_IRF "ctskymap::bkg_subtract_irf(GCTAObservation*)"
  35 +#define G_GET_PARAMETERS "ctskymap::get_parameters()"
  36 +#define G_MAP_EVENTS "ctskymap::map_events(GCTAObservation*)"
  37 +#define G_MAP_BACKGROUND_IRF "ctskymap::map_background_irf(GCTAObservation*)"
  38 +#define G_COMPUTE_EXPMAP "ctskymap::compute_expmap(GCTAObservation*)"
38 39  
39 40 /* __ Debug definitions __________________________________________________ */
40 41  
... ... @@ -235,6 +236,11 @@ void ctskymap::run(void)
235 236 // Compute background sky map
236 237 map_background(obs);
237 238  
  239 + // Optionally compute exposure map
  240 + if (m_exposure_map) {
  241 + compute_expmap(obs);
  242 + }
  243 +
238 244 // Dispose events to free memory
239 245 obs->dispose_events();
240 246  
... ... @@ -285,6 +291,11 @@ void ctskymap::save(void)
285 291 // Write sky map into FITS file
286 292 GFitsHDU* hdu = m_skymap.write(fits);
287 293  
  294 + // If HDU is valid then set sky map units
  295 + if (hdu != NULL) {
  296 + hdu->card("BUNIT", "counts", "Unit of sky map pixels");
  297 + }
  298 +
288 299 // Write keywords into sky map extension
289 300 write_ogip_keywords(hdu);
290 301 write_hdu_keywords(hdu);
... ... @@ -292,12 +303,16 @@ void ctskymap::save(void)
292 303 // If background subtraction is requested then write background map
293 304 // and significance map to FITS file
294 305 if (m_bkgsubtract != "NONE") {
295   -
  306 +
296 307 // Write background map into FITS file
297 308 hdu = m_bkgmap.write(fits);
298 309  
299   - // Set background map extension name
300   - hdu->extname("BACKGROUND");
  310 + // If HDU is valid then set background map extension name and
  311 + // units
  312 + if (hdu != NULL) {
  313 + hdu->extname("BACKGROUND");
  314 + hdu->card("BUNIT", "counts", "Unit of background map pixels");
  315 + }
301 316  
302 317 // Write keywords into background extension
303 318 write_ogip_keywords(hdu);
... ... @@ -306,8 +321,12 @@ void ctskymap::save(void)
306 321 // Write significance map into FITS file
307 322 hdu = m_sigmap.write(fits);
308 323  
309   - // Set significance map extension name
310   - hdu->extname("SIGNIFICANCE");
  324 + // If HDU is valid then set significance map extension name and
  325 + // units
  326 + if (hdu != NULL) {
  327 + hdu->extname("SIGNIFICANCE");
  328 + hdu->card("BUNIT", "sigma", "Unit of significance map pixels");
  329 + }
311 330  
312 331 // Write keywords into significance extension
313 332 write_ogip_keywords(hdu);
... ... @@ -315,6 +334,24 @@ void ctskymap::save(void)
315 334  
316 335 } // endif: background subtraction was requested
317 336  
  337 + // If exposure map computation is requested then write exposure map
  338 + if (m_exposure_map) {
  339 +
  340 + // Write exposure map into FITS file
  341 + hdu = m_expmap.write(fits);
  342 +
  343 + // If HDU is valid then set exposure map extension name and units
  344 + if (hdu != NULL) {
  345 + hdu->extname("EXPOSURE");
  346 + hdu->card("BUNIT", "cm**2 s", "Unit of exposure map pixels");
  347 + }
  348 +
  349 + // Write keywords into background extension
  350 + write_ogip_keywords(hdu);
  351 + write_hdu_keywords(hdu);
  352 +
  353 + } // endif: exposure map was requested
  354 +
318 355 // Save FITS file to disk
319 356 fits.saveto(m_outmap, clobber());
320 357  
... ... @@ -372,13 +409,15 @@ void ctskymap::init_members(void)
372 409 {
373 410 // Initialise members
374 411 m_skymap.clear();
  412 + m_expmap.clear();
375 413 m_bkgmap.clear();
376 414 m_sigmap.clear();
377   - m_emin = 0.0;
378   - m_emax = 0.0;
379   - m_bkgsubtract = "NONE";
380   - m_publish = false;
381   - m_chatter = static_cast<GChatter>(2);
  415 + m_emin = 0.0;
  416 + m_emax = 0.0;
  417 + m_bkgsubtract = "NONE";
  418 + m_exposure_map = false;
  419 + m_publish = false;
  420 + m_chatter = static_cast<GChatter>(2);
382 421  
383 422 // Return
384 423 return;
... ... @@ -393,14 +432,16 @@ void ctskymap::init_members(void)
393 432 void ctskymap::copy_members(const ctskymap& app)
394 433 {
395 434 // Copy attributes
396   - m_skymap = app.m_skymap;
397   - m_bkgmap = app.m_bkgmap;
398   - m_sigmap = app.m_sigmap;
399   - m_emin = app.m_emin;
400   - m_emax = app.m_emax;
401   - m_bkgsubtract = app.m_bkgsubtract;
402   - m_publish = app.m_publish;
403   - m_chatter = app.m_chatter;
  435 + m_skymap = app.m_skymap;
  436 + m_expmap = app.m_expmap;
  437 + m_bkgmap = app.m_bkgmap;
  438 + m_sigmap = app.m_sigmap;
  439 + m_emin = app.m_emin;
  440 + m_emax = app.m_emax;
  441 + m_bkgsubtract = app.m_bkgsubtract;
  442 + m_exposure_map = app.m_exposure_map;
  443 + m_publish = app.m_publish;
  444 + m_chatter = app.m_chatter;
404 445  
405 446 // Return
406 447 return;
... ... @@ -420,6 +461,9 @@ void ctskymap::free_members(void)
420 461 /***********************************************************************//**
421 462 * @brief Get application parameters
422 463 *
  464 + * @exception GException::invalid_value
  465 + * Invalid task parameter values.
  466 + *
423 467 * Get all task parameters from parameter file or (if required) by querying
424 468 * the user. Most parameters are only required if no observation exists so
425 469 * far in the observation container. In this case, a single CTA observation
... ... @@ -435,26 +479,35 @@ void ctskymap::get_parameters(void)
435 479 // Create sky map based on task parameters
436 480 m_skymap = create_map(m_obs);
437 481  
438   - // Get further parameters
439   - m_emin = (*this)["emin"].real();
440   - m_emax = (*this)["emax"].real();
441   - m_bkgsubtract = (*this)["bkgsubtract"].string();
442   -
443   - // If IRF background subtraction is requested then make sure that the
444   - // CTA observations in the observation container have response information
445   - if (m_bkgsubtract == "IRF") {
446   - set_response(m_obs);
  482 + // Get energy range. Throw an exception if maximum energy is not larger
  483 + // than minimum energy
  484 + m_emin = (*this)["emin"].real();
  485 + m_emax = (*this)["emax"].real();
  486 + if (m_emax <= m_emin) {
  487 + std::string msg = "Maximum energy "+gammalib::str(m_emax)+" TeV is not "
  488 + "larger than minimum energy "+gammalib::str(m_emin)+
  489 + " TeV. Please specify a valid energy range.";
  490 + throw GException::invalid_value(G_GET_PARAMETERS, msg);
447 491 }
448 492  
449   - // Get remaining parameters
450   - m_publish = (*this)["publish"].boolean();
451   - m_chatter = static_cast<GChatter>((*this)["chatter"].integer());
  493 + // Get further parameters
  494 + m_bkgsubtract = (*this)["bkgsubtract"].string();
  495 + m_exposure_map = (*this)["expmap"].boolean();
  496 + m_publish = (*this)["publish"].boolean();
  497 + m_chatter = static_cast<GChatter>((*this)["chatter"].integer());
452 498  
453 499 // Read ahead parameters
454 500 if (read_ahead()) {
455 501 m_outmap = (*this)["outmap"].filename();
456 502 }
457 503  
  504 + // If IRF background subtraction or exposure map is requested then make
  505 + // sure that the CTA observations in the observation container have
  506 + // response information
  507 + if (m_bkgsubtract == "IRF" || m_exposure_map) {
  508 + set_response(m_obs);
  509 + }
  510 +
458 511 // Create background map and significance map if background subtraction
459 512 // is requested
460 513 if (m_bkgsubtract != "NONE") {
... ... @@ -462,6 +515,11 @@ void ctskymap::get_parameters(void)
462 515 m_sigmap = create_map(m_obs);
463 516 }
464 517  
  518 + // Create exposure map if exposure computation is requested
  519 + if (m_exposure_map) {
  520 + m_expmap = create_map(m_obs);
  521 + }
  522 +
465 523 // Write parameters into logger
466 524 log_parameters(TERSE);
467 525  
... ... @@ -489,7 +547,7 @@ void ctskymap::map_events(GCTAObservation* obs)
489 547 // Make sure that the observation holds a CTA event list. If this
490 548 // is not the case then throw an exception.
491 549 if (events == NULL) {
492   - throw GException::no_list(G_BIN_EVENTS);
  550 + throw GException::no_list(G_MAP_EVENTS);
493 551 }
494 552  
495 553 // Setup energy range covered by data
... ... @@ -609,7 +667,7 @@ void ctskymap::map_background_irf(GCTAObservation* obs)
609 667 get_obs_header(obs)+" to compute IRF background. "
610 668 "Please specify response information or use "
611 669 "another background subtraction method.";
612   - throw GException::invalid_value(G_BKG_SUBTRACT_IRF, msg);
  670 + throw GException::invalid_value(G_MAP_BACKGROUND_IRF, msg);
613 671 }
614 672  
615 673 // Get IRF background template
... ... @@ -621,7 +679,7 @@ void ctskymap::map_background_irf(GCTAObservation* obs)
621 679 "response function for "+
622 680 get_obs_header(obs)+". Please specify an instrument "
623 681 "response function containing a background template.";
624   - throw GException::invalid_value(G_BKG_SUBTRACT_IRF, msg);
  682 + throw GException::invalid_value(G_MAP_BACKGROUND_IRF, msg);
625 683 }
626 684  
627 685 // Compute natural logarithm of energy range in MeV
... ... @@ -651,7 +709,7 @@ void ctskymap::map_background_irf(GCTAObservation* obs)
651 709 }
652 710  
653 711 // Setup integration function
654   - ctskymap::irf_kern integrand(bkg, &instdir);
  712 + ctskymap::bkg_kern integrand(bkg, &instdir);
655 713 GIntegral integral(&integrand);
656 714  
657 715 // Set precision (has been carefully adjusted using a test simulation
... ... @@ -663,7 +721,7 @@ void ctskymap::map_background_irf(GCTAObservation* obs)
663 721  
664 722 // Update number of background function calls
665 723 calls += integral.calls();
666   -
  724 +
667 725 // Multiply background rate with livetime and solid angle
668 726 value *= obs->livetime() * m_bkgmap.solidangle(i);
669 727  
... ... @@ -674,7 +732,7 @@ void ctskymap::map_background_irf(GCTAObservation* obs)
674 732 total += value;
675 733  
676 734 } // endfor: looped over background map pixels
677   -
  735 +
678 736 // Log background subtraction results
679 737 log_value(NORMAL, "Events in background", int(total+0.5));
680 738 log_value(NORMAL, "Background evaluations", calls);
... ... @@ -685,6 +743,103 @@ void ctskymap::map_background_irf(GCTAObservation* obs)
685 743  
686 744  
687 745 /***********************************************************************//**
  746 + * @brief Compute the exposure map for an observation
  747 + *
  748 + * @param[in] obs CTA observation.
  749 + *
  750 + * @exception GException::invalid_value
  751 + * No response information available for observation.
  752 + * No effective area available in instrument response function.
  753 + *
  754 + * Estimates the background in the sky map using the IRF template for a given
  755 + * observation and adds this estimate to the background sky map. The IRF
  756 + * template is integrated numerically in energy.
  757 + ***************************************************************************/
  758 +void ctskymap::compute_expmap(GCTAObservation* obs)
  759 +{
  760 + // Get IRF response
  761 + const GCTAResponseIrf* rsp = dynamic_cast<const GCTAResponseIrf*>(obs->response());
  762 +
  763 + // Throw an exception if observation has no instrument response function
  764 + if (rsp == NULL) {
  765 + std::string msg = "No response information available for "+
  766 + get_obs_header(obs)+" to compute exposure map. "
  767 + "Please specify response information.";
  768 + throw GException::invalid_value(G_COMPUTE_EXPMAP, msg);
  769 + }
  770 +
  771 + // Get effective area response
  772 + const GCTAAeff* aeff = rsp->aeff();
  773 +
  774 + // Throw an exception if observation has no effective area
  775 + if (aeff == NULL) {
  776 + std::string msg = "No effective area found in instrument response "
  777 + "function for "+
  778 + get_obs_header(obs)+". Please specify an instrument "
  779 + "response function containing an effective area.";
  780 + throw GException::invalid_value(G_COMPUTE_EXPMAP, msg);
  781 + }
  782 +
  783 + // Extract pointing direction from observation
  784 + GSkyDir pnt = obs->pointing().dir();
  785 +
  786 + // Compute natural logarithm of energy range in TeV
  787 + double lnEmin = std::log(m_emin);
  788 + double lnEmax = std::log(m_emax);
  789 +
  790 + // Extract region of interest from observation
  791 + GCTARoi roi = obs->roi();
  792 +
  793 + // Initialise statistics
  794 + int calls = 0;
  795 +
  796 + // Compute normalisation factor
  797 + double norm = obs->livetime() / (m_emax - m_emin);
  798 +
  799 + // Loop over all exposure map pixels
  800 + for (int i = 0; i < m_expmap.npix(); ++i) {
  801 +
  802 + // Get sky direction of pixel
  803 + GSkyDir dir = m_expmap.inx2dir(i);
  804 +
  805 + // If RoI is valid and sky direction is not within RoI then skip pixel
  806 + if (roi.is_valid()) {
  807 + if (roi.centre().dir().dist_deg(dir) > roi.radius()) {
  808 + continue;
  809 + }
  810 + }
  811 +
  812 + // Compute theta angle with respect to pointing direction in
  813 + // radians
  814 + double theta = pnt.dist(dir);
  815 +
  816 + // Setup integration function
  817 + ctskymap::aeff_kern integrand(aeff, theta);
  818 + GIntegral integral(&integrand);
  819 +
  820 + // Set precision
  821 + integral.eps(1.0e-6);
  822 +
  823 + // Do Romberg integration
  824 + double value = integral.romberg(lnEmin, lnEmax) * norm;
  825 +
  826 + // Update number of effective area function calls
  827 + calls += integral.calls();
  828 +
  829 + // Add exposure to map
  830 + m_expmap(i,0) += value;
  831 +
  832 + } // endfor: looped over exposure map pixels
  833 +
  834 + // Log exposure map computation results
  835 + log_value(NORMAL, "Exposure evaluations", calls);
  836 +
  837 + // Return
  838 + return;
  839 +}
  840 +
  841 +
  842 +/***********************************************************************//**
688 843 * @brief Write keywords in FITS HDU
689 844 *
690 845 * @param[in,out] hdu Pointer to FITS HDU.
... ... @@ -710,17 +865,38 @@ void ctskymap::write_hdu_keywords(GFitsHDU* hdu) const
710 865  
711 866  
712 867 /***********************************************************************//**
  868 + * @brief Compute the effective area at a given sky direction and energy
  869 + *
  870 + * @param[in] lnE Natural logarithm of energy in TeV.
  871 + ***************************************************************************/
  872 +double ctskymap::aeff_kern::eval(const double& lnE)
  873 +{
  874 + // Get log10 of energy in TeV
  875 + double logE = lnE * gammalib::inv_ln10;
  876 +
  877 + // Get function value
  878 + double value = (*m_aeff)(logE, m_theta);
  879 +
  880 + // Correct for variable substitution
  881 + value *= std::exp(lnE);
  882 +
  883 + // Return value
  884 + return value;
  885 +}
  886 +
  887 +
  888 +/***********************************************************************//**
713 889 * @brief Estimates the background in sky map based on IRF template
714 890 *
715 891 * @param[in] lnE Natural logarithm of energy in MeV.
716 892 ***************************************************************************/
717   -double ctskymap::irf_kern::eval(const double& lnE)
  893 +double ctskymap::bkg_kern::eval(const double& lnE)
718 894 {
719 895 // Get log10 of energy in TeV
720   - double logE = lnE * gammalib::inv_ln10 - 6;
  896 + double logE = lnE * gammalib::inv_ln10 - 6.0;
721 897  
722 898 // Get function value
723   - double value = (*m_bgd)(logE, m_dir->detx(), m_dir->dety());
  899 + double value = (*m_bkg)(logE, m_dir->detx(), m_dir->dety());
724 900  
725 901 // Correct for variable substitution
726 902 value *= std::exp(lnE);
... ...
src/ctskymap/ctskymap.hpp
... ... @@ -71,33 +71,48 @@ protected:
71 71 void map_events(GCTAObservation* obs);
72 72 void map_background(GCTAObservation* obs);
73 73 void map_background_irf(GCTAObservation* obs);
  74 + void compute_expmap(GCTAObservation* obs);
74 75 void write_hdu_keywords(GFitsHDU* hdu) const;
75 76  
  77 + // Effective area integration kernel
  78 + class aeff_kern : public GFunction {
  79 + public:
  80 + aeff_kern(const GCTAAeff* aeff, double theta) :
  81 + m_aeff(aeff),
  82 + m_theta(theta) { }
  83 + double eval(const double& lnE);
  84 + protected:
  85 + const GCTAAeff* m_aeff; //!< Pointer to effective area
  86 + double m_theta; //!< Offset angle
  87 + };
  88 +
76 89 // Background integration kernel
77   - class irf_kern : public GFunction {
  90 + class bkg_kern : public GFunction {
78 91 public:
79   - irf_kern(const GCTABackground* bgd,
  92 + bkg_kern(const GCTABackground* bkg,
80 93 const GCTAInstDir* dir) :
81   - m_bgd(bgd),
  94 + m_bkg(bkg),
82 95 m_dir(dir) { }
83 96 double eval(const double& lnE);
84 97 protected:
85   - const GCTABackground* m_bgd; //!< Pointer to background
86   - const GCTAInstDir* m_dir; //!< Pointer to instrument direction
  98 + const GCTABackground* m_bkg; //!< Pointer to background
  99 + const GCTAInstDir* m_dir; //!< Pointer to instrument direction
87 100 };
88 101  
89 102 // User parameters
90   - GFilename m_outmap; //!< Output file name
91   - double m_emin; //!< Minimum energy (TeV)
92   - double m_emax; //!< Maximum energy (TeV)
93   - std::string m_bkgsubtract; //!< Background subtraction method
94   - bool m_publish; //!< Publish sky map?
95   - GChatter m_chatter; //!< Chattiness
  103 + GFilename m_outmap; //!< Output file name
  104 + double m_emin; //!< Minimum energy (TeV)
  105 + double m_emax; //!< Maximum energy (TeV)
  106 + std::string m_bkgsubtract; //!< Background subtraction method
  107 + bool m_exposure_map; //!< Compute exposure map?
  108 + bool m_publish; //!< Publish sky map?
  109 + GChatter m_chatter; //!< Chattiness
96 110  
97 111 // Protected members
98   - GSkyMap m_skymap; //!< Sky map
99   - GSkyMap m_bkgmap; //!< Background map
100   - GSkyMap m_sigmap; //!< Significance map
  112 + GSkyMap m_skymap; //!< Sky map
  113 + GSkyMap m_expmap; //!< Exposure map
  114 + GSkyMap m_bkgmap; //!< Background map
  115 + GSkyMap m_sigmap; //!< Significance map
101 116 };
102 117  
103 118  
... ...
src/ctskymap/ctskymap.par
... ... @@ -40,9 +40,10 @@ xref, r, a, 83.63,0,360, &quot;First coordinate of image center in degrees (RA or
40 40 yref, r, a, 22.01,-90,90, "Second coordinate of image center in degrees (DEC or galactic b)"
41 41  
42 42 #
43   -# Background subtraction parameters
44   -#==================================
  43 +# Map generation parameters
  44 +#==========================
45 45 bkgsubtract, s, a, NONE, NONE|IRF,, "Background subtraction method"
  46 +expmap, b, h, no,,, "Generate exposure map?"
46 47  
47 48 #
48 49 # Standard parameters
... ...
test/test_ctskymap.py
... ... @@ -217,7 +217,7 @@ class Test(test):
217 217 skymap['proj'] = 'CAR'
218 218 skymap['xref'] = 184.5575
219 219 skymap['yref'] = -5.7844
220   - skymap['bkgsubtract'] = 'IRF'
  220 + skymap['bkgsubtract'] = 'IRF'
221 221 skymap['caldb'] = self._caldb
222 222 skymap['irf'] = self._irf
223 223 skymap['outmap'] = 'ctskymap_py4.fits'
... ... @@ -229,7 +229,38 @@ class Test(test):
229 229 skymap.execute()
230 230  
231 231 # Check result file
232   - self._check_result_file('ctskymap_py3.fits')
  232 + self._check_result_file('ctskymap_py4.fits')
  233 + self._check_result_file('ctskymap_py4.fits[BACKGROUND]')
  234 + self._check_result_file('ctskymap_py4.fits[SIGNIFICANCE]')
  235 +
  236 + # Now create also an exposure map
  237 + skymap = ctools.ctskymap()
  238 + skymap['inobs'] = self._events
  239 + skymap['emin'] = 0.1
  240 + skymap['emax'] = 100
  241 + skymap['nxpix'] = 200
  242 + skymap['nypix'] = 200
  243 + skymap['binsz'] = 0.02
  244 + skymap['coordsys'] = 'CEL'
  245 + skymap['proj'] = 'CAR'
  246 + skymap['xref'] = 83.63
  247 + skymap['yref'] = 22.01
  248 + skymap['expmap'] = True
  249 + skymap['bkgsubtract'] = 'NONE'
  250 + skymap['caldb'] = self._caldb
  251 + skymap['irf'] = self._irf
  252 + skymap['outmap'] = 'ctskymap_py5.fits'
  253 + skymap['logfile'] = 'ctskymap_py5.log'
  254 + skymap['chatter'] = 2
  255 +
  256 + # Run ctskymap tool
  257 + skymap.logFileOpen() # Make sure we get a log file
  258 + skymap.run()
  259 + skymap.save()
  260 +
  261 + # Check result file
  262 + self._check_result_file('ctskymap_py5.fits')
  263 + self._check_result_file('ctskymap_py5.fits[EXPOSURE]')
233 264  
234 265 # Return
235 266 return
... ...