Commit 34b8b420acc979d8ffbbf95a923e98203cb871f2

Authored by Jürgen Knödlseder
1 parent ad650400

Add kludge to handle noisy CTA 2D energy dispersion (#2342)

ChangeLog
1   -2018-02-15
  1 +2018-02-23
2 2  
3 3 * Bug fix version 1.5.1 released
4 4 ================================
5 5  
  6 + Add kludge to handle noisy CTA 2D energy dispersion (#2342).
6 7 Prevent unintentional deallocation of arguments (#2340).
7 8 Correct sky map smoothing.
8 9  
... ...
1 1 New Features and Important Changes in GammaLib 1.5.1
2 2  
3   -15 February 2018
  3 +23 February 2018
4 4  
5 5  
6 6 1. Introduction
... ... @@ -148,6 +148,11 @@ None
148 148  
149 149 20. CTA interface
150 150 -----------------
  151 +Add a kludge to handle the noisy energy dispersion that is shipped with the 1DC
  152 +data and that is included in the Prod3 release. Noisy pixel values are skipped
  153 +and a slight smoothing is applied in true energy to reduce the noise level
  154 +(#2342).
  155 +
151 156 The GCTAObservation::response() and GCTAOnOffObservation::response() methods
152 157 now only deallocate the previous content if the reference to the argument
153 158 differs from the reference of the previous content. Otherwise the method would
... ...
doc/source/admin/release_history/1.5.1.rst
... ... @@ -10,6 +10,8 @@ GammaLib 1.5.1 is a bug fix release for GammaLib release 1.5.
10 10  
11 11 The following bugs were fixed:
12 12  
  13 +* [`2342 <https://cta-redmine.irap.omp.eu/issues/2342>`_] -
  14 + Add kludge to handle noisy CTA 2D energy dispersion
13 15 * [`2340 <https://cta-redmine.irap.omp.eu/issues/2340>`_] -
14 16 Prevent unintentional deallocation of arguments
15 17 * Correct sky map smoothing
... ...
inst/cta/include/GCTAEdisp2D.hpp
1 1 /***************************************************************************
2 2 * GCTAEdisp2D.hpp - CTA 2D energy dispersion class *
3 3 * ----------------------------------------------------------------------- *
4   - * copyright (C) 2015-2017 by Florent Forest *
  4 + * copyright (C) 2015-2018 by Florent Forest *
5 5 * ----------------------------------------------------------------------- *
6 6 * *
7 7 * This program is free software: you can redistribute it and/or modify *
... ... @@ -35,6 +35,7 @@
35 35 #include "GCTAResponseTable.hpp"
36 36  
37 37 /* __ Forward declarations _______________________________________________ */
  38 +class GFft;
38 39 class GRan;
39 40 class GFits;
40 41 class GFitsBinTable;
... ... @@ -119,10 +120,16 @@ private:
119 120 const double& phi = 0.0,
120 121 const double& zenith = 0.0,
121 122 const double& azimuth = 0.0) const;
122   - void set_indices(void);
  123 + void set_table(void);
123 124 void set_boundaries(void);
124 125 void set_max_edisp(void) const;
125 126 void normalize_table(void);
  127 + void clip_table(const int& threshold);
  128 + void smooth_table(const double& sigma, const double& threshold);
  129 + GFft fft_smooth_kernel(const int& netrue,
  130 + const int& nmigra,
  131 + const double& sigma) const;
  132 + GNdarray clip_array(const GNdarray& array, const double& threshold) const;
126 133  
127 134 // Protected classes
128 135 class edisp_kern : public GFunction {
... ...
inst/cta/src/GCTAEdisp2D.cpp
1 1 /***************************************************************************
2 2 * GCTAEdisp2D.cpp - CTA 2D energy dispersion class *
3 3 * ----------------------------------------------------------------------- *
4   - * copyright (C) 2015-2017 by Florent Forest *
  4 + * copyright (C) 2015-2018 by Florent Forest *
5 5 * ----------------------------------------------------------------------- *
6 6 * *
7 7 * This program is free software: you can redistribute it and/or modify *
... ... @@ -32,6 +32,8 @@
32 32 #include <vector>
33 33 #include "GTools.hpp"
34 34 #include "GMath.hpp"
  35 +#include "GNdarray.hpp"
  36 +#include "GFft.hpp"
35 37 #include "GException.hpp"
36 38 #include "GIntegral.hpp"
37 39 #include "GFilename.hpp"
... ... @@ -50,6 +52,7 @@
50 52 /* __ Macros _____________________________________________________________ */
51 53  
52 54 /* __ Coding definitions _________________________________________________ */
  55 +#define G_SMOOTH_EDISP_KLUDGE //!< Get rid of noise in energy dispersion
53 56  
54 57 /* __ Debug definitions __________________________________________________ */
55 58  
... ... @@ -282,23 +285,8 @@ void GCTAEdisp2D::table(const GCTAResponseTable&amp; table)
282 285 // Assign response table
283 286 m_edisp = table;
284 287  
285   - // Set indices
286   - set_indices();
287   -
288   - // Set true energy axis to logarithmic scale
289   - m_edisp.axis_log10(m_inx_etrue);
290   -
291   - // Set offset angle axis to radians
292   - m_edisp.axis_radians(m_inx_theta);
293   -
294   - // Set table boundaries
295   - set_boundaries();
296   -
297   - // Normalize energy dispersion table
298   - normalize_table();
299   -
300   - // Set maximum energy dispersion value
301   - set_max_edisp();
  288 + // Set table
  289 + set_table();
302 290  
303 291 // Return
304 292 return;
... ... @@ -347,23 +335,8 @@ void GCTAEdisp2D::read(const GFitsTable&amp; table)
347 335 throw GException::invalid_value(G_READ, msg);
348 336 }
349 337  
350   - // Set table indices
351   - set_indices();
352   -
353   - // Set true energy axis to logarithmic scale
354   - m_edisp.axis_log10(m_inx_etrue);
355   -
356   - // Set offset angle axis to radians
357   - m_edisp.axis_radians(m_inx_theta);
358   -
359   - // Set table boundaries
360   - set_boundaries();
361   -
362   - // Normalize energy dispersion table
363   - normalize_table();
364   -
365   - // Set maximum energy dispersion value
366   - set_max_edisp();
  338 + // Set table
  339 + set_table();
367 340  
368 341 // Return
369 342 return;
... ... @@ -1181,19 +1154,54 @@ void GCTAEdisp2D::compute_ebounds_src(const double&amp; theta,
1181 1154  
1182 1155  
1183 1156 /***********************************************************************//**
1184   - * @brief Set table indices
  1157 + * @brief Set table
1185 1158 *
1186   - * Sets the data members m_inx_etrue, m_inx_migra, m_inx_theta, and
1187   - * m_inx_matrix.
  1159 + * After assigning or loading a response table, performs all necessary steps
  1160 + * to set the table. The method also smoothes the table get rid of numerical
  1161 + * fluctuations and normalises the table properly.
  1162 + *
  1163 + * Sets the data members
  1164 + * m_inx_etrue
  1165 + * m_inx_migra
  1166 + * m_inx_theta
  1167 + * m_inx_matrix
  1168 + * m_logEsrc_min
  1169 + * m_logEsrc_max
  1170 + * m_migra_min
  1171 + * m_migra_max
  1172 + * m_theta_min
  1173 + * m_theta_max
  1174 + * m_max_edisp
1188 1175 ***************************************************************************/
1189   -void GCTAEdisp2D::set_indices(void)
  1176 +void GCTAEdisp2D::set_table(void)
1190 1177 {
1191   - // Get mandatory indices (throw exception if not found)
  1178 + // Set table indices
1192 1179 m_inx_etrue = m_edisp.axis("ETRUE");
1193 1180 m_inx_migra = m_edisp.axis("MIGRA");
1194 1181 m_inx_theta = m_edisp.axis("THETA");
1195 1182 m_inx_matrix = m_edisp.table("MATRIX");
1196 1183  
  1184 + // Set true energy axis to logarithmic scale
  1185 + m_edisp.axis_log10(m_inx_etrue);
  1186 +
  1187 + // Set offset angle axis to radians
  1188 + m_edisp.axis_radians(m_inx_theta);
  1189 +
  1190 + // Set table boundaries
  1191 + set_boundaries();
  1192 +
  1193 + // Smooth energy dispersion table
  1194 + #if defined(G_SMOOTH_EDISP_KLUDGE)
  1195 + clip_table(15);
  1196 + smooth_table(3.0, 0.0001);
  1197 + #endif
  1198 +
  1199 + // Normalize energy dispersion table
  1200 + normalize_table();
  1201 +
  1202 + // Set maximum energy dispersion value
  1203 + set_max_edisp();
  1204 +
1197 1205 // Return
1198 1206 return;
1199 1207 }
... ... @@ -1341,6 +1349,273 @@ void GCTAEdisp2D::normalize_table(void)
1341 1349  
1342 1350  
1343 1351 /***********************************************************************//**
  1352 + * @brief Clip energy dispersion table
  1353 + *
  1354 + * @param[in] threshold Minimum number of consecutive non-zeros
  1355 + *
  1356 + * Clips noise in the energy dispersion table.
  1357 + *
  1358 + * For each offset angle and true energy, the method determines the maximum
  1359 + * value of the energy dispersion table and then walks towards smaller and
  1360 + * larger migration value to determine the first pixel for which the energy
  1361 + * dispersion becomes zero. All pixels beyond these pixels are then clipped
  1362 + * to zero. This removes noise that is at small or large migration values.
  1363 + *
  1364 + * The method then computes the number of remaining contiguos non-zero pixels
  1365 + * for each offset angle and true energy, and if this number is smaller than
  1366 + * 10, all pixels are set to zero. This removes true energy bins that are
  1367 + * sparesely filled.
  1368 + ***************************************************************************/
  1369 +void GCTAEdisp2D::clip_table(const int& threshold)
  1370 +{
  1371 + // Get axes dimensions
  1372 + int netrue = m_edisp.axis_bins(m_inx_etrue);
  1373 + int nmigra = m_edisp.axis_bins(m_inx_migra);
  1374 + int ntheta = m_edisp.axis_bins(m_inx_theta);
  1375 +
  1376 + // Loop over all offset angles
  1377 + for (int itheta = 0; itheta < ntheta; ++itheta) {
  1378 +
  1379 + // Loop ober all true energies
  1380 + for (int ietrue = 0; ietrue < netrue; ++ietrue) {
  1381 +
  1382 + // Compute base index
  1383 + int ibase = ietrue + itheta * netrue * nmigra;
  1384 +
  1385 + // Determine index of maximum value
  1386 + int imax = 0;
  1387 + double max = m_edisp(m_inx_matrix,ibase+ietrue);
  1388 + for (int imigra = 1; imigra < nmigra; ++imigra) {
  1389 + int inx = ibase + imigra*netrue;
  1390 + double value = m_edisp(m_inx_matrix, inx);
  1391 + if (value > max) {
  1392 + imax = imigra;
  1393 + max = value;
  1394 + }
  1395 + }
  1396 +
  1397 + // Do nothing if all pixels are zero
  1398 + if (max == 0.0) {
  1399 + continue;
  1400 + }
  1401 +
  1402 + // Walk from maximum to the first pixel
  1403 + bool clip = false;
  1404 + for (int imigra = imax; imigra >= 0; imigra--) {
  1405 + int inx = ibase + imigra*netrue;
  1406 + if (clip) {
  1407 + m_edisp(m_inx_matrix, inx) = 0.0;
  1408 + }
  1409 + else {
  1410 + double value = m_edisp(m_inx_matrix, inx);
  1411 + if (value == 0.0) {
  1412 + clip = true;
  1413 + }
  1414 + }
  1415 + }
  1416 +
  1417 + // Walk from maximum to the last pixel
  1418 + clip = false;
  1419 + for (int imigra = imax; imigra < nmigra; ++imigra) {
  1420 + int inx = ibase + imigra*netrue;
  1421 + if (clip) {
  1422 + m_edisp(m_inx_matrix, inx) = 0.0;
  1423 + }
  1424 + else {
  1425 + double value = m_edisp(m_inx_matrix, inx);
  1426 + if (value == 0.0) {
  1427 + clip = true;
  1428 + }
  1429 + }
  1430 + }
  1431 +
  1432 + // Determine number of contiguous non-zero pixels
  1433 + int nonzero = 0;
  1434 + int contiguous_nonzero = 0;
  1435 + for (int imigra = 0; imigra < nmigra; ++imigra) {
  1436 + int inx = ibase + imigra*netrue;
  1437 + double value = m_edisp(m_inx_matrix, inx);
  1438 + if (value != 0.0) {
  1439 + nonzero++;
  1440 + if (nonzero > contiguous_nonzero) {
  1441 + contiguous_nonzero = nonzero;
  1442 + }
  1443 + }
  1444 + else {
  1445 + nonzero = 0;
  1446 + }
  1447 + }
  1448 +
  1449 + // If there are less than threshold contiguous non-zero pixels we
  1450 + // only have noise, hence all pixels are set to zero
  1451 + if (contiguous_nonzero < threshold) {
  1452 + for (int imigra = 0; imigra < nmigra; ++imigra) {
  1453 + int inx = ibase + imigra*netrue;
  1454 + m_edisp(m_inx_matrix, inx) = 0.0;
  1455 + }
  1456 + }
  1457 +
  1458 + } // endfor: looped over all true energies
  1459 +
  1460 + } // endfor: looped over all offset angles
  1461 +
  1462 + // Return
  1463 + return;
  1464 +}
  1465 +
  1466 +
  1467 +/***********************************************************************//**
  1468 + * @brief Smooth energy dispersion table
  1469 + *
  1470 + * @param[in] sigma Smoothing width (number of bins in true energy)
  1471 + * @param[in] threshold Clipping threshold
  1472 + *
  1473 + * Smooth the energy dispersion table in true energy. The smoothing is done
  1474 + * using a fast-fourrier transform. For this purpose the energy disperison
  1475 + * table for each offset angle is copied into a 2D array, padded with some
  1476 + * zero pixels on the left and the right to avoid wrap around. After
  1477 + * smoothing the array is clipped using the clip_array() method to avoid
  1478 + * marginal non-zero energy dispersion values.
  1479 + ***************************************************************************/
  1480 +void GCTAEdisp2D::smooth_table(const double& sigma, const double& threshold)
  1481 +{
  1482 + // Get axes dimensions
  1483 + int netrue = m_edisp.axis_bins(m_inx_etrue);
  1484 + int nmigra = m_edisp.axis_bins(m_inx_migra);
  1485 + int ntheta = m_edisp.axis_bins(m_inx_theta);
  1486 + int netrue_pad = int(3.0*sigma);
  1487 + int netrue_padded = netrue + 2*netrue_pad; // Array padded with zeros
  1488 + int npix = netrue * nmigra;
  1489 +
  1490 + // Get smoothing kernel
  1491 + GFft fft_kernel = fft_smooth_kernel(netrue_padded, nmigra, sigma);
  1492 +
  1493 + // Loop over all offset angles
  1494 + for (int itheta = 0; itheta < ntheta; ++itheta) {
  1495 +
  1496 + // Compute base index
  1497 + int ibase = itheta * npix;
  1498 +
  1499 + // Extract matrix in GNdarray
  1500 + GNdarray array(netrue_padded, nmigra);
  1501 + for (int imigra = 0, i = 0; imigra < nmigra; ++imigra) {
  1502 + double* ptr = array.data() + imigra * netrue_padded + netrue_pad;
  1503 + for (int ietrue = 0; ietrue < netrue; ++ietrue, ++i) {
  1504 + *ptr++ = m_edisp(m_inx_matrix, ibase+i);
  1505 + }
  1506 + }
  1507 +
  1508 + // FFT of array
  1509 + GFft fft_array(array);
  1510 +
  1511 + // Smooth array
  1512 + GFft fft_smooth = fft_array * fft_kernel;
  1513 +
  1514 + // Backward transform array
  1515 + GNdarray smooth = fft_smooth.backward();
  1516 +
  1517 + // Clip array
  1518 + smooth = clip_array(smooth, threshold);
  1519 +
  1520 + // Put back array values in matrix
  1521 + for (int imigra = 0, i = 0; imigra < nmigra; ++imigra) {
  1522 + double* ptr = smooth.data() + imigra * netrue_padded + netrue_pad;
  1523 + for (int ietrue = 0; ietrue < netrue; ++ietrue, ++i) {
  1524 + m_edisp(m_inx_matrix, ibase+i) = *ptr++;
  1525 + }
  1526 + }
  1527 +
  1528 + } // endfor: looped over all offset angles
  1529 +
  1530 + // Return
  1531 + return;
  1532 +}
  1533 +
  1534 +
  1535 +/***********************************************************************//**
  1536 + * @brief Get FFT of smoothing kernel
  1537 + *
  1538 + * @return FFT of smoothing kernel
  1539 + *
  1540 + * Returns the fast-fourrier transform of a Gaussian smoothing kernel in
  1541 + * true energy.
  1542 + ***************************************************************************/
  1543 +GFft GCTAEdisp2D::fft_smooth_kernel(const int& netrue,
  1544 + const int& nmigra,
  1545 + const double& sigma) const
  1546 +{
  1547 + // Allocate kernel
  1548 + GNdarray kernel(netrue, nmigra);
  1549 +
  1550 + // Initialise sum and compute Gaussian normalisation
  1551 + double sum = 0.0;
  1552 + double norm = -0.5 / (sigma * sigma);
  1553 +
  1554 + // Set Gaussian kernel
  1555 + for (int i = 0; i < netrue; ++i) {
  1556 + double value = std::exp(norm*double(i*i));
  1557 + kernel(i,0) += value;
  1558 + sum += value;
  1559 + if (i > 0) {
  1560 + kernel(netrue-i,0) += value;
  1561 + sum += value;
  1562 + }
  1563 + }
  1564 +
  1565 + // Normalize kernel
  1566 + if (sum > 0.0) {
  1567 + for (int i = 0; i < netrue; ++i) {
  1568 + kernel(i,0) /= sum;
  1569 + }
  1570 + }
  1571 +
  1572 + // Return FFT of kernel
  1573 + return (GFft(kernel));
  1574 +}
  1575 +
  1576 +
  1577 +/***********************************************************************//**
  1578 + * @brief Clip array
  1579 + *
  1580 + * @param[in] array Ndarray that should be clipped.
  1581 + * @param[in] threshold Clipping threshold in fraction of maximum value.
  1582 + * @return Clipped Ndarray
  1583 + *
  1584 + * Clips all values below a threshold of 10 to zero.
  1585 + ***************************************************************************/
  1586 +GNdarray GCTAEdisp2D::clip_array(const GNdarray& array,
  1587 + const double& threshold) const
  1588 +{
  1589 + // Set clipped array
  1590 + GNdarray clipped = array;
  1591 +
  1592 + // Initialise variables
  1593 + int nsize = clipped.size();
  1594 +
  1595 + // Determine maximum value in array
  1596 + double max = 0.0;
  1597 + double* cptr = clipped.data();
  1598 + for (int i = 0; i < nsize; ++i, cptr++) {
  1599 + if (*cptr > max) {
  1600 + max = *cptr;
  1601 + }
  1602 + }
  1603 +
  1604 + // Clip values below threshold
  1605 + double clip_value = max * threshold;
  1606 + cptr = clipped.data();
  1607 + for (int i = 0; i < nsize; ++i, cptr++) {
  1608 + if (std::abs(*cptr) < clip_value) {
  1609 + *cptr = 0.0;
  1610 + }
  1611 + }
  1612 +
  1613 + // Return clipped array
  1614 + return clipped;
  1615 +}
  1616 +
  1617 +
  1618 +/***********************************************************************//**
1344 1619 * @brief Integration kernel for edisp_kern() class
1345 1620 *
1346 1621 * @param[in] x Function value.
... ...
inst/cta/test/caldb/edisp_matrix.fits.gz
No preview for this file type
inst/cta/test/test_CTA.cpp
... ... @@ -1226,7 +1226,7 @@ void TestGCTAResponse::test_edisp_integration(const GCTAEdisp&amp; edisp,
1226 1226 // Set message string
1227 1227 GEnergy eng(e_src, "TeV");
1228 1228 std::string msg = edisp.classname()+" integration for "+eng.print();
1229   - test_value(sum, 1.0, 0.03, msg);
  1229 + test_value(sum, 1.0, 0.01, msg);
1230 1230  
1231 1231 }
1232 1232  
... ...