Commit 147c3f169b4e09e55c9c50341df023eb991fe6fa

Authored by Jürgen Knödlseder
1 parent 2b15414b

Settle energy dispersion kludge (#2403)

ChangeLog
1 -2018-02-23 1 +2018-03-19
2 2
3 * Bug fix version 1.5.1 released 3 * Bug fix version 1.5.1 released
4 ================================ 4 ================================
5 5
6 - Add kludge to handle noisy CTA 2D energy dispersion (#2342). 6 + Add kludge to handle noisy CTA 2D energy dispersion (#2342, #2403).
7 Prevent unintentional deallocation of arguments (#2340). 7 Prevent unintentional deallocation of arguments (#2340).
8 Correct sky map smoothing. 8 Correct sky map smoothing.
9 9
1 New Features and Important Changes in GammaLib 1.5.1 1 New Features and Important Changes in GammaLib 1.5.1
2 2
3 -23 February 2018 3 +19 Mars 2018
4 4
5 5
6 1. Introduction 6 1. Introduction
@@ -149,9 +149,7 @@ None @@ -149,9 +149,7 @@ None
149 20. CTA interface 149 20. CTA interface
150 ----------------- 150 -----------------
151 Add a kludge to handle the noisy energy dispersion that is shipped with the 1DC 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). 152 +data and that is included in the Prod3 release (#2342, #2403).
155 153
156 The GCTAObservation::response() and GCTAOnOffObservation::response() methods 154 The GCTAObservation::response() and GCTAOnOffObservation::response() methods
157 now only deallocate the previous content if the reference to the argument 155 now only deallocate the previous content if the reference to the argument
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,6 +10,8 @@ GammaLib 1.5.1 is a bug fix release for GammaLib release 1.5.
10 10
11 The following bugs were fixed: 11 The following bugs were fixed:
12 12
  13 +* [`2403 <https://cta-redmine.irap.omp.eu/issues/2403>`_] -
  14 + Refine kludge to handle noisy CTA 2D energy dispersion
13 * [`2342 <https://cta-redmine.irap.omp.eu/issues/2342>`_] - 15 * [`2342 <https://cta-redmine.irap.omp.eu/issues/2342>`_] -
14 Add kludge to handle noisy CTA 2D energy dispersion 16 Add kludge to handle noisy CTA 2D energy dispersion
15 * [`2340 <https://cta-redmine.irap.omp.eu/issues/2340>`_] - 17 * [`2340 <https://cta-redmine.irap.omp.eu/issues/2340>`_] -
inst/cta/include/GCTAEdisp2D.hpp
@@ -126,15 +126,23 @@ private: @@ -126,15 +126,23 @@ private:
126 void normalize_table(void); 126 void normalize_table(void);
127 127
128 // Kludge 128 // Kludge
129 - void clean_table(const int& threshold);  
130 - void clip_table(const double& threshold);  
131 - void denoise_table(const double& sigma);  
132 - GNdarray gaussian_array(const double& mean, const double& rms) const;  
133 - GNdarray get_gaussian_pars(const double& sigma) const;  
134 - GFft fft_smooth_kernel(const int& nbins,  
135 - const double& sigma) const; 129 + void smooth_table(void);
  130 + GNdarray smooth_array(const GNdarray& array,
  131 + const int& nstart,
  132 + const int& nstop,
  133 + const double& limit) const;
  134 + double smoothed_array_value(const int& inx,
  135 + const GNdarray& array,
  136 + const int& nodes,
  137 + const double& limit) const;
  138 + void get_moments(const int& itheta,
  139 + GNdarray* mean,
  140 + GNdarray* rms,
  141 + GNdarray* total) const;
136 void get_mean_rms(const GNdarray& array, double* mean, double* rms) const; 142 void get_mean_rms(const GNdarray& array, double* mean, double* rms) const;
137 - double get_single_event_value(const GNdarray& array) const; 143 + GNdarray gaussian_array(const double& mean,
  144 + const double& rms,
  145 + const double& total) const;
138 146
139 // Protected classes 147 // Protected classes
140 class edisp_kern : public GFunction { 148 class edisp_kern : public GFunction {
inst/cta/src/GCTAEdisp2D.cpp
@@ -1200,9 +1200,7 @@ void GCTAEdisp2D::set_table(void) @@ -1200,9 +1200,7 @@ void GCTAEdisp2D::set_table(void)
1200 1200
1201 // Smooth energy dispersion table 1201 // Smooth energy dispersion table
1202 #if defined(G_SMOOTH_EDISP_KLUDGE) 1202 #if defined(G_SMOOTH_EDISP_KLUDGE)
1203 - denoise_table(10.0);  
1204 - clean_table(15);  
1205 - clip_table(0.001); 1203 + smooth_table();
1206 #endif 1204 #endif
1207 1205
1208 // Normalize energy dispersion table 1206 // Normalize energy dispersion table
@@ -1362,166 +1360,80 @@ void GCTAEdisp2D::normalize_table(void) @@ -1362,166 +1360,80 @@ void GCTAEdisp2D::normalize_table(void)
1362 } 1360 }
1363 1361
1364 1362
  1363 +
1365 /***********************************************************************//** 1364 /***********************************************************************//**
1366 - * @brief Clean energy dispersion table  
1367 - *  
1368 - * @param[in] threshold Minimum number of consecutive non-zeros  
1369 - *  
1370 - * Removes noise from the energy dispersion table.  
1371 - *  
1372 - * For each offset angle and true energy, the method determines the maximum  
1373 - * value of the energy dispersion table and then walks towards smaller and  
1374 - * larger migration value to determine the first pixel for which the energy  
1375 - * dispersion becomes zero. All pixels beyond these pixels are then clipped  
1376 - * to zero. This removes noise that is at small or large migration values.  
1377 - *  
1378 - * The method then computes the number of remaining contiguos non-zero pixels  
1379 - * for each offset angle and true energy, and if this number is smaller than  
1380 - * a threshold, all pixels are set to zero. This removes true energy bins  
1381 - * that are sparesely filled. 1365 + * @brief Smoothed energy dispersion table
1382 ***************************************************************************/ 1366 ***************************************************************************/
1383 -void GCTAEdisp2D::clean_table(const int& threshold) 1367 +void GCTAEdisp2D::smooth_table(void)
1384 { 1368 {
  1369 + // Log entrance
  1370 + //std::cout << "GCTAEdisp2D::smooth_table in" << std::endl;
  1371 +
1385 // Get axes dimensions 1372 // Get axes dimensions
1386 int netrue = m_edisp.axis_bins(m_inx_etrue); 1373 int netrue = m_edisp.axis_bins(m_inx_etrue);
1387 int nmigra = m_edisp.axis_bins(m_inx_migra); 1374 int nmigra = m_edisp.axis_bins(m_inx_migra);
1388 int ntheta = m_edisp.axis_bins(m_inx_theta); 1375 int ntheta = m_edisp.axis_bins(m_inx_theta);
  1376 + int npix = netrue * nmigra;
1389 1377
1390 // Loop over all offset angles 1378 // Loop over all offset angles
1391 for (int itheta = 0; itheta < ntheta; ++itheta) { 1379 for (int itheta = 0; itheta < ntheta; ++itheta) {
1392 1380
1393 - // Loop ober all true energies  
1394 - for (int ietrue = 0; ietrue < netrue; ++ietrue) { 1381 + // Compute means, rms and total as function of etrue
  1382 + GNdarray mean(netrue);
  1383 + GNdarray rms(netrue);
  1384 + GNdarray total(netrue);
  1385 + get_moments(itheta, &mean, &rms, &total);
1395 1386
1396 - // Compute base index  
1397 - int ibase = ietrue + itheta * netrue * nmigra;  
1398 -  
1399 - // Determine index of maximum value  
1400 - int imax = 0;  
1401 - double max = m_edisp(m_inx_matrix,ibase+ietrue);  
1402 - for (int imigra = 1; imigra < nmigra; ++imigra) {  
1403 - int inx = ibase + imigra*netrue;  
1404 - double value = m_edisp(m_inx_matrix, inx);  
1405 - if (value > max) {  
1406 - imax = imigra;  
1407 - max = value;  
1408 - }  
1409 - } 1387 + // Smooth all three
  1388 + mean = smooth_array(mean, 30, 30, 0.5);
  1389 + rms = smooth_array(rms, 30, 30, 0.03);
  1390 + total = smooth_array(total, 30, 30, 0.0);
1410 1391
1411 - // Do nothing if all pixels are zero  
1412 - if (max == 0.0) {  
1413 - continue; 1392 + // Make sure that total is not negative
  1393 + for (int ietrue = 0; ietrue < netrue; ++ietrue) {
  1394 + if (total(ietrue) < 0.0) {
  1395 + total(ietrue) = 0.0;
1414 } 1396 }
  1397 + }
  1398 +
  1399 + // Replace matrix by Gaussians
  1400 + for (int ietrue = 0; ietrue < netrue; ++ietrue) {
1415 1401
1416 - // Walk from maximum to the first pixel  
1417 - bool clip = false;  
1418 - for (int imigra = imax; imigra >= 0; imigra--) {  
1419 - int inx = ibase + imigra*netrue;  
1420 - if (clip) {  
1421 - m_edisp(m_inx_matrix, inx) = 0.0;  
1422 - }  
1423 - else {  
1424 - double value = m_edisp(m_inx_matrix, inx);  
1425 - if (value == 0.0) {  
1426 - clip = true;  
1427 - }  
1428 - }  
1429 - } 1402 + // Continue only if there is information
  1403 + if (total(ietrue) > 0.0) {
1430 1404
1431 - // Walk from maximum to the last pixel  
1432 - clip = false;  
1433 - for (int imigra = imax; imigra < nmigra; ++imigra) {  
1434 - int inx = ibase + imigra*netrue;  
1435 - if (clip) {  
1436 - m_edisp(m_inx_matrix, inx) = 0.0;  
1437 - }  
1438 - else {  
1439 - double value = m_edisp(m_inx_matrix, inx);  
1440 - if (value == 0.0) {  
1441 - clip = true;  
1442 - }  
1443 - }  
1444 - } 1405 + // Get Gaussian
  1406 + GNdarray gauss = gaussian_array(mean(ietrue), rms(ietrue), total(ietrue));
1445 1407
1446 - // Determine number of contiguous non-zero pixels  
1447 - int nonzero = 0;  
1448 - int contiguous_nonzero = 0;  
1449 - for (int imigra = 0; imigra < nmigra; ++imigra) {  
1450 - int inx = ibase + imigra*netrue;  
1451 - double value = m_edisp(m_inx_matrix, inx);  
1452 - if (value != 0.0) {  
1453 - nonzero++;  
1454 - if (nonzero > contiguous_nonzero) {  
1455 - contiguous_nonzero = nonzero; 1408 + // Compute base index
  1409 + int ibase = itheta * npix + ietrue;
  1410 + if ((mean(ietrue) > 0.0) && (rms(ietrue))) {
  1411 + for (int imigra = 0, i = ibase; imigra < nmigra; ++imigra, i += netrue) {
  1412 + m_edisp(m_inx_matrix, i) = gauss(imigra);
1456 } 1413 }
1457 } 1414 }
1458 else { 1415 else {
1459 - nonzero = 0;  
1460 - }  
1461 - }  
1462 -  
1463 - // If there are less than threshold contiguous non-zero pixels we  
1464 - // only have noise, hence all pixels are set to zero  
1465 - if (contiguous_nonzero < threshold) {  
1466 - for (int imigra = 0; imigra < nmigra; ++imigra) {  
1467 - int inx = ibase + imigra*netrue;  
1468 - m_edisp(m_inx_matrix, inx) = 0.0;  
1469 - }  
1470 - }  
1471 -  
1472 - } // endfor: looped over all true energies  
1473 -  
1474 - } // endfor: looped over all offset angles  
1475 -  
1476 - // Return  
1477 - return;  
1478 -}  
1479 -  
1480 -  
1481 -/***********************************************************************//**  
1482 - * @brief Clip energy dispersion table  
1483 - *  
1484 - * @param[in] threshold Clipping threshold as fraction of maximum value.  
1485 - *  
1486 - * Clips all migration vectors at a threshold that is a fraction of the  
1487 - * maximum value in the migration vector.  
1488 - ***************************************************************************/  
1489 -void GCTAEdisp2D::clip_table(const double& threshold)  
1490 -{  
1491 - // Get axes dimensions  
1492 - int netrue = m_edisp.axis_bins(m_inx_etrue);  
1493 - int nmigra = m_edisp.axis_bins(m_inx_migra);  
1494 - int ntheta = m_edisp.axis_bins(m_inx_theta);  
1495 - int npix = netrue * nmigra;  
1496 -  
1497 - // Loop over all offset angles  
1498 - for (int itheta = 0; itheta < ntheta; ++itheta) {  
1499 -  
1500 - // Loop over all true energies  
1501 - for (int ietrue = 0; ietrue < netrue; ++ietrue) {  
1502 -  
1503 - // Compute base index  
1504 - int ibase = itheta * npix + ietrue;  
1505 -  
1506 - // Compute maximum value  
1507 - double max = 0.0;  
1508 - for (int imigra = 0, i = ibase; imigra < nmigra; ++imigra, i += netrue) {  
1509 - if (m_edisp(m_inx_matrix, i) > max) {  
1510 - max = m_edisp(m_inx_matrix, i); 1416 + for (int imigra = 0, i = ibase; imigra < nmigra; ++imigra, i += netrue) {
  1417 + m_edisp(m_inx_matrix, i) = 0.0;
  1418 + }
1511 } 1419 }
1512 - } 1420 +
  1421 + } // endif: there was information
1513 1422
1514 - // Clip values below threshold  
1515 - double clip_value = max * threshold;  
1516 - for (int imigra = 0, i = ibase; imigra < nmigra; ++imigra, i += netrue) {  
1517 - if (m_edisp(m_inx_matrix, i) < clip_value) { 1423 + // ... otherwise reset the matrix to zero
  1424 + else {
  1425 + int ibase = itheta * npix + ietrue;
  1426 + for (int imigra = 0, i = ibase; imigra < nmigra; ++imigra, i += netrue) {
1518 m_edisp(m_inx_matrix, i) = 0.0; 1427 m_edisp(m_inx_matrix, i) = 0.0;
1519 } 1428 }
1520 } 1429 }
1521 1430
1522 } // endfor: looped over true energies 1431 } // endfor: looped over true energies
1523 1432
1524 - } // endfor: looped over all offset angles 1433 + } // endfor: looped over offset angles
  1434 +
  1435 + // Log exit
  1436 + //std::cout << "GCTAEdisp2D::smooth_table out" << std::endl;
1525 1437
1526 // Return 1438 // Return
1527 return; 1439 return;
@@ -1529,334 +1441,198 @@ void GCTAEdisp2D::clip_table(const double&amp; threshold) @@ -1529,334 +1441,198 @@ void GCTAEdisp2D::clip_table(const double&amp; threshold)
1529 1441
1530 1442
1531 /***********************************************************************//** 1443 /***********************************************************************//**
1532 - * @brief Denoise energy dispersion table  
1533 - *  
1534 - * @param[in] sigma Gaussian smoothing single in true energy. 1444 + * @brief Smoothed array
1535 * 1445 *
1536 - * Denoise the energy dispersion table by replacing the energy dispersion  
1537 - * for each offset angle and true energy by a Gaussian function. The mean  
1538 - * and rms of the Gaussian is smoothed as function of true energy using a  
1539 - * Gaussian smoothing kernel with a width of @p sigma pixels. 1446 + * @param[in] array Array.
  1447 + * @param[in] nstart Number of nodes used for regression at first array value.
  1448 + * @param[in] nstop Number of nodes used for regression at last array value.
  1449 + * @param[in] limit Limit for array element exclusion.
  1450 + * @return Smoothed array.
1540 * 1451 *
1541 - * True energy values with no energy dispersion information are interpolated  
1542 - * from neighbouring energy dispersion values. 1452 + * Returns a smoothed array that is computed by locally adjusting a linear
  1453 + * regression law to the array elements.
1543 ***************************************************************************/ 1454 ***************************************************************************/
1544 -void GCTAEdisp2D::denoise_table(const double& sigma) 1455 +GNdarray GCTAEdisp2D::smooth_array(const GNdarray& array,
  1456 + const int& nstart,
  1457 + const int& nstop,
  1458 + const double& limit) const
1545 { 1459 {
1546 - // Log entrance  
1547 - //std::cout << "GCTAEdisp2D::denoise_table in" << std::endl;  
1548 -  
1549 - // Get axes dimensions  
1550 - int netrue = m_edisp.axis_bins(m_inx_etrue);  
1551 - int nmigra = m_edisp.axis_bins(m_inx_migra);  
1552 - int ntheta = m_edisp.axis_bins(m_inx_theta);  
1553 - int npix = netrue * nmigra;  
1554 -  
1555 - // Get Gaussian parameters for all offset angles and true energies  
1556 - GNdarray gpars = get_gaussian_pars(sigma);  
1557 -  
1558 - // Loop over all offset angles  
1559 - for (int itheta = 0; itheta < ntheta; ++itheta) {  
1560 -  
1561 - // Initialise sums  
1562 - std::vector<double> sum_array;  
1563 -  
1564 - // Loop over all true energies  
1565 - for (int ietrue = 0; ietrue < netrue; ++ietrue) {  
1566 -  
1567 - // Compute base index  
1568 - int ibase = itheta * npix + ietrue;  
1569 -  
1570 - // Extract array  
1571 - GNdarray array(nmigra);  
1572 - for (int imigra = 0, i = ibase; imigra < nmigra; ++imigra, i += netrue) {  
1573 - array(imigra) = m_edisp(m_inx_matrix, i);  
1574 - }  
1575 -  
1576 - // Compute single event value  
1577 - double event_value = get_single_event_value(array);  
1578 -  
1579 - // Smooth array  
1580 - GNdarray smoothed_array(nmigra);  
1581 - if (max(array) >= 2.0*event_value) {  
1582 - smoothed_array = gaussian_array(gpars(itheta, ietrue, 0),  
1583 - gpars(itheta, ietrue, 1));  
1584 - }  
1585 -  
1586 - // Compute total of smoothed array  
1587 - double total = sum(smoothed_array);  
1588 -  
1589 - // Store sum  
1590 - sum_array.push_back(total);  
1591 -  
1592 - // Put back smoothed array values in matrix  
1593 - if (total > 0.0) {  
1594 - for (int imigra = 0, i = ibase; imigra < nmigra; ++imigra, i += netrue) {  
1595 - m_edisp(m_inx_matrix, i) = smoothed_array(imigra) / total;  
1596 - }  
1597 - }  
1598 -  
1599 - } // endfor: looped over all true energies 1460 + // Initialise smoothed array
  1461 + GNdarray smoothed_array(array.size());
1600 1462
1601 - // Interpolate gaps  
1602 - for (int ietrue = 0; ietrue < netrue; ++ietrue) {  
1603 -  
1604 - // Fall through if array is not empty  
1605 - if (sum_array[ietrue] > 0.0) {  
1606 - continue;  
1607 - } 1463 + // Compute node step size
  1464 + double nstep = double(nstop - nstart)/double(array.size());
1608 1465
1609 - // Find enclosing indices of non-empty arrays  
1610 - int ileft = ietrue;  
1611 - int iright = ietrue;  
1612 - for (; ileft >= 0; ileft--) {  
1613 - if (sum_array[ileft] > 0.0) {  
1614 - break;  
1615 - }  
1616 - }  
1617 - for (; iright < netrue; ++iright) {  
1618 - if (sum_array[iright] > 0.0) {  
1619 - break;  
1620 - }  
1621 - }  
1622 -  
1623 - // If encosing indices are valid then interpolate array  
1624 - if ((ileft >= 0) && (iright < netrue)) {  
1625 -  
1626 - // Compute weight factors  
1627 - double norm = 1.0 / double(iright-ileft);  
1628 - double wleft = double(iright - ietrue) * norm;  
1629 - double wright = double(ietrue - ileft) * norm;  
1630 -  
1631 - // Fill array by linear interpolation  
1632 - int idst = itheta * npix + ietrue;  
1633 - int isrc_left = itheta * npix + ileft;  
1634 - int isrc_right = itheta * npix + iright;  
1635 - for (int imigra = 0; imigra < nmigra; ++imigra) {  
1636 - m_edisp(m_inx_matrix, idst) =  
1637 - wleft * m_edisp(m_inx_matrix, isrc_left) +  
1638 - wright * m_edisp(m_inx_matrix, isrc_right);  
1639 - idst += netrue;  
1640 - isrc_left += netrue;  
1641 - isrc_right += netrue;  
1642 - }  
1643 -  
1644 - } // endif: there were valid enclosing indices  
1645 -  
1646 - } // endfor: looped over all true energies  
1647 -  
1648 - } // endfor: looped over all offset angles  
1649 -  
1650 - // Log exit  
1651 - //std::cout << "GCTAEdisp2D::denoise_table out" << std::endl; 1466 + // Loop over all array elements and computed the smoothed value
  1467 + for (int i = 0; i < array.size(); ++i) {
  1468 + int nodes = nstart + int(i*nstep);
  1469 + smoothed_array(i) = smoothed_array_value(i, array, nodes, limit);
  1470 + }
1652 1471
1653 - // Return  
1654 - return; 1472 + // Return smoothed array
  1473 + return smoothed_array;
1655 } 1474 }
1656 1475
1657 1476
1658 /***********************************************************************//** 1477 /***********************************************************************//**
1659 - * @brief Return Gaussian approximation of energy dispersion array 1478 + * @brief Get smoothed array value
1660 * 1479 *
1661 - * @param[in] mean Gaussian mean.  
1662 - * @param[in] rms Gaussian rms.  
1663 - * @return Gaussian approximation of energy dispersion array. 1480 + * @param[in] inx Index of array element.
  1481 + * @param[in] array Array.
  1482 + * @param[in] nodes Number of nodes used for regression.
  1483 + * @param[in] limit Limit for array element exclusion.
  1484 + * @return Smoothed array value.
1664 * 1485 *
1665 - * Returns a Gaussian approximation of the energy dispersion array by  
1666 - * computing the mean migration value and its root mean square and by using  
1667 - * these values as the centre and the width of a Gaussian function. The  
1668 - * Gaussian function is normalized so that the sum of the output array is  
1669 - * unity. 1486 + * Returns a smoothed array value that is derived from adjusting a linear
  1487 + * law using regression to @p nodes adjacent array elements. Array elements
  1488 + * with values below @p limit are excluded from the regression.
1670 ***************************************************************************/ 1489 ***************************************************************************/
1671 -GNdarray GCTAEdisp2D::gaussian_array(const double& mean, const double& rms) const 1490 +double GCTAEdisp2D::smoothed_array_value(const int& inx,
  1491 + const GNdarray& array,
  1492 + const int& nodes,
  1493 + const double& limit) const
1672 { 1494 {
1673 - // Initialise empty Gaussian array  
1674 - int nmigra = m_edisp.axis_bins(m_inx_migra);  
1675 - GNdarray gaussian(nmigra);  
1676 -  
1677 - // If the array contains information then compute a Gaussian  
1678 - // approximation  
1679 - if (rms > 0.0) {  
1680 -  
1681 - // Get reference to migration values  
1682 - const GNodeArray& migras = m_edisp.axis_nodes(m_inx_migra);  
1683 - int nmigra = migras.size(); 1495 + // Initialise variables
  1496 + double mean_x = 0.0;
  1497 + double mean_y = 0.0;
  1498 + int ileft = inx - 1;
  1499 + int iright = inx + 1;
  1500 + int nleft = 0;
  1501 + int nright = 0;
  1502 +
  1503 + // Allocate vector of elements used for regression
  1504 + std::vector<double> x_vals;
  1505 + std::vector<double> y_vals;
  1506 +
  1507 + // Add nodes on the left of the element of interest
  1508 + while (nleft < nodes) {
  1509 + if (ileft < 0) {
  1510 + break;
  1511 + }
  1512 + if (array(ileft) > limit) {
  1513 + double x = double(ileft);
  1514 + double y = array(ileft);
  1515 + x_vals.push_back(x);
  1516 + y_vals.push_back(y);
  1517 + mean_x += x;
  1518 + mean_y += y;
  1519 + nleft++;
  1520 + }
  1521 + ileft--;
  1522 + }
1684 1523
1685 - // Compute Gaussian  
1686 - double total_gauss = 0.0;  
1687 - for (int imigra = 0; imigra < nmigra; ++imigra) {  
1688 - double arg = (migras[imigra] - mean) / rms;  
1689 - double value = std::exp(-0.5*arg*arg);  
1690 - gaussian(imigra) = value;  
1691 - total_gauss += value; 1524 + // Add remaining nodes on the right of the element of interest
  1525 + while (nright < 2*nodes-nleft) {
  1526 + if (iright >= array.size()) {
  1527 + break;
  1528 + }
  1529 + if (array(iright) > limit) {
  1530 + double x = double(iright);
  1531 + double y = array(iright);
  1532 + x_vals.push_back(x);
  1533 + y_vals.push_back(y);
  1534 + mean_x += x;
  1535 + mean_y += y;
  1536 + nright++;
1692 } 1537 }
  1538 + iright++;
  1539 + }
1693 1540
1694 - // Normalise Gaussian  
1695 - if (total_gauss > 0.0) {  
1696 - gaussian *= 1.0 / total_gauss; 1541 + // Add remaining nodes on the left if all right nodes were not exhausted
  1542 + while (nleft < 2*nodes-nright) {
  1543 + if (ileft < 0) {
  1544 + break;
  1545 + }
  1546 + if (array(ileft) > limit) {
  1547 + double x = double(ileft);
  1548 + double y = array(ileft);
  1549 + x_vals.push_back(x);
  1550 + y_vals.push_back(y);
  1551 + mean_x += x;
  1552 + mean_y += y;
  1553 + nleft++;
1697 } 1554 }
  1555 + ileft--;
  1556 + }
1698 1557
1699 - } // endif: computed Gaussian approximation 1558 + // Compute mean x and y values
  1559 + double total = double(nleft+nright);
  1560 + if (total > 0.0) {
  1561 + mean_x /= total;
  1562 + mean_y /= total;
  1563 + }
1700 1564
1701 - // Return Gaussian array  
1702 - return gaussian; 1565 + // Compute regression line slope
  1566 + double beta_nom = 0.0;
  1567 + double beta_denom = 0.0;
  1568 + for (int i = 0; i < x_vals.size(); ++i) {
  1569 + double x = x_vals[i];
  1570 + double y = y_vals[i];
  1571 + beta_nom += (x - mean_x) * (y - mean_y);
  1572 + beta_denom += (x - mean_x) * (x - mean_x);
  1573 + }
  1574 + double beta = beta_nom / beta_denom;
  1575 +
  1576 + // Compute regression line offset
  1577 + double alpha = mean_y - beta * mean_x;
  1578 +
  1579 + // Compute value
  1580 + double value = alpha + beta * double(inx);
  1581 +
  1582 + // Return value
  1583 + return value;
1703 } 1584 }
1704 1585
1705 1586
1706 /***********************************************************************//** 1587 /***********************************************************************//**
1707 - * @brief Compute mean and root mean square of migration array 1588 + * @brief Compute moments
1708 * 1589 *
1709 - * @param[in] sigma Smoothing parameter in true energy.  
1710 - * @return Array of mean and rms values for each offset angle and true energy. 1590 + * @param[in] itheta Offset angle index.
  1591 + * @param[out] mean Pointer to mean array.
  1592 + * @param[out] rms Pointer to rms array.
  1593 + * @param[out] total Pointer to total array.
1711 * 1594 *
1712 - * Computes the mean and the root mean square of the migration array for each  
1713 - * offset angle and true energy and applies a Gaussian smoothing in true  
1714 - * energy. 1595 + * Computes the mean and root mean square values as function of true energy
  1596 + * for a given offset angle.
1715 ***************************************************************************/ 1597 ***************************************************************************/
1716 -GNdarray GCTAEdisp2D::get_gaussian_pars(const double& sigma) const 1598 +void GCTAEdisp2D::get_moments(const int& itheta,
  1599 + GNdarray* mean,
  1600 + GNdarray* rms,
  1601 + GNdarray* total) const
1717 { 1602 {
1718 // Get axes dimensions 1603 // Get axes dimensions
1719 - int netrue = m_edisp.axis_bins(m_inx_etrue);  
1720 - int nmigra = m_edisp.axis_bins(m_inx_migra);  
1721 - int ntheta = m_edisp.axis_bins(m_inx_theta);  
1722 - int npix = netrue * nmigra; 1604 + int netrue = m_edisp.axis_bins(m_inx_etrue);
  1605 + int nmigra = m_edisp.axis_bins(m_inx_migra);
  1606 + int npix = netrue * nmigra;
1723 1607
1724 - // Initialise result  
1725 - GNdarray result(ntheta, netrue, 2); 1608 + // Loop over all true energies
  1609 + for (int ietrue = 0; ietrue < netrue; ++ietrue) {
1726 1610
1727 - // Setup smoothing kernel  
1728 - GFft fft_kernel = fft_smooth_kernel(netrue, sigma); 1611 + // Compute base index
  1612 + int ibase = itheta * npix + ietrue;
1729 1613
1730 - // Loop over all offset angles  
1731 - for (int itheta = 0; itheta < ntheta; ++itheta) { 1614 + // Extract array
  1615 + GNdarray array(nmigra);
  1616 + for (int imigra = 0, i = ibase; imigra < nmigra; ++imigra, i += netrue) {
  1617 + array(imigra) = m_edisp(m_inx_matrix, i);
  1618 + }
1732 1619
1733 - // Allocate working arrays  
1734 - GNdarray work_mean(netrue);  
1735 - GNdarray work_rms(netrue);  
1736 - GNdarray work_sum(netrue); 1620 + // Store sum
  1621 + (*total)(ietrue) = sum(array);
1737 1622
1738 - // Initialise mean and rms values 1623 + // Get Gaussian mean and rms
1739 double mean_value = 0.0; 1624 double mean_value = 0.0;
1740 double rms_value = 0.0; 1625 double rms_value = 0.0;
  1626 + get_mean_rms(array, &mean_value, &rms_value);
1741 1627
1742 - // Loop over all true energies  
1743 - for (int ietrue = 0; ietrue < netrue; ++ietrue) {  
1744 -  
1745 - // Compute base index  
1746 - int ibase = itheta * npix + ietrue;  
1747 -  
1748 - // Extract array  
1749 - GNdarray array(nmigra);  
1750 - for (int imigra = 0, i = ibase; imigra < nmigra; ++imigra, i += netrue) {  
1751 - array(imigra) = m_edisp(m_inx_matrix, i);  
1752 - }  
1753 -  
1754 - // Store sum (only results with positive sum will be kept)  
1755 - work_sum(ietrue) = sum(array);  
1756 -  
1757 - // Compute single event value  
1758 - double event_value = get_single_event_value(array);  
1759 -  
1760 - // Get Gaussian mean and rms. Use last mean and rms value in case  
1761 - // that the array is empty  
1762 - if (work_sum(ietrue) > 0) {  
1763 - mean_value = 0.0;  
1764 - rms_value = 0.0;  
1765 - get_mean_rms(array, &mean_value, &rms_value);  
1766 - }  
1767 -  
1768 - // Store mean and rms  
1769 - work_mean(ietrue) = mean_value;  
1770 - work_rms(ietrue) = rms_value;  
1771 -  
1772 - } // endfor: looped over all true energies  
1773 -  
1774 - // Pad front  
1775 - for (int ietrue = 0; ietrue < netrue; ++ietrue) {  
1776 - if (work_mean(ietrue) > 0.0) {  
1777 - for (int i = 0; i < ietrue; ++i) {  
1778 - work_mean(i) = work_mean(ietrue);  
1779 - work_rms(i) = work_rms(ietrue);  
1780 - }  
1781 - break;  
1782 - }  
1783 - }  
1784 -  
1785 - // Pad back  
1786 - for (int ietrue = netrue-1; ietrue >= 0; ietrue--) {  
1787 - if (work_mean(ietrue) > 0.0) {  
1788 - for (int i = ietrue; i < netrue; ++i) {  
1789 - work_mean(i) = work_mean(ietrue);  
1790 - work_rms(i) = work_rms(ietrue);  
1791 - }  
1792 - break;  
1793 - }  
1794 - }  
1795 -  
1796 - // Smooth mean  
1797 - GFft fft_mean(work_mean);  
1798 - GFft fft_mean_smooth = fft_mean * fft_kernel;  
1799 - work_mean = fft_mean_smooth.backward(); 1628 + // Store mean and rms
  1629 + (*mean)(ietrue) = mean_value;
  1630 + (*rms)(ietrue) = rms_value;
1800 1631
1801 - // Smooth rms  
1802 - GFft fft_rms(work_rms);  
1803 - GFft fft_rms_smooth = fft_rms * fft_kernel;  
1804 - work_rms = fft_rms_smooth.backward(); 1632 + } // endfor: looped over all true energies
1805 1633
1806 - // Store result  
1807 - for (int ietrue = 0; ietrue < netrue; ++ietrue) {  
1808 - if (work_sum(ietrue) > 0.0) {  
1809 - result(itheta, ietrue, 0) = work_mean(ietrue);  
1810 - result(itheta, ietrue, 1) = work_rms(ietrue);  
1811 - }  
1812 - }  
1813 -  
1814 - } // endif: looped over all offset angles  
1815 -  
1816 - // Return result  
1817 - return result;  
1818 -}  
1819 -  
1820 -  
1821 -/***********************************************************************//**  
1822 - * @brief Get FFT of smoothing kernel  
1823 - *  
1824 - * @param[in] nbins Number of kernel bins.  
1825 - * @param[in] sigma Gaussian kernel sigma in bins.  
1826 - * @return FFT of smoothing kernel  
1827 - *  
1828 - * Returns the fast-fourrier transform of a Gaussian smoothing kernel.  
1829 - ***************************************************************************/  
1830 -GFft GCTAEdisp2D::fft_smooth_kernel(const int& nbins,  
1831 - const double& sigma) const  
1832 -{  
1833 - // Allocate kernel  
1834 - GNdarray kernel(nbins);  
1835 -  
1836 - // Initialise sum and compute Gaussian normalisation  
1837 - double sum = 0.0;  
1838 - double norm = -0.5 / (sigma * sigma);  
1839 -  
1840 - // Set Gaussian kernel  
1841 - for (int i = 0; i < nbins; ++i) {  
1842 - double value = std::exp(norm*double(i*i));  
1843 - kernel(i) += value;  
1844 - sum += value;  
1845 - if (i > 0) {  
1846 - kernel(nbins-i) += value;  
1847 - sum += value;  
1848 - }  
1849 - }  
1850 -  
1851 - // Normalize kernel  
1852 - if (sum > 0.0) {  
1853 - for (int i = 0; i < nbins; ++i) {  
1854 - kernel(i) /= sum;  
1855 - }  
1856 - }  
1857 -  
1858 - // Return FFT of kernel  
1859 - return (GFft(kernel)); 1634 + // Return
  1635 + return;
1860 } 1636 }
1861 1637
1862 1638
@@ -1910,35 +1686,52 @@ void GCTAEdisp2D::get_mean_rms(const GNdarray&amp; array, @@ -1910,35 +1686,52 @@ void GCTAEdisp2D::get_mean_rms(const GNdarray&amp; array,
1910 1686
1911 1687
1912 /***********************************************************************//** 1688 /***********************************************************************//**
1913 - * @brief Estimate the value of a single event 1689 + * @brief Return Gaussian approximation of energy dispersion array
1914 * 1690 *
1915 - * @param[in] array Energy dispersion array.  
1916 - * @return Value of a single event. 1691 + * @param[in] mean Gaussian mean.
  1692 + * @param[in] rms Gaussian rms.
  1693 + * @param[in] total Gaussian total.
  1694 + * @return Gaussian approximation of energy dispersion array.
1917 * 1695 *
1918 - * Returns the minimum non-zero value in an array as an estimate of the value  
1919 - * of a single event. If the array is empty the method returns zero. 1696 + * Returns a Gaussian approximation of the energy dispersion array by
  1697 + * computing the mean migration value and its root mean square and by using
  1698 + * these values as the centre and the width of a Gaussian function. The
  1699 + * Gaussian function is normalized so that the sum of the output array is
  1700 + * unity.
1920 ***************************************************************************/ 1701 ***************************************************************************/
1921 -double GCTAEdisp2D::get_single_event_value(const GNdarray& array) const 1702 +GNdarray GCTAEdisp2D::gaussian_array(const double& mean,
  1703 + const double& rms,
  1704 + const double& total) const
1922 { 1705 {
1923 - // Initialise minimum value  
1924 - double event_value = 1.0e30; 1706 + // Initialise empty Gaussian array
  1707 + int nmigra = m_edisp.axis_bins(m_inx_migra);
  1708 + GNdarray gaussian(nmigra);
1925 1709
1926 - // Get minimum non-zero value  
1927 - for (int i = 0; i < array.size(); ++i) {  
1928 - double value = array(i);  
1929 - if ((value > 0.0) && (value < event_value)) {  
1930 - event_value = value; 1710 + // If the rms is valid then compute Gaussian
  1711 + if (rms > 0.0) {
  1712 +
  1713 + // Get reference to migration values
  1714 + const GNodeArray& migras = m_edisp.axis_nodes(m_inx_migra);
  1715 + int nmigra = migras.size();
  1716 +
  1717 + // Compute Gaussian
  1718 + double total_gauss = 0.0;
  1719 + for (int imigra = 0; imigra < nmigra; ++imigra) {
  1720 + double arg = (migras[imigra] - mean) / rms;
  1721 + double value = std::exp(-0.5*arg*arg);
  1722 + gaussian(imigra) = value;
  1723 + total_gauss += value;
1931 } 1724 }
1932 - }  
1933 1725
1934 - // If no minimum value was encountered then set single event value to  
1935 - // zero  
1936 - if (event_value == 1.0e30) {  
1937 - event_value = 0.0;  
1938 - } 1726 + // Normalise Gaussian
  1727 + if (total_gauss > 0.0) {
  1728 + gaussian *= total / total_gauss;
  1729 + }
1939 1730
1940 - // Return  
1941 - return event_value; 1731 + } // endif: computed Gaussian
  1732 +
  1733 + // Return Gaussian array
  1734 + return gaussian;
1942 } 1735 }
1943 1736
1944 1737