Commit 2ee8bac10df977f80447a0a74b2aa08a9b5dede0

Authored by Jürgen Knödlseder
1 parent 28b19876

Implement counts cube weights computation in ctbin

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 * Version 1.1.0 released 3 * Version 1.1.0 released
4 ======================== 4 ========================
5 5
  6 + Compute counts cube weights in ctbin (#1789)
6 Adapt to new stacked cube interface (#1788) 7 Adapt to new stacked cube interface (#1788)
7 Add ctools and cscripts unit tests 8 Add ctools and cscripts unit tests
8 Add "bgdoversample" and "bgdinfill" parameters to csroot2caldb 9 Add "bgdoversample" and "bgdinfill" parameters to csroot2caldb
1 New Features and Important Changes in ctools 1.1.0 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 Introduction 6 Introduction
@@ -92,6 +92,14 @@ ctbin - CTA event binning @@ -92,6 +92,14 @@ ctbin - CTA event binning
92 Add publish() method to publish the counts cube via a SAMP Hub to Virtual 92 Add publish() method to publish the counts cube via a SAMP Hub to Virtual
93 Observatory interoperable tools (e.g. Aladin). 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 ctlike - Maximum likelihood fitting 104 ctlike - Maximum likelihood fitting
97 ----------------------------------- 105 -----------------------------------
src/ctbin/ctbin.cpp
@@ -39,9 +39,9 @@ @@ -39,9 +39,9 @@
39 /* __ Debug definitions __________________________________________________ */ 39 /* __ Debug definitions __________________________________________________ */
40 40
41 /* __ Coding definitions _________________________________________________ */ 41 /* __ Coding definitions _________________________________________________ */
  42 +#define G_PLAW_WEIGHTS //!< Use power law in energy weight computation
42 43
43 /* __ Constants __________________________________________________________ */ 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,7 +62,7 @@ ctbin::ctbin(void) : ctool(CTBIN_NAME, CTBIN_VERSION)
62 62
63 // Return 63 // Return
64 return; 64 return;
65 -} // GCOV_EXCL_LINE 65 +}
66 66
67 67
68 /***********************************************************************//** 68 /***********************************************************************//**
@@ -211,7 +211,9 @@ void ctbin::clear(void) @@ -211,7 +211,9 @@ void ctbin::clear(void)
211 * @brief Run the ctbin tool 211 * @brief Run the ctbin tool
212 * 212 *
213 * Gets the user parameters and loops over all CTA observations in the 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 void ctbin::run(void) 218 void ctbin::run(void)
217 { 219 {
@@ -275,6 +277,15 @@ void ctbin::run(void) @@ -275,6 +277,15 @@ void ctbin::run(void)
275 277
276 } // endfor: looped over observations 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 // Set a single cube in the observation container 289 // Set a single cube in the observation container
279 obs_cube(); 290 obs_cube();
280 291
@@ -364,7 +375,7 @@ void ctbin::publish(const std::string&amp; name) @@ -364,7 +375,7 @@ void ctbin::publish(const std::string&amp; name)
364 } 375 }
365 376
366 // Publish counts cube 377 // Publish counts cube
367 - m_cube.publish(user_name); 378 + m_counts.publish(user_name);
368 379
369 // Return 380 // Return
370 return; 381 return;
@@ -389,7 +400,8 @@ void ctbin::init_members(void) @@ -389,7 +400,8 @@ void ctbin::init_members(void)
389 400
390 // Initialise protected members 401 // Initialise protected members
391 m_obs.clear(); 402 m_obs.clear();
392 - m_cube.clear(); 403 + m_counts.clear();
  404 + m_weights.clear();
393 m_ebounds.clear(); 405 m_ebounds.clear();
394 m_gti.clear(); 406 m_gti.clear();
395 m_ontime = 0.0; 407 m_ontime = 0.0;
@@ -414,7 +426,8 @@ void ctbin::copy_members(const ctbin&amp; app) @@ -414,7 +426,8 @@ void ctbin::copy_members(const ctbin&amp; app)
414 426
415 // Copy protected members 427 // Copy protected members
416 m_obs = app.m_obs; 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 m_ebounds = app.m_ebounds; 431 m_ebounds = app.m_ebounds;
419 m_gti = app.m_gti; 432 m_gti = app.m_gti;
420 m_ontime = app.m_ontime; 433 m_ontime = app.m_ontime;
@@ -464,9 +477,11 @@ void ctbin::get_parameters(void) @@ -464,9 +477,11 @@ void ctbin::get_parameters(void)
464 // Create an event cube based on task parameters 477 // Create an event cube based on task parameters
465 GCTAEventCube cube = create_cube(m_obs); 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 // Get energy boundaries 486 // Get energy boundaries
472 m_ebounds = cube.ebounds(); 487 m_ebounds = cube.ebounds();
@@ -497,7 +512,7 @@ void ctbin::get_parameters(void) @@ -497,7 +512,7 @@ void ctbin::get_parameters(void)
497 * @param[in] obs CTA observation. 512 * @param[in] obs CTA observation.
498 * 513 *
499 * @exception GException::invalid_value 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 * Fills the events from an event list in the counts cube setup by init_cube. 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,7 +523,8 @@ void ctbin::fill_cube(GCTAObservation* obs)
508 523
509 // Make sure that the observation holds a CTA event list. If this 524 // Make sure that the observation holds a CTA event list. If this
510 // is not the case then throw an exception. 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 if (events == NULL) { 528 if (events == NULL) {
513 std::string msg = "CTA Observation does not contain an event " 529 std::string msg = "CTA Observation does not contain an event "
514 "list. An event list is needed to fill the " 530 "list. An event list is needed to fill the "
@@ -519,9 +535,6 @@ void ctbin::fill_cube(GCTAObservation* obs) @@ -519,9 +535,6 @@ void ctbin::fill_cube(GCTAObservation* obs)
519 // Get the RoI 535 // Get the RoI
520 const GCTARoi& roi = events->roi(); 536 const GCTARoi& roi = events->roi();
521 537
522 - // Get the ebounds  
523 - const GEbounds& obs_ebounds = events->ebounds();  
524 -  
525 // Check for RoI sanity 538 // Check for RoI sanity
526 if (!roi.is_valid()) { 539 if (!roi.is_valid()) {
527 std::string msg = "No valid RoI found in input observation " 540 std::string msg = "No valid RoI found in input observation "
@@ -537,7 +550,7 @@ void ctbin::fill_cube(GCTAObservation* obs) @@ -537,7 +550,7 @@ void ctbin::fill_cube(GCTAObservation* obs)
537 int num_outside_ebds = 0; 550 int num_outside_ebds = 0;
538 int num_in_map = 0; 551 int num_in_map = 0;
539 552
540 - // Fill sky map 553 + // Fill counts sky map
541 for (int i = 0; i < events->size(); ++i) { 554 for (int i = 0; i < events->size(); ++i) {
542 555
543 // Get event 556 // Get event
@@ -546,7 +559,7 @@ void ctbin::fill_cube(GCTAObservation* obs) @@ -546,7 +559,7 @@ void ctbin::fill_cube(GCTAObservation* obs)
546 // Determine sky pixel 559 // Determine sky pixel
547 GCTAInstDir* inst = (GCTAInstDir*)&(event->dir()); 560 GCTAInstDir* inst = (GCTAInstDir*)&(event->dir());
548 GSkyDir dir = inst->dir(); 561 GSkyDir dir = inst->dir();
549 - GSkyPixel pixel = m_cube.dir2pix(dir); 562 + GSkyPixel pixel = m_counts.dir2pix(dir);
550 563
551 // Skip if pixel is outside RoI 564 // Skip if pixel is outside RoI
552 if (roi.centre().dir().dist_deg(dir) > roi.radius()) { 565 if (roi.centre().dir().dist_deg(dir) > roi.radius()) {
@@ -554,28 +567,42 @@ void ctbin::fill_cube(GCTAObservation* obs) @@ -554,28 +567,42 @@ void ctbin::fill_cube(GCTAObservation* obs)
554 continue; 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 num_outside_map++; 573 num_outside_map++;
561 continue; 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 num_outside_ebds++; 582 num_outside_ebds++;
570 continue; 583 continue;
571 } 584 }
572 585
573 // Fill event in skymap 586 // Fill event in skymap
574 - m_cube(pixel, index) += 1.0; 587 + m_counts(pixel, iebin) += 1.0;
575 num_in_map++; 588 num_in_map++;
576 589
577 } // endfor: looped over all events 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 // Append GTIs 606 // Append GTIs
580 m_gti.extend(events->gti()); 607 m_gti.extend(events->gti());
581 608
@@ -600,7 +627,7 @@ void ctbin::fill_cube(GCTAObservation* obs) @@ -600,7 +627,7 @@ void ctbin::fill_cube(GCTAObservation* obs)
600 // Log cube 627 // Log cube
601 if (logExplicit()) { 628 if (logExplicit()) {
602 log.header1("Counts cube"); 629 log.header1("Counts cube");
603 - log << m_cube << std::endl; 630 + log << m_counts << std::endl;
604 } 631 }
605 632
606 } // endif: observation was valid 633 } // endif: observation was valid
@@ -647,7 +674,7 @@ void ctbin::obs_cube(void) @@ -647,7 +674,7 @@ void ctbin::obs_cube(void)
647 else { 674 else {
648 675
649 // Create empty counts cube 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 // Assign empty cube to observation 679 // Assign empty cube to observation
653 obs->events(cube); 680 obs->events(cube);
@@ -732,3 +759,165 @@ void ctbin::obs_cube(void) @@ -732,3 +759,165 @@ void ctbin::obs_cube(void)
732 // Return 759 // Return
733 return; 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,6 +28,7 @@
28 #define CTBIN_HPP 28 #define CTBIN_HPP
29 29
30 /* __ Includes ___________________________________________________________ */ 30 /* __ Includes ___________________________________________________________ */
  31 +#include <vector>
31 #include "GammaLib.hpp" 32 #include "GammaLib.hpp"
32 #include "GCTALib.hpp" 33 #include "GCTALib.hpp"
33 #include "ctool.hpp" 34 #include "ctool.hpp"
@@ -84,12 +85,14 @@ public: @@ -84,12 +85,14 @@ public:
84 85
85 protected: 86 protected:
86 // Protected methods 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 // User parameters 97 // User parameters
95 GFilename m_outcube; //!< Output counts map file name 98 GFilename m_outcube; //!< Output counts map file name
@@ -98,7 +101,8 @@ protected: @@ -98,7 +101,8 @@ protected:
98 101
99 // Protected members 102 // Protected members
100 GObservations m_obs; //!< Observation container 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 GEbounds m_ebounds; //!< Energy boundaries 106 GEbounds m_ebounds; //!< Energy boundaries
103 GGti m_gti; //!< Good time intervals 107 GGti m_gti; //!< Good time intervals
104 double m_ontime; //!< Total ontime 108 double m_ontime; //!< Total ontime
@@ -128,7 +132,7 @@ inline @@ -128,7 +132,7 @@ inline
128 GCTAEventCube ctbin::cube(void) const 132 GCTAEventCube ctbin::cube(void) const
129 { 133 {
130 // Build event cube 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 // Return cube 137 // Return cube
134 return cube; 138 return cube;