LCOV - code coverage report
Current view: top level - elsa/operators - WeightingFunction.cpp (source / functions) Hit Total Coverage
Test: coverage-all.lcov Lines: 125 210 59.5 %
Date: 2025-01-02 06:42:49 Functions: 8 16 50.0 %

          Line data    Source code
       1             : #include "DataContainer.h"
       2             : #include "IdenticalBlocksDescriptor.h"
       3             : #include "Math.hpp"
       4             : #include "SphericalCoefficientsDescriptor.h"
       5             : #include "TypeCasts.hpp"
       6             : #include "elsaDefines.h"
       7             : #include "SphericalHarmonicsTransform.h"
       8             : #include "XGIDetectorDescriptor.h"
       9             : 
      10             : namespace elsa::axdt
      11             : {
      12             : 
      13             :     namespace detail
      14             :     {
      15             : 
      16             :         template <typename data_t>
      17             :         inline std::array<data_t, 15> exactCoeffsMalecki(const DirVec<data_t> ray,
      18             :                                                          const DirVec<data_t> sensitivity)
      19         719 :         {
      20         719 :             using math::sq;
      21             : 
      22         719 :             const data_t lx = ray[0], ly = ray[1], lz = ray[2];
      23         719 :             const data_t tx = sensitivity[0], ty = sensitivity[1], tz = sensitivity[2];
      24             : 
      25             :             // Thanks, Mathematica!
      26         719 :             data_t h_00 =
      27         719 :                 (4 * sqrt(pi<data_t>)
      28         719 :                  * (1 + sq(ty) - 2 * lx * lz * tx * tz + sq(tz) - 2 * ly * ty * (lx * tx + lz * tz)
      29         719 :                     - sq(ly) * (-1 + 2 * sq(ty) + sq(tz)) - sq(lz) * (-1 + sq(ty) + 2 * sq(tz))))
      30         719 :                 / 15.;
      31             : 
      32         719 :             data_t h_2_2 = (4 * sqrt(pi<data_t> / 15.)
      33         719 :                             * (2 * tx * ((2 + sq(lz)) * ty - ly * lz * tz)
      34         719 :                                + lx * (-2 * lz * ty * tz + ly * (-3 + 2 * sq(tz)))))
      35         719 :                            / 7.;
      36             : 
      37         719 :             data_t h_2_1 = (4 * sqrt(pi<data_t> / 15.)
      38         719 :                             * (2 * sq(ly) * ty * tz + 2 * ty * (lx * lz * tx + (-3 + sq(lz)) * tz)
      39         719 :                                + ly * (2 * lx * tx * tz + lz * (1 + 2 * sq(ty) + 2 * sq(tz)))))
      40         719 :                            / 7.;
      41             : 
      42         719 :             data_t h_20 =
      43         719 :                 (2 * sqrt(pi<data_t> / 5.)
      44         719 :                  * (-1 - 4 * sq(ly) - 7 * sq(lz) + 8 * lx * ly * tx * ty
      45         719 :                     + 4 * (-1 + 2 * sq(ly) + sq(lz)) * sq(ty) - 4 * lz * (lx * tx + ly * ty) * tz
      46         719 :                     + 2 * (7 + 2 * sq(ly) - 2 * sq(lz)) * sq(tz)))
      47         719 :                 / 21.;
      48             : 
      49         719 :             data_t h_21 = (-4 * sqrt(pi<data_t> / 15.)
      50         719 :                            * (2 * tx * (-(ly * lz * ty) + (2 + sq(ly)) * tz)
      51         719 :                               + lx * (lz * (-3 + 2 * sq(ty)) - 2 * ly * ty * tz)))
      52         719 :                           / 7.;
      53             : 
      54         719 :             data_t h_22 =
      55         719 :                 (-2 * sqrt(pi<data_t> / 15.)
      56         719 :                  * (-1 + 8 * sq(ty) + 4 * lx * lz * tx * tz - 4 * ly * lz * ty * tz + 2 * sq(tz)
      57         719 :                     + sq(ly) * (-6 + 4 * sq(tz)) + sq(lz) * (-5 + 4 * sq(ty) + 4 * sq(tz))))
      58         719 :                 / 7.;
      59             : 
      60         719 :             data_t h_4_4 =
      61         719 :                 (4 * sqrt(pi<data_t> / 35.)
      62         719 :                  * ((-1 + 2 * sq(ly) + sq(lz)) * tx * ty + lx * ly * (-1 + 2 * sq(ty) + sq(tz))))
      63         719 :                 / 3.;
      64             : 
      65         719 :             data_t h_4_3 = (-2 * sqrt((2 * pi<data_t>) / 35.)
      66         719 :                             * (2 * sq(ly) * ty * tz + ty * (-2 * lx * lz * tx + (-1 + sq(lz)) * tz)
      67         719 :                                + ly * (-2 * lx * tx * tz + lz * (-1 + 2 * sq(ty) + sq(tz)))))
      68         719 :                            / 3.;
      69             : 
      70         719 :             data_t h_4_2 = (-4 * sqrt(pi<data_t> / 5.)
      71         719 :                             * (tx * ((-1 + 3 * sq(lz)) * ty + 4 * ly * lz * tz)
      72         719 :                                + lx * (4 * lz * ty * tz + ly * (-1 + 3 * sq(tz)))))
      73         719 :                            / 21.;
      74             : 
      75         719 :             data_t h_4_1 =
      76         719 :                 (-2 * sqrt((2 * pi<data_t>) / 5.)
      77         719 :                  * (2 * sq(ly) * ty * tz + ty * (2 * lx * lz * tx + (1 - 5 * sq(lz)) * tz)
      78         719 :                     + ly * (2 * lx * tx * tz + lz * (1 + 2 * sq(ty) - 5 * sq(tz)))))
      79         719 :                 / 21.;
      80             : 
      81         719 :             data_t h_40 =
      82         719 :                 (2 * sqrt(pi<data_t>)
      83         719 :                  * (-3 + 2 * sq(ly) + 7 * sq(lz) - 4 * lx * ly * tx * ty
      84         719 :                     - 2 * (-1 + 2 * sq(ly) + sq(lz)) * sq(ty) + 16 * lz * (lx * tx + ly * ty) * tz
      85         719 :                     + (7 - 2 * sq(ly) - 19 * sq(lz)) * sq(tz)))
      86         719 :                 / 105.;
      87             : 
      88         719 :             data_t h_41 = (2 * sqrt((2 * pi<data_t>) / 5.)
      89         719 :                            * (tx * (-2 * ly * lz * ty + (-3 + 2 * sq(ly) + 7 * sq(lz)) * tz)
      90         719 :                               + lx * (-2 * ly * ty * tz + lz * (-3 + 2 * sq(ty) + 7 * sq(tz)))))
      91         719 :                           / 21.;
      92             : 
      93         719 :             data_t h_42 =
      94         719 :                 (4 * sqrt(pi<data_t> / 5.)
      95         719 :                  * (1 - sq(ty) - 4 * lx * lz * tx * tz + 4 * ly * lz * ty * tz - 2 * sq(tz)
      96         719 :                     + sq(ly) * (-1 + 3 * sq(tz)) + sq(lz) * (-2 + 3 * sq(ty) + 3 * sq(tz))))
      97         719 :                 / 21.;
      98             : 
      99         719 :             data_t h_43 = (-2 * sqrt((2 * pi<data_t>) / 35.)
     100         719 :                            * (tx * (2 * ly * lz * ty + (-1 + 2 * sq(ly) + sq(lz)) * tz)
     101         719 :                               + lx * (2 * ly * ty * tz + lz * (-1 + 2 * sq(ty) + sq(tz)))))
     102         719 :                           / 3.;
     103             : 
     104         719 :             data_t h_44 = (-2 * sqrt(pi<data_t> / 35.)
     105         719 :                            * (-4 * lx * ly * tx * ty + 2 * sq(ly) * (-1 + 2 * sq(ty) + sq(tz))
     106         719 :                               + (-1 + sq(lz)) * (-1 + 2 * sq(ty) + sq(tz))))
     107         719 :                           / 3.;
     108             : 
     109         719 :             return {h_00,  h_2_2, h_2_1, h_20, h_21, h_22, h_4_4, h_4_3,
     110         719 :                     h_4_2, h_4_1, h_40,  h_41, h_42, h_43, h_44};
     111         719 :         }
     112             :     } // namespace detail
     113             : 
     114             :     template <typename data_t>
     115             :     DataContainer<data_t> exactWeightingFunction(const XGIDetectorDescriptor& detectorDescriptor,
     116             :                                                  const Symmetry symmetry, const index_t degree)
     117          36 :     {
     118             : 
     119          36 :         const index_t poses = detectorDescriptor.getNumberOfCoefficientsPerDimension()[2];
     120             : 
     121             :         // setup complete block descriptor
     122          36 :         SphericalCoefficientsDescriptor resultDescriptor{detectorDescriptor, symmetry, degree};
     123             : 
     124             :         // setup factors block
     125             :         // Result has shape (detX×detY)×#poses×#coeffs
     126             :         // Set to zero as odd weights are zero and appear in Symmetry::regular
     127          36 :         auto weights = zeros<data_t>(resultDescriptor);
     128             : 
     129          36 :         if (detectorDescriptor.isParallelBeam()) {
     130             : 
     131          36 : #pragma omp parallel for
     132          36 :             for (index_t pose = 0; pose < poses; ++pose) {
     133             : 
     134             :                 // obtain geometry object for current image
     135           0 :                 const Geometry camera = detectorDescriptor.getGeometryAt(pose);
     136             : 
     137             :                 // beam direction: last column of rotation matrix (scaling is forced to be
     138             :                 // isotropic, z-direction/viewing axis)
     139           0 :                 const DirVec<data_t> l =
     140           0 :                     camera.getRotationMatrix().transpose().col(2).cast<data_t>();
     141             : 
     142             :                 // sensitivity direction: first column of rotation matrix (scaling is forced to
     143             :                 // be isotropic, x-direction/horizontal axis)
     144           0 :                 const DirVec<data_t> t =
     145           0 :                     (camera.getRotationMatrix().transpose() * detectorDescriptor.getSensDir())
     146           0 :                         .cast<data_t>();
     147             : 
     148           0 :                 const auto coeffs = detail::exactCoeffsMalecki(l, t);
     149             : 
     150         700 :                 if (symmetry == Symmetry::regular) {
     151             :                     // Truncation at l = 4 is exact
     152         700 :                     const auto lMax = std::min(degree, index_t{4});
     153         700 :                     auto j = 0;
     154        2114 :                     for (int l = 0; l <= lMax; l += 2) {
     155        6658 :                         for (int m = -l; m <= l; ++m) {
     156        5244 :                             weights.getBlock(l * l + l + m).slice(pose).fill(coeffs[j]);
     157        5244 :                             j++;
     158        5244 :                         }
     159        1414 :                     }
     160             : 
     161 >1844*10^16 :                 } else if (symmetry == Symmetry::even) {
     162          80 :                     const auto coeffCount = std::min(
     163          80 :                         SphericalCoefficientsDescriptor::coefficientCount(symmetry, degree),
     164          80 :                         index_t{15});
     165        1273 :                     for (auto j = 0; j < coeffCount; ++j) {
     166        1193 :                         weights.getBlock(j).slice(pose).fill(coeffs[j]);
     167        1193 :                     }
     168          80 :                 }
     169           0 :             }
     170          36 :         } else {
     171           0 :             const index_t detX = detectorDescriptor.getNumberOfCoefficientsPerDimension()[0];
     172           0 :             const index_t detY = detectorDescriptor.getNumberOfCoefficientsPerDimension()[1];
     173             : 
     174           0 : #pragma omp parallel for
     175           0 :             for (index_t pose = 0; pose < poses; ++pose) {
     176             :                 // set index to base for currently processed image and direction
     177           0 :                 index_t idx = pose * detX * detY;
     178             : 
     179             :                 // obtain geometry object for current image
     180           0 :                 const auto camera = detectorDescriptor.getGeometryAt(pose);
     181             : 
     182             :                 // sensitivity direction: first column of rotation matrix (scaling is
     183             :                 // forced to be isotropic, x-direction/horizontal axis)
     184           0 :                 const DirVec<data_t> t =
     185           0 :                     (camera.getRotationMatrix().transpose() * detectorDescriptor.getSensDir())
     186           0 :                         .cast<data_t>();
     187             : 
     188             :                 // traverse image
     189           0 :                 for (index_t y = 0; y < detY; ++y) {
     190           0 :                     for (index_t x = 0; x < detX; ++x) {
     191             :                         // sampling direction: set above, normalized by design
     192             :                         // beam direction: get ray as provided by projection matrix
     193             : 
     194           0 :                         const IndexVector_t coord{{x, y, pose}};
     195             : 
     196           0 :                         const auto ray = detectorDescriptor.computeRayFromDetectorCoord(coord);
     197             : 
     198           0 :                         const DirVec<data_t> l = ray.direction().cast<data_t>();
     199             : 
     200           0 :                         const auto coeffs = detail::exactCoeffsMalecki(l, t);
     201             : 
     202           0 :                         if (symmetry == Symmetry::regular) {
     203             :                             // Truncation at l = 4 is exact
     204           0 :                             auto lMax = std::min(degree, index_t{4});
     205           0 :                             auto j = 0;
     206           0 :                             for (int l = 0; l <= lMax; l += 2) {
     207           0 :                                 for (int m = -l; m <= l; ++m) {
     208             :                                     // As result has shape (detX×detY)×#poses×#coeffs, the j-th
     209             :                                     // block corresponds to h_j, which is the same for all
     210             :                                     // pixels in the detector at a given pose, hence we fill
     211             :                                     // slice(pose)
     212           0 :                                     weights.getBlock(l * l + l + m)[idx] = coeffs[j];
     213           0 :                                     j++;
     214           0 :                                 }
     215           0 :                             }
     216             : 
     217           0 :                         } else if (symmetry == Symmetry::even) {
     218           0 :                             const auto coeffCount = std::min(
     219           0 :                                 SphericalCoefficientsDescriptor::coefficientCount(symmetry, degree),
     220           0 :                                 index_t{15});
     221           0 :                             for (auto j = 0; j < coeffCount; ++j) {
     222           0 :                                 weights.getBlock(j)[idx] = coeffs[j];
     223           0 :                             }
     224           0 :                         }
     225           0 :                         idx++;
     226           0 :                     }
     227           0 :                 }
     228           0 :             }
     229           0 :         }
     230             : 
     231          36 :         return weights;
     232          36 :     }
     233             : 
     234             :     template <typename data_t>
     235             :     DataContainer<data_t> evaluateMalecki(const XGIDetectorDescriptor& detectorDescriptor,
     236             :                                           const DirVecList<data_t>& directions)
     237          28 :     {
     238          28 :         using namespace axdt;
     239             : 
     240             :         // obtain number of sampling directions, number of measured images
     241          28 :         const index_t numDirs = directions.size();
     242          28 :         const index_t poses = detectorDescriptor.getNumberOfCoefficientsPerDimension()[2];
     243             : 
     244             :         // set up the block descriptor
     245          28 :         auto directionSpace = IdenticalBlocksDescriptor{numDirs, detectorDescriptor};
     246             : 
     247             :         // Shape (detX×detY)×#poses×#dirs
     248          28 :         auto sampledWeightFunction = empty<data_t>(directionSpace);
     249             : 
     250          28 :         if (detectorDescriptor.isParallelBeam()) {
     251             : 
     252        1036 :             for (index_t i = 0; i < numDirs; ++i) {
     253             :                 // obtain sampling direction
     254        1008 :                 DirVec<data_t> e = directions[asUnsigned(i)];
     255        1008 :                 if (abs(e.norm() - 1) > 1e-5)
     256           0 :                     throw std::invalid_argument("direction vector at index " + std::to_string(i)
     257           0 :                                                 + " not normalized");
     258             : 
     259        1008 : #pragma omp parallel for
     260        1008 :                 for (index_t pose = 0; pose < poses; ++pose) {
     261             : 
     262             :                     // obtain geometry object for current image
     263           0 :                     const Geometry camera = detectorDescriptor.getGeometryAt(pose);
     264             : 
     265             :                     // allocate the grating's sensitivity vector
     266           0 :                     DirVec<data_t> t;
     267             : 
     268             :                     // allocate helper objects for ray computation
     269           0 :                     DirVec<data_t> l;
     270             : 
     271             :                     // sampling direction: set above, normalized by design
     272             : 
     273             :                     // beam direction: last column of rotation matrix (scaling is forced to be
     274             :                     // isotropic, z-direction/viewing axis)
     275             :                     // TODO: Why do we take the last row?
     276           0 :                     l = camera.getRotationMatrix().row(2).transpose().cast<data_t>();
     277             : 
     278             :                     // sensitivity direction: first column of rotation matrix (scaling is forced
     279             :                     // to be isotropic, x-direction/horizontal axis)
     280           0 :                     t = (camera.getRotationMatrix().transpose() * detectorDescriptor.getSensDir())
     281           0 :                             .cast<data_t>();
     282             : 
     283             :                     // compute the sensitivty function (|s × e|<e,t>)²
     284           0 :                     auto h = math::sq(l.cross(e).norm() * e.dot(t));
     285             : 
     286             :                     // h is the same for all pixels in the detector
     287             :                     // we can therefore fill the whole slice (corresponding to a slab of shape
     288             :                     // (detX×detY) with the same value
     289           0 :                     sampledWeightFunction.getBlock(i).slice(pose).fill(h);
     290           0 :                 }
     291        1008 :             }
     292          28 :         } else {
     293             : 
     294           0 :             const index_t px = detectorDescriptor.getNumberOfCoefficientsPerDimension()[0];
     295           0 :             const index_t py = detectorDescriptor.getNumberOfCoefficientsPerDimension()[1];
     296             : 
     297           0 :             for (index_t i = 0; i < numDirs; ++i) {
     298             :                 // obtain sampling direction
     299           0 :                 DirVec<data_t> e = directions[asUnsigned(i)];
     300             : 
     301           0 :                 if (abs(e.norm() - 1) > 1e-5)
     302           0 :                     throw std::invalid_argument("direction vector at index " + std::to_string(i)
     303           0 :                                                 + " not normalized");
     304             : 
     305           0 : #pragma omp parallel for
     306           0 :                 for (index_t pose = 0; pose < poses; ++pose) {
     307             :                     // set index to base for currently processed image and direction
     308           0 :                     index_t idx = pose * px * py;
     309             : 
     310             :                     // obtain geometry object for current image
     311           0 :                     auto camera = detectorDescriptor.getGeometryAt(pose);
     312             : 
     313             :                     // allocate the grating's sensitivity vector
     314           0 :                     DirVec<data_t> t;
     315             : 
     316             :                     // allocate helper objects for ray computation
     317           0 :                     DirVec<data_t> s;
     318           0 :                     IndexVector_t pt(3);
     319             : 
     320             :                     // traverse image
     321           0 :                     for (index_t y = 0; y < py; ++y) {
     322           0 :                         for (index_t x = 0; x < px; ++x) {
     323             :                             // sampling direction: set above, normalized by design
     324             :                             // beam direction: get ray as provided by projection matrix
     325           0 :                             pt << x, y, pose;
     326           0 :                             auto ray = detectorDescriptor.computeRayFromDetectorCoord(pt);
     327           0 :                             s = ray.direction().cast<data_t>();
     328             : 
     329             :                             // sensitivity direction: first column of rotation matrix (scaling
     330             :                             // is forced to be isotropic, x-direction/horizontal axis)
     331           0 :                             t = (camera.getRotationMatrix().transpose()
     332           0 :                                  * detectorDescriptor.getSensDir())
     333           0 :                                     .cast<data_t>();
     334             : 
     335             :                             // compute the factor: (|sxe|<e,t>)^2
     336             :                             // apply the factor (the location is x/y/n, but there is no need to
     337             :                             // compute that)
     338           0 :                             const auto h = math::sq(s.cross(e).norm() * e.dot(t));
     339             : 
     340           0 :                             sampledWeightFunction.getBlock(i)[idx++] = h;
     341           0 :                         }
     342           0 :                     }
     343           0 :                 }
     344           0 :             }
     345           0 :         }
     346             : 
     347          28 :         return sampledWeightFunction;
     348          28 :     }
     349             : 
     350             :     template <typename data_t>
     351             :     DataContainer<data_t>
     352             :         numericalWeightingFunction(const XGIDetectorDescriptor& detectorDescriptor,
     353             :                                    const Symmetry symmetry, const index_t degree,
     354             :                                    const DirVecList<data_t>& directions)
     355          28 :     {
     356             :         // Shape of sampled weighting function: (detX×detY)×#poses×#dirs
     357          28 :         auto sampledMalecki = evaluateMalecki(detectorDescriptor, directions);
     358             : 
     359             :         // Rescale in order as to obtain the correct normalization for the legacy tests
     360          28 :         sampledMalecki *= sqrt(4 * pi<data_t> / directions.size());
     361             : 
     362             :         // Result has shape (detX×detY)×#poses×#coeffs
     363          28 :         SphericalCoefficientsDescriptor coefficientDescriptor{detectorDescriptor, symmetry, degree};
     364             : 
     365          28 :         SphericalHarmonicsTransform<data_t> evaluate{coefficientDescriptor, directions};
     366             : 
     367          28 :         return evaluate.applyAdjoint(sampledMalecki);
     368          28 :     }
     369             : 
     370             :     template DataContainer<float> axdt::exactWeightingFunction(const XGIDetectorDescriptor&,
     371             :                                                                const Symmetry, const index_t);
     372             :     template DataContainer<double> axdt::exactWeightingFunction(const XGIDetectorDescriptor&,
     373             :                                                                 const Symmetry, const index_t);
     374             : 
     375             :     template DataContainer<float> axdt::numericalWeightingFunction(const XGIDetectorDescriptor&,
     376             :                                                                    const Symmetry, const index_t,
     377             :                                                                    const DirVecList<float>&);
     378             : 
     379             :     template DataContainer<double> axdt::numericalWeightingFunction(const XGIDetectorDescriptor&,
     380             :                                                                     const Symmetry, const index_t,
     381             :                                                                     const DirVecList<double>&);
     382             : 
     383             : } // namespace elsa::axdt

Generated by: LCOV version 1.14