Commit 63c88ec6ec67ce4aa92c9a44b629b518f2725e65

Authored by Jürgen Knödlseder
2 parents 0be8dd0c 34b8b420

Merge branch '2342-smooth-cta-edisp' into integration

# Conflicts:
#	ChangeLog
#	NEWS
ChangeLog
... ... @@ -7,11 +7,12 @@
7 7 Add GCTABackground::rate_ebin() method (#2308)
8 8  
9 9  
10   -2018-02-15
  10 +2018-02-23
11 11  
12 12 * Bug fix version 1.5.1 released
13 13 ================================
14 14  
  15 + Add kludge to handle noisy CTA 2D energy dispersion (#2342).
15 16 Prevent unintentional deallocation of arguments (#2340).
16 17 Correct sky map smoothing.
17 18  
... ...
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
... ... @@ -1421,7 +1421,7 @@ void TestGCTAResponse::test_edisp_integration(const GCTAEdisp&amp; edisp,
1421 1421 // Set message string
1422 1422 GEnergy eng(e_src, "TeV");
1423 1423 std::string msg = edisp.classname()+" integration for "+eng.print();
1424   - test_value(sum, 1.0, 0.03, msg);
  1424 + test_value(sum, 1.0, 0.01, msg);
1425 1425  
1426 1426 }
1427 1427  
... ...