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 4 ========================
5 5  
6 6 Add csphagen script (#2208)
  7 + Add RING background method to ctskymap (#2207)
7 8 Add keywords to sky maps produced by ctskymap (#2194)
8 9 Enhance efficiency of computations in ctmodel (#2189)
9 10 Add csmodelsois script (#2180)
... ...
... ... @@ -57,6 +57,9 @@ Header keywords informing about the dates and times covered by the observations
57 57 used to generate the skymap, as well as the event energy range, are now appended
58 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 64 ctmodel - CTA counts cube modelling
62 65 -----------------------------------
... ...
doc/source/admin/release_history/1.5.rst
... ... @@ -23,6 +23,8 @@ Bug fixes
23 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 28 * [`2194 <https://cta-redmine.irap.omp.eu/issues/2194>`_] -
27 29 Add keywords to sky maps produced by :ref:`ctskymap`
28 30 * [`2189 <https://cta-redmine.irap.omp.eu/issues/2189>`_] -
... ...
doc/source/users/reference_manual/ctskymap.rst
... ... @@ -74,9 +74,25 @@ General parameters
74 74 ``yref [real]``
75 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 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 97 Standard parameters
82 98 -------------------
... ...
src/ctskymap/ctskymap.cpp
... ... @@ -30,11 +30,14 @@
30 30 #endif
31 31 #include <cstdio>
32 32 #include "ctskymap.hpp"
  33 +#include "GSkyRegions.hpp"
33 34  
34 35 /* __ Method name definitions ____________________________________________ */
35   -#define G_INIT_MAP "ctskymap::init_map(GCTAObservation* obs)"
  36 +#define G_GET_PARAMETERS "ctskymap::get_parameter()"
36 37 #define G_MAP_EVENTS "ctskymap::map_events(GCTAObservation*)"
37 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 42 /* __ Debug definitions __________________________________________________ */
40 43  
... ... @@ -240,17 +243,15 @@ void ctskymap::run(void)
240 243  
241 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 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 252 // Subtract background map from counts map
251 253 m_skymap -= m_bkgmap;
252   -
253   - } // endif: background subtraction was requested
  254 + }
254 255  
255 256 // Optionally publish sky map
256 257 if (m_publish) {
... ... @@ -322,7 +323,7 @@ void ctskymap::save(void)
322 323 // Save FITS file to disk
323 324 fits.saveto(m_outmap, clobber());
324 325  
325   - }
  326 + } // endif: filename and map were not empty
326 327  
327 328 // Write into logger what has been done
328 329 std::string fname = (m_outmap.is_empty()) ? "NONE" : m_outmap.url();
... ... @@ -378,12 +379,22 @@ void ctskymap::init_members(void)
378 379 m_skymap.clear();
379 380 m_bkgmap.clear();
380 381 m_sigmap.clear();
  382 + m_exclmap.clear();
  383 + m_alphamap.clear();
  384 + m_onmap.clear();
381 385 m_emin = 0.0;
382 386 m_emax = 0.0;
383 387 m_bkgsubtract = "NONE";
  388 + m_roiradius = 0.0;
  389 + m_inradius = 0.0;
  390 + m_outradius = 0.0;
384 391 m_publish = false;
385 392 m_chatter = static_cast<GChatter>(2);
386 393  
  394 + // Initialise cache
  395 + m_solidangle.clear();
  396 + m_dirs.clear();
  397 +
387 398 // Return
388 399 return;
389 400 }
... ... @@ -400,12 +411,22 @@ void ctskymap::copy_members(const ctskymap&amp; app)
400 411 m_skymap = app.m_skymap;
401 412 m_bkgmap = app.m_bkgmap;
402 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 417 m_emin = app.m_emin;
404 418 m_emax = app.m_emax;
405 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 423 m_publish = app.m_publish;
407 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 430 // Return
410 431 return;
411 432 }
... ... @@ -444,27 +465,68 @@ void ctskymap::get_parameters(void)
444 465 m_emax = (*this)["emax"].real();
445 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 488 // If IRF background subtraction is requested then make sure that the
448 489 // CTA observations in the observation container have response information
449   - if (m_bkgsubtract == "IRF") {
  490 + if (m_bkgsubtract != "NONE") {
450 491 set_response(m_obs);
451 492 }
452 493  
453 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 498 // Read ahead parameters
458 499 if (read_ahead()) {
459   - m_outmap = (*this)["outmap"].filename();
  500 + m_outmap = (*this)["outmap"].filename();
460 501 }
461 502  
462 503 // Create background map and significance map if background subtraction
463 504 // is requested
464 505 if (m_bkgsubtract != "NONE") {
  506 +
  507 + // Create backgrond and significance maps
465 508 m_bkgmap = create_map(m_obs);
466 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 531 // Write parameters into logger
470 532 log_parameters(TERSE);
... ... @@ -545,7 +607,7 @@ void ctskymap::map_events(GCTAObservation* obs)
545 607 // Fill event in skymap
546 608 m_skymap(pixel, 0) += 1.0;
547 609 num_in_map++;
548   -
  610 +
549 611 } // endfor: looped over all events
550 612  
551 613 // Log binning results
... ... @@ -556,7 +618,7 @@ void ctskymap::map_events(GCTAObservation* obs)
556 618 log_value(NORMAL, "Events outside energies", num_outside_erange);
557 619  
558 620 // Write sky map into header
559   - log_header2(EXPLICIT, "Sky map");
  621 + log_header1(EXPLICIT, "Sky map");
560 622 log_string(EXPLICIT, m_skymap.print(m_chatter));
561 623  
562 624 // Return
... ... @@ -576,6 +638,7 @@ void ctskymap::map_events(GCTAObservation* obs)
576 638 *
577 639 * NONE - No background estimation
578 640 * IRF - Background estimation based on IRF template
  641 + * RING - Ring background estimation
579 642 ***************************************************************************/
580 643 void ctskymap::map_background(GCTAObservation* obs)
581 644 {
... ... @@ -583,6 +646,9 @@ void ctskymap::map_background(GCTAObservation* obs)
583 646 if (m_bkgsubtract == "IRF") {
584 647 map_background_irf(obs);
585 648 }
  649 + else if (m_bkgsubtract == "RING") {
  650 + map_background_ring(obs);
  651 + }
586 652  
587 653 // Return
588 654 return;
... ... @@ -669,7 +735,7 @@ void ctskymap::map_background_irf(GCTAObservation* obs)
669 735 calls += integral.calls();
670 736  
671 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 740 // Add number of background events to background map
675 741 m_bkgmap(i,0) += value;
... ... @@ -678,7 +744,7 @@ void ctskymap::map_background_irf(GCTAObservation* obs)
678 744 total += value;
679 745  
680 746 } // endfor: looped over background map pixels
681   -
  747 +
682 748 // Log background subtraction results
683 749 log_value(NORMAL, "Events in background", int(total+0.5));
684 750 log_value(NORMAL, "Background evaluations", calls);
... ... @@ -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 856 * @brief Write keywords in FITS HDU
693 857 *
694 858 * @param[in,out] hdu Pointer to FITS HDU.
... ... @@ -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 1151 * @brief Estimates the background in sky map based on IRF template
718 1152 *
719 1153 * @param[in] lnE Natural logarithm of energy in MeV.
... ... @@ -732,4 +1166,3 @@ double ctskymap::irf_kern::eval(const double&amp; lnE)
732 1166 // Return value
733 1167 return value;
734 1168 }
735   -
... ...
src/ctskymap/ctskymap.hpp
... ... @@ -71,6 +71,15 @@ 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 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 83 void write_hdu_keywords(GFitsHDU* hdu) const;
75 84  
76 85 // Background integration kernel
... ... @@ -91,13 +100,23 @@ protected:
91 100 double m_emin; //!< Minimum energy (TeV)
92 101 double m_emax; //!< Maximum energy (TeV)
93 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 106 bool m_publish; //!< Publish sky map?
95 107 GChatter m_chatter; //!< Chattiness
96 108  
97 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 42 #
43 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 52 # Standard parameters
... ...
test/test_ctskymap.py
... ... @@ -206,6 +206,10 @@ class Test(test):
206 206 # Publish with name
207 207 skymap.publish('My sky map')
208 208  
  209 + # ================================
  210 + # TEST IRF BACKGROUND SUBTRACTION
  211 + # ================================
  212 +
209 213 # Allocate ctskymap tool from observation container
210 214 skymap = ctools.ctskymap(obs)
211 215 skymap['emin'] = 0.1
... ... @@ -233,6 +237,37 @@ class Test(test):
233 237 self._check_result_file('ctskymap_py4.fits[BACKGROUND]')
234 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 271 # Return
237 272 return
238 273  
... ...