Commit 2ee8bac10df977f80447a0a74b2aa08a9b5dede0
1 parent
28b19876
Implement counts cube weights computation in ctbin
Showing
4 changed files
with
239 additions
and
37 deletions
ChangeLog
| 1 | -2016-06-20 Juergen Knoedlseder <jurgen.knodlseder@irap.omp.eu> | |
| 1 | +2016-06-21 Juergen Knoedlseder <jurgen.knodlseder@irap.omp.eu> | |
| 2 | 2 | |
| 3 | 3 | * Version 1.1.0 released |
| 4 | 4 | ======================== |
| 5 | 5 | |
| 6 | + Compute counts cube weights in ctbin (#1789) | |
| 6 | 7 | Adapt to new stacked cube interface (#1788) |
| 7 | 8 | Add ctools and cscripts unit tests |
| 8 | 9 | Add "bgdoversample" and "bgdinfill" parameters to csroot2caldb | ... | ... |
NEWS
| 1 | 1 | New Features and Important Changes in ctools 1.1.0 |
| 2 | 2 | |
| 3 | -20 June 2016 | |
| 3 | +21 June 2016 | |
| 4 | 4 | |
| 5 | 5 | |
| 6 | 6 | Introduction |
| ... | ... | @@ -92,6 +92,14 @@ ctbin - CTA event binning |
| 92 | 92 | Add publish() method to publish the counts cube via a SAMP Hub to Virtual |
| 93 | 93 | Observatory interoperable tools (e.g. Aladin). |
| 94 | 94 | |
| 95 | +The tool now computes the counts cube weights according to the overlaps | |
| 96 | +between the Region of Interests and energy boundaries with the counts | |
| 97 | +cube bins. This allows in particular for a proper handling of different | |
| 98 | +energy thresholds between observations that will be stacked into a single | |
| 99 | +cube. Also different Region of Interests are now properly handled, and | |
| 100 | +there is no longer an issue with counts cubes that are larger than the | |
| 101 | +Region of Interest of the event list (#1789). | |
| 102 | + | |
| 95 | 103 | |
| 96 | 104 | ctlike - Maximum likelihood fitting |
| 97 | 105 | ----------------------------------- | ... | ... |
src/ctbin/ctbin.cpp
| ... | ... | @@ -39,9 +39,9 @@ |
| 39 | 39 | /* __ Debug definitions __________________________________________________ */ |
| 40 | 40 | |
| 41 | 41 | /* __ Coding definitions _________________________________________________ */ |
| 42 | +#define G_PLAW_WEIGHTS //!< Use power law in energy weight computation | |
| 42 | 43 | |
| 43 | 44 | /* __ Constants __________________________________________________________ */ |
| 44 | -const GEnergy g_energy_margin(1.0e-12, "TeV"); | |
| 45 | 45 | |
| 46 | 46 | |
| 47 | 47 | /*========================================================================== |
| ... | ... | @@ -62,7 +62,7 @@ ctbin::ctbin(void) : ctool(CTBIN_NAME, CTBIN_VERSION) |
| 62 | 62 | |
| 63 | 63 | // Return |
| 64 | 64 | return; |
| 65 | -} // GCOV_EXCL_LINE | |
| 65 | +} | |
| 66 | 66 | |
| 67 | 67 | |
| 68 | 68 | /***********************************************************************//** |
| ... | ... | @@ -211,7 +211,9 @@ void ctbin::clear(void) |
| 211 | 211 | * @brief Run the ctbin tool |
| 212 | 212 | * |
| 213 | 213 | * Gets the user parameters and loops over all CTA observations in the |
| 214 | - * observation container to bin the events into a single counts cube. | |
| 214 | + * observation container to bin the events into a single counts cube. All | |
| 215 | + * observations in the observation container that do not contain CTA event | |
| 216 | + * lists will be skipped. | |
| 215 | 217 | ***************************************************************************/ |
| 216 | 218 | void ctbin::run(void) |
| 217 | 219 | { |
| ... | ... | @@ -275,6 +277,15 @@ void ctbin::run(void) |
| 275 | 277 | |
| 276 | 278 | } // endfor: looped over observations |
| 277 | 279 | |
| 280 | + // Normalize weights counts map | |
| 281 | + if (m_livetime > 0.0) { | |
| 282 | + for (int iebin = 0; iebin < m_weights.nmaps(); ++iebin) { | |
| 283 | + for (int index = 0; index < m_weights.npix(); ++index) { | |
| 284 | + m_weights(index, iebin) /= m_livetime; | |
| 285 | + } | |
| 286 | + } | |
| 287 | + } | |
| 288 | + | |
| 278 | 289 | // Set a single cube in the observation container |
| 279 | 290 | obs_cube(); |
| 280 | 291 | |
| ... | ... | @@ -364,7 +375,7 @@ void ctbin::publish(const std::string& name) |
| 364 | 375 | } |
| 365 | 376 | |
| 366 | 377 | // Publish counts cube |
| 367 | - m_cube.publish(user_name); | |
| 378 | + m_counts.publish(user_name); | |
| 368 | 379 | |
| 369 | 380 | // Return |
| 370 | 381 | return; |
| ... | ... | @@ -389,7 +400,8 @@ void ctbin::init_members(void) |
| 389 | 400 | |
| 390 | 401 | // Initialise protected members |
| 391 | 402 | m_obs.clear(); |
| 392 | - m_cube.clear(); | |
| 403 | + m_counts.clear(); | |
| 404 | + m_weights.clear(); | |
| 393 | 405 | m_ebounds.clear(); |
| 394 | 406 | m_gti.clear(); |
| 395 | 407 | m_ontime = 0.0; |
| ... | ... | @@ -414,7 +426,8 @@ void ctbin::copy_members(const ctbin& app) |
| 414 | 426 | |
| 415 | 427 | // Copy protected members |
| 416 | 428 | m_obs = app.m_obs; |
| 417 | - m_cube = app.m_cube; | |
| 429 | + m_counts = app.m_counts; | |
| 430 | + m_weights = app.m_weights; | |
| 418 | 431 | m_ebounds = app.m_ebounds; |
| 419 | 432 | m_gti = app.m_gti; |
| 420 | 433 | m_ontime = app.m_ontime; |
| ... | ... | @@ -464,9 +477,11 @@ void ctbin::get_parameters(void) |
| 464 | 477 | // Create an event cube based on task parameters |
| 465 | 478 | GCTAEventCube cube = create_cube(m_obs); |
| 466 | 479 | |
| 467 | - // Get the skymap from the cube and initialise all pixels to zero | |
| 468 | - m_cube = cube.counts(); | |
| 469 | - m_cube = 0.0; | |
| 480 | + // Get the skymap from the cube and initialise all counts cube bins and | |
| 481 | + // weights to zero | |
| 482 | + m_counts = cube.counts(); | |
| 483 | + m_counts = 0.0; | |
| 484 | + m_weights = m_counts; | |
| 470 | 485 | |
| 471 | 486 | // Get energy boundaries |
| 472 | 487 | m_ebounds = cube.ebounds(); |
| ... | ... | @@ -497,7 +512,7 @@ void ctbin::get_parameters(void) |
| 497 | 512 | * @param[in] obs CTA observation. |
| 498 | 513 | * |
| 499 | 514 | * @exception GException::invalid_value |
| 500 | - * No event list found in observation. | |
| 515 | + * No event list or valid RoI found in observation. | |
| 501 | 516 | * |
| 502 | 517 | * Fills the events from an event list in the counts cube setup by init_cube. |
| 503 | 518 | ***************************************************************************/ |
| ... | ... | @@ -508,7 +523,8 @@ void ctbin::fill_cube(GCTAObservation* obs) |
| 508 | 523 | |
| 509 | 524 | // Make sure that the observation holds a CTA event list. If this |
| 510 | 525 | // is not the case then throw an exception. |
| 511 | - const GCTAEventList* events = dynamic_cast<const GCTAEventList*>(obs->events()); | |
| 526 | + const GCTAEventList* events = | |
| 527 | + dynamic_cast<const GCTAEventList*>(obs->events()); | |
| 512 | 528 | if (events == NULL) { |
| 513 | 529 | std::string msg = "CTA Observation does not contain an event " |
| 514 | 530 | "list. An event list is needed to fill the " |
| ... | ... | @@ -519,9 +535,6 @@ void ctbin::fill_cube(GCTAObservation* obs) |
| 519 | 535 | // Get the RoI |
| 520 | 536 | const GCTARoi& roi = events->roi(); |
| 521 | 537 | |
| 522 | - // Get the ebounds | |
| 523 | - const GEbounds& obs_ebounds = events->ebounds(); | |
| 524 | - | |
| 525 | 538 | // Check for RoI sanity |
| 526 | 539 | if (!roi.is_valid()) { |
| 527 | 540 | std::string msg = "No valid RoI found in input observation " |
| ... | ... | @@ -537,7 +550,7 @@ void ctbin::fill_cube(GCTAObservation* obs) |
| 537 | 550 | int num_outside_ebds = 0; |
| 538 | 551 | int num_in_map = 0; |
| 539 | 552 | |
| 540 | - // Fill sky map | |
| 553 | + // Fill counts sky map | |
| 541 | 554 | for (int i = 0; i < events->size(); ++i) { |
| 542 | 555 | |
| 543 | 556 | // Get event |
| ... | ... | @@ -546,7 +559,7 @@ void ctbin::fill_cube(GCTAObservation* obs) |
| 546 | 559 | // Determine sky pixel |
| 547 | 560 | GCTAInstDir* inst = (GCTAInstDir*)&(event->dir()); |
| 548 | 561 | GSkyDir dir = inst->dir(); |
| 549 | - GSkyPixel pixel = m_cube.dir2pix(dir); | |
| 562 | + GSkyPixel pixel = m_counts.dir2pix(dir); | |
| 550 | 563 | |
| 551 | 564 | // Skip if pixel is outside RoI |
| 552 | 565 | if (roi.centre().dir().dist_deg(dir) > roi.radius()) { |
| ... | ... | @@ -554,28 +567,42 @@ void ctbin::fill_cube(GCTAObservation* obs) |
| 554 | 567 | continue; |
| 555 | 568 | } |
| 556 | 569 | |
| 557 | - // Skip if pixel is out of range | |
| 558 | - if (pixel.x() < -0.5 || pixel.x() > (m_cube.nx()-0.5) || | |
| 559 | - pixel.y() < -0.5 || pixel.y() > (m_cube.ny()-0.5)) { | |
| 570 | + // Skip if pixel is out of map range | |
| 571 | + if (pixel.x() < -0.5 || pixel.x() > (m_counts.nx()-0.5) || | |
| 572 | + pixel.y() < -0.5 || pixel.y() > (m_counts.ny()-0.5)) { | |
| 560 | 573 | num_outside_map++; |
| 561 | 574 | continue; |
| 562 | 575 | } |
| 563 | 576 | |
| 564 | - // Determine energy bin. Skip if we are outside the energy range | |
| 565 | - int index = m_ebounds.index(event->energy()); | |
| 566 | - if (index == -1 || | |
| 567 | - !obs_ebounds.contains(m_ebounds.emin(index)+g_energy_margin, | |
| 568 | - m_ebounds.emax(index)-g_energy_margin)) { | |
| 577 | + // Determine energy bin | |
| 578 | + int iebin = m_ebounds.index(event->energy()); | |
| 579 | + | |
| 580 | + // Skip if energy bin is outside counts cube boundaries | |
| 581 | + if (iebin == -1) { | |
| 569 | 582 | num_outside_ebds++; |
| 570 | 583 | continue; |
| 571 | 584 | } |
| 572 | 585 | |
| 573 | 586 | // Fill event in skymap |
| 574 | - m_cube(pixel, index) += 1.0; | |
| 587 | + m_counts(pixel, iebin) += 1.0; | |
| 575 | 588 | num_in_map++; |
| 576 | 589 | |
| 577 | 590 | } // endfor: looped over all events |
| 578 | 591 | |
| 592 | + // Get RoI and energy weights | |
| 593 | + GSkyMap roi_weights = set_roi_weights(roi); | |
| 594 | + std::vector<double> energy_weights = set_energy_weights(events->ebounds()); | |
| 595 | + | |
| 596 | + // Add weights multiplied by livetime to weight map. The map is later | |
| 597 | + // divided by the cumulative livetime in run(). | |
| 598 | + for (int iebin = 0; iebin < energy_weights.size(); ++iebin) { | |
| 599 | + for (int index = 0; index < roi_weights.npix(); ++index) { | |
| 600 | + m_weights(index, iebin) += roi_weights(index) * | |
| 601 | + energy_weights[iebin] * | |
| 602 | + obs->livetime(); | |
| 603 | + } | |
| 604 | + } | |
| 605 | + | |
| 579 | 606 | // Append GTIs |
| 580 | 607 | m_gti.extend(events->gti()); |
| 581 | 608 | |
| ... | ... | @@ -600,7 +627,7 @@ void ctbin::fill_cube(GCTAObservation* obs) |
| 600 | 627 | // Log cube |
| 601 | 628 | if (logExplicit()) { |
| 602 | 629 | log.header1("Counts cube"); |
| 603 | - log << m_cube << std::endl; | |
| 630 | + log << m_counts << std::endl; | |
| 604 | 631 | } |
| 605 | 632 | |
| 606 | 633 | } // endif: observation was valid |
| ... | ... | @@ -647,7 +674,7 @@ void ctbin::obs_cube(void) |
| 647 | 674 | else { |
| 648 | 675 | |
| 649 | 676 | // Create empty counts cube |
| 650 | - GCTAEventCube cube(m_cube, m_ebounds, obs->gti()); | |
| 677 | + GCTAEventCube cube(m_counts, m_weights, m_ebounds, obs->gti()); | |
| 651 | 678 | |
| 652 | 679 | // Assign empty cube to observation |
| 653 | 680 | obs->events(cube); |
| ... | ... | @@ -732,3 +759,165 @@ void ctbin::obs_cube(void) |
| 732 | 759 | // Return |
| 733 | 760 | return; |
| 734 | 761 | } |
| 762 | + | |
| 763 | + | |
| 764 | +/***********************************************************************//** | |
| 765 | + * @brief Set RoI weights. | |
| 766 | + * | |
| 767 | + * @param[in] roi Region of Interest. | |
| 768 | + * @return Sky map with Region of Interest weights. | |
| 769 | + * | |
| 770 | + * Computes a sky map of Region of Interest (RoI) weights. All pixels of the | |
| 771 | + * sky map that are fully comprised within the RoI will be set to unity. | |
| 772 | + * Pixels that are only partly comprised will be devided into a 10x10 subgrid, | |
| 773 | + * and a weight corresponding to the fraction of subgrid points that are | |
| 774 | + * fully comprised within the RoI will be assigned to the sky map pixel. | |
| 775 | + * The precision of the weights is about 1%. | |
| 776 | + ***************************************************************************/ | |
| 777 | +GSkyMap ctbin::set_roi_weights(const GCTARoi& roi) const | |
| 778 | +{ | |
| 779 | + // Set number of points in subgrid | |
| 780 | + const int n_subgrid = 10; | |
| 781 | + | |
| 782 | + // Initialise ROI weight sky map with zero weights | |
| 783 | + GSkyMap weights(m_counts.extract(0)); | |
| 784 | + weights = 0.0; | |
| 785 | + | |
| 786 | + // Compute save RoI radius. All pixels with bin centre within that radius | |
| 787 | + // will be set to unity. In case that the counts cube is not a WCS map | |
| 788 | + // no pixels will be considered within the safe radius. | |
| 789 | + double margin(roi.radius()); | |
| 790 | + const GWcs* wcs(dynamic_cast<const GWcs*>(m_counts.projection())); | |
| 791 | + if (wcs != NULL) { | |
| 792 | + double dx = wcs->cdelt(0); | |
| 793 | + double dy = wcs->cdelt(1); | |
| 794 | + margin = (dx > dy) ? dx : dy; | |
| 795 | + } | |
| 796 | + double safe_radius = roi.radius() - margin; | |
| 797 | + | |
| 798 | + // Loop over all sky map pixels | |
| 799 | + for (int i = 0; i < weights.npix(); ++i) { | |
| 800 | + | |
| 801 | + // Get pixel sky direction | |
| 802 | + GSkyDir dir(weights.inx2dir(i)); | |
| 803 | + | |
| 804 | + // If pixel is within the safe radius then set the pixel weight to | |
| 805 | + // unity ... | |
| 806 | + if (roi.centre().dir().dist_deg(dir) <= safe_radius) { | |
| 807 | + weights(i) = 1.0; | |
| 808 | + } | |
| 809 | + | |
| 810 | + // ... otherwise define a subgrid a check all positions within that | |
| 811 | + // subgrid that fall within the RoI. This is an approximation that | |
| 812 | + // determines the weight of the pixels | |
| 813 | + else { | |
| 814 | + | |
| 815 | + // Set pixel centre | |
| 816 | + GSkyPixel centre(weights.inx2pix(i)); | |
| 817 | + | |
| 818 | + // Compute subgrid pixel step size | |
| 819 | + double step = 1.0 / double(n_subgrid-1); | |
| 820 | + | |
| 821 | + // Compute sky direction weight | |
| 822 | + double weight = 1.0 / (double(n_subgrid) * double(n_subgrid)); | |
| 823 | + | |
| 824 | + // Loop over X subgrid | |
| 825 | + for (int ix = 0; ix < n_subgrid; ++ix) { | |
| 826 | + | |
| 827 | + // Compute X pixel value | |
| 828 | + double x = ix * step - 0.5; | |
| 829 | + | |
| 830 | + // Loop over Y subgrid | |
| 831 | + for (int iy = 0; iy < n_subgrid; ++iy) { | |
| 832 | + | |
| 833 | + // Compute Y pixel value | |
| 834 | + double y = iy * step - 0.5; | |
| 835 | + | |
| 836 | + // Set pixel value | |
| 837 | + GSkyPixel pixel(centre.x()+x, centre.y()+y); | |
| 838 | + | |
| 839 | + // Get pixel sky direction | |
| 840 | + GSkyDir dir(weights.pix2dir(pixel)); | |
| 841 | + | |
| 842 | + // If sky direction is within the RoI then add the direction | |
| 843 | + // to the weight for this pixel | |
| 844 | + if (roi.centre().dir().dist_deg(dir) <= roi.radius()) { | |
| 845 | + weights(i) += weight; | |
| 846 | + } | |
| 847 | + | |
| 848 | + } // endfor: loop over Y subgrid | |
| 849 | + | |
| 850 | + } // endfor: loop over X subgrid | |
| 851 | + | |
| 852 | + } // endelse: pixel was outside safe radius | |
| 853 | + | |
| 854 | + } // endfor: looped over all pixel sky maps | |
| 855 | + | |
| 856 | + // Return | |
| 857 | + return weights; | |
| 858 | +} | |
| 859 | + | |
| 860 | + | |
| 861 | +/***********************************************************************//** | |
| 862 | + * @brief Set energy weights. | |
| 863 | + * | |
| 864 | + * @param[in] ebounds Energy boundaries. | |
| 865 | + * @return Vector of energy boundary weights. | |
| 866 | + * | |
| 867 | + * Computes a vector of energy boundary weights from the overlap of the | |
| 868 | + * event list energy boundaries with the counts cube boundaries. It is | |
| 869 | + * assumed that within a bin the events are distributed according to a power | |
| 870 | + * law with spectral index of -2, and the weights are computed according | |
| 871 | + * to the fraction of this power law that is comprised within a counts cube | |
| 872 | + * bin. | |
| 873 | + * | |
| 874 | + * This method properly handles event lists with multiple energy boundaries. | |
| 875 | + ***************************************************************************/ | |
| 876 | +std::vector<double> ctbin::set_energy_weights(const GEbounds& ebounds) const | |
| 877 | +{ | |
| 878 | + // Initialise energy boundary weights with zero weights | |
| 879 | + std::vector<double> weights(m_ebounds.size(), 0.0); | |
| 880 | + | |
| 881 | + // Loop over all energy bins of counts cube | |
| 882 | + for (int i = 0; i < m_ebounds.size(); ++i) { | |
| 883 | + | |
| 884 | + // Get energy boundaries of counts cube bin | |
| 885 | + GEnergy emin(m_ebounds.emin(i)); | |
| 886 | + GEnergy emax(m_ebounds.emax(i)); | |
| 887 | + | |
| 888 | + // Loop over all energy boundaries of the event list and add the | |
| 889 | + // coverage fraction to the weights | |
| 890 | + for (int k = 0; k < ebounds.size(); ++k) { | |
| 891 | + | |
| 892 | + // If there is no overlap then check next boundary | |
| 893 | + if ((ebounds.emax(k) <= emin) || (ebounds.emin(k) >= emax)) { | |
| 894 | + continue; | |
| 895 | + } | |
| 896 | + | |
| 897 | + // Get minimum and maximum energy of overlap region | |
| 898 | + GEnergy elow = (ebounds.emin(k) > emin) ? ebounds.emin(k) : emin; | |
| 899 | + GEnergy ehigh = (ebounds.emax(k) < emax) ? ebounds.emax(k) : emax; | |
| 900 | + | |
| 901 | + // Compute overlap weight | |
| 902 | + #if defined(G_PLAW_WEIGHTS) | |
| 903 | + double w_overlap = gammalib::plaw_photon_flux(elow.TeV(), | |
| 904 | + ehigh.TeV(), | |
| 905 | + 1.0, -2.0); | |
| 906 | + double w_full = gammalib::plaw_photon_flux(emin.TeV(), | |
| 907 | + emax.TeV(), | |
| 908 | + 1.0, -2.0); | |
| 909 | + double weight = w_overlap / w_full; | |
| 910 | + #else | |
| 911 | + double weight = (ehigh - elow) / (emax - emin); | |
| 912 | + #endif | |
| 913 | + | |
| 914 | + // Add overlap weight | |
| 915 | + weights[i] += weight; | |
| 916 | + | |
| 917 | + } // endfor: looped over all energy boundaries of the event list | |
| 918 | + | |
| 919 | + } // endfor: looped over all energy boundaries of the counts cube | |
| 920 | + | |
| 921 | + // Return | |
| 922 | + return weights; | |
| 923 | +} | ... | ... |
src/ctbin/ctbin.hpp
| ... | ... | @@ -28,6 +28,7 @@ |
| 28 | 28 | #define CTBIN_HPP |
| 29 | 29 | |
| 30 | 30 | /* __ Includes ___________________________________________________________ */ |
| 31 | +#include <vector> | |
| 31 | 32 | #include "GammaLib.hpp" |
| 32 | 33 | #include "GCTALib.hpp" |
| 33 | 34 | #include "ctool.hpp" |
| ... | ... | @@ -84,12 +85,14 @@ public: |
| 84 | 85 | |
| 85 | 86 | protected: |
| 86 | 87 | // Protected methods |
| 87 | - void init_members(void); | |
| 88 | - void copy_members(const ctbin& app); | |
| 89 | - void free_members(void); | |
| 90 | - void get_parameters(void); | |
| 91 | - void fill_cube(GCTAObservation* obs); | |
| 92 | - void obs_cube(void); | |
| 88 | + void init_members(void); | |
| 89 | + void copy_members(const ctbin& app); | |
| 90 | + void free_members(void); | |
| 91 | + void get_parameters(void); | |
| 92 | + void fill_cube(GCTAObservation* obs); | |
| 93 | + void obs_cube(void); | |
| 94 | + GSkyMap set_roi_weights(const GCTARoi& roi) const; | |
| 95 | + std::vector<double> set_energy_weights(const GEbounds& ebounds) const; | |
| 93 | 96 | |
| 94 | 97 | // User parameters |
| 95 | 98 | GFilename m_outcube; //!< Output counts map file name |
| ... | ... | @@ -98,7 +101,8 @@ protected: |
| 98 | 101 | |
| 99 | 102 | // Protected members |
| 100 | 103 | GObservations m_obs; //!< Observation container |
| 101 | - GSkyMap m_cube; //!< Event cube | |
| 104 | + GSkyMap m_counts; //!< Event cube counts | |
| 105 | + GSkyMap m_weights; //!< Event cube weights | |
| 102 | 106 | GEbounds m_ebounds; //!< Energy boundaries |
| 103 | 107 | GGti m_gti; //!< Good time intervals |
| 104 | 108 | double m_ontime; //!< Total ontime |
| ... | ... | @@ -128,7 +132,7 @@ inline |
| 128 | 132 | GCTAEventCube ctbin::cube(void) const |
| 129 | 133 | { |
| 130 | 134 | // Build event cube |
| 131 | - GCTAEventCube cube(m_cube, m_ebounds, m_gti); | |
| 135 | + GCTAEventCube cube(m_counts, m_weights, m_ebounds, m_gti); | |
| 132 | 136 | |
| 133 | 137 | // Return cube |
| 134 | 138 | return cube; | ... | ... |