/***************************************************************************
* ctskymap - Sky mapping tool *
* ----------------------------------------------------------------------- *
* copyright (C) 2011-2019 by Juergen Knoedlseder *
* ----------------------------------------------------------------------- *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see . *
* *
***************************************************************************/
/**
* @file ctskymap.cpp
* @brief Sky mapping tool implementation
* @author Juergen Knoedlseder
*/
/* __ Includes ___________________________________________________________ */
#ifdef HAVE_CONFIG_H
#include
#endif
#include
#include "ctskymap.hpp"
/* __ Method name definitions ____________________________________________ */
#define G_GET_PARAMETERS "ctskymap::get_parameter()"
#define G_FILL_MAPS_COUNTS "ctskymap::fill_maps_counts(GCTAObservation*)"
#define G_FILL_MAPS_ACCEPTANCE "ctskymap::fill_maps_acceptance("\
"GCTAObservation*)"
#define G_RING_BOUNDING_BOX "ctskymap::ring_bounding_box(int&, int&, int&, "\
"int&, int&)"
#define G_RING_KERNEL "ctskymap::ring_kernel(double&, double&)"
/* __ Debug definitions __________________________________________________ */
/* __ Coding definitions _________________________________________________ */
/*==========================================================================
= =
= Constructors/destructors =
= =
==========================================================================*/
/***********************************************************************//**
* @brief Void constructor
*
* Constructs an empty sky mapping tool.
***************************************************************************/
ctskymap::ctskymap(void) : ctobservation(CTSKYMAP_NAME, VERSION)
{
// Initialise members
init_members();
// Return
return;
}
/***********************************************************************//**
* @brief Observations constructor
*
* param[in] obs Observation container.
*
* Constructs sky mapping tool from an observation container.
***************************************************************************/
ctskymap::ctskymap(const GObservations& obs) :
ctobservation(CTSKYMAP_NAME, VERSION, obs)
{
// Initialise members
init_members();
// Return
return;
}
/***********************************************************************//**
* @brief Command line constructor
*
* @param[in] argc Number of arguments in command line.
* @param[in] argv Array of command line arguments.
*
* Constructs sky mapping tool using command line arguments for user
* parameter setting.
***************************************************************************/
ctskymap::ctskymap(int argc, char *argv[]) :
ctobservation(CTSKYMAP_NAME, VERSION, argc, argv)
{
// Initialise members
init_members();
// Return
return;
}
/***********************************************************************//**
* @brief Copy constructor
*
* @param[in] app Sky mapping tool.
***************************************************************************/
ctskymap::ctskymap(const ctskymap& app) : ctobservation(app)
{
// Initialise members
init_members();
// Copy members
copy_members(app);
// Return
return;
}
/***********************************************************************//**
* @brief Destructor
***************************************************************************/
ctskymap::~ctskymap(void)
{
// Free members
free_members();
// Return
return;
}
/*==========================================================================
= =
= Operators =
= =
==========================================================================*/
/***********************************************************************//**
* @brief Assignment operator
*
* @param[in] app Sky mapping tool.
* @return Sky mapping tool.
***************************************************************************/
ctskymap& ctskymap::operator=(const ctskymap& app)
{
// Execute only if object is not identical
if (this != &app) {
// Copy base class members
this->ctobservation::operator=(app);
// Free members
free_members();
// Initialise members
init_members();
// Copy members
copy_members(app);
} // endif: object was not identical
// Return this object
return *this;
}
/*==========================================================================
= =
= Public methods =
= =
==========================================================================*/
/***********************************************************************//**
* @brief Clear sky mapping tool
*
* Clears sky mapping tool.
***************************************************************************/
void ctskymap::clear(void)
{
// Free members
free_members();
this->ctool::free_members();
this->ctobservation::free_members();
// Clear base class (needed to conserve tool name and version)
this->GApplication::clear();
// Initialise members
this->ctool::init_members();
this->ctobservation::init_members();
init_members();
// Write header into logger
log_header();
// Return
return;
}
/***********************************************************************//**
* @brief Run the sky mapping tool
*
* Generates a sky map from event list by looping over all unbinned CTA
* observation in the observation container and filling all events into
* a sky map.
***************************************************************************/
void ctskymap::run(void)
{
// Switch screen logging on in debug mode
if (logDebug()) {
log.cout(true);
}
// Get parameters
get_parameters();
// Setup maps
setup_maps();
// If no input map exists then fill counts and acceptance maps from
// observation container
if (!m_has_inmap) {
fill_maps();
}
// Compute maps
compute_maps();
// Optionally publish sky map
if (m_publish) {
publish();
}
// Create the output fits object
construct_fits();
// Return
return;
}
/***********************************************************************//**
* @brief Save sky map
*
* Saves the sky map into a FITS file. The FITS file name is specified by
* the @p outname parameter.
***************************************************************************/
void ctskymap::save(void)
{
// Write header
log_header1(TERSE, "Save sky map");
// Save sky map if filename is valid and sky map is not empty
if ((*this)["outmap"].is_valid() && !m_skymap.is_empty()) {
// Get sky map filename
m_outmap = (*this)["outmap"].filename();
// Save FITS file to disk
m_fits.saveto(m_outmap, clobber());
} // endif: filename and map were not empty
// Write into logger what has been done
std::string fname = (m_outmap.is_empty()) ? "NONE" : m_outmap.url();
if (m_skymap.is_empty()) {
fname.append(" (map is empty, no file created)");
}
log_value(NORMAL, "Sky map file", fname);
// Return
return;
}
/***********************************************************************//**
* @brief Construct GFits object consisting of all maps
*
* Assembles all of the skymaps into a GFits object. This object is saved
* when ctskymap::save() is called. the object can also be accessed by
* calling ctskymap::fits().
***************************************************************************/
void ctskymap::construct_fits(void)
{
// Clear FITS object
m_fits.clear();
// Write sky map into FITS object
write_map(m_fits, m_skymap, "SKYMAP");
// If background subtraction is requested then write the background map,
// the significance map, the counts map and the acceptance map to the
// FITS file
if (m_bkgsubtract != "NONE") {
// Write background map into FITS file
write_map(m_fits, m_bkgmap, "BACKGROUND");
// Write significance map into FITS file
write_map(m_fits, m_sigmap, "SIGNIFICANCE");
// Write counts map into FITS file
write_map(m_fits, m_counts, "COUNTS");
// Write acceptance map into FITS file
write_map(m_fits, m_acceptance, "ACCEPTANCE");
// If RING background subtraction is requested then write also
// the exclusion map to the FITS file
if (m_bkgsubtract == "RING") {
write_map(m_fits, m_exclmap, "EXCLUSION");
}
} // endif: background subtraction was requested
return;
}
/***********************************************************************//**
* @brief Publish sky map
*
* @param[in] name Sky map name.
***************************************************************************/
void ctskymap::publish(const std::string& name)
{
// Write header into logger
log_header1(TERSE, "Publish sky map");
// Set default name if user name is empty
std::string user_name(name);
if (user_name.empty()) {
user_name = CTSKYMAP_NAME;
}
// Write sky map name into logger
log_value(NORMAL, "Sky map name", user_name);
// Publish sky map
m_skymap.publish(user_name);
// Return
return;
}
/*==========================================================================
= =
= Private methods =
= =
==========================================================================*/
/***********************************************************************//**
* @brief Initialise class members
***************************************************************************/
void ctskymap::init_members(void)
{
// Initialise User parameters
m_outmap.clear();
m_inexclusion.clear();
m_emin = 0.0;
m_emax = 0.0;
m_bkgsubtract = "NONE";
m_roiradius = 0.0;
m_inradius = 0.0;
m_outradius = 0.0;
m_iterations = 0;
m_threshold = 5.0;
m_usefft = true;
m_publish = false;
m_chatter = static_cast(2);
// Initialise members
m_has_inmap = false;
m_skymap.clear();
m_bkgmap.clear();
m_sigmap.clear();
m_counts.clear();
m_acceptance.clear();
m_exclmap.clear();
m_fits.clear();
// Initialise cache
m_solidangle.clear();
m_dirs.clear();
m_cos_roiradius = 1.0;
m_cos_inradius = 1.0;
m_cos_outradius = 1.0;
// Return
return;
}
/***********************************************************************//**
* @brief Copy class members
*
* @param[in] app Application.
***************************************************************************/
void ctskymap::copy_members(const ctskymap& app)
{
// Copy User parameters
m_outmap = app.m_outmap;
m_inexclusion = app.m_inexclusion;
m_emin = app.m_emin;
m_emax = app.m_emax;
m_bkgsubtract = app.m_bkgsubtract;
m_roiradius = app.m_roiradius;
m_inradius = app.m_inradius;
m_outradius = app.m_outradius;
m_iterations = app.m_iterations;
m_threshold = app.m_threshold;
m_usefft = app.m_usefft;
m_publish = app.m_publish;
m_chatter = app.m_chatter;
// Copy members
m_has_inmap = app.m_has_inmap;
m_skymap = app.m_skymap;
m_bkgmap = app.m_bkgmap;
m_sigmap = app.m_sigmap;
m_counts = app.m_counts;
m_acceptance = app.m_acceptance;
m_exclmap = app.m_exclmap;
m_fits = app.m_fits;
// Copy cache
m_solidangle = app.m_solidangle;
m_dirs = app.m_dirs;
m_cos_roiradius = app.m_cos_roiradius;
m_cos_inradius = app.m_cos_inradius;
m_cos_outradius = app.m_cos_outradius;
// Return
return;
}
/***********************************************************************//**
* @brief Delete class members
***************************************************************************/
void ctskymap::free_members(void)
{
// Return
return;
}
/***********************************************************************//**
* @brief Get application parameters
*
* Get all task parameters from parameter file or (if required) by querying
* the user. Most parameters are only required if no observation exists so
* far in the observation container. In this case, a single CTA observation
* will be added to the container, using the definition provided in the
* parameter file.
***************************************************************************/
void ctskymap::get_parameters(void)
{
// So far we have no valid input map
m_has_inmap = false;
// If an input map was specified then extract try loading the COUNTS
// and ACCEPTANCE extensions from that file
if ((*this)["inmap"].is_valid()) {
// Read inmap filename
GFilename inmap = (*this)["inmap"].filename();
// Open file
GFits fits(inmap);
// Continue only if required extensions exist
if (fits.contains("COUNTS") && fits.contains("ACCEPTANCE")) {
// Get HDUs
GFitsHDU &counts = *fits["COUNTS"];
GFitsHDU &acceptance = *fits["ACCEPTANCE"];
// Read counts and acceptance map
m_counts.read(counts);
m_acceptance.read(acceptance);
// Extract OGIP parameters
read_ogip_keywords(&counts);
// Extract energy range
m_emin = (counts.has_card("E_MIN")) ? counts.real("E_MIN") : 0.0;
m_emax = (counts.has_card("E_MAX")) ? counts.real("E_MAX") : 0.0;
// Signal availability of sky map
m_has_inmap = true;
}
else {
std::string msg = "Input sky map \""+inmap.url()+"\" does not "
"contain \"COUNTS\" and \"ACCEPTANCE\" "
"extensions.";
throw GException::invalid_value(G_GET_PARAMETERS, msg);
}
fits.close();
}
// If no valid input map exists then request observations
if (!m_has_inmap) {
// Setup observations from "inobs" parameter. Do not request response
// information and do not accept counts cubes.
setup_observations(m_obs, false, true, false);
// Create counts and acceptance maps from the User parameters
m_counts = create_map(m_obs);
m_acceptance = m_counts;
// Get further parameters
m_emin = (*this)["emin"].real();
m_emax = (*this)["emax"].real();
} // endif: no valid input map existed
// Get background method
m_bkgsubtract = (*this)["bkgsubtract"].string();
// Get RING background parameters
if (m_bkgsubtract == "RING") {
// Get (or query) parameters
m_roiradius = (*this)["roiradius"].real();
m_inradius = (*this)["inradius"].real();
m_outradius = (*this)["outradius"].real();
m_iterations = (*this)["iterations"].integer();
if (m_iterations > 0) {
m_threshold = (*this)["threshold"].real();
}
(*this)["inexclusion"].query();
m_usefft = (*this)["usefft"].boolean();
// Make sure that (roiradius < inradius < outradius)
if (m_roiradius > m_inradius) {
std::string msg("\"roiradius\" must be smaller than \"inradius\"");
throw GException::invalid_value(G_GET_PARAMETERS, msg);
}
else if (m_inradius > m_outradius) {
std::string msg("\"inradius\" must be smaller than \"outradius\"");
throw GException::invalid_value(G_GET_PARAMETERS, msg);
}
} // endif: read RING background parameters
// If background subtraction is requested then make sure that the CTA
// observations in the observation container have response information
if (m_bkgsubtract != "NONE" && !m_has_inmap) {
set_response(m_obs);
}
// Get remaining parameters
m_publish = (*this)["publish"].boolean();
m_chatter = static_cast((*this)["chatter"].integer());
// If needed later, query output filename now
if (read_ahead()) {
(*this)["outmap"].query();
}
// Write parameters into logger
log_parameters(TERSE);
// Return
return;
}
/***********************************************************************//**
* @brief Setup maps
*
* Setup maps for sky map generation.
***************************************************************************/
void ctskymap::setup_maps(void)
{
// Clear cache
m_solidangle.clear();
m_dirs.clear();
// Create background map and significance map if background subtraction
// is requested
if (m_bkgsubtract != "NONE") {
// Setup the exclusions map
if (m_exclmap.is_empty()) {
setup_exclusion_map();
}
else {
adjust_exclusion_map();
}
// Cache the pixel solid angles and sky directions
m_solidangle.reserve(m_counts.npix());
m_dirs.reserve(m_counts.npix());
for (int i = 0; i < m_counts.npix(); ++i) {
m_solidangle.push_back(m_counts.solidangle(i));
m_dirs.push_back(m_counts.inx2dir(i));
}
} // endif: background subtraction selected
// Compute cosine of ring radii for RING background method
if (m_bkgsubtract == "RING") {
m_cos_roiradius = std::cos(m_roiradius * gammalib::deg2rad);
m_cos_inradius = std::cos(m_inradius * gammalib::deg2rad);
m_cos_outradius = std::cos(m_outradius * gammalib::deg2rad);
}
// Return
return;
}
/***********************************************************************//**
* @brief Generates map of pixel exclusions
*
* Generates a sky map of the pixels that are to be excluded from the
* background estimation. Pixels with values different from 0 will be
* excluded.
***************************************************************************/
void ctskymap::setup_exclusion_map(void)
{
// Create exlusion map
m_exclmap = m_counts;
// Set all pixels to 0 (no pixel excluded)
m_exclmap = 0.0;
// Generate map only if exclusion map filename is valid
if ((*this)["inexclusion"].is_valid()) {
// Get exclusion map filename
m_inexclusion = (*this)["inexclusion"].filename();
// Fill the exclusions based on the regions supplied
if (m_inexclusion.is_fits()) {
setup_exclusion_map_fits(m_inexclusion);
}
else {
setup_exclusion_map_region(m_inexclusion);
}
} // endif: filename was valid
// Return
return;
}
/***********************************************************************//**
* @brief Fills exclusions map from FITS image
*
* @param[in] filename FITS image file name.
*
* Sets all exclusion map pixels to 1 that correspond to non-zero pixels in
* the exclusion sky map FITS file.
***************************************************************************/
void ctskymap::setup_exclusion_map_fits(const GFilename& filename)
{
// Load the fits image to the exclusion map
m_exclmap = GSkyMap(filename);
// Verify exclusion map
adjust_exclusion_map();
// Return
return;
}
/***********************************************************************//**
* @brief Fills exclusions map from DS9 region file
*
* @param[in] filename DS9 region file name.
*
* Sets all exclusion map pixels to 1 that are contained in any of the DS9
* regions.
***************************************************************************/
void ctskymap::setup_exclusion_map_region(const GFilename& filename)
{
// Load the exclusion regions
GSkyRegions regions(filename);
// Loop through the individual pixels in the exclusion map
for (int i = 0; i < m_exclmap.npix(); ++i) {
// Get the pixel position
GSkyDir dir = m_exclmap.pix2dir(i);
// If pixel position overlaps with the regions
if (regions.contains(dir)) {
m_exclmap(i) = 1.0;
}
} // endfor: looped over exclusion map pixels
// Return
return;
}
/***********************************************************************//**
* @brief Verifys that exclusion map points in fov of counts
*
* Adjust exclusion map. Reinitialise exclusion map via the counts object
* and transfer the masked areas provided by the current exclusion map to the
* updated fov.
***************************************************************************/
void ctskymap::adjust_exclusion_map(void)
{
// Initialise working map
GSkyMap dest_map = m_counts;
// Set all pixels to 0 (no pixel excluded)
dest_map = 0.0;
// Loop through the individual pixels in the exclusion map
for (int i = 0; i < dest_map.npix(); ++i) {
// Get the pixel direction
GSkyDir dir = dest_map.pix2dir(i);
// Check this sky position in the exclusion map
if (m_exclmap.contains(dir) && (m_exclmap(m_exclmap.dir2inx(dir)) != 0.0)) {
// Set the pixel to 1
dest_map(i) = 1.0;
} // endif: pixel,region overlap check
} // endfor: looped over exclusion map pixels
// Set exclusion map
m_exclmap = dest_map;
// Return
return;
}
/***********************************************************************//**
* @brief Fill maps from observation container
*
* Fill counts and optionally acceptance maps from data in the observation
* container.
***************************************************************************/
void ctskymap::fill_maps(void)
{
// Write input observation container into logger
log_observations(NORMAL, m_obs, "Input observation");
// Write header into logger
log_header1(TERSE, gammalib::number("Find unbinned observation",
m_obs.size()));
// Find all unbinned CTA observations in m_obs
std::vector obs_list(0);
for (GCTAObservation* obs = first_unbinned_observation(); obs != NULL;
obs = next_unbinned_observation()) {
// Push observation into list
obs_list.push_back(obs);
// Write message
std::string msg = " Including unbinned "+obs->instrument()+
" observation";
log_string(NORMAL, msg);
}
// Write header into logger
log_header1(TERSE, gammalib::number("Fill map from observation",
m_obs.size()));
// Loop over all unbinned CTA observations in the container
#pragma omp parallel for
for (int i = 0; i < obs_list.size(); ++i) {
// Get pointer to observation
GCTAObservation* obs = obs_list[i];
// Fill events into counts map
fill_maps_counts(obs);
// Optionally compute acceptance sky map
if (m_bkgsubtract != "NONE") {
fill_maps_acceptance(obs);
}
// Dispose events to free memory
obs->dispose_events();
} // endfor: looped over observations
// Return
return;
}
/***********************************************************************//**
* @brief Fill events into counts map
*
* @param[in] obs CTA observation.
*
* @exception GException::no_list
* No event list found in observation.
*
* Fills the events found in a CTA events list into a sky map. The method
* adds the events to the m_counts member.
***************************************************************************/
void ctskymap::fill_maps_counts(GCTAObservation* obs)
{
// Get non-const pointer on a CTA event list
GCTAEventList* events = dynamic_cast
(const_cast(obs->events()));
// Make sure that the observation holds a CTA event list. If this
// is not the case then throw an exception.
if (events == NULL) {
throw GException::no_list(G_FILL_MAPS_COUNTS);
}
// Setup energy range covered by data
GEnergy emin;
GEnergy emax;
GEbounds ebds;
emin.TeV(m_emin);
emax.TeV(m_emax);
// Initialise binning statistics
int num_outside_roi = 0;
int num_outside_map = 0;
int num_outside_erange = 0;
int num_in_map = 0;
// Extract region of interest from observation
GCTARoi roi = obs->roi();
// Fill sky map
for (int i = 0; i < events->size(); ++i) {
// Get event
GCTAEventAtom* event = (*events)[i];
// Skip if energy is out of range
if (event->energy() < emin || event->energy() > emax) {
num_outside_erange++;
continue;
}
// Determine sky pixel
GCTAInstDir* inst = (GCTAInstDir*)&(event->dir());
GSkyDir dir = inst->dir();
GSkyPixel pixel = m_counts.dir2pix(dir);
// Skip if pixel is out of range
if (pixel.x() < -0.5 || pixel.x() > (m_counts.nx() - 0.5) ||
pixel.y() < -0.5 || pixel.y() > (m_counts.ny() - 0.5)) {
num_outside_map++;
continue;
}
// If RoI is valid then skip if instrument direction is not within RoI
if (roi.is_valid() && !roi.contains(*inst)) {
num_outside_roi++;
continue;
}
// Fill event in skymap
#pragma omp critical(ctskymap_fill_maps_counts_1)
m_counts(pixel) += 1.0;
num_in_map++;
} // endfor: looped over all events
// Log binning results
#pragma omp critical(ctskymap_fill_maps_counts_2)
{
log_header3(TERSE, get_obs_header(obs));
log_value(NORMAL, "Events in list", obs->events()->size());
log_value(NORMAL, "Events in map", num_in_map);
log_value(NORMAL, "Events outside RoI", num_outside_roi);
log_value(NORMAL, "Events outside map area", num_outside_map);
log_value(NORMAL, "Events outside energies", num_outside_erange);
// Write sky map into header
log_header1(EXPLICIT, "Sky map");
log_string(EXPLICIT, m_counts.print(m_chatter));
}
// Return
return;
}
/***********************************************************************//**
* @brief Compute background acceptance sky map based on IRF template
*
* @param[in] obs CTA observation.
*
* @exception GException::invalid_value
* No response information available for observation.
* No background template available in instrument response function.
*
* Computes a background acceptance sky map for a given observation and adds
* the estimate to the acceptance sky map.
*
* If the response contains a background template, that template is used to
* compute the background acceptance map.
*
* If no background template is available, the outcome depends on the
* background subtraction method. If ``IRF`` background subtraction is
* requested, an exception is thrown. For ``RING`` background subtraction,
* a constant background rate is assumed.
***************************************************************************/
void ctskymap::fill_maps_acceptance(GCTAObservation* obs)
{
// Initialise IRF background template pointer
const GCTABackground* bkg = NULL;
// Get IRF response
const GCTAResponseIrf* rsp = dynamic_cast
(obs->response());
// Throw an exception if observation has no instrument response function
if ((rsp == NULL) && (m_bkgsubtract != "RING")) {
std::string msg = "No response information available for "+
get_obs_header(obs)+" to compute IRF background. "
"Please specify response information or use "
"another background subtraction method.";
throw GException::invalid_value(G_FILL_MAPS_ACCEPTANCE, msg);
}
// ... otherwise get IRF background template
else {
// Get IRF background template
bkg = rsp->background();
// Throw an exception if observation has no IRF background template
if ((bkg == NULL) && (m_bkgsubtract != "RING")) {
std::string msg = "No IRF background template found in instrument "
"response function for "+
get_obs_header(obs)+". Please specify an "
"instrument response function containing a "
"background template.";
throw GException::invalid_value(G_FILL_MAPS_ACCEPTANCE, msg);
}
} // endelse: got IRF background template
// Set minimum and maximum energy as GEnergy instances
GEnergy emin(m_emin, "TeV");
GEnergy emax(m_emax, "TeV");
// Extract region of interest center and radius from observation and
// compute the cosine of the RoI radius. This allows to mimimize as
// much as possible the trigonometric computations. Note that the cosine
// of an angle is maximal for an angle of zero. This explains later why
// we chose ">=" to test whether an angle is smaller than a given radius.
GSkyDir roi_centre = obs->roi().centre().dir();
double cos_roi_radius = std::cos(obs->roi().radius() * gammalib::deg2rad);
// Extract exposure
double exposure = obs->livetime();
// Initialise statistics
double total = 0.0;
// Loop over all acceptance map pixels
for (int i = 0; i < m_acceptance.npix(); ++i) {
// Get sky direction of pixel
GSkyDir& skydir = m_dirs[i];
// Skip pixel if it is not within the RoI
if (roi_centre.cos_dist(skydir) < cos_roi_radius) {
continue;
}
// Convert sky direction to instrument direction
GCTAInstDir instdir = obs->pointing().instdir(skydir);
// Compute background value
double value = (bkg != NULL) ? bkg->rate_ebin(instdir, emin, emax) : 1.0;
// Multiply background rate with livetime and solid angle
value *= exposure * m_solidangle[i];
// Add number of background events to acceptance map
#pragma omp critical(ctskymap_fill_maps_acceptance_1)
m_acceptance(i) += value;
// Update total number of background events
total += value;
} // endfor: looped over acceptance map pixels
// Log acceptance results
#pragma omp critical(ctskymap_fill_maps_acceptance_2)
{
// Set message string
std::string msg = (bkg != NULL) ? "Use IRF background template"
: "Use constant background rate";
// Log information
log_value(NORMAL, "Background estimate", msg);
log_value(NORMAL, "Events in background", int(total+0.5));
}
// Return
return;
}
/***********************************************************************//**
* @brief Compute sky map, background map and significance map
*
* Computes the sky map, background map and significance map. The following
* background subtraction methods are supported:
*
* NONE
* ====
*
* The sky map is simply the binned counts map, no background and
* significance maps are computed.
*
* IRF
* ===
*
* The background map is the acceptance map, and this background map is
* subtracted from the counts map and assigned to the sky map. The
* significance \f$\sigma_i\f$ is computed assuming Poisson statistic in
* the Gaussian limit, using
*
* \f[\sigma_i = \frac{N_i - B_i}{\sqrt{N_i}}\f]
*
* where
* \f$N_i\f$ is the number of observed counts and
* \f$B_i\f$ is the estimated number of background counts.
*
* RING
* ====
*
* For the @c RING method the Li & Ma significance is computed for each
* pixel, using the @c roiradius, @c inradius and @c outradius parameters
* to specify the radius of the On region and the Off background ring,
* respectively. Depending on the @c usefft parameter, either an FFT is used
* to compute the number of events and the acceptance of the On and Off
* regions, or a direct computation is performed. While the former is faster
* it is less accurate since it assumes Euclidean distances in the sky map.
* The latter is slower but compute exact distance between pixels of the sky
* map. Use FFT if the sky map is close to a cartesian grid, and use the
* direct method if the sky map shows important distortions.
***************************************************************************/
void ctskymap::compute_maps(void)
{
// Write header into logger
log_header1(TERSE, "Compute maps");
// If no background subtraction is requested then simply assign the
// counts map to the skymap
if (m_bkgsubtract == "NONE") {
m_skymap = m_counts;
}
// else if IRF background is selected then use the acceptance as background
else if (m_bkgsubtract == "IRF") {
m_skymap = m_counts - m_acceptance;
m_bkgmap = m_acceptance;
m_sigmap = (m_counts - m_acceptance) / sqrt(m_counts);
}
// else if RING background is selected then compute the On
else if (m_bkgsubtract == "RING") {
// Compute initial RING background
if (m_usefft) {
compute_maps_ring_fft();
}
else {
compute_maps_ring_direct();
}
// Store copy of exclusion regions
GSkyMap exclmap = m_exclmap;
// Iterative computation of exclusion regions
for (int iter = 0; iter < m_iterations; ++iter) {
// Set exclusion region from significance map
m_exclmap = exclmap;
for (int i = 0; i < m_exclmap.npix(); ++i) {
if (m_sigmap(i) > m_threshold) {
m_exclmap(i) = 1.0;
}
}
// Re-compute RING background
if (m_usefft) {
compute_maps_ring_fft();
}
else {
compute_maps_ring_direct();
}
} // endfor: looped over iterations
}
// Return
return;
}
/***********************************************************************//**
* @brief Compute the maps for RING background using a FFT
*
* Computes the Li & Ma significance for each sky map pixel and replaces
* the sky and background maps by the On- and Off-count maps. The computation
* is done using a FFT.
***************************************************************************/
void ctskymap::compute_maps_ring_fft(void)
{
// Log message about what is being done
log_header3(NORMAL, "Computing ring background map (FFT method)");
// Initialise maps
m_skymap = m_counts;
m_bkgmap = m_counts;
m_sigmap = m_counts;
m_skymap = 0.0;
m_bkgmap = 0.0;
m_sigmap = 0.0;
// Initialise statistics
int num_bad_alpha = 0;
// Set-up sky and acceptance maps with excluded pixels according to the
// exclusion map
GSkyMap excl_counts = m_counts;
GSkyMap excl_acceptance = m_acceptance;
for (int i = 0; i < m_counts.npix(); ++i) {
if (m_exclmap(i) != 0.0) {
excl_counts(i) = 0.0;
excl_acceptance(i) = 0.0;
}
}
// Convolve maps for On-region and Off-region
GSkyMap on_counts = ring_convolve(m_counts, 0.0, m_roiradius);
GSkyMap on_alpha = ring_convolve(m_acceptance, 0.0, m_roiradius);
GSkyMap off_counts = ring_convolve(excl_counts, m_inradius, m_outradius);
GSkyMap off_alpha = ring_convolve(excl_acceptance, m_inradius, m_outradius);
// Compute maps by loop over sky map pixels
for (int i = 0; i < m_sigmap.npix(); ++i) {
// Get On-counts, Off-counts and alpha value for this bin
double n_on = on_counts(i);
double n_off = off_counts(i);
double alpha;
if (off_alpha(i) == 0.0) {
alpha = 0.0;
n_on = 0.0;
n_off = 0.0;
}
else {
alpha = on_alpha(i) / off_alpha(i);
}
// Store the On and alpha-weighted Off-counts
m_bkgmap(i) = alpha * n_off;
m_skymap(i) = n_on - m_bkgmap(i);
// If alpha is zero then increment the bad alpha counter
if (alpha == 0.0) {
num_bad_alpha++;
}
// ... otherwise compute and store the results
else {
m_sigmap(i) = sigma_li_ma(n_on, n_off, alpha);
}
} // endfor: looped over all pixels
// Log the number of bad-alpha bins
log_value(NORMAL, "Total number of pixels", m_sigmap.npix());
log_value(NORMAL, "Pixels with alpha=0", num_bad_alpha);
// Now take the square root. Since some bins can be negative, first
// take the absolute value of the map, square root that, then multiply
// each bin by its sign to preserve the +/- significance.
m_sigmap = sign(m_sigmap) * sqrt(abs(m_sigmap));
// Return
return;
}
/***********************************************************************//**
* @brief Compute the pixel significance for RING background
*
* Computes the Li & Ma significance for each sky map pixel and replaces
* the sky and background maps by the On- and Off-count maps. The computation
* is done by computing the exact distances between pixels in the sky map.
***************************************************************************/
void ctskymap::compute_maps_ring_direct(void)
{
// Log message about what is being done
log_header3(NORMAL, "Computing ring background map (direct method)");
log_value(NORMAL, "Total pixels to process", m_counts.npix());
// Initialise maps
m_skymap = m_counts;
m_bkgmap = m_counts;
m_sigmap = m_counts;
m_skymap = 0.0;
m_bkgmap = 0.0;
m_sigmap = 0.0;
// Initialise statistics
int num_bad_alpha = 0;
// Set progress logging frequency. By default the user is updated on the
// progress when another 10% of pixels is processed, however, if there
// are many pixels the user is updated each 100000 pixels.
int n_logpix = m_counts.npix() / 10;
if (n_logpix > 100000) {
n_logpix = 100000;
}
// Compute maps by loop over sky map pixels
#pragma omp parallel for
for (int i = 0; i < m_sigmap.npix(); ++i) {
// Initialise the on/off counts and alpha for this bin
double n_on = 0.0;
double n_off = 0.0;
double alpha = 0.0;
// Update user about progress (only if there are more than 10000 pixels)
if (i % n_logpix == 0 && n_logpix > 10000) {
#pragma omp critical(ctskymap_map_significance_ring)
log_value(NORMAL, "Pixels remaining", m_sigmap.npix()-i);
}
// Compute the alpha and counts for this bin
compute_ring_values(i, m_counts, m_acceptance, n_on, n_off, alpha);
// Store the On and alpha-weighted Off-counts
m_bkgmap(i) = alpha * n_off;
m_skymap(i) = n_on - m_bkgmap(i);
// If alpha is zero then increment the bad alpha counter
if (alpha == 0.0) {
num_bad_alpha++;
#pragma omp critical(ctskymap_compute_maps_ring_direct)
m_sigmap(i) = 0.0;
}
// ... otherwise store the results
else {
// Compute and store significance (Li & Ma eq. 17)
double sigma = sigma_li_ma(n_on, n_off, alpha);
#pragma omp critical(ctskymap_compute_maps_ring_direct)
m_sigmap(i) = sigma;
} // endelse: alpha was non-zero
} // endfor: looped over all pixels
// Log the number of bad-alpha bins
log_value(NORMAL, "Pixels with alpha=0", num_bad_alpha);
// Now take the square root. Since some bins can be negative, first
// take the absolute value of the map, square root that, then multiply
// each bin by its sign to preserve the +/- significance.
m_sigmap = sign(m_sigmap) * sqrt(abs(m_sigmap));
// Return
return;
}
/***********************************************************************//**
* @brief Computes Non, Noff and alpha for a counts map and sensitivity map
*
* @param[in] ipixel Sky map pixel to consider.
* @param[in] counts Counts map.
* @param[in] background Background map.
* @param[out] non Returned estimate of On-counts.
* @param[out] noff Returned estimate of Off-counts.
* @param[out] alpha Returned estimate of alpha.
*
* Computes the On- and Off-counts values at a given position from in counts
* map and the alpha parameter from the background map.
*
* To speed-up the computations the method considers only the pixels within
* a bounding box that comprises the outer background ring radius. The method
* considers wrapping around of pixel indices in the Right Ascension /
* Galactic longitude direction.
*
* If the alpha of the ring region is zero the values of @p non, @p noff,
* and @p alpha will be set to zero.
***************************************************************************/
void ctskymap::compute_ring_values(const int& ipixel,
const GSkyMap& counts,
const GSkyMap& background,
double& non,
double& noff,
double& alpha)
{
// Initialise return values (non, noff and alpha)
non = 0.0;
noff = 0.0;
alpha = 0.0;
// Initialise On and Off alpha
double alpha_on = 0.0;
double alpha_off = 0.0;
// Get sky direction of pixel
GSkyDir& position = m_dirs[ipixel];
// Get pixel bounding box
int ix_start;
int ix_stop;
int iy_start;
int iy_stop;
ring_bounding_box(ipixel, ix_start, ix_stop, iy_start, iy_stop);
// Get number of x pixels in sky map
int nx = counts.nx();
// Loop over x pixels of bounding box
for (int ix_nowrap = ix_start; ix_nowrap < ix_stop; ++ix_nowrap) {
// Compute x pixel index by wrapping the pixel index into the
// interval [0,nx[
int ix = ix_nowrap;
if (ix < 0) {
ix += nx;
}
else if (ix >= nx) {
ix -= nx;
}
// Initialise pixel index
int i = ix + iy_start * nx;
// Loop over y pixels of bounding box
for (int iy = iy_start; iy < iy_stop; ++iy, i += nx) {
// Get the index and sky direction of this pixel
GSkyDir& skydir = m_dirs[i];
// Only consider pixels within the outer radius of the background
// region
if (position.cos_dist(skydir) >= m_cos_outradius) {
// Check if pixel is inside the background region
if ((m_exclmap(i) == 0.0) &&
(position.cos_dist(skydir) < m_cos_inradius)) {
// Update n_off
noff += counts(i);
// Update alpha_off
alpha_off += background(i);
}
// ... otherwise check if pixel is inside source region
else if ((position.cos_dist(skydir) >= m_cos_roiradius) ||
(ipixel == i)) {
// Update n_on
non += counts(i);
// Update alpha_on
alpha_on += background(i);
} // endif: source and background region check
} // endif: pixel is within outer radius of background region
} // endfor: looped over y pixels
} // endfor: looped over x pixels
// Compute alpha. If the off region does not have any sensitivity then
// set Non = Noff = 0
if (alpha_off == 0.0) {
alpha = 0.0;
non = 0.0;
noff = 0.0;
}
else {
alpha = alpha_on / alpha_off;
}
// Return
return;
}
/***********************************************************************//**
* @brief Computes bounding box for RING background computation
*
* @param[in] ipixel Sky map pixel to consider.
* @param[out] ix1 Index of first pixel in x.
* @param[out] ix2 Index after last pixel in x.
* @param[out] iy1 Index of first pixel in y.
* @param[out] iy2 Index after last pixel in y.
*
* Computes the bounding box that contained the background ring for a
* specific pixel for the RING background method.
*
* The method determines the local pixel scale for the requested pixel and
* draws a bounding box with 1.5 times the outer background ring radius
* around the pixel. In the x direction the pixel indices are unconstrained,
* and the client has to assure that the pixel value is comprised within
* the validity range (this is required to handle the longitude wrap around).
* In the y direction the pixel indices are constrained to [0,ny], where
* ny is the number of y pixels in the sky map.
***************************************************************************/
void ctskymap::ring_bounding_box(const int& ipixel, int& ix1, int& ix2,
int& iy1, int& iy2) const
{
// Get number of pixels in x and y direction
int nx = m_counts.nx();
int ny = m_counts.ny();
// Get x and y index of pixel to consider
int ix0 = ipixel % nx;
int iy0 = ipixel / nx;
// Get pointer on WCS projection
const GWcs* wcs = dynamic_cast(m_counts.projection());
if (wcs == NULL) {
std::string msg = "Sky map is not a WCS projection. Method is only "
"valid for WCS projections.";
throw GException::invalid_value(G_RING_BOUNDING_BOX, msg);
}
// Get X and Y step size
double dx_binsz = wcs->cdelt(0);
double dy_binsz = wcs->cdelt(1);
// Compute pixel increment in x-direction
double dx = 0.0;
int ix = (ix0 > 0) ? ix0 - 1 : ix0 + 1;
if (ix < nx) {
dx = m_dirs[ipixel].dist_deg(m_dirs[ix+iy0*nx]);
}
if (dx < dx_binsz) {
dx = dx_binsz;
}
// Compute pixel increment in y-direction
double dy = 0.0;
int iy = (iy0 > 0) ? iy0 - 1 : iy0 + 1;
if (iy < ny) {
dx = m_dirs[ipixel].dist_deg(m_dirs[ix0+iy*nx]);
}
if (dy < dy_binsz) {
dy = dy_binsz;
}
// Compute bounding box half size in x and y. The outer radius is
// multiplied by 1.5 to have some margin in case of map distortions
int nx_bbox = int(1.5 * m_outradius / dx);
int ny_bbox = int(1.5 * m_outradius / dy);
// Compute index range for x. We allow that indices are smaller than 0
// or equal or larger than nx to handle wrap around, which needs to be
// done in the client method.
if (2*nx_bbox < nx) {
ix1 = ix0 - nx_bbox;
ix2 = ix0 + nx_bbox;
}
else {
ix1 = 0;
ix2 = nx;
}
// Compute index range for y. The index range is restricted to [0,ny].
if (2*ny_bbox < ny) {
iy1 = iy0 - ny_bbox;
iy2 = iy0 + ny_bbox;
if (iy1 < 0) {
iy1 = 0;
}
if (iy2 > ny) {
iy2 = ny;
}
}
else {
iy1 = 0;
iy2 = ny;
}
// Return
return;
}
/***********************************************************************//**
* @brief Return FFT kernel for background ring
*
* @param[in] map Sky map to be convolved with a background ring.
* @param[in] rmin Minimum ring radius (degrees).
* @param[in] rmax Maximum ring radius (degrees).
* @return FFT kernel.
*
* Computes the FFT kernel for a backgrund ring.
***************************************************************************/
GSkyMap ctskymap::ring_convolve(const GSkyMap& map, const double& rmin,
const double& rmax) const
{
// Copy input map
GSkyMap conv_map = map;
// Get FFT of ring kernel
GFft fft_kernel(ring_kernel(rmin, rmax));
// Extract sky map into Ndarray
GNdarray array(conv_map.nx(), conv_map.ny());
double *dst = array.data();
for (int i = 0; i < conv_map.npix(); ++i) {
*dst++ = conv_map(i);
}
// FFT of sky map
GFft fft_array = GFft(array);
// Multiply FFT of sky map with FFT of kernel
GFft fft_smooth = fft_array * fft_kernel;
// Backward transform sky map
GNdarray smooth = fft_smooth.backward();
// Insert sky map
double *src = smooth.data();
for (int i = 0; i < conv_map.npix(); ++i) {
conv_map(i) = *src++;
}
// Return convolved map
return conv_map;
}
/***********************************************************************//**
* @brief Return FFT kernel for background ring
*
* @param[in] rmin Minimum ring radius (degrees).
* @param[in] rmax Maximum ring radius (degrees).
* @return FFT kernel.
*
* @exception GException::invalid_value
* Sky map is not a WCS projection.
*
* Computes the FFT kernel for a background ring. The computation is done
* assuming that the sky map represents a cartesian grid, and distances are
* computed in that grid using Euclidean distances. The @p rmin and @p rmax
* arguments refer to a ring radius in these Euclidean distances.
***************************************************************************/
GNdarray ctskymap::ring_kernel(const double& rmin, const double& rmax) const
{
// Get pointer on WCS projection
const GWcs* wcs = dynamic_cast(m_counts.projection());
if (wcs == NULL) {
std::string msg = "Sky map is not a WCS projection. Method is only "
"valid for WCS projections.";
throw GException::invalid_value(G_RING_KERNEL, msg);
}
// Get X and Y step size
double dx = wcs->cdelt(0);
double dy = wcs->cdelt(1);
// Initialise kernel
GNdarray kern(m_counts.nx(), m_counts.ny());
// Fill kernel
for (int ix1 = 0, ix2 = m_counts.nx(); ix1 < m_counts.nx(); ++ix1, --ix2) {
double x = ix1 * dx;
double xqs = x * x;
for (int iy1 = 0, iy2 = m_counts.ny(); iy1 < m_counts.ny(); ++iy1, --iy2) {
double y = iy1 * dy;
double r = std::sqrt(xqs + y*y);
if ((r >= rmin) && (r <= rmax)) {
kern(ix1,iy1) += 1.0;
if (ix2 < m_counts.nx()) {
kern(ix2,iy1) += 1.0;
}
if (iy2 < m_counts.ny()) {
kern(ix1,iy2) += 1.0;
}
if ((ix2 < m_counts.nx()) && (iy2 < m_counts.ny())) {
kern(ix2,iy2) += 1.0;
}
}
}
}
// Return kernel
return kern;
}
/***********************************************************************//**
* @brief Compute significance following Li & Ma
*
* @param[in] n_on Number of On-counts.
* @param[in] n_off Number of Off-counts.
* @param[in] alpha Alpha parameters.
* @return Significance.
*
* Computes the significance following Li & Ma, Equation (17).
***************************************************************************/
double ctskymap::sigma_li_ma(const double& n_on,
const double& n_off,
const double& alpha) const
{
// Allocate result
double sigma;
// Handle special case of no On-counts
if (n_on == 0.0) {
sigma = -2.0 * n_off * std::log(1.0+alpha);
}
// Handle special case of no Off-counts
else if (n_off == 0.0) {
sigma = 2.0 * n_on * std::log((1.0+alpha)/alpha);
}
// ... otherwise handle general case
else {
sigma = (n_on < (alpha*n_off) ? -2.0 : 2.0) *
(n_on * std::log((1.0+alpha) * n_on / (alpha * (n_on+n_off))) +
n_off * std::log((1.0+alpha) * n_off / (n_on+n_off)));
}
// Return sigma
return sigma;
}
/***********************************************************************//**
* @brief Write sky map into FITS file
*
* @param[in,out] fits FITS file.
* @param[in] map Sky map.
* @param[in] extname Extension name.
*
* Write one sky map with the extension name and keywords into the FITS file.
***************************************************************************/
void ctskymap::write_map(GFits& fits, const GSkyMap& map, const std::string& extname) const
{
// Write map into FITS file
GFitsHDU* hdu = map.write(fits);
// Set map extension name
if (hdu != NULL) {
hdu->extname(extname);
}
// Write keywords into map extension
write_ogip_keywords(hdu);
write_hdu_keywords(hdu);
// Return
return;
}
/***********************************************************************//**
* @brief Write keywords in FITS HDU
*
* @param[in,out] hdu Pointer to FITS HDU.
*
* Writes keywords in FITS HDU.
***************************************************************************/
void ctskymap::write_hdu_keywords(GFitsHDU* hdu) const
{
// Continue only if pointer is valid
if (hdu != NULL) {
// Set keywords
hdu->card("BKGSUB", m_bkgsubtract, "Background substraction method");
hdu->card("E_MIN", m_emin, "[TeV] Lower energy boundary");
hdu->card("E_MAX", m_emax, "[TeV] Upper energy boundary");
hdu->card("EUNIT", "TeV", "Units for E_MIN and E_MAX");
// Write RING background keywords
if (m_bkgsubtract == "RING") {
hdu->card("USEFFT", m_usefft, "Use FFT for RING background");
hdu->card("ROIRAD", m_roiradius, "[deg] Source region radius");
hdu->card("INRAD", m_inradius, "[deg] Inner background ring radius");
hdu->card("OUTRAD", m_outradius, "[deg] Outer background ring radius");
hdu->card("ITER", m_iterations, "Exclusion map iterations");
hdu->card("THRES", m_threshold, "Exclusion map threshold");
hdu->card("EXCLMAP", m_inexclusion.url(), "Exclusion map name");
}
} // endif: pointer was valid
// Return
return;
}