Commit e8719183f1b457c156db04d9fe3795dd2156fff6

Authored by Jürgen Knödlseder
1 parent 74bcfe43

Revert to considering only fully contained counts cube bins (#1819)

It turned out that the partial coverage of counts cube bins using the weighting array does not work. The problem is spill over of events from one RoI to another than can not be dealt with at the level of a stacked analysis. The old logic of using only fully contained pixel is put back into place. The weighting array is (for the moment) conserved as it could serve other purposes. However, excluding of pixels is already accomplished by setting a pixel value to -1, so it remains to be seen whether we would like to keep that logic or if we would like to use the weighting array (which is more universal).
src/ctbin/ctbin.cpp
@@ -33,8 +33,9 @@ @@ -33,8 +33,9 @@
33 #include "GTools.hpp" 33 #include "GTools.hpp"
34 34
35 /* __ Method name definitions ____________________________________________ */ 35 /* __ Method name definitions ____________________________________________ */
36 -#define G_FILL_CUBE "ctbin::fill_cube(GCTAObservation*)"  
37 #define G_GET_PARAMETERS "ctbin::get_parameters()" 36 #define G_GET_PARAMETERS "ctbin::get_parameters()"
  37 +#define G_FILL_CUBE "ctbin::fill_cube(GCTAObservation*)"
  38 +#define G_SET_WEIGHTS "ctbin::set_weights(GCTAObservation*)"
38 39
39 /* __ Debug definitions __________________________________________________ */ 40 /* __ Debug definitions __________________________________________________ */
40 41
@@ -229,7 +230,7 @@ void ctbin::run(void) @@ -229,7 +230,7 @@ void ctbin::run(void)
229 if (logTerse()) { 230 if (logTerse()) {
230 log << std::endl; 231 log << std::endl;
231 log.header1(gammalib::number("Observation", m_obs.size())); 232 log.header1(gammalib::number("Observation", m_obs.size()));
232 - log << m_obs << std::endl; 233 + log << m_obs.print(m_chatter) << std::endl;
233 } 234 }
234 235
235 // Write header 236 // Write header
@@ -272,20 +273,14 @@ void ctbin::run(void) @@ -272,20 +273,14 @@ void ctbin::run(void)
272 // Fill the cube 273 // Fill the cube
273 fill_cube(obs); 274 fill_cube(obs);
274 275
  276 + // Set the counts cube weights
  277 + set_weights(obs);
  278 +
275 // Dispose events to free memory 279 // Dispose events to free memory
276 obs->dispose_events(); 280 obs->dispose_events();
277 281
278 } // endfor: looped over observations 282 } // endfor: looped over observations
279 283
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 -  
289 // Set a single cube in the observation container 284 // Set a single cube in the observation container
290 obs_cube(); 285 obs_cube();
291 286
@@ -293,7 +288,7 @@ void ctbin::run(void) @@ -293,7 +288,7 @@ void ctbin::run(void)
293 if (logTerse()) { 288 if (logTerse()) {
294 log << std::endl; 289 log << std::endl;
295 log.header1(gammalib::number("Binned observation", m_obs.size())); 290 log.header1(gammalib::number("Binned observation", m_obs.size()));
296 - log << m_obs << std::endl; 291 + log << m_obs.print(m_chatter) << std::endl;
297 } 292 }
298 293
299 // Optionally publish counts cube 294 // Optionally publish counts cube
@@ -397,6 +392,7 @@ void ctbin::init_members(void) @@ -397,6 +392,7 @@ void ctbin::init_members(void)
397 m_outcube.clear(); 392 m_outcube.clear();
398 m_usepnt = false; 393 m_usepnt = false;
399 m_publish = false; 394 m_publish = false;
  395 + m_chatter = static_cast<GChatter>(2);
400 396
401 // Initialise protected members 397 // Initialise protected members
402 m_obs.clear(); 398 m_obs.clear();
@@ -423,15 +419,16 @@ void ctbin::copy_members(const ctbin&amp; app) @@ -423,15 +419,16 @@ void ctbin::copy_members(const ctbin&amp; app)
423 m_outcube = app.m_outcube; 419 m_outcube = app.m_outcube;
424 m_usepnt = app.m_usepnt; 420 m_usepnt = app.m_usepnt;
425 m_publish = app.m_publish; 421 m_publish = app.m_publish;
  422 + m_chatter = app.m_chatter;
426 423
427 // Copy protected members 424 // Copy protected members
428 - m_obs = app.m_obs;  
429 - m_counts = app.m_counts;  
430 - m_weights = app.m_weights;  
431 - m_ebounds = app.m_ebounds;  
432 - m_gti = app.m_gti;  
433 - m_ontime = app.m_ontime;  
434 - m_livetime = app.m_livetime; 425 + m_obs = app.m_obs;
  426 + m_counts = app.m_counts;
  427 + m_weights = app.m_weights;
  428 + m_ebounds = app.m_ebounds;
  429 + m_gti = app.m_gti;
  430 + m_ontime = app.m_ontime;
  431 + m_livetime = app.m_livetime;
435 432
436 // Return 433 // Return
437 return; 434 return;
@@ -484,10 +481,11 @@ void ctbin::get_parameters(void) @@ -484,10 +481,11 @@ void ctbin::get_parameters(void)
484 m_weights = m_counts; 481 m_weights = m_counts;
485 482
486 // Get energy boundaries 483 // Get energy boundaries
487 - m_ebounds = cube.ebounds(); 484 + m_ebounds = cube.ebounds();
488 485
489 // Get remaining parameters 486 // Get remaining parameters
490 m_publish = (*this)["publish"].boolean(); 487 m_publish = (*this)["publish"].boolean();
  488 + m_chatter = static_cast<GChatter>((*this)["chatter"].integer());
491 489
492 // Optionally read ahead parameters so that they get correctly 490 // Optionally read ahead parameters so that they get correctly
493 // dumped into the log file 491 // dumped into the log file
@@ -523,8 +521,8 @@ void ctbin::fill_cube(GCTAObservation* obs) @@ -523,8 +521,8 @@ void ctbin::fill_cube(GCTAObservation* obs)
523 521
524 // Make sure that the observation holds a CTA event list. If this 522 // Make sure that the observation holds a CTA event list. If this
525 // is not the case then throw an exception. 523 // is not the case then throw an exception.
526 - const GCTAEventList* events =  
527 - dynamic_cast<const GCTAEventList*>(obs->events()); 524 + const GCTAEventList* events = dynamic_cast<const GCTAEventList*>
  525 + (obs->events());
528 if (events == NULL) { 526 if (events == NULL) {
529 std::string msg = "CTA Observation does not contain an event " 527 std::string msg = "CTA Observation does not contain an event "
530 "list. An event list is needed to fill the " 528 "list. An event list is needed to fill the "
@@ -544,6 +542,9 @@ void ctbin::fill_cube(GCTAObservation* obs) @@ -544,6 +542,9 @@ void ctbin::fill_cube(GCTAObservation* obs)
544 throw GException::invalid_value(G_FILL_CUBE, msg); 542 throw GException::invalid_value(G_FILL_CUBE, msg);
545 } 543 }
546 544
  545 + // Get counts cube usage flags
  546 + std::vector<bool> usage = cube_layer_usage(events->ebounds());
  547 +
547 // Initialise binning statistics 548 // Initialise binning statistics
548 int num_outside_roi = 0; 549 int num_outside_roi = 0;
549 int num_outside_map = 0; 550 int num_outside_map = 0;
@@ -561,24 +562,27 @@ void ctbin::fill_cube(GCTAObservation* obs) @@ -561,24 +562,27 @@ void ctbin::fill_cube(GCTAObservation* obs)
561 GSkyDir dir = inst->dir(); 562 GSkyDir dir = inst->dir();
562 GSkyPixel pixel = m_counts.dir2pix(dir); 563 GSkyPixel pixel = m_counts.dir2pix(dir);
563 564
564 - // Skip if pixel is outside RoI 565 + // Skip event if corresponding counts cube pixel is outside RoI
565 if (roi.centre().dir().dist_deg(dir) > roi.radius()) { 566 if (roi.centre().dir().dist_deg(dir) > roi.radius()) {
566 num_outside_roi++; 567 num_outside_roi++;
567 continue; 568 continue;
568 } 569 }
569 570
570 - // Skip if pixel is out of map range 571 + // Skip event if corresponding counts cube pixel is outside the
  572 + // counts cube map range
571 if (pixel.x() < -0.5 || pixel.x() > (m_counts.nx()-0.5) || 573 if (pixel.x() < -0.5 || pixel.x() > (m_counts.nx()-0.5) ||
572 pixel.y() < -0.5 || pixel.y() > (m_counts.ny()-0.5)) { 574 pixel.y() < -0.5 || pixel.y() > (m_counts.ny()-0.5)) {
573 num_outside_map++; 575 num_outside_map++;
574 continue; 576 continue;
575 } 577 }
576 578
577 - // Determine energy bin 579 + // Determine counts cube energy bin
578 int iebin = m_ebounds.index(event->energy()); 580 int iebin = m_ebounds.index(event->energy());
579 581
580 - // Skip if energy bin is outside counts cube boundaries  
581 - if (iebin == -1) { 582 + // Skip event if the corresponding counts cube energy bin is not
  583 + // fully contained in the event list energy range. This avoids
  584 + // having partially filled bins.
  585 + if (!usage[iebin]) {
582 num_outside_ebds++; 586 num_outside_ebds++;
583 continue; 587 continue;
584 } 588 }
@@ -587,21 +591,10 @@ void ctbin::fill_cube(GCTAObservation* obs) @@ -587,21 +591,10 @@ void ctbin::fill_cube(GCTAObservation* obs)
587 m_counts(pixel, iebin) += 1.0; 591 m_counts(pixel, iebin) += 1.0;
588 num_in_map++; 592 num_in_map++;
589 593
590 - } // endfor: looped over all events 594 + // Signal that bin was filled
  595 + m_weights(pixel, iebin) = 1.0;
591 596
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 - } 597 + } // endfor: looped over all events
605 598
606 // Append GTIs 599 // Append GTIs
607 m_gti.extend(events->gti()); 600 m_gti.extend(events->gti());
@@ -638,6 +631,80 @@ void ctbin::fill_cube(GCTAObservation* obs) @@ -638,6 +631,80 @@ void ctbin::fill_cube(GCTAObservation* obs)
638 631
639 632
640 /***********************************************************************//** 633 /***********************************************************************//**
  634 + * @brief Set counts cube weights for a given observation
  635 + *
  636 + * @param[in] obs CTA observation.
  637 + *
  638 + * @exception GException::invalid_value
  639 + * No event list or valid RoI found in observation.
  640 + *
  641 + * Sets the counts cube weights for all bins that are considered for the
  642 + * specific observation to unity.
  643 + ***************************************************************************/
  644 +void ctbin::set_weights(GCTAObservation* obs)
  645 +{
  646 + // Continue only if observation pointer is valid
  647 + if (obs != NULL) {
  648 +
  649 + // Make sure that the observation holds a CTA event list. If this
  650 + // is not the case then throw an exception.
  651 + const GCTAEventList* events = dynamic_cast<const GCTAEventList*>
  652 + (obs->events());
  653 + if (events == NULL) {
  654 + std::string msg = "CTA Observation does not contain an event "
  655 + "list. An event list is needed to fill the "
  656 + "counts cube.";
  657 + throw GException::invalid_value(G_SET_WEIGHTS, msg);
  658 + }
  659 +
  660 + // Get the RoI
  661 + const GCTARoi& roi = events->roi();
  662 +
  663 + // Check for RoI sanity
  664 + if (!roi.is_valid()) {
  665 + std::string msg = "No valid RoI found in input observation "
  666 + "\""+obs->name()+"\". Run ctselect to specify "
  667 + "an RoI for this observation before running "
  668 + "ctbin.";
  669 + throw GException::invalid_value(G_SET_WEIGHTS, msg);
  670 + }
  671 +
  672 + // Get counts cube usage flags
  673 + std::vector<bool> usage = cube_layer_usage(events->ebounds());
  674 +
  675 + // Loop over all pixels in counts cube
  676 + for (int pixel = 0; pixel < m_counts.npix(); ++pixel) {
  677 +
  678 + // Get pixel sky direction
  679 + GSkyDir dir = m_counts.inx2dir(pixel);
  680 +
  681 + // Skip pixel if it is outside the RoI
  682 + if (roi.centre().dir().dist_deg(dir) > roi.radius()) {
  683 + continue;
  684 + }
  685 +
  686 + // Loop over all energy layers of counts cube
  687 + for (int iebin = 0; iebin < m_ebounds.size(); ++iebin){
  688 +
  689 + // Skip energy layer if the usage flag is false
  690 + if (!usage[iebin]) {
  691 + continue;
  692 + }
  693 + // Signal that bin was filled
  694 + m_weights(pixel, iebin) = 1.0;
  695 +
  696 + } // endfor: looped over energy layers of counts cube
  697 +
  698 + } // endfor: looped over pixels of counts cube
  699 +
  700 + } // endif: observation was valid
  701 +
  702 + // Return
  703 + return;
  704 +}
  705 +
  706 +
  707 +/***********************************************************************//**
641 * @brief Create output observation container. 708 * @brief Create output observation container.
642 * 709 *
643 * Creates an output observation container that combines all input CTA 710 * Creates an output observation container that combines all input CTA
@@ -761,163 +828,42 @@ void ctbin::obs_cube(void) @@ -761,163 +828,42 @@ void ctbin::obs_cube(void)
761 } 828 }
762 829
763 830
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 831
860 832
861 /***********************************************************************//** 833 /***********************************************************************//**
862 - * @brief Set energy weights.  
863 - *  
864 - * @param[in] ebounds Energy boundaries.  
865 - * @return Vector of energy boundary weights. 834 + * @brief Determine the counts cube layer usage.
866 * 835 *
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. 836 + * @param[in] ebounds Energy boundaries of the event list.
  837 + * @return Vector of usage flags.
873 * 838 *
874 - * This method properly handles event lists with multiple energy boundaries. 839 + * Determines a vector of counts cube layer usage flags that signal whether
  840 + * an event should actually be filled in the counts cube or not. This makes
  841 + * sure that no partially filled bins will exist in the counts cube.
875 ***************************************************************************/ 842 ***************************************************************************/
876 -std::vector<double> ctbin::set_energy_weights(const GEbounds& ebounds) const 843 +std::vector<bool> ctbin::cube_layer_usage(const GEbounds& ebounds) const
877 { 844 {
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; 845 + // Set energy margin
  846 + const GEnergy energy_margin(0.01, "GeV");
  847 +
  848 + // Initialise usage vector
  849 + std::vector<bool> usage(m_ebounds.size(), true);
  850 +
  851 + // Loop over all energy bins of the cube
  852 + for (int iebin = 0; iebin < m_ebounds.size(); ++iebin) {
  853 +
  854 + // If the counts cube energy bin is fully contained within the
  855 + // energy boundaries of the event list the signal usage of this
  856 + // counts cube bin. Partially overlapping energy bins are signaled
  857 + // for non-usage, which avoids having partially filled bins in the
  858 + // counts cube. Some margin is applied that effectively reduces the
  859 + // width of the counts cube energy bin. This should cope with any
  860 + // rounding errors that come from reading and writing the energy
  861 + // boundary information to a FITS file.
  862 + usage[iebin] = ebounds.contains(m_ebounds.emin(iebin) + energy_margin,
  863 + m_ebounds.emax(iebin) - energy_margin);
  864 +
  865 + } // endfor: looped over all energy bins
  866 +
  867 + // Return usage
  868 + return usage;
923 } 869 }
src/ctbin/ctbin.hpp
@@ -90,14 +90,15 @@ protected: @@ -90,14 +90,15 @@ protected:
90 void free_members(void); 90 void free_members(void);
91 void get_parameters(void); 91 void get_parameters(void);
92 void fill_cube(GCTAObservation* obs); 92 void fill_cube(GCTAObservation* obs);
  93 + void set_weights(GCTAObservation* obs);
93 void obs_cube(void); 94 void obs_cube(void);
94 - GSkyMap set_roi_weights(const GCTARoi& roi) const;  
95 - std::vector<double> set_energy_weights(const GEbounds& ebounds) const; 95 + std::vector<bool> cube_layer_usage(const GEbounds& ebounds) const;
96 96
97 // User parameters 97 // User parameters
98 GFilename m_outcube; //!< Output counts map file name 98 GFilename m_outcube; //!< Output counts map file name
99 bool m_usepnt; //!< Use pointing instead of xref/yref parameters 99 bool m_usepnt; //!< Use pointing instead of xref/yref parameters
100 bool m_publish; //!< Publish counts cube? 100 bool m_publish; //!< Publish counts cube?
  101 + GChatter m_chatter; //!< Chattiness
101 102
102 // Protected members 103 // Protected members
103 GObservations m_obs; //!< Observation container 104 GObservations m_obs; //!< Observation container