Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Priors/Hessian method corrections #901

Merged
merged 28 commits into from
Jul 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
9c437cb
RDP/accumulate_Hessian_times_input
Jun 7, 2021
edabac0
Fix issue with logic
robbietuk Jun 7, 2021
af28f24
Make prior test method `test_gradient`
robbietuk Jun 8, 2021
06def6b
Adds test_Hessian method
robbietuk Jun 8, 2021
4f68880
Test an array of different cases for the Hessian condition
robbietuk Jun 9, 2021
3ec12e5
improve Hessian documentation and info
robbietuk Jun 9, 2021
887d44e
Improve dot product
robbietuk Jun 11, 2021
79c02f9
Adds _is_convex variable to priors. default is false
robbietuk Jun 11, 2021
5b68532
Reimplementation add_multiplication_with_approximate_Hessian
robbietuk Jun 11, 2021
ddf6999
Correct math in QP accumulate_Hessian_times_input
robbietuk Jun 11, 2021
5185960
Improve QP comments
robbietuk Jun 11, 2021
661305f
[ci skip] Add two methods to RDP class :(off_)diagonal_second_derivative
robbietuk Jun 15, 2021
afcec9a
Prior Hessian methods use second partial derivative functions (#14)
Jun 15, 2021
279c566
compute_Hessian method to all priors and test improvements in general
Jun 19, 2021
e9168b2
[ci skip] Add documentation to `test_gradient`
robbietuk Jun 19, 2021
1b4c0fe
Increase RDP epsilon to be 0.1, increase so well above test `eps` value
robbietuk Jun 24, 2021
6ede608
Add variables and set methods for toggling tests
robbietuk Jun 24, 2021
bf7bbea
Cleanup and improve debugging checks
robbietuk Jun 24, 2021
03db044
Remove _is_convex variable and better implement is_convex() method
robbietuk Jul 19, 2021
d20fb96
Update src/include/stir/recon_buildblock/GeneralisedPrior.h
Jul 19, 2021
7511175
Update src/include/stir/recon_buildblock/LogcoshPrior.h
Jul 19, 2021
bb43294
[ci skip] Remove unnecessary documentation
robbietuk Jul 19, 2021
60a91b5
[ci skip] Rework and rename of second derivative methods for priors
robbietuk Jul 19, 2021
620ca75
All prior Hessian methods return void values
robbietuk Jul 20, 2021
6596d29
[ci skip] Simplify test prior configuration methods
robbietuk Jun 27, 2022
9163f6b
Clang-Tidy test_priors
robbietuk Jun 27, 2022
ac54288
Merge remote-tracking branch 'UCL/master' into RDP/HessianVectorProduct
robbietuk Jul 7, 2022
1d9fa65
[ci skip] Documentation fixes
robbietuk Jul 7, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion documentation/release_5.1.htm
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ <h2>Overall summary</h2>
improvements to the documentation.
</p>

<p>This release contains mainly code written by Nikos Eftimiou (UPenn and MGH), with support by Kris Thielemans (UCL)
<p>This release contains mainly code written by Nikos Eftimiou (UPenn and MGH), with support by Kris Thielemans (UCL).
Additional changes to penalty functions were made by Robert Twyman (UCL).
</p>

<h2>Patch release info</h2>
Expand All @@ -43,6 +44,8 @@ <h3>New functionality</h3>
</li>
<li>Support for PENNPET Explorer listmode data (if proprietary libraries are found) by Nikos Efthimiou, see <a href="https://github.com/UCL/STIR/pull/1028/">PR #1028</a>.
</li>
<li> Improvements to penalty functions including Hessian methods, see <a href="https://github.com/UCL/STIR/pull/901/">PR #901</a>.
</li>
</ul>


Expand All @@ -66,6 +69,11 @@ <h3>Documentation changes</h3>
<h3>recon_test_pack changes</h3>

<h3>Other changes to tests</h3>
<li> Significant changes made to <code>test_priors</code> that tests the Hessian's convexity, given
<code>x(Hx) > 0</code>, and a perturbation response, using gradients, was added to determine the Hessian
(for a single densel) is within tolerance.
Tests the Quadratic, Relative Difference (in two configurations) and Log-Cosh Penalties (Robert Twyman, UCL).
</li>

</body>

Expand Down
2 changes: 2 additions & 0 deletions src/include/stir/recon_buildblock/FilterRootPrior.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ class FilterRootPrior: public
FilterRootPrior(shared_ptr<DataProcessor<DataT> >const&,
const float penalization_factor);

bool is_convex() const;

//! compute the value of the function
/*! \warning Generally there is no function associated to this prior,
so we just return 0 and write a warning the first time it's called.
Expand Down
21 changes: 19 additions & 2 deletions src/include/stir/recon_buildblock/GeneralisedPrior.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,27 @@ class GeneralisedPrior:
virtual void compute_gradient(DataT& prior_gradient,
const DataT &current_estimate) =0;

//! This computes a single row of the Hessian
/*! Default implementation just call error(). This function needs to be overridden by the
derived class.

The method computes a row (i.e. at a densel/voxel, indicated by \c coords) of the Hessian at \c current_estimate.
Note that a row corresponds to an object of `DataT`.
The method (as implemented in derived classes) should store the result in \c prior_Hessian_for_single_densel.
*/
virtual void
compute_Hessian(DataT& prior_Hessian_for_single_densel,
const BasicCoordinate<3,int>& coords,
const DataT& current_image_estimate) const;

//! This should compute the multiplication of the Hessian with a vector and add it to \a output
/*! Default implementation just call error(). This function needs to be overridden by the
derived class.
This method assumes that the hessian of the prior is 1 and hence the function quadratic.
Instead, accumulate_Hessian_times_input() should be used. This method remains for backwards comparability.
\warning The derived class should accumulate in \a output.
*/
virtual Succeeded
virtual void
add_multiplication_with_approximate_Hessian(DataT& output,
const DataT& input) const;

Expand All @@ -76,7 +89,7 @@ class GeneralisedPrior:
derived class.
\warning The derived class should accumulate in \a output.
*/
virtual Succeeded
virtual void
accumulate_Hessian_times_input(DataT& output,
const DataT& current_estimate,
const DataT& input) const;
Expand All @@ -89,6 +102,10 @@ class GeneralisedPrior:
virtual Succeeded
set_up(shared_ptr<const DataT> const& target_sptr);

//! Indicates if the prior is a smooth convex function
/*! If true, the prior is expected to have 0th, 1st and 2nd order behaviour implemented.*/
virtual bool is_convex() const = 0;

protected:
float penalisation_factor;
//! sets value for penalisation factor
Expand Down
40 changes: 23 additions & 17 deletions src/include/stir/recon_buildblock/LogcoshPrior.h
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,12 @@ class LogcoshPrior: public
//! Constructs it explicitly with scalar
LogcoshPrior(const bool only_2D, float penalization_factor, const float scalar);

virtual bool
parabolic_surrogate_curvature_depends_on_argument() const
{ return false; }

bool is_convex() const;

//! compute the value of the function
double
compute_value(const DiscretisedDensity<3,elemT> &current_image_estimate);
Expand All @@ -120,13 +126,13 @@ class LogcoshPrior: public
void parabolic_surrogate_curvature(DiscretisedDensity<3,elemT>& parabolic_surrogate_curvature,
const DiscretisedDensity<3,elemT> &current_image_estimate);

//! compute Hessian
void compute_Hessian(DiscretisedDensity<3,elemT>& prior_Hessian_for_single_densel,
const BasicCoordinate<3,int>& coords,
const DiscretisedDensity<3,elemT> &current_image_estimate);
virtual void
compute_Hessian(DiscretisedDensity<3,elemT>& prior_Hessian_for_single_densel,
const BasicCoordinate<3,int>& coords,
const DiscretisedDensity<3,elemT> &current_image_estimate) const;

//! Compute the multiplication of the hessian of the prior multiplied by the input.
virtual Succeeded accumulate_Hessian_times_input(DiscretisedDensity<3,elemT>& output,
//! Compute the multiplication of the hessian of the prior (at \c current_estimate) and the \c input.
virtual void accumulate_Hessian_times_input(DiscretisedDensity<3,elemT>& output,
const DiscretisedDensity<3,elemT>& current_estimate,
const DiscretisedDensity<3,elemT>& input) const;

Expand Down Expand Up @@ -220,21 +226,21 @@ class LogcoshPrior: public
{ return tanh(x)/x; }
}

//! The Hessian of log(cosh()) is sech^2(x) = (1/cosh(x))^2
//! The second partial derivatives of the LogCosh Prior
/*!
This function returns the hessian of the logcosh function
* @param d the difference between the ith and jth voxel.
* @param scalar is the logcosh scalar value controlling the priors transition between the quadratic and linear behaviour
* @return the second derivative of the log-cosh function
derivative_20 refers to the second derivative w.r.t. x_j only (i.e. diagonal elements of the Hessian)
derivative_11 refers to the second derivative w.r.t. x_j and x_k (i.e. off-diagonal elements of the Hessian)
* @param x_j is the target voxel.
* @param x_k is the voxel in the neighbourhood.
* @return the second order partial derivatives of the LogCosh Prior
*/
static inline float Hessian(const float d, const float scalar)
{
const float x = d * scalar;
return square((1/ cosh(x)));
}
//@{
elemT derivative_20(const elemT x_j, const elemT x_k) const;
elemT derivative_11(const elemT x_j, const elemT x_k) const;
//@}
};


END_NAMESPACE_STIR

#endif
#endif
2 changes: 2 additions & 0 deletions src/include/stir/recon_buildblock/PLSPrior.h
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ class PLSPrior: public
/*! \todo set the anatomical image to zero if not defined */
virtual Succeeded set_up(shared_ptr<const DiscretisedDensity<3,elemT> > const& target_sptr);

bool is_convex() const;

//! compute the value of the function
double
compute_value(const DiscretisedDensity<3,elemT> &current_image_estimate);
Expand Down
27 changes: 21 additions & 6 deletions src/include/stir/recon_buildblock/QuadraticPrior.h
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ class QuadraticPrior: public
parabolic_surrogate_curvature_depends_on_argument() const
{ return false; }

bool is_convex() const;

//! compute the value of the function
double
compute_value(const DiscretisedDensity<3,elemT> &current_image_estimate);
Expand All @@ -116,20 +118,20 @@ class QuadraticPrior: public
void parabolic_surrogate_curvature(DiscretisedDensity<3,elemT>& parabolic_surrogate_curvature,
const DiscretisedDensity<3,elemT> &current_image_estimate);

//! compute Hessian
void compute_Hessian(DiscretisedDensity<3,elemT>& prior_Hessian_for_single_densel,
const BasicCoordinate<3,int>& coords,
const DiscretisedDensity<3,elemT> &current_image_estimate);
virtual void
compute_Hessian(DiscretisedDensity<3,elemT>& prior_Hessian_for_single_densel,
const BasicCoordinate<3,int>& coords,
const DiscretisedDensity<3,elemT> &current_image_estimate) const;

//! Call accumulate_Hessian_times_input
virtual Succeeded
virtual void
add_multiplication_with_approximate_Hessian(DiscretisedDensity<3,elemT>& output,
const DiscretisedDensity<3,elemT>& input) const;

//! Compute the multiplication of the hessian of the prior multiplied by the input.
//! For the quadratic function, the hessian of the prior is 1.
//! Therefore this will return the weights multiplied by the input.
virtual Succeeded accumulate_Hessian_times_input(DiscretisedDensity<3,elemT>& output,
virtual void accumulate_Hessian_times_input(DiscretisedDensity<3,elemT>& output,
const DiscretisedDensity<3,elemT>& current_estimate,
const DiscretisedDensity<3,elemT>& input) const;

Expand Down Expand Up @@ -179,6 +181,19 @@ class QuadraticPrior: public
virtual bool post_processing();
private:
shared_ptr<const DiscretisedDensity<3,elemT> > kappa_ptr;

//! The second partial derivatives of the Quadratic Prior
/*!
derivative_20 refers to the second derivative w.r.t. x_j (i.e. diagonal elements of the Hessian)
derivative_11 refers to the second derivative w.r.t. x_j and x_k (i.e. off-diagonal elements of the Hessian)
* @param x_j is the target voxel.
* @param x_k is the voxel in the neighbourhood.
* @return the second order partial derivatives of the Quadratic Prior
*/
//@{
elemT derivative_20(const elemT x_j, const elemT x_k) const;
elemT derivative_11(const elemT x_j, const elemT x_k) const;
//@}
};


Expand Down
28 changes: 27 additions & 1 deletion src/include/stir/recon_buildblock/RelativeDifferencePrior.h
Original file line number Diff line number Diff line change
Expand Up @@ -121,11 +121,19 @@ class RelativeDifferencePrior: public
void compute_gradient(DiscretisedDensity<3,elemT>& prior_gradient,
const DiscretisedDensity<3,elemT> &current_image_estimate);

virtual void compute_Hessian(DiscretisedDensity<3,elemT>& prior_Hessian_for_single_densel,
const BasicCoordinate<3,int>& coords,
const DiscretisedDensity<3,elemT> &current_image_estimate) const;

virtual Succeeded
virtual void
add_multiplication_with_approximate_Hessian(DiscretisedDensity<3,elemT>& output,
const DiscretisedDensity<3,elemT>& input) const;

//! Compute the multiplication of the hessian of the prior multiplied by the input.
virtual void accumulate_Hessian_times_input(DiscretisedDensity<3,elemT>& output,
const DiscretisedDensity<3,elemT>& current_estimate,
const DiscretisedDensity<3,elemT>& input) const;

//! get the gamma value used in RDP
float get_gamma() const;
//! set the gamma value used in the RDP
Expand Down Expand Up @@ -156,6 +164,8 @@ class RelativeDifferencePrior: public
//! Has to be called before using this object
virtual Succeeded set_up(shared_ptr<DiscretisedDensity<3,elemT> > const& target_sptr);

bool is_convex() const;

protected:
//! Create variable gamma for Relative Difference Penalty
float gamma;
Expand Down Expand Up @@ -189,6 +199,22 @@ class RelativeDifferencePrior: public
virtual bool post_processing();
private:
shared_ptr<DiscretisedDensity<3,elemT> > kappa_ptr;

//! The second partial derivatives of the Relative Difference Prior
/*!
derivative_20 refers to the second derivative w.r.t. x_j only (i.e. diagonal elements of the Hessian)
derivative_11 refers to the second derivative w.r.t. x_j and x_k (i.e. off-diagonal elements of the Hessian)
See J. Nuyts, et al., 2002, Equation 7.
In the instance x_j, x_k and epsilon equal 0.0, these functions return 0.0 to prevent returning an undefined value
due to 0/0 computation. This is a "reasonable" solution to this issue.
* @param x_j is the target voxel.
* @param x_k is the voxel in the neighbourhood.
* @return the second order partial derivatives of the Relative Difference Prior
*/
//@{
elemT derivative_20(const elemT x_j, const elemT x_k) const;
elemT derivative_11(const elemT x_j, const elemT x_k) const;
//@}
};


Expand Down
6 changes: 6 additions & 0 deletions src/recon_buildblock/FilterRootPrior.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ FilterRootPrior(shared_ptr<DataProcessor<DataT> >const& filter_sptr, float penal
this->penalisation_factor = penalisation_factor_v;
}

template <typename DataT>
bool FilterRootPrior<DataT>::
is_convex() const
{
return false;
}

template < class T>
static inline int
Expand Down
9 changes: 2 additions & 7 deletions src/recon_buildblock/GeneralisedObjectiveFunction.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -263,10 +263,7 @@ add_multiplication_with_approximate_sub_Hessian(TargetT& output,
{
// TODO used boost:scoped_ptr
shared_ptr<TargetT> prior_output_sptr(output.get_empty_copy());
if (this->prior_sptr->add_multiplication_with_approximate_Hessian(*prior_output_sptr, output) ==
Succeeded::no)
return Succeeded::no;

this->prior_sptr->add_multiplication_with_approximate_Hessian(*prior_output_sptr, output);

// (*prior_output_sptr)/= num_subsets;
// output -= *prior_output_sptr;
Expand Down Expand Up @@ -383,9 +380,7 @@ accumulate_sub_Hessian_times_input(TargetT& output,
{
// TODO used boost:scoped_ptr
shared_ptr<TargetT> prior_output_sptr(output.get_empty_copy());
if (this->prior_sptr->accumulate_Hessian_times_input(*prior_output_sptr, current_image_estimate, output) ==
Succeeded::no)
return Succeeded::no;
this->prior_sptr->accumulate_Hessian_times_input(*prior_output_sptr, current_image_estimate, output);

typename TargetT::const_full_iterator prior_output_iter = prior_output_sptr->begin_all_const();
const typename TargetT::const_full_iterator end_prior_output_iter = prior_output_sptr->end_all_const();
Expand Down
19 changes: 15 additions & 4 deletions src/recon_buildblock/GeneralisedPrior.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -52,26 +52,37 @@ set_up(shared_ptr<const TargetT> const&)
}

template <typename TargetT>
Succeeded
void
GeneralisedPrior<TargetT>::
compute_Hessian(TargetT& output,
const BasicCoordinate<3,int>& coords,
const TargetT& current_image_estimate) const
{
if (this->is_convex())
error("GeneralisedPrior:\n compute_Hessian implementation is not overloaded by your convex prior.");
else
error("GeneralisedPrior:\n compute_Hessian is not implemented for this (non-convex) prior.");
}

template <typename TargetT>
void
GeneralisedPrior<TargetT>::
add_multiplication_with_approximate_Hessian(TargetT& output,
const TargetT& input) const
{
error("GeneralisedPrior:\n"
"add_multiplication_with_approximate_Hessian implementation is not overloaded by your prior.");
return Succeeded::no;
}

template <typename TargetT>
Succeeded
void
GeneralisedPrior<TargetT>::
accumulate_Hessian_times_input(TargetT& output,
const TargetT& current_estimate,
const TargetT& input) const
{
error("GeneralisedPrior:\n"
"accumulate_Hessian_times_input implementation is not overloaded by your prior.");
return Succeeded::no;
}

template <typename TargetT>
Expand Down
Loading