Commit ab3bcbff5d29f3cdc10989b28849ca32189ffd69
Merge branch '2207-ctskymap_RING_bkgsubtract' into integration
# Conflicts: # ChangeLog
Showing
8 changed files
with
536 additions
and
23 deletions
ChangeLog
NEWS
| ... | ... | @@ -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& 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& 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, "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 | ... | ... |