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 33 #include "GTools.hpp"
34 34  
35 35 /* __ Method name definitions ____________________________________________ */
36   -#define G_FILL_CUBE "ctbin::fill_cube(GCTAObservation*)"
37 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 40 /* __ Debug definitions __________________________________________________ */
40 41  
... ... @@ -229,7 +230,7 @@ void ctbin::run(void)
229 230 if (logTerse()) {
230 231 log << std::endl;
231 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 236 // Write header
... ... @@ -272,20 +273,14 @@ void ctbin::run(void)
272 273 // Fill the cube
273 274 fill_cube(obs);
274 275  
  276 + // Set the counts cube weights
  277 + set_weights(obs);
  278 +
275 279 // Dispose events to free memory
276 280 obs->dispose_events();
277 281  
278 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 284 // Set a single cube in the observation container
290 285 obs_cube();
291 286  
... ... @@ -293,7 +288,7 @@ void ctbin::run(void)
293 288 if (logTerse()) {
294 289 log << std::endl;
295 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 294 // Optionally publish counts cube
... ... @@ -397,6 +392,7 @@ void ctbin::init_members(void)
397 392 m_outcube.clear();
398 393 m_usepnt = false;
399 394 m_publish = false;
  395 + m_chatter = static_cast<GChatter>(2);
400 396  
401 397 // Initialise protected members
402 398 m_obs.clear();
... ... @@ -423,15 +419,16 @@ void ctbin::copy_members(const ctbin&amp; app)
423 419 m_outcube = app.m_outcube;
424 420 m_usepnt = app.m_usepnt;
425 421 m_publish = app.m_publish;
  422 + m_chatter = app.m_chatter;
426 423  
427 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 433 // Return
437 434 return;
... ... @@ -484,10 +481,11 @@ void ctbin::get_parameters(void)
484 481 m_weights = m_counts;
485 482  
486 483 // Get energy boundaries
487   - m_ebounds = cube.ebounds();
  484 + m_ebounds = cube.ebounds();
488 485  
489 486 // Get remaining parameters
490 487 m_publish = (*this)["publish"].boolean();
  488 + m_chatter = static_cast<GChatter>((*this)["chatter"].integer());
491 489  
492 490 // Optionally read ahead parameters so that they get correctly
493 491 // dumped into the log file
... ... @@ -523,8 +521,8 @@ void ctbin::fill_cube(GCTAObservation* obs)
523 521  
524 522 // Make sure that the observation holds a CTA event list. If this
525 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 526 if (events == NULL) {
529 527 std::string msg = "CTA Observation does not contain an event "
530 528 "list. An event list is needed to fill the "
... ... @@ -544,6 +542,9 @@ void ctbin::fill_cube(GCTAObservation* obs)
544 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 548 // Initialise binning statistics
548 549 int num_outside_roi = 0;
549 550 int num_outside_map = 0;
... ... @@ -561,24 +562,27 @@ void ctbin::fill_cube(GCTAObservation* obs)
561 562 GSkyDir dir = inst->dir();
562 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 566 if (roi.centre().dir().dist_deg(dir) > roi.radius()) {
566 567 num_outside_roi++;
567 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 573 if (pixel.x() < -0.5 || pixel.x() > (m_counts.nx()-0.5) ||
572 574 pixel.y() < -0.5 || pixel.y() > (m_counts.ny()-0.5)) {
573 575 num_outside_map++;
574 576 continue;
575 577 }
576 578  
577   - // Determine energy bin
  579 + // Determine counts cube energy bin
578 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 586 num_outside_ebds++;
583 587 continue;
584 588 }
... ... @@ -587,21 +591,10 @@ void ctbin::fill_cube(GCTAObservation* obs)
587 591 m_counts(pixel, iebin) += 1.0;
588 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 599 // Append GTIs
607 600 m_gti.extend(events->gti());
... ... @@ -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 708 * @brief Create output observation container.
642 709 *
643 710 * Creates an output observation container that combines all input CTA
... ... @@ -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 90 void free_members(void);
91 91 void get_parameters(void);
92 92 void fill_cube(GCTAObservation* obs);
  93 + void set_weights(GCTAObservation* obs);
93 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 97 // User parameters
98 98 GFilename m_outcube; //!< Output counts map file name
99 99 bool m_usepnt; //!< Use pointing instead of xref/yref parameters
100 100 bool m_publish; //!< Publish counts cube?
  101 + GChatter m_chatter; //!< Chattiness
101 102  
102 103 // Protected members
103 104 GObservations m_obs; //!< Observation container
... ...