Commit ab3bcbff5d29f3cdc10989b28849ca32189ffd69

Authored by Jürgen Knödlseder
2 parents 45f8d7fd cf079ae0

Merge branch '2207-ctskymap_RING_bkgsubtract' into integration

# Conflicts:
#	ChangeLog
ChangeLog
@@ -4,6 +4,7 @@ @@ -4,6 +4,7 @@
4 ======================== 4 ========================
5 5
6 Add csphagen script (#2208) 6 Add csphagen script (#2208)
  7 + Add RING background method to ctskymap (#2207)
7 Add keywords to sky maps produced by ctskymap (#2194) 8 Add keywords to sky maps produced by ctskymap (#2194)
8 Enhance efficiency of computations in ctmodel (#2189) 9 Enhance efficiency of computations in ctmodel (#2189)
9 Add csmodelsois script (#2180) 10 Add csmodelsois script (#2180)
@@ -57,6 +57,9 @@ Header keywords informing about the dates and times covered by the observations @@ -57,6 +57,9 @@ Header keywords informing about the dates and times covered by the observations
57 used to generate the skymap, as well as the event energy range, are now appended 57 used to generate the skymap, as well as the event energy range, are now appended
58 as header keywords to the FITS file (#2194). 58 as header keywords to the FITS file (#2194).
59 59
  60 +Add the RING background method, which is a classical analysis methods for IACT
  61 +data analysis, for background subtraction (#2207).
  62 +
60 63
61 ctmodel - CTA counts cube modelling 64 ctmodel - CTA counts cube modelling
62 ----------------------------------- 65 -----------------------------------
doc/source/admin/release_history/1.5.rst
@@ -23,6 +23,8 @@ Bug fixes @@ -23,6 +23,8 @@ Bug fixes
23 Improvements 23 Improvements
24 ------------ 24 ------------
25 25
  26 +* [`2207 <https://cta-redmine.irap.omp.eu/issues/2207>`_] -
  27 + Add ``RING`` background method to :ref:`ctskymap`
26 * [`2194 <https://cta-redmine.irap.omp.eu/issues/2194>`_] - 28 * [`2194 <https://cta-redmine.irap.omp.eu/issues/2194>`_] -
27 Add keywords to sky maps produced by :ref:`ctskymap` 29 Add keywords to sky maps produced by :ref:`ctskymap`
28 * [`2189 <https://cta-redmine.irap.omp.eu/issues/2189>`_] - 30 * [`2189 <https://cta-redmine.irap.omp.eu/issues/2189>`_] -
doc/source/users/reference_manual/ctskymap.rst
@@ -74,9 +74,25 @@ General parameters @@ -74,9 +74,25 @@ General parameters
74 ``yref [real]`` 74 ``yref [real]``
75 Declination / Galactic latitude of image centre (J2000, in degrees). 75 Declination / Galactic latitude of image centre (J2000, in degrees).
76 76
77 -``bkgsubtract <NONE|IRF> [string]`` 77 +
  78 +Background subtraction configuration parameters
  79 +-----------------------------------------------
  80 +
  81 +``bkgsubtract <NONE|IRF|RING> [string]``
78 Background subtraction method. 82 Background subtraction method.
79 83
  84 +``roiradius [real]``
  85 + Source region radius for estimating on-counts (in degrees).
  86 +
  87 +``inradius [real]``
  88 + Inner ring radius for RING subtraction (in degrees).
  89 +
  90 +``outradius [real]``
  91 + Outer ring radius for RING subtraction (in degrees).
  92 +
  93 +``(regfile = NONE) [file]``
  94 + Exclusion region file as either a FITS map or DS9 region file.
  95 +
80 96
81 Standard parameters 97 Standard parameters
82 ------------------- 98 -------------------
src/ctskymap/ctskymap.cpp
@@ -30,11 +30,14 @@ @@ -30,11 +30,14 @@
30 #endif 30 #endif
31 #include <cstdio> 31 #include <cstdio>
32 #include "ctskymap.hpp" 32 #include "ctskymap.hpp"
  33 +#include "GSkyRegions.hpp"
33 34
34 /* __ Method name definitions ____________________________________________ */ 35 /* __ Method name definitions ____________________________________________ */
35 -#define G_INIT_MAP "ctskymap::init_map(GCTAObservation* obs)" 36 +#define G_GET_PARAMETERS "ctskymap::get_parameter()"
36 #define G_MAP_EVENTS "ctskymap::map_events(GCTAObservation*)" 37 #define G_MAP_EVENTS "ctskymap::map_events(GCTAObservation*)"
37 #define G_MAP_BACKGROUND_IRF "ctskymap::map_background_irf(GCTAObservation*)" 38 #define G_MAP_BACKGROUND_IRF "ctskymap::map_background_irf(GCTAObservation*)"
  39 +#define G_MAP_BACKGROUND_RING "ctskymap::map_background_ring("\
  40 + "GCTAObservation*)"
38 41
39 /* __ Debug definitions __________________________________________________ */ 42 /* __ Debug definitions __________________________________________________ */
40 43
@@ -240,17 +243,15 @@ void ctskymap::run(void) @@ -240,17 +243,15 @@ void ctskymap::run(void)
240 243
241 } // endfor: looped over observations 244 } // endfor: looped over observations
242 245
243 - // If background subtract was required then compute significance map  
244 - // and subtract background map from counts map 246 + // If background subtraction is selected then compute significance map
245 if (m_bkgsubtract != "NONE") { 247 if (m_bkgsubtract != "NONE") {
246 248
247 - // Compute significance map  
248 - m_sigmap = (m_skymap - m_bkgmap) / sqrt(m_skymap); 249 + // Compute the significance map
  250 + map_significance();
249 251
250 // Subtract background map from counts map 252 // Subtract background map from counts map
251 m_skymap -= m_bkgmap; 253 m_skymap -= m_bkgmap;
252 -  
253 - } // endif: background subtraction was requested 254 + }
254 255
255 // Optionally publish sky map 256 // Optionally publish sky map
256 if (m_publish) { 257 if (m_publish) {
@@ -322,7 +323,7 @@ void ctskymap::save(void) @@ -322,7 +323,7 @@ void ctskymap::save(void)
322 // Save FITS file to disk 323 // Save FITS file to disk
323 fits.saveto(m_outmap, clobber()); 324 fits.saveto(m_outmap, clobber());
324 325
325 - } 326 + } // endif: filename and map were not empty
326 327
327 // Write into logger what has been done 328 // Write into logger what has been done
328 std::string fname = (m_outmap.is_empty()) ? "NONE" : m_outmap.url(); 329 std::string fname = (m_outmap.is_empty()) ? "NONE" : m_outmap.url();
@@ -378,12 +379,22 @@ void ctskymap::init_members(void) @@ -378,12 +379,22 @@ void ctskymap::init_members(void)
378 m_skymap.clear(); 379 m_skymap.clear();
379 m_bkgmap.clear(); 380 m_bkgmap.clear();
380 m_sigmap.clear(); 381 m_sigmap.clear();
  382 + m_exclmap.clear();
  383 + m_alphamap.clear();
  384 + m_onmap.clear();
381 m_emin = 0.0; 385 m_emin = 0.0;
382 m_emax = 0.0; 386 m_emax = 0.0;
383 m_bkgsubtract = "NONE"; 387 m_bkgsubtract = "NONE";
  388 + m_roiradius = 0.0;
  389 + m_inradius = 0.0;
  390 + m_outradius = 0.0;
384 m_publish = false; 391 m_publish = false;
385 m_chatter = static_cast<GChatter>(2); 392 m_chatter = static_cast<GChatter>(2);
386 393
  394 + // Initialise cache
  395 + m_solidangle.clear();
  396 + m_dirs.clear();
  397 +
387 // Return 398 // Return
388 return; 399 return;
389 } 400 }
@@ -400,12 +411,22 @@ void ctskymap::copy_members(const ctskymap&amp; app) @@ -400,12 +411,22 @@ void ctskymap::copy_members(const ctskymap&amp; app)
400 m_skymap = app.m_skymap; 411 m_skymap = app.m_skymap;
401 m_bkgmap = app.m_bkgmap; 412 m_bkgmap = app.m_bkgmap;
402 m_sigmap = app.m_sigmap; 413 m_sigmap = app.m_sigmap;
  414 + m_exclmap = app.m_exclmap;
  415 + m_alphamap = app.m_alphamap;
  416 + m_onmap = app.m_onmap;
403 m_emin = app.m_emin; 417 m_emin = app.m_emin;
404 m_emax = app.m_emax; 418 m_emax = app.m_emax;
405 m_bkgsubtract = app.m_bkgsubtract; 419 m_bkgsubtract = app.m_bkgsubtract;
  420 + m_roiradius = app.m_roiradius;
  421 + m_inradius = app.m_inradius;
  422 + m_outradius = app.m_outradius;
406 m_publish = app.m_publish; 423 m_publish = app.m_publish;
407 m_chatter = app.m_chatter; 424 m_chatter = app.m_chatter;
408 425
  426 + // Copy cache
  427 + m_solidangle = app.m_solidangle;
  428 + m_dirs = app.m_dirs;
  429 +
409 // Return 430 // Return
410 return; 431 return;
411 } 432 }
@@ -444,27 +465,68 @@ void ctskymap::get_parameters(void) @@ -444,27 +465,68 @@ void ctskymap::get_parameters(void)
444 m_emax = (*this)["emax"].real(); 465 m_emax = (*this)["emax"].real();
445 m_bkgsubtract = (*this)["bkgsubtract"].string(); 466 m_bkgsubtract = (*this)["bkgsubtract"].string();
446 467
  468 + // Get RING background parameters
  469 + if (m_bkgsubtract == "RING") {
  470 +
  471 + // Get parameters
  472 + m_roiradius = (*this)["roiradius"].real();
  473 + m_inradius = (*this)["inradius"].real();
  474 + m_outradius = (*this)["outradius"].real();
  475 +
  476 + // Make sure that (roiradius < inradius < outradius)
  477 + if (m_roiradius > m_inradius) {
  478 + std::string msg("'roiradius' must be smaller than 'inradius'");
  479 + throw GException::invalid_value(G_GET_PARAMETERS, msg);
  480 + }
  481 + else if (m_inradius > m_outradius) {
  482 + std::string msg("'inradius' must be smaller than 'outradius'");
  483 + throw GException::invalid_value(G_GET_PARAMETERS, msg);
  484 + }
  485 +
  486 + } // endif: read RING background parameters
  487 +
447 // If IRF background subtraction is requested then make sure that the 488 // If IRF background subtraction is requested then make sure that the
448 // CTA observations in the observation container have response information 489 // CTA observations in the observation container have response information
449 - if (m_bkgsubtract == "IRF") { 490 + if (m_bkgsubtract != "NONE") {
450 set_response(m_obs); 491 set_response(m_obs);
451 } 492 }
452 493
453 // Get remaining parameters 494 // Get remaining parameters
454 - m_publish = (*this)["publish"].boolean();  
455 - m_chatter = static_cast<GChatter>((*this)["chatter"].integer()); 495 + m_publish = (*this)["publish"].boolean();
  496 + m_chatter = static_cast<GChatter>((*this)["chatter"].integer());
456 497
457 // Read ahead parameters 498 // Read ahead parameters
458 if (read_ahead()) { 499 if (read_ahead()) {
459 - m_outmap = (*this)["outmap"].filename(); 500 + m_outmap = (*this)["outmap"].filename();
460 } 501 }
461 502
462 // Create background map and significance map if background subtraction 503 // Create background map and significance map if background subtraction
463 // is requested 504 // is requested
464 if (m_bkgsubtract != "NONE") { 505 if (m_bkgsubtract != "NONE") {
  506 +
  507 + // Create backgrond and significance maps
465 m_bkgmap = create_map(m_obs); 508 m_bkgmap = create_map(m_obs);
466 m_sigmap = create_map(m_obs); 509 m_sigmap = create_map(m_obs);
467 - } 510 +
  511 + // Setup the exclusions map
  512 + map_exclusions((*this)["inexclusion"].filename());
  513 +
  514 + // Cache the pixel solid angles and sky directions
  515 + m_solidangle.reserve(m_bkgmap.npix());
  516 + m_dirs.reserve(m_bkgmap.npix());
  517 + for (int i = 0; i < m_bkgmap.npix(); ++i) {
  518 + m_solidangle.push_back(m_bkgmap.solidangle(i));
  519 + m_dirs.push_back(m_bkgmap.inx2dir(i));
  520 + }
  521 +
  522 + // If doing a ring background subtraction, generate an alpha map and
  523 + // make sure to cache the counts for each observation
  524 + if (m_bkgsubtract == "RING") {
  525 + m_alphamap = create_map(m_obs);
  526 + m_onmap = create_map(m_obs);
  527 + }
  528 +
  529 + } // endif: background subtraction selected
468 530
469 // Write parameters into logger 531 // Write parameters into logger
470 log_parameters(TERSE); 532 log_parameters(TERSE);
@@ -545,7 +607,7 @@ void ctskymap::map_events(GCTAObservation* obs) @@ -545,7 +607,7 @@ void ctskymap::map_events(GCTAObservation* obs)
545 // Fill event in skymap 607 // Fill event in skymap
546 m_skymap(pixel, 0) += 1.0; 608 m_skymap(pixel, 0) += 1.0;
547 num_in_map++; 609 num_in_map++;
548 - 610 +
549 } // endfor: looped over all events 611 } // endfor: looped over all events
550 612
551 // Log binning results 613 // Log binning results
@@ -556,7 +618,7 @@ void ctskymap::map_events(GCTAObservation* obs) @@ -556,7 +618,7 @@ void ctskymap::map_events(GCTAObservation* obs)
556 log_value(NORMAL, "Events outside energies", num_outside_erange); 618 log_value(NORMAL, "Events outside energies", num_outside_erange);
557 619
558 // Write sky map into header 620 // Write sky map into header
559 - log_header2(EXPLICIT, "Sky map"); 621 + log_header1(EXPLICIT, "Sky map");
560 log_string(EXPLICIT, m_skymap.print(m_chatter)); 622 log_string(EXPLICIT, m_skymap.print(m_chatter));
561 623
562 // Return 624 // Return
@@ -576,6 +638,7 @@ void ctskymap::map_events(GCTAObservation* obs) @@ -576,6 +638,7 @@ void ctskymap::map_events(GCTAObservation* obs)
576 * 638 *
577 * NONE - No background estimation 639 * NONE - No background estimation
578 * IRF - Background estimation based on IRF template 640 * IRF - Background estimation based on IRF template
  641 + * RING - Ring background estimation
579 ***************************************************************************/ 642 ***************************************************************************/
580 void ctskymap::map_background(GCTAObservation* obs) 643 void ctskymap::map_background(GCTAObservation* obs)
581 { 644 {
@@ -583,6 +646,9 @@ void ctskymap::map_background(GCTAObservation* obs) @@ -583,6 +646,9 @@ void ctskymap::map_background(GCTAObservation* obs)
583 if (m_bkgsubtract == "IRF") { 646 if (m_bkgsubtract == "IRF") {
584 map_background_irf(obs); 647 map_background_irf(obs);
585 } 648 }
  649 + else if (m_bkgsubtract == "RING") {
  650 + map_background_ring(obs);
  651 + }
586 652
587 // Return 653 // Return
588 return; 654 return;
@@ -669,7 +735,7 @@ void ctskymap::map_background_irf(GCTAObservation* obs) @@ -669,7 +735,7 @@ void ctskymap::map_background_irf(GCTAObservation* obs)
669 calls += integral.calls(); 735 calls += integral.calls();
670 736
671 // Multiply background rate with livetime and solid angle 737 // Multiply background rate with livetime and solid angle
672 - value *= obs->livetime() * m_bkgmap.solidangle(i); 738 + value *= obs->livetime() * m_solidangle[i];
673 739
674 // Add number of background events to background map 740 // Add number of background events to background map
675 m_bkgmap(i,0) += value; 741 m_bkgmap(i,0) += value;
@@ -678,7 +744,7 @@ void ctskymap::map_background_irf(GCTAObservation* obs) @@ -678,7 +744,7 @@ void ctskymap::map_background_irf(GCTAObservation* obs)
678 total += value; 744 total += value;
679 745
680 } // endfor: looped over background map pixels 746 } // endfor: looped over background map pixels
681 - 747 +
682 // Log background subtraction results 748 // Log background subtraction results
683 log_value(NORMAL, "Events in background", int(total+0.5)); 749 log_value(NORMAL, "Events in background", int(total+0.5));
684 log_value(NORMAL, "Background evaluations", calls); 750 log_value(NORMAL, "Background evaluations", calls);
@@ -689,6 +755,104 @@ void ctskymap::map_background_irf(GCTAObservation* obs) @@ -689,6 +755,104 @@ void ctskymap::map_background_irf(GCTAObservation* obs)
689 755
690 756
691 /***********************************************************************//** 757 /***********************************************************************//**
  758 + * @brief Estimates the background in sky map based on the ring background
  759 + * method
  760 + *
  761 + * @param[in] obs CTA observation.
  762 + *
  763 + * @exception GException::invalid_value
  764 + * No response information available for observation.
  765 + * No background template available in instrument response function.
  766 + *
  767 + * Estimates the background in the sky map by summing the events within a
  768 + * ring centered at a given pixel's position. The pixels in the ring are
  769 + * weighted also by the background IRF value.
  770 + ***************************************************************************/
  771 +void ctskymap::map_background_ring(GCTAObservation* obs)
  772 +{
  773 + // Get IRF response (to scale background counts)
  774 + const GCTAResponseIrf* rsp = dynamic_cast<const GCTAResponseIrf*>(obs->response());
  775 +
  776 + // Throw an exception if observation has no instrument response function
  777 + if (rsp == NULL) {
  778 + std::string msg = "No response information available for "+
  779 + get_obs_header(obs)+" to compute IRF background. "
  780 + "Please specify response information or use "
  781 + "another background subtraction method.";
  782 + throw GException::invalid_value(G_MAP_BACKGROUND_RING, msg);
  783 + }
  784 +
  785 + // Get IRF background template
  786 + const GCTABackground* bkg = rsp->background();
  787 +
  788 + // Throw an exception if observation has no IRF background template
  789 + if (bkg == NULL) {
  790 + std::string msg = "No IRF background template found in instrument "
  791 + "response function for "+
  792 + get_obs_header(obs)+". Please specify an instrument "
  793 + "response function containing a background template.";
  794 + throw GException::invalid_value(G_MAP_BACKGROUND_RING, msg);
  795 + }
  796 +
  797 + // Compute natural logarithm of energy range in MeV
  798 + double lnEmin = std::log(m_emin * 1.0e6);
  799 + double lnEmax = std::log(m_emax * 1.0e6);
  800 +
  801 + // Extract region of interest from observation
  802 + GCTARoi roi = obs->roi();
  803 +
  804 + // Initialise statistics
  805 + int calls = 0;
  806 + double exposure = obs->livetime();
  807 +
  808 + // Loop over all map pixels
  809 + for (int i = 0; i < m_bkgmap.npix(); ++i) {
  810 +
  811 + // Get sky direction of pixel
  812 + GSkyDir& skydir = m_dirs[i];
  813 +
  814 + // Convert sky direction in instrument direction
  815 + GCTAInstDir instdir = obs->pointing().instdir(skydir);
  816 +
  817 + // If RoI is valid and instrument direction is not within RoI then
  818 + // skip pixel
  819 + if (roi.is_valid() && !roi.contains(instdir)) {
  820 + continue;
  821 + }
  822 +
  823 + // Setup integration function
  824 + ctskymap::irf_kern integrand(bkg, &instdir);
  825 + GIntegral integral(&integrand);
  826 +
  827 + // Set precision
  828 + integral.eps(1.0e-6);
  829 +
  830 + // Do Romberg integration to get the sensitivity (note: this assumes
  831 + // the background IRF is a good approximation for the radial
  832 + // sensitivity)
  833 + m_alphamap(i,0) += integral.romberg(lnEmin, lnEmax) *
  834 + m_solidangle[i] * exposure;
  835 +
  836 + // Store the counts in this bin
  837 + m_onmap(i,0) += m_skymap(i,0);
  838 +
  839 + // Update number of background function calls
  840 + calls += integral.calls();
  841 +
  842 + } // endfor: Loop for caching bkg IRF sensitivity
  843 +
  844 + // Log background subtraction results
  845 + log_value(NORMAL, "Alpha map evaluations", calls);
  846 +
  847 + // Zero out the counts map to prepare it for the next observation
  848 + m_skymap = 0.0;
  849 +
  850 + // Return
  851 + return;
  852 +}
  853 +
  854 +
  855 +/***********************************************************************//**
692 * @brief Write keywords in FITS HDU 856 * @brief Write keywords in FITS HDU
693 * 857 *
694 * @param[in,out] hdu Pointer to FITS HDU. 858 * @param[in,out] hdu Pointer to FITS HDU.
@@ -714,6 +878,276 @@ void ctskymap::write_hdu_keywords(GFitsHDU* hdu) const @@ -714,6 +878,276 @@ void ctskymap::write_hdu_keywords(GFitsHDU* hdu) const
714 878
715 879
716 /***********************************************************************//** 880 /***********************************************************************//**
  881 + * @brief Computes the bin-by-bin significance if background subtraction
  882 + * requested
  883 + *
  884 + * Method computes the bin-by bin significance if a background subtraction
  885 + * method is specified. If method is "IRF", Poisson statistics (in the
  886 + * Gaussian limit) are assumed. If method is "RING" a Li & Ma significance
  887 + * is computed for each bin (as well as a map of the computed alpha values).
  888 + ***************************************************************************/
  889 +void ctskymap::map_significance(void)
  890 +{
  891 + // Compute significance from "RING" method (Li & Ma eq. 17)
  892 + if (m_bkgsubtract == "RING") {
  893 +
  894 + // Log message about what is being done
  895 + log_header1(NORMAL, "Computing RBM map");
  896 + log_value(NORMAL, "Total pixels to process", m_onmap.npix());
  897 +
  898 + // Store the number of bins with an inappropriate alpha parameter
  899 + int num_bad_alpha = 0;
  900 +
  901 + // Loop through each bin in the on-counts map
  902 + for (int i = 0; i < m_onmap.npix(); ++i) {
  903 +
  904 + // Initialise the on/off counts and alpha for this bin
  905 + double n_on = 0.0;
  906 + double n_off = 0.0;
  907 + double alpha = 0.0;
  908 +
  909 + // Since this can take a long time, keep the user updated on
  910 + // the progress when another 10% of pixels is processed
  911 + if (i % (m_onmap.npix()/10) == 0) {
  912 + log_value(NORMAL, "Pixels remaining", m_onmap.npix()-i);
  913 + }
  914 +
  915 + // Get bin coordinates
  916 + GSkyDir& skydir = m_dirs[i];
  917 +
  918 + // Compute the alpha and counts for this bin
  919 + compute_ring_values(m_onmap, m_alphamap, skydir,
  920 + n_on, n_off, alpha);
  921 +
  922 + // If alpha is zero then increment the bad alpha counter
  923 + if (alpha == 0.0) {
  924 + num_bad_alpha++;
  925 + }
  926 +
  927 + // ... otherwise store the results
  928 + else {
  929 +
  930 + // Store the on-counts and alpha-weighted off-counts
  931 + m_skymap(i,0) = n_on;
  932 + m_bkgmap(i,0) = alpha * n_off;
  933 +
  934 + // Compute and store significance (Li & Ma eq. 17)
  935 + if (n_on == 0.0) {
  936 + m_sigmap(i,0) = -2.0 * n_off * std::log(1.0+alpha);
  937 + }
  938 + else {
  939 + m_sigmap(i,0) = (n_on < (alpha*n_off) ? -2.0 : 2.0) *
  940 + (n_on * std::log((1.0+alpha) *
  941 + n_on / (alpha * (n_on+n_off))) +
  942 + n_off * std::log((1.0+alpha) *
  943 + n_off / (n_on+n_off)));
  944 + }
  945 +
  946 + } // endelse: alpha was non-zero
  947 +
  948 + } // endfor: looped over all pixels
  949 +
  950 + // Log the number of bad-alpha bins
  951 + log_value(NORMAL, "Bins with alpha=0", num_bad_alpha);
  952 +
  953 + // Now take the square root. Since some bins can be negative, first
  954 + // take the absolute value of the map, square root that, then multiply
  955 + // each bin by its sign to preserve the +/- significance.
  956 + m_sigmap = sign(m_sigmap) * sqrt(abs(m_sigmap));
  957 +
  958 + } // endif: ring background selected
  959 +
  960 + // Compute significance using Poisson statistics in the Gaussian limit
  961 + else {
  962 + m_sigmap = (m_skymap - m_bkgmap) / sqrt(m_skymap);
  963 + }
  964 +
  965 + // Return
  966 + return;
  967 +}
  968 +
  969 +
  970 +/***********************************************************************//**
  971 + * @brief Computes Non, Noff and alpha for a counts map and sensitivity map
  972 + *
  973 + * @param[in] counts Counts map
  974 + * @param[in] sensitivity Sensitivity map
  975 + * @param[in] position Position of the ring & roi centers
  976 + * @param[out] non Returned estimate of ON counts
  977 + * @param[out] noff Returned estimate of OFF counts
  978 + * @param[out] alpha Returned estimate of alpha
  979 + *
  980 + * This method computes the Non counts Noff values at a given position. It
  981 + * also computes the alpha parameter from the passed sensitivity map.
  982 + ***************************************************************************/
  983 +void ctskymap::compute_ring_values(const GSkyMap& counts,
  984 + const GSkyMap& sensitivity,
  985 + const GSkyDir& position,
  986 + double& non,
  987 + double& noff,
  988 + double& alpha)
  989 +{
  990 + // Reset Non and Noff
  991 + non = 0.0;
  992 + noff = 0.0;
  993 +
  994 + // Initialise On and Off alpha
  995 + double alpha_on = 0.0;
  996 + double alpha_off = 0.0;
  997 +
  998 + // Define the regions necessary to do the pixel checks
  999 + GSkyRegionCircle roi_reg(position, m_roiradius);
  1000 + GSkyRegionCircle inner_reg(position, m_inradius);
  1001 + GSkyRegionCircle outer_reg(position, m_outradius);
  1002 +
  1003 + // Loop over every pixel in the observation to compute Non, Noff
  1004 + for (int j = 0; j < counts.npix(); ++j) {
  1005 +
  1006 + // Get the index and sky direction of this pixel
  1007 + GSkyDir& skydir = m_dirs[j];
  1008 +
  1009 + // Check if pixel is inside the background region
  1010 + if ((m_exclmap(j,0) == 0.0) && outer_reg.contains(skydir) &&
  1011 + !inner_reg.contains(skydir)) {
  1012 +
  1013 + // Update n_off
  1014 + noff += counts(j,0);
  1015 +
  1016 + // Update alpha_off
  1017 + alpha_off += sensitivity(j,0);
  1018 +
  1019 + }
  1020 +
  1021 + // ... otherwise check if pixel is inside source region
  1022 + else if (roi_reg.contains(skydir)) {
  1023 +
  1024 + // Update n_on for significance computation
  1025 + non += counts(j,0);
  1026 +
  1027 + // Update alpha_on
  1028 + alpha_on += sensitivity(j,0);
  1029 +
  1030 + } // endif: source and background region check
  1031 +
  1032 + } // endfor: looped over pixels
  1033 +
  1034 + // Compute alpha. If the off region does not have any sensitivity then
  1035 + // set Non = Noff = 0
  1036 + if (alpha_off == 0.0) {
  1037 + alpha = 0.0;
  1038 + non = 0.0;
  1039 + noff = 0.0;
  1040 + }
  1041 + else {
  1042 + alpha = alpha_on / alpha_off;
  1043 + }
  1044 +
  1045 + // Return
  1046 + return;
  1047 +}
  1048 +
  1049 +
  1050 +/***********************************************************************//**
  1051 + * @brief Generates map of pixel exclusions
  1052 + *
  1053 + * @param[in] filename Exclusion file name.
  1054 + *
  1055 + * Generates a sky map of the pixels that are to be excluded from the
  1056 + * background estimation. Pixels with values different from 0 will be
  1057 + * excluded.
  1058 + ***************************************************************************/
  1059 +void ctskymap::map_exclusions(const GFilename& filename)
  1060 +{
  1061 + // Create exlusion map
  1062 + m_exclmap = create_map(m_obs);
  1063 +
  1064 + // Set all pixels to 0 (no pixel excluded)
  1065 + m_exclmap = 0.0;
  1066 +
  1067 + // Make sure the exclusions filename is valid
  1068 + if (is_valid_filename(filename)) {
  1069 +
  1070 + // Fill the exclusions based on the regions supplied
  1071 + if (filename.is_fits()) {
  1072 + map_exclusions_fits(filename);
  1073 + }
  1074 + else {
  1075 + map_exclusions_reg(filename);
  1076 + }
  1077 +
  1078 + } // endif: filename was valid
  1079 +
  1080 + // Return
  1081 + return;
  1082 +}
  1083 +
  1084 +
  1085 +/***********************************************************************//**
  1086 + * @brief Fills exclusions map from FITS image
  1087 + *
  1088 + * @param[in] filename FITS image file name.
  1089 + *
  1090 + * Sets all exclusion map pixels to 1 that correspond to non-zero pixels in
  1091 + * the exclusion sky map FITS file.
  1092 + ***************************************************************************/
  1093 +void ctskymap::map_exclusions_fits(const GFilename& filename)
  1094 +{
  1095 + // Load the fits image
  1096 + GSkyMap inmap(filename);
  1097 +
  1098 + // Loop through the individual pixels in the exclusion map
  1099 + for (int i = 0; i < m_exclmap.npix(); ++i) {
  1100 +
  1101 + // Get the pixel direction
  1102 + GSkyDir dir = m_exclmap.pix2dir(i);
  1103 +
  1104 + // Check this sky position in the fits map
  1105 + if (inmap.contains(dir) && (inmap(inmap.dir2inx(dir)) != 0.0)) {
  1106 +
  1107 + // Set the pixel to 1
  1108 + m_exclmap(i) = 1.0;
  1109 +
  1110 + } // endif: pixel,region overlap check
  1111 +
  1112 + } // endfor: looped over exclusion map pixels
  1113 +
  1114 + // Return
  1115 + return;
  1116 +}
  1117 +
  1118 +
  1119 +/***********************************************************************//**
  1120 + * @brief Fills exclusions map from DS9 region file
  1121 + *
  1122 + * @param[in] filename DS9 region file name.
  1123 + *
  1124 + * Sets all exclusion map pixels to 1 that are contained in any of the DS9
  1125 + * regions.
  1126 + ***************************************************************************/
  1127 +void ctskymap::map_exclusions_reg(const GFilename& filename)
  1128 +{
  1129 + // Load the exclusion regions
  1130 + GSkyRegions regions(filename);
  1131 +
  1132 + // Loop through the individual pixels in the exclusion map
  1133 + for (int i = 0; i < m_exclmap.npix(); ++i) {
  1134 +
  1135 + // Get the pixel position
  1136 + GSkyDir dir = m_exclmap.pix2dir(i);
  1137 +
  1138 + // If pixel position overlaps with the regions
  1139 + if (regions.contains(dir)) {
  1140 + m_exclmap(i) = 1.0;
  1141 + }
  1142 +
  1143 + } // endfor: looped over exclusion map pixels
  1144 +
  1145 + // Return
  1146 + return;
  1147 +}
  1148 +
  1149 +
  1150 +/***********************************************************************//**
717 * @brief Estimates the background in sky map based on IRF template 1151 * @brief Estimates the background in sky map based on IRF template
718 * 1152 *
719 * @param[in] lnE Natural logarithm of energy in MeV. 1153 * @param[in] lnE Natural logarithm of energy in MeV.
@@ -732,4 +1166,3 @@ double ctskymap::irf_kern::eval(const double&amp; lnE) @@ -732,4 +1166,3 @@ double ctskymap::irf_kern::eval(const double&amp; lnE)
732 // Return value 1166 // Return value
733 return value; 1167 return value;
734 } 1168 }
735 -  
src/ctskymap/ctskymap.hpp
@@ -71,6 +71,15 @@ protected: @@ -71,6 +71,15 @@ protected:
71 void map_events(GCTAObservation* obs); 71 void map_events(GCTAObservation* obs);
72 void map_background(GCTAObservation* obs); 72 void map_background(GCTAObservation* obs);
73 void map_background_irf(GCTAObservation* obs); 73 void map_background_irf(GCTAObservation* obs);
  74 + void map_background_ring(GCTAObservation* obs);
  75 + void map_significance(void);
  76 + void map_exclusions(const GFilename& filename);
  77 + void map_exclusions_fits(const GFilename& filename);
  78 + void map_exclusions_reg(const GFilename& filename);
  79 + void compute_ring_values(const GSkyMap& counts,
  80 + const GSkyMap& sensitivity,
  81 + const GSkyDir& position,
  82 + double& non, double& noff, double& alpha);
74 void write_hdu_keywords(GFitsHDU* hdu) const; 83 void write_hdu_keywords(GFitsHDU* hdu) const;
75 84
76 // Background integration kernel 85 // Background integration kernel
@@ -91,13 +100,23 @@ protected: @@ -91,13 +100,23 @@ protected:
91 double m_emin; //!< Minimum energy (TeV) 100 double m_emin; //!< Minimum energy (TeV)
92 double m_emax; //!< Maximum energy (TeV) 101 double m_emax; //!< Maximum energy (TeV)
93 std::string m_bkgsubtract; //!< Background subtraction method 102 std::string m_bkgsubtract; //!< Background subtraction method
  103 + double m_roiradius; //!< Region of interest radius for RING bkg.
  104 + double m_inradius; //!< Inner ring radius for RING bkg.
  105 + double m_outradius; //!< Outer ring radius for RING bkg.
94 bool m_publish; //!< Publish sky map? 106 bool m_publish; //!< Publish sky map?
95 GChatter m_chatter; //!< Chattiness 107 GChatter m_chatter; //!< Chattiness
96 108
97 // Protected members 109 // Protected members
98 - GSkyMap m_skymap; //!< Sky map  
99 - GSkyMap m_bkgmap; //!< Background map  
100 - GSkyMap m_sigmap; //!< Significance map 110 + GSkyMap m_skymap; //!< Sky map
  111 + GSkyMap m_bkgmap; //!< Background map
  112 + GSkyMap m_sigmap; //!< Significance map
  113 + GSkyMap m_exclmap; //!< Exclusion map for RING background
  114 + GSkyMap m_alphamap; //!< Alpha map values for RING background
  115 + GSkyMap m_onmap; //!< On counts map for RING background
  116 +
  117 + // Caching variables to prevent multiple computations of the same thing
  118 + std::vector<double> m_solidangle; //!< Cached pixel solid angles
  119 + std::vector<GSkyDir> m_dirs; //!< Cached pixel directions
101 }; 120 };
102 121
103 122
src/ctskymap/ctskymap.par
@@ -42,7 +42,11 @@ yref, r, a, 22.01,-90,90, &quot;Second coordinate of image center in degrees (DEC @@ -42,7 +42,11 @@ yref, r, a, 22.01,-90,90, &quot;Second coordinate of image center in degrees (DEC
42 # 42 #
43 # Background subtraction parameters 43 # Background subtraction parameters
44 #================================== 44 #==================================
45 -bkgsubtract, s, a, NONE, NONE|IRF,, "Background subtraction method" 45 +bkgsubtract, s, a, NONE, NONE|IRF|RING,, "Background subtraction method"
  46 +roiradius, r, a, 0.0,,, "Source region radius for estimating on-counts (degrees)"
  47 +inradius, r, a, 0.6,,, "Inner ring radius (degrees)"
  48 +outradius, r, a, 0.8,,, "Outer ring radius (degrees)"
  49 +inexclusion, f, h, NONE,,, "Exclusion map or DS9 region file name"
46 50
47 # 51 #
48 # Standard parameters 52 # Standard parameters
test/test_ctskymap.py
@@ -206,6 +206,10 @@ class Test(test): @@ -206,6 +206,10 @@ class Test(test):
206 # Publish with name 206 # Publish with name
207 skymap.publish('My sky map') 207 skymap.publish('My sky map')
208 208
  209 + # ================================
  210 + # TEST IRF BACKGROUND SUBTRACTION
  211 + # ================================
  212 +
209 # Allocate ctskymap tool from observation container 213 # Allocate ctskymap tool from observation container
210 skymap = ctools.ctskymap(obs) 214 skymap = ctools.ctskymap(obs)
211 skymap['emin'] = 0.1 215 skymap['emin'] = 0.1
@@ -233,6 +237,37 @@ class Test(test): @@ -233,6 +237,37 @@ class Test(test):
233 self._check_result_file('ctskymap_py4.fits[BACKGROUND]') 237 self._check_result_file('ctskymap_py4.fits[BACKGROUND]')
234 self._check_result_file('ctskymap_py4.fits[SIGNIFICANCE]') 238 self._check_result_file('ctskymap_py4.fits[SIGNIFICANCE]')
235 239
  240 + # Test RING background subtraction
  241 + skymap = ctools.ctskymap(obs)
  242 + skymap['emin'] = 0.1
  243 + skymap['emax'] = 100
  244 + skymap['nxpix'] = 100 # Make region smaller since the 'RING'
  245 + skymap['nypix'] = 100 # method takes longer to run than 'IRF'
  246 + skymap['binsz'] = 0.02
  247 + skymap['coordsys'] = 'CEL'
  248 + skymap['proj'] = 'CAR'
  249 + skymap['xref'] = 83.63
  250 + skymap['yref'] = 22.01
  251 + skymap['bkgsubtract'] = 'RING'
  252 + skymap['roiradius'] = 0.1
  253 + skymap['inradius'] = 0.6
  254 + skymap['outradius'] = 0.8
  255 + skymap['inexclusion'] = 'NONE'
  256 + skymap['caldb'] = self._caldb
  257 + skymap['irf'] = self._irf
  258 + skymap['outmap'] = 'ctskymap_py5.fits'
  259 + skymap['logfile'] = 'ctskymap_py5.log'
  260 + skymap['chatter'] = 4
  261 +
  262 + # Execute tool
  263 + skymap.logFileOpen() # Needed to get a new log file
  264 + skymap.execute()
  265 +
  266 + # Check result file
  267 + self._check_result_file('ctskymap_py5.fits', 100, 100)
  268 + self._check_result_file('ctskymap_py5.fits[BACKGROUND]', 100, 100)
  269 + self._check_result_file('ctskymap_py5.fits[SIGNIFICANCE]', 100, 100)
  270 +
236 # Return 271 # Return
237 return 272 return
238 273