Commit 3b8c660717a587f0ba1fee613053bdc382a41399

Authored by Jürgen Knödlseder
1 parent 19f117ae

Add more methods to test energy dispersion smoothing

inst/cta/include/GCTAEdisp2D.hpp
... ... @@ -124,12 +124,30 @@ private:
124 124 void set_boundaries(void);
125 125 void set_max_edisp(void) const;
126 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;
  127 +
  128 + // Kludge
  129 + void clean_table(const int& threshold);
  130 + GFft fft_smooth_kernel(const int& nbins,
  131 + const double& sigma) const;
  132 + void clip_table(const double& threshold);
  133 + void denoise_table(void);
  134 + GNdarray smooth_array(const GNdarray& array, const double& mean,
  135 + const double& rms) const;
  136 + GNdarray gaussian_array(const double& mean, const double& rms) const;
  137 + GNdarray get_gaussian_pars(const double& sigma) const;
  138 + void get_mean_rms(const GNdarray& array, double* mean, double* rms) const;
  139 + double get_single_event_value(const GNdarray& array) const;
  140 +
  141 + // Obsolete kludge
  142 + void smooth_table(const double& sigma);
  143 + void smooth_table2(const double& sigma_etrue, const double& sigma_migra);
  144 + GFft fft_smooth_kernel(const int& netrue,
  145 + const int& nmigra,
  146 + const double& sigma) const;
  147 + GFft fft_smooth_kernel2(const int& netrue,
  148 + const int& nmigra,
  149 + const double& sigma_etrue,
  150 + const double& sigma_migra) const;
133 151  
134 152 // Protected classes
135 153 class edisp_kern : public GFunction {
... ...
inst/cta/src/GCTAEdisp2D.cpp
... ... @@ -52,7 +52,8 @@
52 52 /* __ Macros _____________________________________________________________ */
53 53  
54 54 /* __ Coding definitions _________________________________________________ */
55   -#define G_SMOOTH_EDISP_KLUDGE //!< Get rid of noise in energy dispersion
  55 +#define G_SMOOTH_EDISP_KLUDGE //!< Get rid of noise in energy dispersion
  56 +#define G_SMOOTH_EDISP_SAVE_TEST //!< Save test matrix
56 57  
57 58 /* __ Debug definitions __________________________________________________ */
58 59  
... ... @@ -1199,8 +1200,15 @@ void GCTAEdisp2D::set_table(void)
1199 1200  
1200 1201 // Smooth energy dispersion table
1201 1202 #if defined(G_SMOOTH_EDISP_KLUDGE)
1202   - clip_table(15);
1203   - smooth_table(3.0, 0.0001);
  1203 + denoise_table();
  1204 + clean_table(15);
  1205 + clip_table(0.001);
  1206 + /*
  1207 + denoise_table();
  1208 + clean_table(20);
  1209 + smooth_table(5.0);
  1210 + clip_table(0.01);
  1211 + */
1204 1212 #endif
1205 1213  
1206 1214 // Normalize energy dispersion table
... ... @@ -1209,6 +1217,11 @@ void GCTAEdisp2D::set_table(void)
1209 1217 // Set maximum energy dispersion value
1210 1218 set_max_edisp();
1211 1219  
  1220 + // Save test
  1221 + #if defined(G_SMOOTH_EDISP_SAVE_TEST)
  1222 + this->save("test_edisp.fits", true);
  1223 + #endif
  1224 +
1212 1225 // Return
1213 1226 return;
1214 1227 }
... ... @@ -1370,10 +1383,10 @@ void GCTAEdisp2D::normalize_table(void)
1370 1383 *
1371 1384 * The method then computes the number of remaining contiguos non-zero pixels
1372 1385 * for each offset angle and true energy, and if this number is smaller than
1373   - * 10, all pixels are set to zero. This removes true energy bins that are
1374   - * sparesely filled.
  1386 + * a threshold, all pixels are set to zero. This removes true energy bins
  1387 + * that are sparesely filled.
1375 1388 ***************************************************************************/
1376   -void GCTAEdisp2D::clip_table(const int& threshold)
  1389 +void GCTAEdisp2D::clean_table(const int& threshold)
1377 1390 {
1378 1391 // Get axes dimensions
1379 1392 int netrue = m_edisp.axis_bins(m_inx_etrue);
... ... @@ -1475,16 +1488,13 @@ void GCTAEdisp2D::clip_table(const int&amp; threshold)
1475 1488 * @brief Smooth energy dispersion table
1476 1489 *
1477 1490 * @param[in] sigma Smoothing width (number of bins in true energy)
1478   - * @param[in] threshold Clipping threshold
1479 1491 *
1480 1492 * Smooth the energy dispersion table in true energy. The smoothing is done
1481 1493 * using a fast-fourrier transform. For this purpose the energy disperison
1482 1494 * table for each offset angle is copied into a 2D array, padded with some
1483   - * zero pixels on the left and the right to avoid wrap around. After
1484   - * smoothing the array is clipped using the clip_array() method to avoid
1485   - * marginal non-zero energy dispersion values.
  1495 + * zero pixels on the left and the right to avoid wrap around.
1486 1496 ***************************************************************************/
1487   -void GCTAEdisp2D::smooth_table(const double& sigma, const double& threshold)
  1497 +void GCTAEdisp2D::smooth_table(const double& sigma)
1488 1498 {
1489 1499 // Get axes dimensions
1490 1500 int netrue = m_edisp.axis_bins(m_inx_etrue);
... ... @@ -1505,10 +1515,10 @@ void GCTAEdisp2D::smooth_table(const double&amp; sigma, const double&amp; threshold)
1505 1515  
1506 1516 // Extract matrix in GNdarray
1507 1517 GNdarray array(netrue_padded, nmigra);
1508   - for (int imigra = 0, i = 0; imigra < nmigra; ++imigra) {
  1518 + for (int imigra = 0, i = ibase; imigra < nmigra; ++imigra) {
1509 1519 double* ptr = array.data() + imigra * netrue_padded + netrue_pad;
1510 1520 for (int ietrue = 0; ietrue < netrue; ++ietrue, ++i) {
1511   - *ptr++ = m_edisp(m_inx_matrix, ibase+i);
  1521 + *ptr++ = m_edisp(m_inx_matrix, i);
1512 1522 }
1513 1523 }
1514 1524  
... ... @@ -1521,14 +1531,74 @@ void GCTAEdisp2D::smooth_table(const double&amp; sigma, const double&amp; threshold)
1521 1531 // Backward transform array
1522 1532 GNdarray smooth = fft_smooth.backward();
1523 1533  
1524   - // Clip array
1525   - smooth = clip_array(smooth, threshold);
  1534 + // Put back array values in matrix
  1535 + for (int imigra = 0, i = ibase; imigra < nmigra; ++imigra) {
  1536 + double* ptr = smooth.data() + imigra * netrue_padded + netrue_pad;
  1537 + for (int ietrue = 0; ietrue < netrue; ++ietrue, ++i) {
  1538 + m_edisp(m_inx_matrix, i) = *ptr++;
  1539 + }
  1540 + }
  1541 +
  1542 + } // endfor: looped over all offset angles
  1543 +
  1544 + // Return
  1545 + return;
  1546 +}
  1547 +
  1548 +
  1549 +/***********************************************************************//**
  1550 + * @brief Smooth energy dispersion table
  1551 + *
  1552 + * @param[in] sigma Smoothing width (number of bins in true energy)
  1553 + *
  1554 + * Smooth the energy dispersion table in true energy. The smoothing is done
  1555 + * using a fast-fourrier transform. For this purpose the energy disperison
  1556 + * table for each offset angle is copied into a 2D array, padded with some
  1557 + * zero pixels on the left and the right to avoid wrap around.
  1558 + ***************************************************************************/
  1559 +void GCTAEdisp2D::smooth_table2(const double& sigma_etrue,
  1560 + const double& sigma_migra)
  1561 +{
  1562 + // Get axes dimensions
  1563 + int netrue = m_edisp.axis_bins(m_inx_etrue);
  1564 + int nmigra = m_edisp.axis_bins(m_inx_migra);
  1565 + int ntheta = m_edisp.axis_bins(m_inx_theta);
  1566 + int netrue_pad = int(3.0*sigma_etrue);
  1567 + int netrue_padded = netrue + 2*netrue_pad; // Array padded with zeros
  1568 + int npix = netrue * nmigra;
  1569 +
  1570 + // Get smoothing kernel
  1571 + GFft fft_kernel = fft_smooth_kernel2(netrue_padded, nmigra, sigma_etrue, sigma_migra);
  1572 +
  1573 + // Loop over all offset angles
  1574 + for (int itheta = 0; itheta < ntheta; ++itheta) {
  1575 +
  1576 + // Compute base index
  1577 + int ibase = itheta * npix;
  1578 +
  1579 + // Extract matrix in GNdarray
  1580 + GNdarray array(netrue_padded, nmigra);
  1581 + for (int imigra = 0, i = ibase; imigra < nmigra; ++imigra) {
  1582 + double* ptr = array.data() + imigra * netrue_padded + netrue_pad;
  1583 + for (int ietrue = 0; ietrue < netrue; ++ietrue, ++i) {
  1584 + *ptr++ = m_edisp(m_inx_matrix, i);
  1585 + }
  1586 + }
  1587 +
  1588 + // FFT of array
  1589 + GFft fft_array(array);
  1590 +
  1591 + // Smooth array
  1592 + GFft fft_smooth = fft_array * fft_kernel;
  1593 +
  1594 + // Backward transform array
  1595 + GNdarray smooth = fft_smooth.backward();
1526 1596  
1527 1597 // Put back array values in matrix
1528   - for (int imigra = 0, i = 0; imigra < nmigra; ++imigra) {
  1598 + for (int imigra = 0, i = ibase; imigra < nmigra; ++imigra) {
1529 1599 double* ptr = smooth.data() + imigra * netrue_padded + netrue_pad;
1530 1600 for (int ietrue = 0; ietrue < netrue; ++ietrue, ++i) {
1531   - m_edisp(m_inx_matrix, ibase+i) = *ptr++;
  1601 + m_edisp(m_inx_matrix, i) = *ptr++;
1532 1602 }
1533 1603 }
1534 1604  
... ... @@ -1542,6 +1612,51 @@ void GCTAEdisp2D::smooth_table(const double&amp; sigma, const double&amp; threshold)
1542 1612 /***********************************************************************//**
1543 1613 * @brief Get FFT of smoothing kernel
1544 1614 *
  1615 + * @param[in] nbins Number of kernel bins.
  1616 + * @param[in] sigma Gaussian kernel sigma in bins.
  1617 + * @return FFT of smoothing kernel
  1618 + *
  1619 + * Returns the fast-fourrier transform of a Gaussian smoothing kernel.
  1620 + ***************************************************************************/
  1621 +GFft GCTAEdisp2D::fft_smooth_kernel(const int& nbins,
  1622 + const double& sigma) const
  1623 +{
  1624 + // Allocate kernel
  1625 + GNdarray kernel(nbins);
  1626 +
  1627 + // Initialise sum and compute Gaussian normalisation
  1628 + double sum = 0.0;
  1629 + double norm = -0.5 / (sigma * sigma);
  1630 +
  1631 + // Set Gaussian kernel
  1632 + for (int i = 0; i < nbins; ++i) {
  1633 + double value = std::exp(norm*double(i*i));
  1634 + kernel(i) += value;
  1635 + sum += value;
  1636 + if (i > 0) {
  1637 + kernel(nbins-i) += value;
  1638 + sum += value;
  1639 + }
  1640 + }
  1641 +
  1642 + // Normalize kernel
  1643 + if (sum > 0.0) {
  1644 + for (int i = 0; i < nbins; ++i) {
  1645 + kernel(i) /= sum;
  1646 + }
  1647 + }
  1648 +
  1649 + // Return FFT of kernel
  1650 + return (GFft(kernel));
  1651 +}
  1652 +
  1653 +
  1654 +/***********************************************************************//**
  1655 + * @brief Get FFT of smoothing kernel for true energy smoothing
  1656 + *
  1657 + * @param[in] netrue Number of true energy bins.
  1658 + * @param[in] nmigra Number of migration bins bins.
  1659 + * @param[in] sigma Gaussian kernel sigma in true energy direction.
1545 1660 * @return FFT of smoothing kernel
1546 1661 *
1547 1662 * Returns the fast-fourrier transform of a Gaussian smoothing kernel in
... ... @@ -1582,14 +1697,126 @@ GFft GCTAEdisp2D::fft_smooth_kernel(const int&amp; netrue,
1582 1697  
1583 1698  
1584 1699 /***********************************************************************//**
  1700 + * @brief Get FFT of smoothing kernel for 2D smoothing
  1701 + *
  1702 + * @param[in] netrue Number of true energy bins.
  1703 + * @param[in] nmigra Number of migration bins bins.
  1704 + * @param[in] sigma_etrue Gaussian kernel sigma in true energy direction.
  1705 + * @param[in] sigma_migra Gaussian kernel sigma in migration direction.
  1706 + * @return FFT of smoothing kernel
  1707 + *
  1708 + * Returns the fast-fourrier transform of a Gaussian smoothing kernel in
  1709 + * true energy and migration.
  1710 + ***************************************************************************/
  1711 +GFft GCTAEdisp2D::fft_smooth_kernel2(const int& netrue,
  1712 + const int& nmigra,
  1713 + const double& sigma_etrue,
  1714 + const double& sigma_migra) const
  1715 +{
  1716 + // Allocate kernel
  1717 + GNdarray kernel(netrue, nmigra);
  1718 +
  1719 + // Initialise sum and compute Gaussian normalisation
  1720 + double sum = 0.0;
  1721 + double norm_etrue = -0.5 / (sigma_etrue * sigma_etrue);
  1722 + double norm_migra = -0.5 / (sigma_migra * sigma_migra);
  1723 +
  1724 + // Set Gaussian kernel
  1725 + for (int itrue = 0; itrue < netrue; ++itrue) {
  1726 + double value_true = std::exp(norm_etrue*double(itrue*itrue));
  1727 + for (int imigra = 0; imigra < nmigra; ++imigra) {
  1728 + double value = std::exp(norm_migra*double(imigra*imigra)) * value_true;
  1729 + kernel(itrue,imigra) += value;
  1730 + sum += value;
  1731 + if (itrue > 0) {
  1732 + kernel(netrue-itrue,imigra) += value;
  1733 + sum += value;
  1734 + }
  1735 + if (imigra > 0) {
  1736 + kernel(itrue,nmigra-imigra) += value;
  1737 + sum += value;
  1738 + }
  1739 + if ((itrue > 0) && (imigra > 0)) {
  1740 + kernel(netrue-itrue,nmigra-imigra) += value;
  1741 + sum += value;
  1742 + }
  1743 + }
  1744 + }
  1745 +
  1746 + // Normalize kernel
  1747 + if (sum > 0.0) {
  1748 + for (int itrue = 0; itrue < netrue; ++itrue) {
  1749 + for (int imigra = 0; imigra < nmigra; ++imigra) {
  1750 + kernel(itrue,imigra) /= sum;
  1751 + }
  1752 + }
  1753 + }
  1754 +
  1755 + // Return FFT of kernel
  1756 + return (GFft(kernel));
  1757 +}
  1758 +
  1759 +
  1760 +/***********************************************************************//**
  1761 + * @brief Clip energy dispersion table
  1762 + *
  1763 + * @param[in] threshold Clipping threshold in fraction of maximum value.
  1764 + *
  1765 + * Clips all migration vectors at a threshold that is a fraction of the
  1766 + * maximum value in the migration vector.
  1767 + ***************************************************************************/
  1768 +void GCTAEdisp2D::clip_table(const double& threshold)
  1769 +{
  1770 + // Get axes dimensions
  1771 + int netrue = m_edisp.axis_bins(m_inx_etrue);
  1772 + int nmigra = m_edisp.axis_bins(m_inx_migra);
  1773 + int ntheta = m_edisp.axis_bins(m_inx_theta);
  1774 + int npix = netrue * nmigra;
  1775 +
  1776 + // Loop over all offset angles
  1777 + for (int itheta = 0; itheta < ntheta; ++itheta) {
  1778 +
  1779 + // Loop over all true energies
  1780 + for (int ietrue = 0; ietrue < netrue; ++ietrue) {
  1781 +
  1782 + // Compute base index
  1783 + int ibase = itheta * npix + ietrue;
  1784 +
  1785 + // Compute maximum value
  1786 + double max = 0.0;
  1787 + for (int imigra = 0, i = ibase; imigra < nmigra; ++imigra, i += netrue) {
  1788 + if (m_edisp(m_inx_matrix, i) > max) {
  1789 + max = m_edisp(m_inx_matrix, i);
  1790 + }
  1791 + }
  1792 +
  1793 + // Clip values below threshold
  1794 + double clip_value = max * threshold;
  1795 + for (int imigra = 0, i = ibase; imigra < nmigra; ++imigra, i += netrue) {
  1796 + if (m_edisp(m_inx_matrix, i) < clip_value) {
  1797 + m_edisp(m_inx_matrix, i) = 0.0;
  1798 + }
  1799 + }
  1800 +
  1801 + } // endfor: looped over true energies
  1802 +
  1803 + } // endfor: looped over all offset angles
  1804 +
  1805 + // Return
  1806 + return;
  1807 +}
  1808 +
  1809 +
  1810 +/***********************************************************************//**
1585 1811 * @brief Clip array
1586 1812 *
1587 1813 * @param[in] array Ndarray that should be clipped.
1588 1814 * @param[in] threshold Clipping threshold in fraction of maximum value.
1589 1815 * @return Clipped Ndarray
1590 1816 *
1591   - * Clips all values below a threshold of 10 to zero.
  1817 + * Clips all values below a threshold to zero.
1592 1818 ***************************************************************************/
  1819 +/*
1593 1820 GNdarray GCTAEdisp2D::clip_array(const GNdarray& array,
1594 1821 const double& threshold) const
1595 1822 {
... ... @@ -1620,6 +1847,495 @@ GNdarray GCTAEdisp2D::clip_array(const GNdarray&amp; array,
1620 1847 // Return clipped array
1621 1848 return clipped;
1622 1849 }
  1850 +*/
  1851 +
  1852 +
  1853 +/***********************************************************************//**
  1854 + * @brief Denoise energy dispersion table
  1855 + *
  1856 + * Denoise the energy dispersion table by smoothing the energy dispersion
  1857 + * for each offset angle and true energy using the smooth_array() method.
  1858 + *
  1859 + * True energy values with no energy dispersion information are interpolated
  1860 + * from neighbouring energy dispersion values.
  1861 + ***************************************************************************/
  1862 +void GCTAEdisp2D::denoise_table(void)
  1863 +{
  1864 + // Log entrance
  1865 + //std::cout << "GCTAEdisp2D::denoise_table in" << std::endl;
  1866 +
  1867 + // Get axes dimensions
  1868 + int netrue = m_edisp.axis_bins(m_inx_etrue);
  1869 + int nmigra = m_edisp.axis_bins(m_inx_migra);
  1870 + int ntheta = m_edisp.axis_bins(m_inx_theta);
  1871 + int npix = netrue * nmigra;
  1872 +
  1873 + // Get Gaussian parameters for all offset angles and true energies
  1874 + GNdarray gpars = get_gaussian_pars(5.0);
  1875 +
  1876 + // Loop over all offset angles
  1877 + for (int itheta = 0; itheta < ntheta; ++itheta) {
  1878 +
  1879 + // Initialise sums
  1880 + std::vector<double> sum_array;
  1881 +
  1882 + // Loop over all true energies
  1883 + for (int ietrue = 0; ietrue < netrue; ++ietrue) {
  1884 +
  1885 + // Compute base index
  1886 + int ibase = itheta * npix + ietrue;
  1887 +
  1888 + // Extract array
  1889 + GNdarray array(nmigra);
  1890 + for (int imigra = 0, i = ibase; imigra < nmigra; ++imigra, i += netrue) {
  1891 + array(imigra) = m_edisp(m_inx_matrix, i);
  1892 + }
  1893 +
  1894 + // Compute single event value
  1895 + double event_value = get_single_event_value(array);
  1896 +
  1897 + // Smooth array
  1898 + GNdarray smoothed_array(nmigra);
  1899 + if (max(array) >= 2.0*event_value) {
  1900 + smoothed_array = gaussian_array(gpars(itheta, ietrue, 0),
  1901 + gpars(itheta, ietrue, 1));
  1902 + }
  1903 +
  1904 + // Compute total of smoothed array
  1905 + double total = sum(smoothed_array);
  1906 +
  1907 + // Store sum
  1908 + sum_array.push_back(total);
  1909 +
  1910 + // Put back smoothed array values in matrix
  1911 + if (total > 0.0) {
  1912 + for (int imigra = 0, i = ibase; imigra < nmigra; ++imigra, i += netrue) {
  1913 + m_edisp(m_inx_matrix, i) = smoothed_array(imigra) / total;
  1914 + }
  1915 + }
  1916 +
  1917 + } // endfor: looped over all true energies
  1918 +
  1919 + // Interpolate gaps
  1920 + for (int ietrue = 0; ietrue < netrue; ++ietrue) {
  1921 +
  1922 + // Fall through if array is not empty
  1923 + if (sum_array[ietrue] > 0.0) {
  1924 + continue;
  1925 + }
  1926 +
  1927 + // Find enclosing indices of non-empty arrays
  1928 + int ileft = ietrue;
  1929 + int iright = ietrue;
  1930 + for (; ileft >= 0; ileft--) {
  1931 + if (sum_array[ileft] > 0.0) {
  1932 + break;
  1933 + }
  1934 + }
  1935 + for (; iright < netrue; ++iright) {
  1936 + if (sum_array[iright] > 0.0) {
  1937 + break;
  1938 + }
  1939 + }
  1940 +
  1941 + // If encosing indices are valid then interpolate array
  1942 + if ((ileft >= 0) && (iright < netrue)) {
  1943 +
  1944 + // Compute weight factors
  1945 + double norm = 1.0 / double(iright-ileft);
  1946 + double wleft = double(iright - ietrue) * norm;
  1947 + double wright = double(ietrue - ileft) * norm;
  1948 +
  1949 + // Fill array by linear interpolation
  1950 + int idst = itheta * npix + ietrue;
  1951 + int isrc_left = itheta * npix + ileft;
  1952 + int isrc_right = itheta * npix + iright;
  1953 + for (int imigra = 0; imigra < nmigra; ++imigra) {
  1954 + m_edisp(m_inx_matrix, idst) =
  1955 + wleft * m_edisp(m_inx_matrix, isrc_left) +
  1956 + wright * m_edisp(m_inx_matrix, isrc_right);
  1957 + idst += netrue;
  1958 + isrc_left += netrue;
  1959 + isrc_right += netrue;
  1960 + }
  1961 +
  1962 + } // endif: there were valid enclosing indices
  1963 +
  1964 + } // endfor: looped over all true energies
  1965 +
  1966 + } // endfor: looped over all offset angles
  1967 +
  1968 + // Log exit
  1969 + //std::cout << "GCTAEdisp2D::denoise_table out" << std::endl;
  1970 +
  1971 + // Return
  1972 + return;
  1973 +}
  1974 +
  1975 +
  1976 +/***********************************************************************//**
  1977 + * @brief Smooth array
  1978 + *
  1979 + * @param[in] array Energy dispersion array.
  1980 + * @param[in] mean Mean of Gaussian.
  1981 + * @param[in] rms Root mean square of Gaussian.
  1982 + * @return Smoothed energy dispersion array.
  1983 + ***************************************************************************/
  1984 +GNdarray GCTAEdisp2D::smooth_array(const GNdarray& array,
  1985 + const double& mean,
  1986 + const double& rms) const
  1987 +{
  1988 + // Get reference to migration values
  1989 + const GNodeArray& migras = m_edisp.axis_nodes(m_inx_migra);
  1990 + int nmigra = migras.size();
  1991 +
  1992 + // Initialise empty smoothed array
  1993 + GNdarray smoothed_array(nmigra);
  1994 +
  1995 + // Set required number of events and minimum and maximum sigma
  1996 + double num_events = 200.0; // required number of events under Gaussian
  1997 + double min_events = 2.0; // at least 2 events are required
  1998 + double sigma_min = 1.0; // minimum sigma
  1999 + double sigma_max = 0.1 * nmigra; // maximum sigma (< 20)
  2000 + if (sigma_max > 20.0) {
  2001 + sigma_max = 20.0;
  2002 + }
  2003 +
  2004 + // Compute single event value
  2005 + double event_value = get_single_event_value(array);
  2006 +
  2007 + // Continue only there are pixels with the requested minimum number of
  2008 + // events (otherwise the returned vector will be zero)
  2009 + if (max(array) >= min_events*event_value) {
  2010 +
  2011 + // Get Gaussian approximation
  2012 + GNdarray gaussian = gaussian_array(mean, rms);
  2013 +
  2014 + // Get residual by subtracting Gaussian from array
  2015 + GNdarray residual = array - gaussian;
  2016 +
  2017 + // Initialise smoothed residual
  2018 + GNdarray smoothed_residual(nmigra);
  2019 +
  2020 + // Loop over all migration pixels
  2021 + for (int imigra = 0; imigra < nmigra; ++imigra) {
  2022 +
  2023 + // Get pixel value
  2024 + double value = array(imigra);
  2025 +
  2026 + // Fall through if pixel value is empty
  2027 + if (value == 0.0) {
  2028 + continue;
  2029 + }
  2030 +
  2031 + // Set Gaussian sigma. Make sure that sigma is within the allowed
  2032 + // range
  2033 + double sigma = num_events * event_value / value;
  2034 + if (sigma < sigma_min) {
  2035 + sigma = sigma_min;
  2036 + }
  2037 + else if (sigma > sigma_max) {
  2038 + sigma = sigma_max;
  2039 + }
  2040 +
  2041 + // Convolve residual with a Gaussian function
  2042 + double total = 0.0;
  2043 + double norm = -0.5 / (sigma * sigma);
  2044 + GNdarray work(nmigra);
  2045 + for (int k = 0; k < nmigra; ++k) {
  2046 + double weight = std::exp(norm*k*k);
  2047 + if (weight > 1.0e-30) {
  2048 + if (k == 0) {
  2049 + work(imigra) = weight;
  2050 + total += weight;
  2051 + }
  2052 + else {
  2053 + int ileft = imigra - k;
  2054 + if (ileft >= 0) {
  2055 + work(ileft) = weight;
  2056 + total += weight;
  2057 + }
  2058 + int iright = imigra + k;
  2059 + if (iright < nmigra) {
  2060 + work(iright) = weight;
  2061 + total += weight;
  2062 + }
  2063 + }
  2064 + }
  2065 + }
  2066 + if (total > 0.0) {
  2067 + smoothed_residual += work * residual(imigra) / total;
  2068 + }
  2069 +
  2070 + } // endfor: looped over all migration pixels
  2071 +
  2072 + // Set smoothed array
  2073 + smoothed_array = gaussian; // + smoothed_residual;
  2074 +
  2075 + // Make sure that smooth array is not negative
  2076 + for (int imigra = 0; imigra < nmigra; ++imigra) {
  2077 + if (smoothed_array(imigra) < 0.0) {
  2078 + smoothed_array(imigra) = 0.0;
  2079 + }
  2080 + }
  2081 +
  2082 + } // endif: single event value was positive
  2083 +
  2084 + // Return smoothed array
  2085 + return smoothed_array;
  2086 +}
  2087 +
  2088 +
  2089 +/***********************************************************************//**
  2090 + * @brief Return Gaussian approximation of energy dispersion array
  2091 + *
  2092 + * @param[in] mean Gaussian mean.
  2093 + * @param[in] rms Gaussian rms.
  2094 + * @return Gaussian approximation of energy dispersion array.
  2095 + *
  2096 + * Returns a Gaussian approximation of the energy dispersion array by
  2097 + * computing the mean migration value and its root mean square and by using
  2098 + * these values as the centre and the width of a Gaussian function. The
  2099 + * Gaussian function is normalized so that the sum of the output array is
  2100 + * unity.
  2101 + ***************************************************************************/
  2102 +GNdarray GCTAEdisp2D::gaussian_array(const double& mean, const double& rms) const
  2103 +{
  2104 + // Initialise empty Gaussian array
  2105 + int nmigra = m_edisp.axis_bins(m_inx_migra);
  2106 + GNdarray gaussian(nmigra);
  2107 +
  2108 + // Get mean and rms of migration values in array
  2109 + /*
  2110 + double mean = 0.0;
  2111 + double rms = 0.0;
  2112 + get_mean_rms(array, &mean, &rms);
  2113 + */
  2114 +
  2115 + // If the array contains information then compute a Gaussian
  2116 + // approximation
  2117 + if (rms > 0.0) {
  2118 +
  2119 + // Get reference to migration values
  2120 + const GNodeArray& migras = m_edisp.axis_nodes(m_inx_migra);
  2121 + int nmigra = migras.size();
  2122 +
  2123 + // Compute Gaussian
  2124 + double total_gauss = 0.0;
  2125 + for (int imigra = 0; imigra < nmigra; ++imigra) {
  2126 + double arg = (migras[imigra] - mean) / rms;
  2127 + double value = std::exp(-0.5*arg*arg);
  2128 + gaussian(imigra) = value;
  2129 + total_gauss += value;
  2130 + }
  2131 +
  2132 + // Normalise Gaussian
  2133 + if (total_gauss > 0.0) {
  2134 + gaussian *= 1.0 / total_gauss;
  2135 + }
  2136 +
  2137 + } // endif: computed Gaussian approximation
  2138 +
  2139 + // Return Gaussian array
  2140 + return gaussian;
  2141 +}
  2142 +
  2143 +
  2144 +/***********************************************************************//**
  2145 + * @brief Compute mean and root mean square of migration array
  2146 + *
  2147 + * @param[in] sigma Smoothing parameter in true energy.
  2148 + * @return Array of mean and rms values for each offset angle and true energy.
  2149 + *
  2150 + * Computes the mean and the root mean square of the migration array for each
  2151 + * offset angle and true energy and applies a Gaussian smoothing in true
  2152 + * energy.
  2153 + ***************************************************************************/
  2154 +GNdarray GCTAEdisp2D::get_gaussian_pars(const double& sigma) const
  2155 +{
  2156 + // Get axes dimensions
  2157 + int netrue = m_edisp.axis_bins(m_inx_etrue);
  2158 + int nmigra = m_edisp.axis_bins(m_inx_migra);
  2159 + int ntheta = m_edisp.axis_bins(m_inx_theta);
  2160 + int npix = netrue * nmigra;
  2161 +
  2162 + // Initialise result
  2163 + GNdarray result(ntheta, netrue, 2);
  2164 +
  2165 + // Setup smoothing kernel
  2166 + GFft fft_kernel = fft_smooth_kernel(netrue, sigma);
  2167 +
  2168 + // Loop over all offset angles
  2169 + for (int itheta = 0; itheta < ntheta; ++itheta) {
  2170 +
  2171 + // Allocate working arrays
  2172 + GNdarray work_mean(netrue);
  2173 + GNdarray work_rms(netrue);
  2174 + GNdarray work_sum(netrue);
  2175 +
  2176 + // Initialise mean and rms values
  2177 + double mean_value = 0.0;
  2178 + double rms_value = 0.0;
  2179 +
  2180 + // Loop over all true energies
  2181 + for (int ietrue = 0; ietrue < netrue; ++ietrue) {
  2182 +
  2183 + // Compute base index
  2184 + int ibase = itheta * npix + ietrue;
  2185 +
  2186 + // Extract array
  2187 + GNdarray array(nmigra);
  2188 + for (int imigra = 0, i = ibase; imigra < nmigra; ++imigra, i += netrue) {
  2189 + array(imigra) = m_edisp(m_inx_matrix, i);
  2190 + }
  2191 +
  2192 + // Store sum (only results with positive sum will be kept)
  2193 + work_sum(ietrue) = sum(array);
  2194 +
  2195 + // Compute single event value
  2196 + double event_value = get_single_event_value(array);
  2197 +
  2198 + // Get Gaussian mean and rms. Use last mean and rms value in case
  2199 + // that the array is empty
  2200 + if (work_sum(ietrue) > 0) {
  2201 + mean_value = 0.0;
  2202 + rms_value = 0.0;
  2203 + get_mean_rms(array, &mean_value, &rms_value);
  2204 + }
  2205 +
  2206 + // Store mean and rms
  2207 + work_mean(ietrue) = mean_value;
  2208 + work_rms(ietrue) = rms_value;
  2209 +
  2210 + } // endfor: looped over all true energies
  2211 +
  2212 + // Pad front
  2213 + for (int ietrue = 0; ietrue < netrue; ++ietrue) {
  2214 + if (work_mean(ietrue) > 0.0) {
  2215 + for (int i = 0; i < ietrue; ++i) {
  2216 + work_mean(i) = work_mean(ietrue);
  2217 + work_rms(i) = work_rms(ietrue);
  2218 + }
  2219 + break;
  2220 + }
  2221 + }
  2222 +
  2223 + // Pad back
  2224 + for (int ietrue = netrue-1; ietrue >= 0; ietrue--) {
  2225 + if (work_mean(ietrue) > 0.0) {
  2226 + for (int i = ietrue; i < netrue; ++i) {
  2227 + work_mean(i) = work_mean(ietrue);
  2228 + work_rms(i) = work_rms(ietrue);
  2229 + }
  2230 + break;
  2231 + }
  2232 + }
  2233 +
  2234 + // Smooth mean
  2235 + GFft fft_mean(work_mean);
  2236 + GFft fft_mean_smooth = fft_mean * fft_kernel;
  2237 + work_mean = fft_mean_smooth.backward();
  2238 +
  2239 + // Smooth rms
  2240 + GFft fft_rms(work_rms);
  2241 + GFft fft_rms_smooth = fft_rms * fft_kernel;
  2242 + work_rms = fft_rms_smooth.backward();
  2243 +
  2244 + // Store result
  2245 + for (int ietrue = 0; ietrue < netrue; ++ietrue) {
  2246 + if (work_sum(ietrue) > 0.0) {
  2247 + result(itheta, ietrue, 0) = work_mean(ietrue);
  2248 + result(itheta, ietrue, 1) = work_rms(ietrue);
  2249 + }
  2250 + }
  2251 +
  2252 + } // endif: looped over all offset angles
  2253 +
  2254 + // Return result
  2255 + return result;
  2256 +}
  2257 +
  2258 +
  2259 +/***********************************************************************//**
  2260 + * @brief Compute mean and root mean square of migration array
  2261 + *
  2262 + * @param[in] array Energy dispersion array.
  2263 + * @param[out] mean Pointer to mean migration value.
  2264 + * @param[out] rms Pointer to root mean square value.
  2265 + *
  2266 + * Computes the mean and the root mean square of the migration array. If the
  2267 + * migration array is empty the mean and root mean square values are set to
  2268 + * zero.
  2269 + *
  2270 + * The method does not check whether the @p mean and @p rms pointers are
  2271 + * valid.
  2272 + ***************************************************************************/
  2273 +void GCTAEdisp2D::get_mean_rms(const GNdarray& array,
  2274 + double* mean,
  2275 + double* rms) const
  2276 +{
  2277 + // Initialise mean and rms
  2278 + *mean = 0.0;
  2279 + *rms = 0.0;
  2280 +
  2281 + // Get reference to migration values
  2282 + const GNodeArray& migras = m_edisp.axis_nodes(m_inx_migra);
  2283 + int nmigra = migras.size();
  2284 +
  2285 + // Pre-compute values for mean and rms computation
  2286 + double sum = 0.0;
  2287 + for (int imigra = 0; imigra < nmigra; ++imigra) {
  2288 + double migra = migras[imigra];
  2289 + double weight = migra * array(imigra);
  2290 + *mean += migra * array(imigra);
  2291 + *rms += weight * migra;
  2292 + sum += array(imigra);
  2293 + }
  2294 +
  2295 + // If the array is not empty then compute now mean and standard deviation
  2296 + if (sum > 0.0) {
  2297 + *mean /= sum;
  2298 + *rms /= sum;
  2299 + *rms -= (*mean) * (*mean);
  2300 + *rms = std::sqrt(*rms);
  2301 + }
  2302 +
  2303 + // Return
  2304 + return;
  2305 +}
  2306 +
  2307 +
  2308 +/***********************************************************************//**
  2309 + * @brief Estimate the value of a single event
  2310 + *
  2311 + * @param[in] array Energy dispersion array.
  2312 + * @return Value of a single event.
  2313 + *
  2314 + * Returns the minimum non-zero value in an array as an estimate of the value
  2315 + * of a single event. If the array is empty the method returns zero.
  2316 + ***************************************************************************/
  2317 +double GCTAEdisp2D::get_single_event_value(const GNdarray& array) const
  2318 +{
  2319 + // Initialise minimum value
  2320 + double event_value = 1.0e30;
  2321 +
  2322 + // Get minimum non-zero value
  2323 + for (int i = 0; i < array.size(); ++i) {
  2324 + double value = array(i);
  2325 + if ((value > 0.0) && (value < event_value)) {
  2326 + event_value = value;
  2327 + }
  2328 + }
  2329 +
  2330 + // If no minimum value was encountered then set single event value to
  2331 + // zero
  2332 + if (event_value == 1.0e30) {
  2333 + event_value = 0.0;
  2334 + }
  2335 +
  2336 + // Return
  2337 + return event_value;
  2338 +}
1623 2339  
1624 2340  
1625 2341 /***********************************************************************//**
... ...