Commit 433c9b7b999534f0e2645840de390ff4ffffde95
Merge branch '2714-joint-binned' into integration
# Conflicts: # ChangeLog # doc/source/admin/release_history/1.6.rst
Showing
12 changed files
with
575 additions
and
174 deletions
ChangeLog
| ... | ... | @@ -4,6 +4,7 @@ |
| 4 | 4 | ======================== |
| 5 | 5 | |
| 6 | 6 | Apply event bin weighting in ctmodel for stacked computation (#2721) |
| 7 | + Enable ctbin to produce counts cubes for a joint binned analysis (#2714) | |
| 7 | 8 | Add Jupyter notebooks for H.E.S.S. DR1 (#2712) |
| 8 | 9 | Add code generator (#2709) |
| 9 | 10 | Add csbkgmodel script (#2694) | ... | ... |
NEWS
| ... | ... | @@ -61,6 +61,14 @@ of the filename is now done by the GApplicationPar class. |
| 61 | 61 | |
| 62 | 62 | ctbin - CTA event binning |
| 63 | 63 | ------------------------- |
| 64 | +Added the option to produce an individual counts cube for each event list in | |
| 65 | +the input observation definition XML file. The option is enabled by setting | |
| 66 | +the new hidden parameter "stack" to "no". The cube() method now has an optional | |
| 67 | +index argument, providing access to the i'th counts cube that was produced. | |
| 68 | +The cubes() method specifies the number of counts cubes that were produced. | |
| 69 | +If no counts cube was produced, the cube() method now throws an exception | |
| 70 | +(#2714). | |
| 71 | + | |
| 64 | 72 | ctbin now accouts for varying energy thresholds when computing the weight |
| 65 | 73 | array (#2685). |
| 66 | 74 | ... | ... |
cscripts/Makefile.am
| ... | ... | @@ -164,6 +164,7 @@ testscripts = $(srcdir)/tests/__init__.py \ |
| 164 | 164 | # Test data to be distributed in $(pyexecdir)/cscripts/tests/data |
| 165 | 165 | testdata = $(top_srcdir)/test/data/crab.xml \ |
| 166 | 166 | $(top_srcdir)/test/data/obs_unbinned.xml \ |
| 167 | + $(top_srcdir)/test/data/obs_unbinned_two.xml \ | |
| 167 | 168 | $(top_srcdir)/test/data/obs_stacked.xml \ |
| 168 | 169 | $(top_srcdir)/test/data/obs_stacked_two.xml \ |
| 169 | 170 | $(top_srcdir)/test/data/crab_events.fits \ | ... | ... |
doc/source/admin/release_history/1.6.rst
| ... | ... | @@ -43,6 +43,9 @@ Bug fixes |
| 43 | 43 | Improvements |
| 44 | 44 | ------------ |
| 45 | 45 | |
| 46 | +* [`2714 <https://cta-redmine.irap.omp.eu/issues/2714>`_] - | |
| 47 | + Enable :ref:`ctbin` to produce counts cubes for a joint binned analysis. | |
| 48 | + Rename ``outcube`` to ``outobs`` parameter. | |
| 46 | 49 | * [`2712 <https://cta-redmine.irap.omp.eu/issues/2712>`_] - |
| 47 | 50 | Add Jupyter notebooks for H.E.S.S. DR1 |
| 48 | 51 | * [`2685 <https://cta-redmine.irap.omp.eu/issues/2685>`_] - | ... | ... |
doc/source/users/reference_manual/ctbin.rst
| ... | ... | @@ -14,17 +14,27 @@ a 3-dimensional data cube spanned by Right Ascension or Galactic longitude, |
| 14 | 14 | Declination or Galactic latitude, and energy. The energy binning may be either |
| 15 | 15 | linear, logarithmic, or custom defined using an input file. The events are |
| 16 | 16 | either taken from a single event list file or from the event lists that are |
| 17 | -specified in an observation definition file. In case that multiple event | |
| 18 | -lists are given in an observation definition file, the tool will loop over | |
| 19 | -all event lists and fill all events into a single counts cube. | |
| 17 | +specified in an observation definition file. | |
| 20 | 18 | |
| 21 | -:ref:`ctbin` generates a counts cube FITS file comprising three extensions. The | |
| 19 | +In case that multiple event lists are given in an observation definition file | |
| 20 | +there are two options. By default, the tool will loop over all event lists and | |
| 21 | +fill all events into a single counts cube. If ``stack=no`` is specified, the | |
| 22 | +tool will bin each event list into a separate counts cube. | |
| 23 | + | |
| 24 | +:ref:`ctbin` generates counts cube FITS file(s) comprising three extensions. The | |
| 22 | 25 | primary extension contains a 3-dimensional image that contains the counts |
| 23 | 26 | cube values. The next extension named ``EBOUNDS`` contains a binary table |
| 24 | 27 | that defines the energy boundaries of the counts cube. The last extension |
| 25 | 28 | named ``GTI`` contains a binary table that defines the Good Time Intervals |
| 26 | 29 | of all event lists that have been filled into the counts cube. |
| 27 | 30 | |
| 31 | +By default, the tool will write out a single, stacked, counts cube. If ``stack=no`` | |
| 32 | +is specified, the tool will write all counts cubes into a separate FITS file, | |
| 33 | +prefixing the input event list file name with the string that is specified by | |
| 34 | +the hidden ``prefix`` parameter. In addition, the tool will create an observation | |
| 35 | +definition XML file that lists all produced counts cubes, and that can be used | |
| 36 | +as input for any further analysis step. | |
| 37 | + | |
| 28 | 38 | |
| 29 | 39 | General parameters |
| 30 | 40 | ------------------ |
| ... | ... | @@ -32,8 +42,18 @@ General parameters |
| 32 | 42 | ``inobs [file]`` |
| 33 | 43 | Input event list or observation definition XML file. |
| 34 | 44 | |
| 35 | -``outcube [file]`` | |
| 36 | - Output counts cube file. | |
| 45 | +``outobs [file]`` | |
| 46 | + Output counts cube file or observation definition XML file. | |
| 47 | + | |
| 48 | +``(stack = yes) [boolean]`` | |
| 49 | + Specify whether stacking of the events in a single counts cube is requested. | |
| 50 | + By default this hidden parameter is set to yes, and the tool produces a | |
| 51 | + single counts cube FITS file on output. If ``no`` is specified, the tool | |
| 52 | + will produce a counts cube for each event list that is found in the input | |
| 53 | + observation, and will also write an observation definition XML file. | |
| 54 | + | |
| 55 | +``(prefix = cntcube_) [string]`` | |
| 56 | + Prefix for output counts cube in observation definition XML file. | |
| 37 | 57 | |
| 38 | 58 | ``ebinalg <FILE|LIN|LOG> [string]`` |
| 39 | 59 | Algorithm for defining energy bins. For ``FILE``, the energy bins are defined | ... | ... |
doc/source/users/reference_manual/ctselect.rst
| ... | ... | @@ -52,7 +52,7 @@ General parameters |
| 52 | 52 | Output event list or observation definition XML file. |
| 53 | 53 | |
| 54 | 54 | ``(prefix = "selected_") [string]`` |
| 55 | - Prefix for output event lists in observation definition file. | |
| 55 | + Prefix for output event lists in observation definition XML file. | |
| 56 | 56 | |
| 57 | 57 | ``(usepnt = yes) [boolean]`` |
| 58 | 58 | Use pointing instead of RA/DEC parameters? | ... | ... |
pyext/ctbin.i
| 1 | 1 | /*************************************************************************** |
| 2 | 2 | * ctbin - Event binning tool * |
| 3 | 3 | * ----------------------------------------------------------------------- * |
| 4 | - * copyright (C) 2010-2016 by Juergen Knoedlseder * | |
| 4 | + * copyright (C) 2010-2018 by Juergen Knoedlseder * | |
| 5 | 5 | * ----------------------------------------------------------------------- * |
| 6 | 6 | * * |
| 7 | 7 | * This program is free software: you can redistribute it and/or modify * |
| ... | ... | @@ -46,9 +46,10 @@ public: |
| 46 | 46 | // Methods |
| 47 | 47 | void clear(void); |
| 48 | 48 | void run(void); |
| 49 | + int cubes(void) const; | |
| 50 | + const GCTAEventCube& cube(const int& index = 0) const; | |
| 49 | 51 | void save(void); |
| 50 | 52 | void publish(const std::string& name = ""); |
| 51 | - const GCTAEventCube& cube(void) const; | |
| 52 | 53 | }; |
| 53 | 54 | |
| 54 | 55 | ... | ... |
src/ctbin/ctbin.cpp
| ... | ... | @@ -38,6 +38,7 @@ |
| 38 | 38 | #endif |
| 39 | 39 | |
| 40 | 40 | /* __ Method name definitions ____________________________________________ */ |
| 41 | +#define G_CUBE "ctbin::cube(int&)" | |
| 41 | 42 | #define G_GET_PARAMETERS "ctbin::get_parameters()" |
| 42 | 43 | #define G_FILL_CUBE "ctbin::fill_cube(GCTAObservation*)" |
| 43 | 44 | #define G_SET_WEIGHTS "ctbin::set_weights(GCTAObservation*)" |
| ... | ... | @@ -234,6 +235,9 @@ void ctbin::run(void) |
| 234 | 235 | // Get task parameters |
| 235 | 236 | get_parameters(); |
| 236 | 237 | |
| 238 | + // Initialise sky direction cache for stacking | |
| 239 | + init_sky_dir_cache(); | |
| 240 | + | |
| 237 | 241 | // Write input observation container into logger |
| 238 | 242 | log_observations(NORMAL, m_obs, "Input observation"); |
| 239 | 243 | |
| ... | ... | @@ -253,64 +257,171 @@ void ctbin::run(void) |
| 253 | 257 | std::string msg = " Including unbinned "+obs->instrument()+ |
| 254 | 258 | " observation"; |
| 255 | 259 | log_string(NORMAL, msg); |
| 260 | + | |
| 261 | + } // endfor: looped over all unbinned observations | |
| 262 | + | |
| 263 | + // Set number of relevant observations | |
| 264 | + int nobs = obs_list.size(); | |
| 265 | + | |
| 266 | + // Initialise arrays | |
| 267 | + if (nobs > 0) { | |
| 268 | + m_counts = std::vector<GSkyMap>(nobs); | |
| 269 | + m_weights = std::vector<GSkyMap>(nobs); | |
| 270 | + m_cubes = std::vector<GCTAEventCube>(nobs); | |
| 271 | + } | |
| 272 | + else { | |
| 273 | + m_counts.clear(); | |
| 274 | + m_weights.clear(); | |
| 275 | + m_cubes.clear(); | |
| 256 | 276 | } |
| 257 | 277 | |
| 258 | 278 | // Write header into logger |
| 259 | - log_header1(TERSE, gammalib::number("Bin observation", m_obs.size())); | |
| 279 | + log_header1(TERSE, gammalib::number("Bin observation", nobs)); | |
| 260 | 280 | |
| 261 | 281 | // Loop over all unbinned CTA observations in the container |
| 262 | 282 | #pragma omp parallel for |
| 263 | - for (int i = 0; i < obs_list.size(); ++i) { | |
| 283 | + for (int i = 0; i < nobs; ++i) { | |
| 264 | 284 | |
| 265 | 285 | // Get pointer to observation |
| 266 | 286 | GCTAObservation* obs = obs_list[i]; |
| 267 | 287 | |
| 268 | 288 | // Fill the cube |
| 269 | - fill_cube(obs); | |
| 289 | + GSkyMap counts = fill_cube(obs); | |
| 270 | 290 | |
| 271 | 291 | // Set the counts cube weights |
| 272 | - set_weights(obs); | |
| 292 | + GSkyMap weights = set_weights(obs); | |
| 273 | 293 | |
| 274 | 294 | // Dispose events to free memory |
| 275 | 295 | obs->dispose_events(); |
| 276 | 296 | |
| 297 | + // Post process cubes | |
| 298 | + #pragma omp critical(ctbin_fill_cube) | |
| 299 | + { | |
| 300 | + // If stacking of cubes is requested then add up counts cube | |
| 301 | + // and set weights in the first slot of the m_counts and | |
| 302 | + // m_weights arrays | |
| 303 | + if (m_stack) { | |
| 304 | + if (i == 0) { | |
| 305 | + m_counts[0] = counts; | |
| 306 | + m_weights[0] = weights; | |
| 307 | + } | |
| 308 | + else { | |
| 309 | + for (int iebin = 0; iebin < m_ebounds.size(); ++iebin) { | |
| 310 | + for (int pixel = 0; pixel < counts.npix(); ++pixel) { | |
| 311 | + m_counts[0](pixel, iebin) += counts(pixel, iebin); | |
| 312 | + if (weights(pixel, iebin) == 1.0) { | |
| 313 | + m_weights[0](pixel, iebin) = 1.0; | |
| 314 | + } | |
| 315 | + } | |
| 316 | + } | |
| 317 | + } | |
| 318 | + } | |
| 319 | + | |
| 320 | + // ... otherwise store counts and weights | |
| 321 | + else { | |
| 322 | + m_counts[i] = counts; | |
| 323 | + m_weights[i] = weights; | |
| 324 | + } | |
| 325 | + | |
| 326 | + } // end: omp critical section | |
| 327 | + | |
| 277 | 328 | } // endfor: looped over observations |
| 278 | 329 | |
| 279 | - // Compute total livetime in all observations | |
| 280 | - double livetime = 0.0; | |
| 281 | - for (int i = 0; i < obs_list.size(); ++i) { | |
| 282 | - livetime += obs_list[i]->livetime(); | |
| 283 | - } | |
| 330 | + // Combine GTIs of all observations and compute total ontime and livetime | |
| 331 | + for (int i = 0; i < nobs; ++i) { | |
| 332 | + | |
| 333 | + // Get pointer to observation | |
| 334 | + GCTAObservation* obs = obs_list[i]; | |
| 284 | 335 | |
| 285 | - // Compute livetime fraction per energy bin so that varying energy | |
| 286 | - // thresholds are correctly taken into account in the weighting | |
| 287 | - // array. Computational speed is not optimum, we have to see later | |
| 288 | - // whether this is an issue. | |
| 289 | - for (int iebin = 0; iebin < m_ebounds.size(); ++iebin) { | |
| 290 | - double livetime_ebin = 0.0; | |
| 291 | - for (int i = 0; i < obs_list.size(); ++i) { | |
| 292 | - const GCTAEventList* events = dynamic_cast<const GCTAEventList*>(obs_list[i]->events()); | |
| 293 | - std::vector<bool> usage = cube_layer_usage(m_ebounds, events->ebounds()); | |
| 294 | - if (usage[iebin]) { | |
| 295 | - livetime_ebin += obs_list[i]->livetime(); | |
| 336 | + // Get Good Time Intervals for observation | |
| 337 | + GGti gti = obs->gti(); | |
| 338 | + | |
| 339 | + // If resulting GTI is not filled then copy over the reference from | |
| 340 | + // the observation and use it for the resulting GTI | |
| 341 | + if (m_gti.is_empty()) { | |
| 342 | + m_gti.reference(gti.reference()); | |
| 343 | + } | |
| 344 | + | |
| 345 | + // Append GTIs | |
| 346 | + m_gti.extend(gti); | |
| 347 | + | |
| 348 | + // Update ontime and livetime | |
| 349 | + m_ontime += obs->ontime(); | |
| 350 | + m_livetime += obs->livetime(); | |
| 351 | + | |
| 352 | + } // endfor: looped over all observations | |
| 353 | + | |
| 354 | + // Post process data for a stacked cube | |
| 355 | + if (m_stack && nobs > 0) { | |
| 356 | + | |
| 357 | + // Compute livetime fraction per energy bin so that varying energy | |
| 358 | + // thresholds are correctly taken into account in the weighting | |
| 359 | + // array. Computational speed is not optimum, we have to see later | |
| 360 | + // whether this is an issue. | |
| 361 | + for (int iebin = 0; iebin < m_ebounds.size(); ++iebin) { | |
| 362 | + double livetime_ebin = 0.0; | |
| 363 | + for (int i = 0; i < obs_list.size(); ++i) { | |
| 364 | + std::vector<bool> usage = | |
| 365 | + cube_layer_usage(m_ebounds, obs_list[i]->ebounds()); | |
| 366 | + if (usage[iebin]) { | |
| 367 | + livetime_ebin += obs_list[i]->livetime(); | |
| 368 | + } | |
| 296 | 369 | } |
| 297 | - } | |
| 298 | - if (livetime > 0.0) { | |
| 299 | - double fraction = livetime_ebin / livetime; | |
| 300 | - for (int pixel = 0; pixel < m_counts.npix(); ++pixel) { | |
| 301 | - if (m_weights(pixel, iebin) > 0.0) { | |
| 302 | - m_weights(pixel, iebin) *= fraction; | |
| 370 | + if (m_livetime > 0.0) { | |
| 371 | + double fraction = livetime_ebin / m_livetime; | |
| 372 | + for (int pixel = 0; pixel < m_counts[0].npix(); ++pixel) { | |
| 373 | + if (m_weights[0](pixel, iebin) > 0.0) { | |
| 374 | + m_weights[0](pixel, iebin) *= fraction; | |
| 375 | + } | |
| 303 | 376 | } |
| 304 | 377 | } |
| 305 | 378 | } |
| 306 | - } | |
| 307 | 379 | |
| 308 | - // Build event cube (needs to come before obs_cube() since this method | |
| 309 | - // relies on correct setting of m_cube) | |
| 310 | - m_cube = GCTAEventCube(m_counts, m_weights, m_ebounds, m_gti); | |
| 380 | + // Set a single cube in the observation container | |
| 381 | + obs_cube_stacked(); | |
| 382 | + | |
| 383 | + } // endif: stacked cube post processing | |
| 384 | + | |
| 385 | + // Post process data for non-stacked cubes | |
| 386 | + else { | |
| 387 | + | |
| 388 | + // Loop over all relevant observations | |
| 389 | + for (int i = 0; i < nobs; ++i) { | |
| 390 | + | |
| 391 | + // Get pointer to observation | |
| 392 | + GCTAObservation* obs = obs_list[i]; | |
| 393 | + | |
| 394 | + // Build counts cube | |
| 395 | + m_cubes[i] = GCTAEventCube(m_counts[i], m_weights[i], m_ebounds, | |
| 396 | + obs->gti()); | |
| 397 | + | |
| 398 | + // Attach event cube in the correct slot of the observation | |
| 399 | + // container | |
| 400 | + obs->events(m_cubes[i]); | |
| 401 | + | |
| 402 | + // Construct unique filename for possible saving of the counts | |
| 403 | + // cube into FITS files. Each observation has a unique pair of | |
| 404 | + // instrument() and id() attributes, hence we construct a lower | |
| 405 | + // case file name from these attributes, making sure that all | |
| 406 | + // whitespaces are converted into "_" characters. Note that the | |
| 407 | + // id() attribute is allowed to be blank, which can only happen | |
| 408 | + // for a single observation. | |
| 409 | + std::string inst = gammalib::tolower(obs->instrument()); | |
| 410 | + std::string id = gammalib::tolower(obs->id()); | |
| 411 | + inst = gammalib::replace_segment(inst, " ", "_"); | |
| 412 | + id = gammalib::replace_segment(id, " ", "_"); | |
| 413 | + std::string filename = m_prefix + inst; | |
| 414 | + if (!id.empty()) { | |
| 415 | + filename += "_" + id; | |
| 416 | + } | |
| 417 | + filename += ".fits"; | |
| 311 | 418 | |
| 312 | - // Set a single cube in the observation container | |
| 313 | - obs_cube(); | |
| 419 | + // Set filename of observation | |
| 420 | + obs->eventfile(filename); | |
| 421 | + | |
| 422 | + } // endfor: looped over relevant observations | |
| 423 | + | |
| 424 | + } // endelse: non-stacked cube post processing | |
| 314 | 425 | |
| 315 | 426 | // Write resulting observation container into logger |
| 316 | 427 | log_observations(NORMAL, m_obs, "Binned observation"); |
| ... | ... | @@ -326,37 +437,106 @@ void ctbin::run(void) |
| 326 | 437 | |
| 327 | 438 | |
| 328 | 439 | /***********************************************************************//** |
| 440 | + * @brief Return event cube at index | |
| 441 | + * | |
| 442 | + * @param[in] index Cube index (0,...,cubes()-1). | |
| 443 | + * @return Reference to event cube. | |
| 444 | + * | |
| 445 | + * @exception GException::out_of_range | |
| 446 | + * Event cube index out of range. | |
| 447 | + * | |
| 448 | + * Returns a reference to the event cube at the given index. | |
| 449 | + ***************************************************************************/ | |
| 450 | +const GCTAEventCube& ctbin::cube(const int& index) const | |
| 451 | +{ | |
| 452 | + // Compile option: raise an exception if index is out of range | |
| 453 | + if (index < 0 || index >= cubes()) { | |
| 454 | + throw GException::out_of_range(G_CUBE, "Cube index", index, cubes()); | |
| 455 | + } | |
| 456 | + | |
| 457 | + // Return cube | |
| 458 | + return m_cubes[index]; | |
| 459 | +} | |
| 460 | + | |
| 461 | + | |
| 462 | +/***********************************************************************//** | |
| 329 | 463 | * @brief Save counts cube |
| 330 | 464 | * |
| 331 | - * Saves the counts cube into a FITS file. | |
| 465 | + * Saves the counts cube. If the counts cube was stacked single FITS file | |
| 466 | + * will be written. Otherwise each generated counts cube will be written | |
| 467 | + * into a FITS file, and an observation definition XML file will be written | |
| 468 | + * out that contains an observation for each counts cube. | |
| 332 | 469 | ***************************************************************************/ |
| 333 | 470 | void ctbin::save(void) |
| 334 | 471 | { |
| 335 | 472 | // Write header |
| 336 | 473 | log_header1(TERSE, "Save counts cube"); |
| 337 | 474 | |
| 338 | - // Save only if filename is valid and if there is at least one | |
| 339 | - // observation | |
| 340 | - if ((*this)["outcube"].is_valid() && m_obs.size() > 0) { | |
| 475 | + // If counts cube was stacked then save first observation | |
| 476 | + if (m_stack) { | |
| 341 | 477 | |
| 342 | - // Get counts cube filename | |
| 343 | - m_outcube = (*this)["outcube"].filename(); | |
| 478 | + // Save only if filename is valid and if there is at least one | |
| 479 | + // observation | |
| 480 | + if ((*this)["outobs"].is_valid() && m_obs.size() > 0) { | |
| 344 | 481 | |
| 345 | - // Get CTA observation from observation container | |
| 346 | - GCTAObservation* obs = dynamic_cast<GCTAObservation*>(m_obs[0]); | |
| 482 | + // Get counts cube filename | |
| 483 | + GFilename outobs = (*this)["outobs"].filename(); | |
| 347 | 484 | |
| 348 | - // Save only if observation is valid | |
| 349 | - if (obs != NULL) { | |
| 485 | + // Get CTA observation from observation container | |
| 486 | + GCTAObservation* obs = dynamic_cast<GCTAObservation*>(m_obs[0]); | |
| 487 | + | |
| 488 | + // Save only if observation is valid | |
| 489 | + if (obs != NULL) { | |
| 490 | + | |
| 491 | + // Log counts cube file name | |
| 492 | + log_value(NORMAL, "Counts cube file", outobs.url()); | |
| 350 | 493 | |
| 351 | - // Log counts cube file name | |
| 352 | - log_value(NORMAL, "Counts cube file", m_outcube.url()); | |
| 494 | + // Save cube | |
| 495 | + obs->save(outobs, clobber()); | |
| 353 | 496 | |
| 354 | - // Save cube | |
| 355 | - obs->save(m_outcube, clobber()); | |
| 497 | + } // endif: observation was valid | |
| 498 | + | |
| 499 | + } // endif: outobs file was valid | |
| 500 | + | |
| 501 | + } // endif: counts cube was stacked | |
| 502 | + | |
| 503 | + // ... otherwise write out all relevant counts cubes and the observation | |
| 504 | + // definition XML file | |
| 505 | + else { | |
| 506 | + | |
| 507 | + // Loop over all observations | |
| 508 | + for (int i = 0; i < m_obs.size(); ++i) { | |
| 509 | + | |
| 510 | + // Get CTA observation from observation container | |
| 511 | + GCTAObservation* obs = dynamic_cast<GCTAObservation*>(m_obs[i]); | |
| 356 | 512 | |
| 357 | - } // endif: observation was valid | |
| 513 | + // Continue only if observation is valid | |
| 514 | + if (obs != NULL) { | |
| 358 | 515 | |
| 359 | - } // endif: outcube file was valid | |
| 516 | + // Retrieve filename | |
| 517 | + std::string filename = obs->eventfile(); | |
| 518 | + | |
| 519 | + // Save counts cube | |
| 520 | + obs->save(filename, clobber()); | |
| 521 | + | |
| 522 | + } // endif: observation was valid | |
| 523 | + | |
| 524 | + } // endfor: looped over all observations | |
| 525 | + | |
| 526 | + // Write XML file | |
| 527 | + if ((*this)["outobs"].is_valid()) { | |
| 528 | + | |
| 529 | + // Get observation definition XML filename | |
| 530 | + std::string outobs = (*this)["outobs"].filename(); | |
| 531 | + | |
| 532 | + // Issue warning if output filename has no .xml suffix | |
| 533 | + log_string(TERSE, warn_xml_suffix(outobs)); | |
| 534 | + | |
| 535 | + // Save XML file | |
| 536 | + m_obs.save(outobs); | |
| 537 | + } | |
| 538 | + | |
| 539 | + } // endelse: counts cube were not stacked | |
| 360 | 540 | |
| 361 | 541 | // Return |
| 362 | 542 | return; |
| ... | ... | @@ -382,8 +562,10 @@ void ctbin::publish(const std::string& name) |
| 382 | 562 | // Write counts cube name into logger |
| 383 | 563 | log_value(NORMAL, "Counts cube name", user_name); |
| 384 | 564 | |
| 385 | - // Publish counts cube | |
| 386 | - m_counts.publish(user_name); | |
| 565 | + // Publish first counts cube if it exists | |
| 566 | + if (cubes() > 0) { | |
| 567 | + m_counts[0].publish(user_name); | |
| 568 | + } | |
| 387 | 569 | |
| 388 | 570 | // Return |
| 389 | 571 | return; |
| ... | ... | @@ -402,17 +584,18 @@ void ctbin::publish(const std::string& name) |
| 402 | 584 | void ctbin::init_members(void) |
| 403 | 585 | { |
| 404 | 586 | // Initialise members |
| 405 | - m_outcube.clear(); | |
| 406 | 587 | m_usepnt = false; |
| 588 | + m_stack = true; | |
| 589 | + m_prefix.clear(); | |
| 407 | 590 | m_publish = false; |
| 408 | 591 | m_chatter = static_cast<GChatter>(2); |
| 409 | 592 | |
| 410 | 593 | // Initialise protected members |
| 594 | + m_cubes.clear(); | |
| 411 | 595 | m_counts.clear(); |
| 412 | 596 | m_weights.clear(); |
| 413 | 597 | m_ebounds.clear(); |
| 414 | 598 | m_gti.clear(); |
| 415 | - m_cube.clear(); | |
| 416 | 599 | m_ontime = 0.0; |
| 417 | 600 | m_livetime = 0.0; |
| 418 | 601 | |
| ... | ... | @@ -432,17 +615,18 @@ void ctbin::init_members(void) |
| 432 | 615 | void ctbin::copy_members(const ctbin& app) |
| 433 | 616 | { |
| 434 | 617 | // Copy attributes |
| 435 | - m_outcube = app.m_outcube; | |
| 436 | 618 | m_usepnt = app.m_usepnt; |
| 619 | + m_stack = app.m_stack; | |
| 620 | + m_prefix = app.m_prefix; | |
| 437 | 621 | m_publish = app.m_publish; |
| 438 | 622 | m_chatter = app.m_chatter; |
| 439 | 623 | |
| 440 | 624 | // Copy protected members |
| 625 | + m_cubes = app.m_cubes; | |
| 441 | 626 | m_counts = app.m_counts; |
| 442 | 627 | m_weights = app.m_weights; |
| 443 | 628 | m_ebounds = app.m_ebounds; |
| 444 | 629 | m_gti = app.m_gti; |
| 445 | - m_cube = app.m_cube; | |
| 446 | 630 | m_ontime = app.m_ontime; |
| 447 | 631 | m_livetime = app.m_livetime; |
| 448 | 632 | |
| ... | ... | @@ -479,32 +663,23 @@ void ctbin::get_parameters(void) |
| 479 | 663 | // information and do not accept counts cubes. |
| 480 | 664 | setup_observations(m_obs, false, true, false); |
| 481 | 665 | |
| 482 | - // Create an event cube based on task parameters | |
| 483 | - GCTAEventCube cube = create_cube(m_obs); | |
| 484 | - | |
| 485 | - // Get the skymap from the cube and initialise all counts cube bins and | |
| 486 | - // weights to zero | |
| 487 | - m_counts = cube.counts(); | |
| 488 | - m_counts = 0.0; | |
| 489 | - m_weights = m_counts; | |
| 666 | + // Create an event cube to query task parameters | |
| 667 | + GCTAEventCube cube = this->ctool::create_cube(m_obs); | |
| 490 | 668 | |
| 491 | 669 | // Get energy boundaries |
| 492 | 670 | m_ebounds = cube.ebounds(); |
| 493 | 671 | |
| 672 | + // Get stack and prefix parameters | |
| 673 | + m_stack = (*this)["stack"].boolean(); | |
| 674 | + m_prefix = (*this)["prefix"].string(); | |
| 675 | + | |
| 494 | 676 | // Get remaining parameters |
| 495 | 677 | m_publish = (*this)["publish"].boolean(); |
| 496 | 678 | m_chatter = static_cast<GChatter>((*this)["chatter"].integer()); |
| 497 | 679 | |
| 498 | 680 | // If needed later, query output filename now |
| 499 | 681 | if (read_ahead()) { |
| 500 | - (*this)["outcube"].query(); | |
| 501 | - } | |
| 502 | - | |
| 503 | - // Cache sky directions in m_counts | |
| 504 | - m_dirs.resize(m_counts.npix()); | |
| 505 | - for (int pix = 0; pix < m_counts.npix(); ++pix) { | |
| 506 | - m_dirs[pix] = m_counts.inx2dir(pix); | |
| 507 | - m_dirs[pix].radec(m_dirs[pix].ra(), m_dirs[pix].dec()); | |
| 682 | + (*this)["outobs"].query(); | |
| 508 | 683 | } |
| 509 | 684 | |
| 510 | 685 | // Set number of OpenMP threads |
| ... | ... | @@ -524,6 +699,95 @@ void ctbin::get_parameters(void) |
| 524 | 699 | |
| 525 | 700 | |
| 526 | 701 | /***********************************************************************//** |
| 702 | + * @brief Initialise sky direction cache for cube stack | |
| 703 | + * | |
| 704 | + * Initialises a cache that holds the sky directions of each counts cube | |
| 705 | + * pixel for a stacked analysis. This will speed up the computations later. | |
| 706 | + * | |
| 707 | + * If no stacking is requested, the method does nothing. | |
| 708 | + ***************************************************************************/ | |
| 709 | +void ctbin::init_sky_dir_cache(void) | |
| 710 | +{ | |
| 711 | + // Continue only is stacking is requested | |
| 712 | + if (m_stack) { | |
| 713 | + | |
| 714 | + // Create a sky map from task parameters | |
| 715 | + GSkyMap map = this->ctool::create_map(m_obs); | |
| 716 | + | |
| 717 | + // Initialise sky direction | |
| 718 | + m_dirs.resize(map.npix()); | |
| 719 | + | |
| 720 | + // Compute sky direction for each pixel | |
| 721 | + for (int pixel = 0; pixel < map.npix(); ++pixel) { | |
| 722 | + m_dirs[pixel] = map.inx2dir(pixel); | |
| 723 | + } | |
| 724 | + | |
| 725 | + } // endif: stacking was requested | |
| 726 | + | |
| 727 | + // Return | |
| 728 | + return; | |
| 729 | +} | |
| 730 | + | |
| 731 | + | |
| 732 | +/***********************************************************************//** | |
| 733 | + * @brief Create counts cube sky map for current observation | |
| 734 | + * | |
| 735 | + * @param[in] obs CTA observation. | |
| 736 | + * | |
| 737 | + * Creates a counts cube sky map for current observations. | |
| 738 | + ***************************************************************************/ | |
| 739 | +GSkyMap ctbin::create_cube(const GCTAObservation* obs) | |
| 740 | +{ | |
| 741 | + // Read coordinate system and projection | |
| 742 | + std::string coordsys = (*this)["coordsys"].string(); | |
| 743 | + std::string proj = (*this)["proj"].string(); | |
| 744 | + | |
| 745 | + // Read sky map reference | |
| 746 | + double xref = 0.0; | |
| 747 | + double yref = 0.0; | |
| 748 | + bool usepnt = (*this)["usepnt"].boolean(); | |
| 749 | + if (!usepnt) { | |
| 750 | + xref = (*this)["xref"].real(); | |
| 751 | + yref = (*this)["yref"].real(); | |
| 752 | + } | |
| 753 | + | |
| 754 | + // Read sky map pixel size and number of bins | |
| 755 | + double binsz = (*this)["binsz"].real(); | |
| 756 | + int nxpix = (*this)["nxpix"].integer(); | |
| 757 | + int nypix = (*this)["nypix"].integer(); | |
| 758 | + | |
| 759 | + // Read energy boundaries | |
| 760 | + GEbounds ebounds = create_ebounds(); | |
| 761 | + | |
| 762 | + // If requested, get pointing from observations | |
| 763 | + if (usepnt) { | |
| 764 | + | |
| 765 | + // Get pointing direction for observation | |
| 766 | + GSkyDir pnt = obs->pointing().dir(); | |
| 767 | + | |
| 768 | + // Set xref/yref based on the coordinate system | |
| 769 | + if (gammalib::toupper(coordsys) == "GAL") { | |
| 770 | + xref = pnt.l_deg(); | |
| 771 | + yref = pnt.b_deg(); | |
| 772 | + } | |
| 773 | + else { | |
| 774 | + xref = pnt.ra_deg(); | |
| 775 | + yref = pnt.dec_deg(); | |
| 776 | + } | |
| 777 | + | |
| 778 | + } // endif: got pointing from observation | |
| 779 | + | |
| 780 | + // Initialise counts cube | |
| 781 | + GSkyMap cube = GSkyMap(proj, coordsys, xref, yref, -binsz, binsz, | |
| 782 | + nxpix, nypix, ebounds.size()); | |
| 783 | + | |
| 784 | + | |
| 785 | + // Return cube | |
| 786 | + return cube; | |
| 787 | +} | |
| 788 | + | |
| 789 | + | |
| 790 | +/***********************************************************************//** | |
| 527 | 791 | * @brief Fill events into counts cube |
| 528 | 792 | * |
| 529 | 793 | * @param[in] obs CTA observation. |
| ... | ... | @@ -533,10 +797,10 @@ void ctbin::get_parameters(void) |
| 533 | 797 | * |
| 534 | 798 | * Fills the events from an event list into the counts cube. |
| 535 | 799 | ***************************************************************************/ |
| 536 | -void ctbin::fill_cube(GCTAObservation* obs) | |
| 800 | +GSkyMap ctbin::fill_cube(const GCTAObservation* obs) | |
| 537 | 801 | { |
| 538 | - // Make sure that the observation holds a CTA event list. If this | |
| 539 | - // is not the case then throw an exception. | |
| 802 | + // Make sure that the observation holds a CTA event list. If this is | |
| 803 | + // not the case then throw an exception. | |
| 540 | 804 | const GCTAEventList* events = dynamic_cast<const GCTAEventList*> |
| 541 | 805 | (obs->events()); |
| 542 | 806 | if (events == NULL) { |
| ... | ... | @@ -547,7 +811,7 @@ void ctbin::fill_cube(GCTAObservation* obs) |
| 547 | 811 | } |
| 548 | 812 | |
| 549 | 813 | // Get the RoI |
| 550 | - const GCTARoi& roi = events->roi(); | |
| 814 | + const GCTARoi& roi = obs->roi(); | |
| 551 | 815 | |
| 552 | 816 | // Check for RoI sanity |
| 553 | 817 | if (!roi.is_valid()) { |
| ... | ... | @@ -559,7 +823,7 @@ void ctbin::fill_cube(GCTAObservation* obs) |
| 559 | 823 | } |
| 560 | 824 | |
| 561 | 825 | // Get counts cube usage flags |
| 562 | - std::vector<bool> usage = cube_layer_usage(m_ebounds, events->ebounds()); | |
| 826 | + std::vector<bool> usage = cube_layer_usage(m_ebounds, obs->ebounds()); | |
| 563 | 827 | |
| 564 | 828 | // Initialise binning statistics |
| 565 | 829 | int num_outside_roi = 0; |
| ... | ... | @@ -568,6 +832,13 @@ void ctbin::fill_cube(GCTAObservation* obs) |
| 568 | 832 | int num_outside_ebds = 0; |
| 569 | 833 | int num_in_map = 0; |
| 570 | 834 | |
| 835 | + // Create counts cube | |
| 836 | + GSkyMap counts = create_cube(obs); | |
| 837 | + | |
| 838 | + // Extract counts cube boundaries | |
| 839 | + double pixel_x_max = counts.nx() - 0.5; | |
| 840 | + double pixel_y_max = counts.ny() - 0.5; | |
| 841 | + | |
| 571 | 842 | // Fill counts sky map |
| 572 | 843 | for (int i = 0; i < events->size(); ++i) { |
| 573 | 844 | |
| ... | ... | @@ -587,7 +858,7 @@ void ctbin::fill_cube(GCTAObservation* obs) |
| 587 | 858 | // Determine sky pixel |
| 588 | 859 | GSkyPixel pixel; |
| 589 | 860 | try { |
| 590 | - pixel = m_counts.dir2pix(dir); | |
| 861 | + pixel = counts.dir2pix(dir); | |
| 591 | 862 | } |
| 592 | 863 | catch (std::exception &e) { |
| 593 | 864 | num_invalid_wcs++; |
| ... | ... | @@ -596,8 +867,8 @@ void ctbin::fill_cube(GCTAObservation* obs) |
| 596 | 867 | |
| 597 | 868 | // Skip event if corresponding counts cube pixel is outside the |
| 598 | 869 | // counts cube map range |
| 599 | - if (pixel.x() < -0.5 || pixel.x() > (m_counts.nx()-0.5) || | |
| 600 | - pixel.y() < -0.5 || pixel.y() > (m_counts.ny()-0.5)) { | |
| 870 | + if (pixel.x() < -0.5 || pixel.x() > pixel_x_max || | |
| 871 | + pixel.y() < -0.5 || pixel.y() > pixel_y_max) { | |
| 601 | 872 | num_outside_map++; |
| 602 | 873 | continue; |
| 603 | 874 | } |
| ... | ... | @@ -614,31 +885,13 @@ void ctbin::fill_cube(GCTAObservation* obs) |
| 614 | 885 | } |
| 615 | 886 | |
| 616 | 887 | // Fill event in skymap |
| 617 | - #pragma omp critical(ctbin_fill_cube) | |
| 618 | - m_counts(pixel, iebin) += 1.0; | |
| 888 | + counts(pixel, iebin) += 1.0; | |
| 619 | 889 | |
| 620 | 890 | // Increment number of maps |
| 621 | 891 | num_in_map++; |
| 622 | 892 | |
| 623 | 893 | } // endfor: looped over all events |
| 624 | 894 | |
| 625 | - // Update time information | |
| 626 | - #pragma omp critical(ctbin_fill_cube) | |
| 627 | - { | |
| 628 | - // If GTI is empty then set its time reference from the observation. | |
| 629 | - // From then on we keep that time reference | |
| 630 | - if (m_gti.is_empty()) { | |
| 631 | - m_gti.reference(events->gti().reference()); | |
| 632 | - } | |
| 633 | - | |
| 634 | - // Append GTIs | |
| 635 | - m_gti.extend(events->gti()); | |
| 636 | - | |
| 637 | - // Update ontime and livetime | |
| 638 | - m_ontime += obs->ontime(); | |
| 639 | - m_livetime += obs->livetime(); | |
| 640 | - } | |
| 641 | - | |
| 642 | 895 | // Log filling results |
| 643 | 896 | #pragma omp critical(ctbin_fill_cube) |
| 644 | 897 | { |
| ... | ... | @@ -652,7 +905,7 @@ void ctbin::fill_cube(GCTAObservation* obs) |
| 652 | 905 | } |
| 653 | 906 | |
| 654 | 907 | // Return |
| 655 | - return; | |
| 908 | + return counts; | |
| 656 | 909 | } |
| 657 | 910 | |
| 658 | 911 | |
| ... | ... | @@ -667,21 +920,10 @@ void ctbin::fill_cube(GCTAObservation* obs) |
| 667 | 920 | * Sets the counts cube weights for all bins that are considered for the |
| 668 | 921 | * specific observation to unity. |
| 669 | 922 | ***************************************************************************/ |
| 670 | -void ctbin::set_weights(GCTAObservation* obs) | |
| 923 | +GSkyMap ctbin::set_weights(const GCTAObservation* obs) | |
| 671 | 924 | { |
| 672 | - // Make sure that the observation holds a CTA event list. If this | |
| 673 | - // is not the case then throw an exception. | |
| 674 | - const GCTAEventList* events = dynamic_cast<const GCTAEventList*> | |
| 675 | - (obs->events()); | |
| 676 | - if (events == NULL) { | |
| 677 | - std::string msg = "CTA Observation does not contain an event " | |
| 678 | - "list. An event list is needed to fill the " | |
| 679 | - "counts cube."; | |
| 680 | - throw GException::invalid_value(G_SET_WEIGHTS, msg); | |
| 681 | - } | |
| 682 | - | |
| 683 | 925 | // Get the RoI |
| 684 | - const GCTARoi& roi = events->roi(); | |
| 926 | + const GCTARoi& roi = obs->roi(); | |
| 685 | 927 | |
| 686 | 928 | // Check for RoI sanity |
| 687 | 929 | if (!roi.is_valid()) { |
| ... | ... | @@ -692,15 +934,32 @@ void ctbin::set_weights(GCTAObservation* obs) |
| 692 | 934 | throw GException::invalid_value(G_SET_WEIGHTS, msg); |
| 693 | 935 | } |
| 694 | 936 | |
| 937 | + // Get the RoI centre and radius in radians | |
| 938 | + GSkyDir roi_centre = roi.centre().dir(); | |
| 939 | + double roi_radius = roi.radius() * gammalib::deg2rad; | |
| 940 | + | |
| 695 | 941 | // Get counts cube usage flags |
| 696 | - std::vector<bool> usage = cube_layer_usage(m_ebounds, events->ebounds()); | |
| 942 | + std::vector<bool> usage = cube_layer_usage(m_ebounds, obs->ebounds()); | |
| 943 | + | |
| 944 | + // Create weights cube | |
| 945 | + GSkyMap weights = create_cube(obs); | |
| 946 | + | |
| 947 | + // Set sky direction cache availability flag | |
| 948 | + bool cache = (m_stack && m_dirs.size() == weights.npix()); | |
| 697 | 949 | |
| 698 | 950 | // Loop over all pixels in counts cube |
| 699 | - for (int pixel = 0; pixel < m_counts.npix(); ++pixel) { | |
| 951 | + for (int pixel = 0; pixel < weights.npix(); ++pixel) { | |
| 700 | 952 | |
| 701 | 953 | // Skip pixel if it is outside the RoI |
| 702 | - if (roi.centre().dir().dist_deg(m_dirs[pixel]) > roi.radius()) { | |
| 703 | - continue; | |
| 954 | + if (cache) { | |
| 955 | + if (m_dirs[pixel].dist(roi_centre) > roi_radius) { | |
| 956 | + continue; | |
| 957 | + } | |
| 958 | + } | |
| 959 | + else { | |
| 960 | + if (roi_centre.dist(weights.inx2dir(pixel)) > roi_radius) { | |
| 961 | + continue; | |
| 962 | + } | |
| 704 | 963 | } |
| 705 | 964 | |
| 706 | 965 | // Loop over all energy layers of counts cube |
| ... | ... | @@ -712,15 +971,14 @@ void ctbin::set_weights(GCTAObservation* obs) |
| 712 | 971 | } |
| 713 | 972 | |
| 714 | 973 | // Signal that bin was filled |
| 715 | - #pragma omp critical(ctbin_set_weights) | |
| 716 | - m_weights(pixel, iebin) = 1.0; | |
| 974 | + weights(pixel, iebin) = 1.0; | |
| 717 | 975 | |
| 718 | 976 | } // endfor: looped over energy layers of counts cube |
| 719 | 977 | |
| 720 | 978 | } // endfor: looped over pixels of counts cube |
| 721 | 979 | |
| 722 | 980 | // Return |
| 723 | - return; | |
| 981 | + return weights; | |
| 724 | 982 | } |
| 725 | 983 | |
| 726 | 984 | |
| ... | ... | @@ -735,8 +993,11 @@ void ctbin::set_weights(GCTAObservation* obs) |
| 735 | 993 | * information in case that a single CTA observation is provided to support |
| 736 | 994 | * binned analysis. |
| 737 | 995 | ***************************************************************************/ |
| 738 | -void ctbin::obs_cube(void) | |
| 996 | +void ctbin::obs_cube_stacked(void) | |
| 739 | 997 | { |
| 998 | + // Set event cube in first slot | |
| 999 | + m_cubes[0] = GCTAEventCube(m_counts[0], m_weights[0], m_ebounds, m_gti); | |
| 1000 | + | |
| 740 | 1001 | // If we have only a single CTA observation in the container, then |
| 741 | 1002 | // keep that observation and just attach the event cube to it. Reset |
| 742 | 1003 | // the filename, otherwise we still will have the old event filename |
| ... | ... | @@ -751,7 +1012,7 @@ void ctbin::obs_cube(void) |
| 751 | 1012 | if (obs->eventtype() == "EventList") { |
| 752 | 1013 | |
| 753 | 1014 | // Assign cube to the observation |
| 754 | - obs->events(this->cube()); | |
| 1015 | + obs->events(m_cubes[0]); | |
| 755 | 1016 | |
| 756 | 1017 | } |
| 757 | 1018 | |
| ... | ... | @@ -761,7 +1022,8 @@ void ctbin::obs_cube(void) |
| 761 | 1022 | else { |
| 762 | 1023 | |
| 763 | 1024 | // Create empty counts cube |
| 764 | - GCTAEventCube cube(m_counts, m_weights, m_ebounds, obs->gti()); | |
| 1025 | + GCTAEventCube cube(m_counts[0], m_weights[0], m_ebounds, | |
| 1026 | + obs->gti()); | |
| 765 | 1027 | |
| 766 | 1028 | // Assign empty cube to observation |
| 767 | 1029 | obs->events(cube); |
| ... | ... | @@ -787,7 +1049,7 @@ void ctbin::obs_cube(void) |
| 787 | 1049 | GCTAObservation obs; |
| 788 | 1050 | |
| 789 | 1051 | // Attach event cube to CTA observation |
| 790 | - obs.events(this->cube()); | |
| 1052 | + obs.events(m_cubes[0]); | |
| 791 | 1053 | |
| 792 | 1054 | // Compute average pointing direction for all CTA event lists |
| 793 | 1055 | double ra = 0.0; | ... | ... |
src/ctbin/ctbin.hpp
| 1 | 1 | /*************************************************************************** |
| 2 | 2 | * ctbin - Event binning tool * |
| 3 | 3 | * ----------------------------------------------------------------------- * |
| 4 | - * copyright (C) 2010-2017 by Juergen Knoedlseder * | |
| 4 | + * copyright (C) 2010-2018 by Juergen Knoedlseder * | |
| 5 | 5 | * ----------------------------------------------------------------------- * |
| 6 | 6 | * * |
| 7 | 7 | * This program is free software: you can redistribute it and/or modify * |
| ... | ... | @@ -39,7 +39,7 @@ |
| 39 | 39 | * |
| 40 | 40 | * @brief Event binning tool |
| 41 | 41 | * |
| 42 | - * This class bins event list(s) into a single counts cube. The class can | |
| 42 | + * This class bins event list(s) into counts cubes. The class can | |
| 43 | 43 | * operate on predefined observation containers, on individual event list |
| 44 | 44 | * FITS files, and on observation definition XML files. |
| 45 | 45 | * |
| ... | ... | @@ -47,6 +47,13 @@ |
| 47 | 47 | * XML definition file, the class will merge these events into a single |
| 48 | 48 | * counts cube. |
| 49 | 49 | * |
| 50 | + * If the hidden parameter stack=no is used, one counts cube is generated for | |
| 51 | + * each individual observation. These cubes are saved to a path that can be | |
| 52 | + * specified by the hidden parameter prefix, followed by the corresponding | |
| 53 | + * observation id. An observation definition XML file containing the paths | |
| 54 | + * to the newly generated counts cubes is written to path given in the | |
| 55 | + * parameter outcube. | |
| 56 | + * | |
| 50 | 57 | * Results are stored in an observation container that can be written to disk |
| 51 | 58 | * in form of a single FITS file. On output, the observation container will |
| 52 | 59 | * have merged the input event lists into a single observation. |
| ... | ... | @@ -74,51 +81,55 @@ public: |
| 74 | 81 | // Methods |
| 75 | 82 | void clear(void); |
| 76 | 83 | void run(void); |
| 84 | + int cubes(void) const; | |
| 85 | + const GCTAEventCube& cube(const int& index = 0) const; | |
| 77 | 86 | void save(void); |
| 78 | 87 | void publish(const std::string& name = ""); |
| 79 | - const GCTAEventCube& cube(void) const; | |
| 80 | 88 | |
| 81 | 89 | protected: |
| 82 | 90 | // Protected methods |
| 83 | - void init_members(void); | |
| 84 | - void copy_members(const ctbin& app); | |
| 85 | - void free_members(void); | |
| 86 | - void get_parameters(void); | |
| 87 | - void fill_cube(GCTAObservation* obs); | |
| 88 | - void set_weights(GCTAObservation* obs); | |
| 89 | - void obs_cube(void); | |
| 91 | + void init_members(void); | |
| 92 | + void copy_members(const ctbin& app); | |
| 93 | + void free_members(void); | |
| 94 | + void get_parameters(void); | |
| 95 | + void init_sky_dir_cache(void); | |
| 96 | + GSkyMap create_cube(const GCTAObservation* obs); | |
| 97 | + GSkyMap fill_cube(const GCTAObservation* obs); | |
| 98 | + GSkyMap set_weights(const GCTAObservation* obs); | |
| 99 | + void obs_cube_stacked(void); | |
| 90 | 100 | |
| 91 | 101 | // User parameters |
| 92 | - GFilename m_outcube; //!< Output counts map file name | |
| 93 | - bool m_usepnt; //!< Use pointing instead of xref/yref parameters | |
| 94 | - bool m_publish; //!< Publish counts cube? | |
| 95 | - GChatter m_chatter; //!< Chattiness | |
| 102 | + bool m_usepnt; //!< Use pointing instead of xref/yref parameters | |
| 103 | + bool m_stack; //!< Output one stacked cube or multiple cubes | |
| 104 | + std::string m_prefix; //!< Prefix for multiple cubes | |
| 105 | + bool m_publish; //!< Publish counts cube? | |
| 106 | + GChatter m_chatter; //!< Chattiness | |
| 96 | 107 | |
| 97 | 108 | // Protected members |
| 98 | - GSkyMap m_counts; //!< Event cube counts | |
| 99 | - GSkyMap m_weights; //!< Event cube weights | |
| 100 | - GEbounds m_ebounds; //!< Energy boundaries | |
| 101 | - GGti m_gti; //!< Good time intervals | |
| 102 | - GCTAEventCube m_cube; //!< Events cube (for cube() method) | |
| 103 | - double m_ontime; //!< Total ontime | |
| 104 | - double m_livetime; //!< Total livetime | |
| 109 | + std::vector<GCTAEventCube> m_cubes; //!< Event cubes | |
| 110 | + std::vector<GSkyMap> m_counts; //!< List of event cube counts | |
| 111 | + std::vector<GSkyMap> m_weights; //!< List of event cube weights | |
| 112 | + GEbounds m_ebounds; //!< Energy boundaries | |
| 113 | + GGti m_gti; //!< Stacked Good time intervals | |
| 114 | + double m_ontime; //!< Total ontime | |
| 115 | + double m_livetime; //!< Total livetime | |
| 105 | 116 | |
| 106 | 117 | // Cache members |
| 107 | - std::vector<GSkyDir> m_dirs; //!< Cached GSkyDir for each pixel in m_counts | |
| 118 | + std::vector<GSkyDir> m_dirs; //!< Cached GSkyDir for all pixels | |
| 108 | 119 | }; |
| 109 | 120 | |
| 110 | 121 | |
| 111 | 122 | /***********************************************************************//** |
| 112 | - * @brief Return event cube | |
| 123 | + * @brief Return number of event cubes | |
| 113 | 124 | * |
| 114 | - * @return Reference to event cube | |
| 125 | + * @return Number of event cubes. | |
| 115 | 126 | * |
| 116 | - * Returns a reference to the event cube. | |
| 127 | + * Returns number of event cubes. | |
| 117 | 128 | ***************************************************************************/ |
| 118 | 129 | inline |
| 119 | -const GCTAEventCube& ctbin::cube(void) const | |
| 130 | +int ctbin::cubes(void) const | |
| 120 | 131 | { |
| 121 | - return m_cube; | |
| 132 | + return m_cubes.size(); | |
| 122 | 133 | } |
| 123 | 134 | |
| 124 | 135 | #endif /* CTBIN_HPP */ | ... | ... |
src/ctbin/ctbin.par
| ... | ... | @@ -16,8 +16,10 @@ |
| 16 | 16 | # |
| 17 | 17 | # General parameters |
| 18 | 18 | #=================== |
| 19 | -inobs, f, a, events.fits,,, "Input event list or observation definition XML file" | |
| 20 | -outcube, f, a, cntcube.fits,,, "Output counts cube file" | |
| 19 | +inobs, f, a, events.fits,,, "Input event list or observation definition XML file" | |
| 20 | +outobs, f, a, cntcube.fits,,, "Output counts cube file or observation definition XML file" | |
| 21 | +stack, b, h, yes,,, "Stack events into one cube?" | |
| 22 | +prefix, s, h, cntcube_,,, "Prefix for counts cubes in observation definition XML file" | |
| 21 | 23 | |
| 22 | 24 | # |
| 23 | 25 | # Energy binning parameters | ... | ... |
test/data/obs_unbinned_two.xml
0 → 100644
| 1 | +<?xml version="1.0" encoding="UTF-8" standalone="no"?> | |
| 2 | +<observation_list title="observation list"> | |
| 3 | + <observation name="" id="00001" instrument="CTA"> | |
| 4 | + <parameter name="EventList" file="$(TEST_DATA)/crab_events.fits" /> | |
| 5 | + <parameter name="EffectiveArea" file="$(TEST_DATA)/irf_file.fits" /> | |
| 6 | + <parameter name="PointSpreadFunction" file="$(TEST_DATA)/irf_file.fits" /> | |
| 7 | + <parameter name="EnergyDispersion" file="$(TEST_DATA)/irf_file.fits" /> | |
| 8 | + <parameter name="Background" file="$(TEST_DATA)/irf_file.fits" /> | |
| 9 | + </observation> | |
| 10 | + <observation name="" id="00002" instrument="CTA"> | |
| 11 | + <parameter name="EventList" file="$(TEST_DATA)/crab_events.fits" /> | |
| 12 | + <parameter name="EffectiveArea" file="$(TEST_DATA)/irf_file.fits" /> | |
| 13 | + <parameter name="PointSpreadFunction" file="$(TEST_DATA)/irf_file.fits" /> | |
| 14 | + <parameter name="EnergyDispersion" file="$(TEST_DATA)/irf_file.fits" /> | |
| 15 | + <parameter name="Background" file="$(TEST_DATA)/irf_file.fits" /> | |
| 16 | + </observation> | |
| 17 | +</observation_list> | ... | ... |
test/test_ctbin.py
| ... | ... | @@ -43,6 +43,9 @@ class Test(test): |
| 43 | 43 | # Call base class constructor |
| 44 | 44 | test.__init__(self) |
| 45 | 45 | |
| 46 | + # Set members | |
| 47 | + self._inobs_two = self._datadir + '/obs_unbinned_two.xml' | |
| 48 | + | |
| 46 | 49 | # Return |
| 47 | 50 | return |
| 48 | 51 | |
| ... | ... | @@ -71,7 +74,7 @@ class Test(test): |
| 71 | 74 | |
| 72 | 75 | # Setup ctbin command |
| 73 | 76 | cmd = ctbin+' inobs="'+self._events+'"'+ \ |
| 74 | - ' outcube="cntmap_cmd1.fits"'+\ | |
| 77 | + ' outobs="cntmap_cmd1.fits"'+\ | |
| 75 | 78 | ' emin=1.0 emax=100.0 enumbins=10 ebinalg="LOG"'+ \ |
| 76 | 79 | ' nxpix=40 nypix=40 binsz=0.1 coordsys="CEL"'+ \ |
| 77 | 80 | ' xref=83.63 yref=22.01 proj="CAR"'+ \ |
| ... | ... | @@ -87,7 +90,7 @@ class Test(test): |
| 87 | 90 | |
| 88 | 91 | # Setup ctbin command |
| 89 | 92 | cmd = ctbin+' inobs="events_that_do_not_exist.fits"'+ \ |
| 90 | - ' outcube="cntmap_cmd2.fits"'+\ | |
| 93 | + ' outobs="cntmap_cmd2.fits"'+\ | |
| 91 | 94 | ' emin=1.0 emax=100.0 enumbins=10 ebinalg="LOG"'+ \ |
| 92 | 95 | ' nxpix=40 nypix=40 binsz=0.1 coordsys="CEL"'+ \ |
| 93 | 96 | ' xref=83.63 yref=22.01 proj="CAR"'+ \ |
| ... | ... | @@ -99,7 +102,7 @@ class Test(test): |
| 99 | 102 | |
| 100 | 103 | # Setup ctbin command with different ebounds than input |
| 101 | 104 | cmd = ctbin+' inobs="'+self._events+'"'+ \ |
| 102 | - ' outcube="cntmap_cmd3.fits"'+\ | |
| 105 | + ' outobs="cntmap_cmd3.fits"'+\ | |
| 103 | 106 | ' emin=2.0 emax=20.0 enumbins=10 ebinalg="LOG"'+ \ |
| 104 | 107 | ' nxpix=40 nypix=40 binsz=0.1 coordsys="CEL"'+ \ |
| 105 | 108 | ' xref=83.63 yref=22.01 proj="CAR"'+ \ |
| ... | ... | @@ -113,6 +116,40 @@ class Test(test): |
| 113 | 116 | evt = gammalib.GCTAEventCube('cntmap_cmd3.fits') |
| 114 | 117 | self._check_cube(evt, 115) |
| 115 | 118 | |
| 119 | + # Check joint (unstacked) binning | |
| 120 | + | |
| 121 | + # Setup ctbin command | |
| 122 | + cmd = ctbin+' inobs="'+self._inobs_two+'"'+ \ | |
| 123 | + ' outobs="obs_unbinned_two_binned.xml"'+\ | |
| 124 | + ' emin=1.0 emax=100.0 enumbins=10 ebinalg="LOG"'+ \ | |
| 125 | + ' nxpix=40 nypix=40 binsz=0.1 coordsys="CEL"'+ \ | |
| 126 | + ' xref=83.63 yref=22.01 proj="CAR"'+ \ | |
| 127 | + ' logfile="ctbin_cmd4.log" chatter=1 stack=no'+ \ | |
| 128 | + ' prefix="cntmap_cmd4_"' | |
| 129 | + | |
| 130 | + # Check if execution was successful | |
| 131 | + self.test_value(self._execute(cmd), 0, | |
| 132 | + 'Check successful execution from command line with stack=no') | |
| 133 | + | |
| 134 | + # Load counts cubes and check content. | |
| 135 | + evt = gammalib.GCTAEventCube('cntmap_cmd4_00001.fits') | |
| 136 | + self._check_cube(evt, 245) | |
| 137 | + | |
| 138 | + # Load counts cubes and check content. | |
| 139 | + evt = gammalib.GCTAEventCube('cntmap_cmd4_00002.fits') | |
| 140 | + self._check_cube(evt, 245) | |
| 141 | + | |
| 142 | + # Check observation definition XML output file | |
| 143 | + obs = gammalib.GObservations('obs_unbinned_two_binned.xml') | |
| 144 | + self.test_value(obs.size(), 2, 'Check for 2 observations in XML file') | |
| 145 | + | |
| 146 | + # Check for content of observations file | |
| 147 | + self.test_value(obs[0].eventfile().file(), 'cntmap_cmd4_cta_00001.fits', | |
| 148 | + 'Check first counts cube file name') | |
| 149 | + self.test_value(obs[1].eventfile().file(), 'cntmap_cmd4_cta_00002.fits', | |
| 150 | + 'Check second counts cube file name') | |
| 151 | + | |
| 152 | + | |
| 116 | 153 | # Check ctbin --help |
| 117 | 154 | self._check_help(ctbin) |
| 118 | 155 | |
| ... | ... | @@ -131,12 +168,12 @@ class Test(test): |
| 131 | 168 | # cube |
| 132 | 169 | self.test_value(binning.obs().size(), 0, |
| 133 | 170 | 'Check that empty ctbin has an empty observation container') |
| 134 | - self.test_value(binning.cube().size(), 0, | |
| 171 | + self.test_value(binning.cubes(), 0, | |
| 135 | 172 | 'Check that empty ctbin has an empty counts cube') |
| 136 | 173 | |
| 137 | 174 | # Check that saving does not nothing |
| 138 | 175 | binning['logfile'] = 'ctbin_py0.log' |
| 139 | - binning['outcube'] = 'ctbin_py0.fits' | |
| 176 | + binning['outobs'] = 'ctbin_py0.fits' | |
| 140 | 177 | binning.logFileOpen() |
| 141 | 178 | binning.save() |
| 142 | 179 | self.test_assert(not os.path.isfile('ctbin_py0.fits'), |
| ... | ... | @@ -150,7 +187,7 @@ class Test(test): |
| 150 | 187 | |
| 151 | 188 | # Now set ctbin parameters |
| 152 | 189 | binning['inobs'] = self._events |
| 153 | - binning['outcube'] = 'ctbin_py1.fits' | |
| 190 | + binning['outobs'] = 'ctbin_py1.fits' | |
| 154 | 191 | binning['ebinalg'] = 'LOG' |
| 155 | 192 | binning['emin'] = 1.0 |
| 156 | 193 | binning['emax'] = 100.0 |
| ... | ... | @@ -186,11 +223,13 @@ class Test(test): |
| 186 | 223 | cpy_bin.logFileOpen() |
| 187 | 224 | cpy_bin.run() |
| 188 | 225 | |
| 189 | - # Check content of observation container and counts cube. Now an empty | |
| 190 | - # event cube is expected since on input the observation is binned, and | |
| 191 | - # any binned observation will be skipped. | |
| 192 | - self._check_observation(cpy_bin, 0) | |
| 193 | - self._check_cube(cpy_bin.cube(), 0) | |
| 226 | + # Check content of observation container and number of counts cubes. | |
| 227 | + # There should be a single binned observation in the observation | |
| 228 | + # container, which is the one that was produced in the run before. | |
| 229 | + # Since ctbin finds no unbinned observation in the container, the | |
| 230 | + # number of cubes should be zero. | |
| 231 | + self._check_observation(cpy_bin, 245) | |
| 232 | + self.test_value(cpy_bin.cubes(), 0, 'Check that there are no counts cubes') | |
| 194 | 233 | |
| 195 | 234 | # Save counts cube |
| 196 | 235 | binning.save() |
| ... | ... | @@ -206,7 +245,7 @@ class Test(test): |
| 206 | 245 | # counts cube |
| 207 | 246 | self.test_value(binning.obs().size(), 0, |
| 208 | 247 | 'Check that empty ctbin has an empty observation container') |
| 209 | - self.test_value(binning.cube().size(), 0, | |
| 248 | + self.test_value(binning.cubes(), 0, | |
| 210 | 249 | 'Check that empty ctbin has an empty counts cube') |
| 211 | 250 | |
| 212 | 251 | # Prepare observation container for stacking of events into a |
| ... | ... | @@ -215,7 +254,7 @@ class Test(test): |
| 215 | 254 | |
| 216 | 255 | # Set-up ctbin using an observation container |
| 217 | 256 | binning = ctools.ctbin(obs) |
| 218 | - binning['outcube'] = 'ctbin_py3.fits' | |
| 257 | + binning['outobs'] = 'ctbin_py3.fits' | |
| 219 | 258 | binning['ebinalg'] = 'LOG' |
| 220 | 259 | binning['emin'] = 1.0 |
| 221 | 260 | binning['emax'] = 100.0 |
| ... | ... | @@ -249,7 +288,7 @@ class Test(test): |
| 249 | 288 | pars = {'inobs': self._events, 'ebinalg': 'LIN', 'emin': 1.0, |
| 250 | 289 | 'emax': 100.0, 'enumbins': 10, 'nxpix': 40, 'nypix': 40, |
| 251 | 290 | 'binsz': 0.1, 'coordsys': 'CEL', 'proj': 'CAR', |
| 252 | - 'xref': 83.63, 'yref': 22.01, 'outcube': 'ctbin_py4.fits', | |
| 291 | + 'xref': 83.63, 'yref': 22.01, 'outobs': 'ctbin_py4.fits', | |
| 253 | 292 | 'logfile': 'ctbin_py4.log', 'chatter': 2} |
| 254 | 293 | binning = ctools.ctbin() |
| 255 | 294 | binning.pardict(pars) |
| ... | ... | @@ -260,6 +299,42 @@ class Test(test): |
| 260 | 299 | evt = gammalib.GCTAEventCube('ctbin_py4.fits') |
| 261 | 300 | self._check_cube(evt, 245) |
| 262 | 301 | |
| 302 | + # Test unstacked version | |
| 303 | + pars = {'inobs': self._datadir+'/obs_unbinned_two.xml', 'ebinalg': 'LIN', 'emin': 1.0, | |
| 304 | + 'emax': 100.0, 'enumbins': 10, 'nxpix': 40, 'nypix': 40, | |
| 305 | + 'binsz': 0.1, 'coordsys': 'CEL', 'proj': 'CAR', | |
| 306 | + 'xref': 83.63, 'yref': 22.01, 'outobs': 'ctbin_py5.xml', | |
| 307 | + 'logfile': 'ctbin_py5.log', 'chatter': 2, | |
| 308 | + 'prefix': 'cntcube_py5_', 'stack' : False} | |
| 309 | + binning = ctools.ctbin() | |
| 310 | + binning.pardict(pars) | |
| 311 | + | |
| 312 | + binning.logFileOpen() | |
| 313 | + binning.run() | |
| 314 | + | |
| 315 | + # Check individual cubes | |
| 316 | + self._check_cube(binning.cube(0), 245) | |
| 317 | + self._check_cube(binning.cube(1), 245) | |
| 318 | + | |
| 319 | + # Save counts cube | |
| 320 | + binning.save() | |
| 321 | + | |
| 322 | + # Load observations | |
| 323 | + obs = gammalib.GObservations('ctbin_py5.xml') | |
| 324 | + self.test_value(obs.size(), 2, 'Check number of output observations') | |
| 325 | + | |
| 326 | + # Check counts cubes | |
| 327 | + evt = obs[0].events() | |
| 328 | + self._check_cube(evt, 245) | |
| 329 | + evt = obs[1].events() | |
| 330 | + self._check_cube(evt, 245) | |
| 331 | + | |
| 332 | + # Load counts cubes and check content | |
| 333 | + evt = gammalib.GCTAEventCube('cntcube_py5_00001.fits') | |
| 334 | + self._check_cube(evt, 245) | |
| 335 | + evt = gammalib.GCTAEventCube('cntcube_py5_00002.fits') | |
| 336 | + self._check_cube(evt, 245) | |
| 337 | + | |
| 263 | 338 | # Return |
| 264 | 339 | return |
| 265 | 340 | ... | ... |