示例#1
0
/***********************************************************************//**
 * @brief Test CTA Npred computation
 *
 * Tests the Npred computation for the diffuse source model. This is done
 * by loading the model from the XML file and by calling the
 * GCTAObservation::npred method which in turn calls the
 * GCTAResponse::npred_diffuse method. The test takes a few seconds.
 ***************************************************************************/
void TestGCTAResponse::test_response_npred_diffuse(void)
{
    // Set reference value
    double ref = 11212.26274;

    // Set parameters
    double src_ra  = 201.3651;
    double src_dec = -43.0191;
    double roi_rad =   4.0;

    // Setup ROI centred on Cen A with a radius of 4 deg
    GCTARoi     roi;
    GCTAInstDir instDir;
    instDir.radec_deg(src_ra, src_dec);
    roi.centre(instDir);
    roi.radius(roi_rad);

    // Setup pointing on Cen A
    GSkyDir skyDir;
    skyDir.radec_deg(src_ra, src_dec);
    GCTAPointing pnt;
    pnt.dir(skyDir);

    // Setup dummy event list
    GGti     gti;
    GEbounds ebounds;
    GTime    tstart(0.0);
    GTime    tstop(1800.0);
    GEnergy  emin;
    GEnergy  emax;
    emin.TeV(0.1);
    emax.TeV(100.0);
    gti.append(tstart, tstop);
    ebounds.append(emin, emax);
    GCTAEventList events;
    events.roi(roi);
    events.gti(gti);
    events.ebounds(ebounds);

    // Setup dummy CTA observation
    GCTAObservation obs;
    obs.ontime(1800.0);
    obs.livetime(1600.0);
    obs.deadc(1600.0/1800.0);
    obs.response(cta_irf, cta_caldb);
    obs.events(&events);
    obs.pointing(pnt);

    // Load models for Npred computation
    GModels models(cta_rsp_xml);

    // Perform Npred computation
    double npred = obs.npred(models, NULL);

    // Test Npred
    test_value(npred, ref, 1.0e-5, "Diffuse Npred computation");

    // Return
    return;
}
示例#2
0
    // Generate an EventCube, rate is the number of event per second. Events have a time between tmin and tmax.
    virtual GTestEventCube* generateCube(const double &rate, const GTime &tmin, const GTime &tmax, GRan &ran)
    {
        
        // Create an event list
        GTestEventCube* cube = new GTestEventCube();
        
        // Set min and max energy for ebounds
        // npred method integrate the model on time and energy.
        // In order to have a rate which not depend on energy we create an interval of 1 Mev.
        GEnergy engmin,engmax;
        engmin.MeV(1.0);
        engmax.MeV(2.0);
        
        // Instrument Direction
        GTestInstDir dir;
        
        // Generate an times list.
        GTimes times = m_modelTps->mc(rate, tmin, tmax, ran);

        GTestEventBin bin;
        bin.time(times[0]);
        bin.energy(engmin);
        bin.ewidth(engmax-engmin);
        bin.dir(dir);
        bin.ontime(10); // 10 sec per bin

        for (int i = 0; i < times.size(); ++i) {
            if ((bin.time().secs() + bin.ontime()) < times[i].secs()) {

                // Add the event to the cube
                cube->append(bin);
                
                bin.counts(0.0);
                bin.time(times[i]);
                bin.energy(engmin);
                bin.ewidth(engmax-engmin);
                bin.dir(dir);
                bin.ontime(10); // 10 sec per bin
            }
  
            bin.counts(bin.counts()+1);

        }
        
        
        // Create a time interval and add it to the list.
        GGti gti;
        gti.append(tmin,tmax);
        cube->gti(gti);
        
        // Create an energy interval and add it to the list
        GEbounds ebounds;
        ebounds.append(engmin,engmax);
        cube->ebounds(ebounds);
        
        return cube;
    };
示例#3
0
文件: GGti.cpp 项目: TarekHC/gammalib
/***********************************************************************//**
 * @brief Append Good Time Intervals
 *
 * @param[in] gti Good Time Intervals.
 *
 * Append Good Time Intervals to the container. The method performs automatic
 * time reference conversion in case that the specified Good Time Intervals
 * @p gti have a time reference that differs from that of the current
 * instance.
 ***************************************************************************/
void GGti::extend(const GGti& gti)
{
    // Do nothing if Good Time Intervals are empty
    if (!gti.is_empty()) {

        // Allocate new intervals
        int    num   = m_num+gti.size();
        GTime* start = new GTime[num];
        GTime* stop  = new GTime[num];

        // Initialise index
        int inx = 0;
        
        // Copy existing intervals
        for (; inx < m_num; ++inx) {
            start[inx] = m_start[inx];
            stop[inx]  = m_stop[inx];
        }

        // Append intervals. Convert to GTI reference on the fly.
        for (int i = 0; i < gti.size(); ++i, ++inx) {
            double tstart = gti.m_start[i].convert(gti.reference());
            double tstop  = gti.m_stop[i].convert(gti.reference());
            start[inx].set(tstart, this->reference());
            stop[inx].set(tstop,   this->reference());
        }

        // Free memory
        if (m_start != NULL) delete [] m_start;
        if (m_stop  != NULL) delete [] m_stop;

        // Set new memory
        m_start = start;
        m_stop  = stop;

        // Set number of elements
        m_num = num;

        // Set attributes
        set_attributes();

    } // endif: Good Time Intervals were not empty

    // Return
    return;
}
示例#4
0
 // Generate an EventList, rate is the number of event per second. Events have a time between tmin and tmax.
 virtual GTestEventList* generateList(const double &rate, const GTime &tmin, const GTime &tmax, GRan &ran)
 {
      // Create an event list
      GTestEventList * list = new GTestEventList();
      
      // Set min and max energy for ebounds
      // npred method integrate the model on time and energy.
      // In order to have a rate which not depend on energy we create an interval of 1 Mev.
      GEnergy engmin,engmax;
      engmin.MeV(1.0);
      engmax.MeV(2.0);
      
      // Instrument Direction
      GTestInstDir dir;
      
      // Generate an times list.
      GTimes times = m_modelTps->mc(rate,tmin, tmax,ran);
      
      for (int i = 0; i < times.size() ; ++i) {
          GTestEventAtom event;
          event.dir(dir);
          event.energy(engmin);
          event.time(times[i]);
          
          // Add the event to the list
          list->append(event);
      }
      
      // Create a time interval and add it to the list.
      GGti gti;
      gti.append(tmin,tmax);
      list->gti(gti);
      
      // Create an energy interval and add it to the list
      GEbounds ebounds;
      ebounds.append(engmin,engmax);
      list->ebounds(ebounds);
      
      return list;
  }
/***********************************************************************//**
 * @brief Test GGti
 ***************************************************************************/
void TestGObservation::test_gti(void)
{
    // Test void constructor
    test_try("Void constructor");
    try {
        GGti gti;
        test_try_success();
    }
    catch (std::exception &e) {
        test_try_failure(e);
    }

    // Manipulate GTIs starting from an empty object
    GGti gti;
    test_value(gti.size(), 0, "GGti should have zero size.");
    test_assert(gti.is_empty(), "GGti should be empty.");
    test_value(gti.tstart().secs(), 0.0, 1.0e-10, "Start time should be 0.");
    test_value(gti.tstop().secs(), 0.0, 1.0e-10, "Stop time should be 0.");

    // Add empty interval
    gti.append(GTime(1.0), GTime(1.0));
    test_value(gti.size(), 0, "GGti should have zero size.");
    test_assert(gti.is_empty(), "GGti should be empty.");
    test_value(gti.tstart().secs(), 0.0, 1.0e-10, "Start time should be 0.");
    test_value(gti.tstop().secs(), 0.0, 1.0e-10, "Stop time should be 0.");

    // Add one interval
    gti.append(GTime(1.0), GTime(10.0));
    test_value(gti.size(), 1, "GGti should have 1 interval.");
    test_assert(!gti.is_empty(), "GGti should not be empty.");
    test_value(gti.tstart().secs(), 1.0, 1.0e-10, "Start time should be 1.");
    test_value(gti.tstop().secs(), 10.0, 1.0e-10, "Stop time should be 10.");

    // Remove interval
    gti.remove(0);
    test_value(gti.size(), 0, "GGti should have zero size.");
    test_assert(gti.is_empty(), "GGti should be empty.");
    test_value(gti.tstart().secs(), 0.0, 1.0e-10, "Start time should be 0.");
    test_value(gti.tstop().secs(), 0.0, 1.0e-10, "Stop time should be 0.");

    // Append two overlapping intervals
    gti.append(GTime(1.0), GTime(100.0));
    gti.append(GTime(10.0), GTime(1000.0));
    test_value(gti.size(), 2, "GGti should have 2 intervals.");
    test_assert(!gti.is_empty(), "GGti should not be empty.");
    test_value(gti.tstart().secs(), 1.0, 1.0e-10, "Start time should be 1.");
    test_value(gti.tstop().secs(), 1000.0, 1.0e-10, "Stop time should be 1000.");

    // Clear object
    gti.clear();
    test_value(gti.size(), 0, "GGti should have zero size.");
    test_assert(gti.is_empty(), "GGti should be empty.");
    test_value(gti.tstart().secs(), 0.0, 1.0e-10, "Start time should be 0.");
    test_value(gti.tstop().secs(), 0.0, 1.0e-10, "Stop time should be 0.");

    // Append two overlapping intervals in inverse order
    gti.clear();
    gti.append(GTime(10.0), GTime(1000.0));
    gti.append(GTime(1.0), GTime(100.0));
    test_value(gti.size(), 2, "GGti should have 2 intervals.");
    test_assert(!gti.is_empty(), "GGti should not be empty.");
    test_value(gti.tstart().secs(), 1.0, 1.0e-10, "Start time should be 1.");
    test_value(gti.tstop().secs(), 1000.0, 1.0e-10, "Stop time should be 1000.");

    // Insert two overlapping intervals
    gti.clear();
    gti.insert(GTime(1.0), GTime(100.0));
    gti.insert(GTime(10.0), GTime(1000.0));
    test_value(gti.size(), 2, "GGti should have 2 intervals.");
    test_assert(!gti.is_empty(), "GGti should not be empty.");
    test_value(gti.tstart().secs(), 1.0, 1.0e-10, "Start time should be 1.");
    test_value(gti.tstop().secs(), 1000.0, 1.0e-10, "Stop time should be 1000.");

    // Insert two overlapping intervals in inverse order
    gti.clear();
    gti.insert(GTime(10.0), GTime(1000.0));
    gti.insert(GTime(1.0), GTime(100.0));
    test_value(gti.size(), 2, "GGti should have 2 intervals.");
    test_assert(!gti.is_empty(), "GGti should not be empty.");
    test_value(gti.tstart().secs(), 1.0, 1.0e-10, "Start time should be 1.");
    test_value(gti.tstop().secs(), 1000.0, 1.0e-10, "Stop time should be 1000.");

    // Merge two overlapping intervals
    gti.clear();
    gti.merge(GTime(1.0), GTime(100.0));
    gti.merge(GTime(10.0), GTime(1000.0));
    test_value(gti.size(), 1, "GGti should have 1 interval.");
    test_assert(!gti.is_empty(), "GGti should not be empty.");
    test_value(gti.tstart().secs(), 1.0, 1.0e-10, "Start time should be 1.");
    test_value(gti.tstop().secs(), 1000.0, 1.0e-10, "Stop time should be 1000.");

    // Merge two overlapping intervals in inverse order
    gti.clear();
    gti.merge(GTime(10.0), GTime(1000.0));
    gti.merge(GTime(1.0), GTime(100.0));
    test_value(gti.size(), 1, "GGti should have 1 interval.");
    test_assert(!gti.is_empty(), "GGti should not be empty.");
    test_value(gti.tstart().secs(), 1.0, 1.0e-10, "Start time should be 1.");
    test_value(gti.tstop().secs(), 1000.0, 1.0e-10, "Stop time should be 1000.");

    // Check extension
    gti.clear();
    gti.append(GTime(1.0), GTime(10.0));
    gti.append(GTime(10.0), GTime(100.0));
    GGti ext;
    ext.append(GTime(100.0), GTime(1000.0));
    gti.extend(ext);
    test_value(gti.size(), 3, "GGti should have 3 intervals.");
    test_assert(!gti.is_empty(), "GGti should not be empty.");
    test_value(gti.tstart(0).secs(), 1.0, 1.0e-10, "Bin 0 start time should be 1.");
    test_value(gti.tstart(1).secs(), 10.0, 1.0e-10, "Bin 1 start time should be 10.");
    test_value(gti.tstart(2).secs(), 100.0, 1.0e-10, "Bin 2 start time should be 100.");
    test_value(gti.tstop(0).secs(), 10.0, 1.0e-10, "Bin 0 stop time should be 10.");
    test_value(gti.tstop(1).secs(), 100.0, 1.0e-10, "Bin 1 stop time should be 100.");
    test_value(gti.tstop(2).secs(), 1000.0, 1.0e-10, "Bin 2 stop time should be 1000.");
    test_value(gti.tstart().secs(), 1.0, 1.0e-10, "Start time should be 1.");
    test_value(gti.tstop().secs(), 1000.0, 1.0e-10, "Stop time should be 1000.");

    // Return
    return;
}
示例#6
0
/***********************************************************************//**
 * @brief Return deadtime correction
 *
 * @return Deadtime correction factor.
 ***************************************************************************/
inline
double GCTACubeExposure::deadc(void) const
{
    return ((m_gti.ontime() > 0.0) ? m_livetime/m_gti.ontime() : 1.0);
}
示例#7
0
/***********************************************************************//**
 * @brief Return ontime
 *
 * @return Ontime (seconds).
 ***************************************************************************/
inline
const double& GCTACubeExposure::ontime(void) const
{
    return (m_gti.ontime());
}
示例#8
0
/***********************************************************************//**
 * @brief Test CTA IRF computation for diffuse source model
 *
 * Tests the IRF computation for the diffuse source model. This is done
 * by calling the GCTAObservation::model method which in turn calls the
 * GCTAResponse::irf_diffuse method. The test is done for a small counts
 * map to keep the test executing reasonably fast.
 ***************************************************************************/
void TestGCTAResponse::test_response_irf_diffuse(void)
{
    // Set reference value
    double ref = 13803.800313356;

    // Set parameters
    double src_ra  = 201.3651;
    double src_dec = -43.0191;
    int    nebins  = 5;

    // Setup pointing on Cen A
    GSkyDir skyDir;
    skyDir.radec_deg(src_ra, src_dec);
    GCTAPointing pnt;
    pnt.dir(skyDir);

    // Setup skymap (10 energy layers)
    GSkymap map("CAR", "CEL", src_ra, src_dec, 0.5, 0.5, 10, 10, nebins);

    // Setup time interval
    GGti  gti;
    GTime tstart(0.0);
    GTime tstop(1800.0);
    gti.append(tstart, tstop);

    // Setup energy boundaries
    GEbounds ebounds;
    GEnergy  emin;
    GEnergy  emax;
    emin.TeV(0.1);
    emax.TeV(100.0);
    ebounds.setlog(emin, emax, nebins);

    // Setup event cube centered on Cen A
    GCTAEventCube cube(map, ebounds, gti);

    // Setup dummy CTA observation
    GCTAObservation obs;
    obs.ontime(1800.0);
    obs.livetime(1600.0);
    obs.deadc(1600.0/1800.0);
    obs.response(cta_irf, cta_caldb);
    obs.events(&cube);
    obs.pointing(pnt);

    // Load model for IRF computation
    GModels models(cta_rsp_xml);

    // Reset sum
    double sum = 0.0;

    // Iterate over all bins in event cube
    for (int i = 0; i < obs.events()->size(); ++i) {

        // Get event pointer
        const GEventBin* bin = (*(static_cast<const GEventCube*>(obs.events())))[i];

        // Get model and add to sum
        double model = obs.model(models, *bin, NULL) * bin->size();
        sum += model;

    }

    // Test sum
    test_value(sum, ref, 1.0e-5, "Diffuse IRF computation");

    // Return
    return;
}
示例#9
0
/***********************************************************************//**
 * @brief Setup observation container
 *
 * @exception GException::no_cube
 *            No event cube found in CTA observation.
 *
 * This method sets up the observation container for processing. There are
 * two cases:
 *
 * If there are no observations in the actual observation container, the
 * method will check in "infile" parameter. If this parameter is "NONE" or
 * empty, the task parameters will be used to construct a model map.
 * Otherwise, the method first tries to interpret the "infile" parameter as
 * a counts map, and attemps loading of the file in an event cube. If this
 * fails, the method tries to interpret the "infile" parameter as an
 * observation definition XML file. If this also fails, an exception will
 * be thrown.
 *
 * If observations exist already in the observation container, the method
 * will simply keep them.
 *
 * Test if all CTA observations contain counts maps.
 *
 * Finally, if no models exist so far in the observation container, the
 * models will be loaded from the model XML file.
 ***************************************************************************/
void ctmodel::setup_obs(void)
{
    // If there are no observations in the container then try to build some
    if (m_obs.size() == 0) {

        // If no input filename has been specified, then create a model map
        // from the task parameters
        if ((m_infile == "NONE") || (gammalib::strip_whitespace(m_infile) == "")) {

            // Set pointing direction
            GCTAPointing pnt;
            GSkyDir      skydir;
            skydir.radec_deg(m_ra, m_dec);
            pnt.dir(skydir);

            // Setup energy range covered by model
            GEnergy  emin(m_emin, "TeV");
            GEnergy  emax(m_emax, "TeV");
            GEbounds ebds(m_enumbins, emin, emax);

            // Setup time interval covered by model
            GGti  gti;
            GTime tmin(m_tmin);
            GTime tmax(m_tmax);
            gti.append(tmin, tmax);

            // Setup skymap
            GSkymap map = GSkymap(m_proj, m_coordsys,
                                  m_xref, m_yref, -m_binsz, m_binsz,
                                  m_nxpix, m_nypix, m_enumbins);

            // Create model cube from sky map
            GCTAEventCube cube(map, ebds, gti);

            // Allocate CTA observation
            GCTAObservation obs;

            // Set CTA observation attributes
            obs.pointing(pnt);
            obs.ontime(gti.ontime());
            obs.livetime(gti.ontime()*m_deadc);
            obs.deadc(m_deadc);

            // Set event cube in observation
            obs.events(cube);

            // Append CTA observation to container
            m_obs.append(obs);

            // Signal that no XML file should be used for storage
            m_use_xml = false;

        } // endif: created model map from task parameters

        // ... otherwise try to load information from the file
        else {

            // First try to open the file as a counts map
            try {

                // Allocate CTA observation
                GCTAObservation obs;

                // Load counts map in CTA observation
                obs.load_binned(m_infile);

                // Append CTA observation to container
                m_obs.append(obs);

                // Signal that no XML file should be used for storage
                m_use_xml = false;

            }

            // ... otherwise try to open as XML file
            catch (GException::fits_open_error &e) {

                // Load observations from XML file. This will throw
                // an exception if it fails.
                m_obs.load(m_infile);

                // Signal that XML file should be used for storage
                m_use_xml = true;

            }

        } // endelse: loaded information from input file

    } // endif: there was no observation in the container

    // If there are no models associated with the observations then
    // load now the model definition from the XML file
    if (m_obs.models().size() == 0) {
        m_obs.models(GModels(m_srcmdl));
    }

    // Check if all CTA observations contain an event cube and setup response
    // for all observations
    for (int i = 0; i < m_obs.size(); ++i) {

        // Is this observation a CTA observation?
        GCTAObservation* obs = dynamic_cast<GCTAObservation*>(m_obs[i]);

        // Yes ...
        if (obs != NULL) {

            // Throw an exception if this observation does not contain
            // an event cube
            if (dynamic_cast<const GCTAEventCube*>(obs->events()) == NULL) {
                throw GException::no_cube(G_SETUP_OBS);
            }

            // Set response if it isn't set already
            if (obs->response().aeff() == NULL) {

                // Set calibration database. If specified parameter is a
                // directory then use this as the pathname to the calibration
                // database. Otherwise interpret this as the instrument name,
                // the mission being "cta"
                GCaldb caldb;
                if (gammalib::dir_exists(m_caldb)) {
                    caldb.rootdir(m_caldb);
                }
                else {
                    caldb.open("cta", m_caldb);
                }

                // Set reponse
                obs->response(m_irf, caldb);

            } // endif: observation already has a response

        } // endif: observation was a CTA observation

    } // endfor: looped over all observations

    // Return
    return;
}
示例#10
0
/***********************************************************************//**
 * @brief Select events
 *
 * @param[in] obs CTA observation.
 * @param[in] filename File name.
 *
 * Select events from a FITS file by making use of the selection possibility
 * of the cfitsio library on loading a file. A selection string is created
 * from the specified criteria that is appended to the filename so that
 * cfitsio will automatically filter the event data. This selection string
 * is then applied when opening the FITS file. The opened FITS file is then
 * saved into a temporary file which is the loaded into the actual CTA
 * observation, overwriting the old CTA observation. The ROI, GTI and EBounds
 * of the CTA event list are then set accordingly to the specified selection.
 * Finally, the temporary file created during this process is removed.
 *
 * Good Time Intervals of the observation will be limited to the time
 * interval [m_tmin, m_tmax]. If m_tmin=m_tmax=0, no time selection is
 * performed.
 *
 * @todo Use INDEF instead of 0.0 for pointing as RA/DEC selection
 ***************************************************************************/
void ctselect::select_events(GCTAObservation* obs, const std::string& filename)
{
    // Allocate selection string
    std::string selection;
    char        cmin[80];
    char        cmax[80];
    char        cra[80];
    char        cdec[80];
    char        crad[80];

    // Set requested selections
    bool select_time = (m_tmin != 0.0 || m_tmax != 0.0);

    // Set RA/DEC selection
    double ra  = m_ra;
    double dec = m_dec;
    if (m_usepnt) {
        const GCTAPointing *pnt = obs->pointing();
        ra = pnt->dir().ra_deg();
        dec = pnt->dir().dec_deg();
    }

    // Set time selection interval. We make sure here that the time selection
    // interval cannot be wider than the GTIs covering the data. This is done
    // using GGti's reduce() method.
    if (select_time) {

        // Reduce GTIs to specified time interval. The complicated cast is
        // necessary here because the gti() method is declared const, so
        // we're not officially allowed to modify the GTIs.
        ((GGti*)(&obs->events()->gti()))->reduce(m_timemin, m_timemax);

    } // endif: time selection was required

    // Save GTI for later usage
    GGti gti = obs->events()->gti();

    // Make time selection
    if (select_time) {
    
        // Extract effective time interval in CTA reference time. We need
        // this reference for filtering.
        double tmin = gti.tstart().convert(m_cta_ref);
        double tmax = gti.tstop().convert(m_cta_ref);

        // Format time with sufficient accuracy and add to selection string
        sprintf(cmin, "%.8f", tmin);
        sprintf(cmax, "%.8f", tmax);
        selection = "TIME >= "+std::string(cmin)+" && TIME <= "+std::string(cmax);
        if (logTerse()) {
            log << parformat("Time range");
            log << tmin << " - " << tmax << " s" << std::endl;
        }
        if (selection.length() > 0) {
            selection += " && ";
        }
    }

    // Make energy selection
    sprintf(cmin, "%.8f", m_emin);
    sprintf(cmax, "%.8f", m_emax);
    selection += "ENERGY >= "+std::string(cmin)+" && ENERGY <= "+std::string(cmax);
    if (logTerse()) {
        log << parformat("Energy range");
        log << m_emin << " - " << m_emax << " TeV" << std::endl;
    }
    if (selection.length() > 0) {
        selection += " && ";
    }

    // Make ROI selection
    sprintf(cra,  "%.6f", ra);
    sprintf(cdec, "%.6f", dec);
    sprintf(crad, "%.6f", m_rad);
    selection += "ANGSEP("+std::string(cra)+"," +
                 std::string(cdec)+",RA,DEC) <= " +
                 std::string(crad);
    if (logTerse()) {
        log << parformat("Acceptance cone centre");
        log << "RA=" << ra << ", DEC=" << dec << " deg" << std::endl;
        log << parformat("Acceptance cone radius");
        log << m_rad << " deg" << std::endl;
    }
    if (logTerse()) {
        log << parformat("cfitsio selection");
        log << selection << std::endl;
    }

    // Add additional expression
    if (strip_whitespace(m_expr).length() > 0) {
        if (selection.length() > 0) {
            selection += " && ";
        }
        selection += "("+strip_whitespace(m_expr)+")";
    }

    // Build input filename including selection expression
    std::string expression = filename;
    if (selection.length() > 0)
        expression += "[EVENTS]["+selection+"]";
    if (logTerse()) {
        log << parformat("FITS filename");
        log << expression << std::endl;
    }

    // Open FITS file
    GFits file(expression);

    // Log selected FITS file
    if (logExplicit()) {
        log << std::endl;
        log.header1("FITS file content after selection");
        log << file << std::endl;
    }

    // Check if we have an EVENTS HDU
    if (!file.hashdu("EVENTS")) {
        std::string message = "No \"EVENTS\" extension found in FITS file "+
                              expression+".";
        throw GException::app_error(G_SELECT_EVENTS, message);
    }

    // Determine number of events in EVENTS HDU
    int nevents = file.table("EVENTS")->nrows();

    // If the selected event list is empty then append an empty event list
    // to the observation. Otherwise load the data from the temporary file.
    if (nevents < 1) {

        // Create empty event list
        GCTAEventList eventlist;

        // Append list to observation
        obs->events(&eventlist);

    }
    else {

        // Get temporary file name
        std::string tmpname = std::tmpnam(NULL);

        // Save FITS file to temporary file
        file.saveto(tmpname, true);

        // Load observation from temporary file
        obs->load_unbinned(tmpname);

        // Remove temporary file
        std::remove(tmpname.c_str());

    }

    // Get CTA event list pointer
    GCTAEventList* list =
        static_cast<GCTAEventList*>(const_cast<GEvents*>(obs->events()));

    // Set ROI
    GCTARoi     roi;
    GCTAInstDir instdir;
    instdir.radec_deg(ra, dec);
    roi.centre(instdir);
    roi.radius(m_rad);
    list->roi(roi);

    // Set GTI
    list->gti(gti);

    // Set energy boundaries
    GEbounds ebounds;
    GEnergy  emin;
    GEnergy  emax;
    emin.TeV(m_emin);
    emax.TeV(m_emax);
    ebounds.append(emin, emax);
    list->ebounds(ebounds);

    // Recompute ontime and livetime.
    GTime meantime = 0.5 * (gti.tstart() + gti.tstop());
    obs->ontime(gti.ontime());
    obs->livetime(gti.ontime() * obs->deadc(meantime));

    // Return
    return;
}