-
Notifications
You must be signed in to change notification settings - Fork 29
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
Computing Hessian products with image data objects and related stuff #1253
Conversation
typedef xSTIR_PoissonLLhLinModMeanListDataProjMatBin3DF LMObjFun; | ||
|
||
extern "C" | ||
void* cSTIR_objFunListModeSetInterval(void* ptr_f, size_t ptr_data) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not really for a question for this PR, but why size_t ptr_data
and not void * ptr_data
? On some systems, they are not the same size, and this could therefore create trouble.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I vaguely remember having some SWIG trouble with void*
but no longer remember what it was. Perhaps with the latest SWIG void*
is now ok.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BTW SWIG actually still does not like passing the numpy array data pointer as void*
- tried with cSTIR_getImageData
, got this:
Traceback (most recent call last):
File "/home/sirfuser/devel/buildVM/sources/SIRF/examples/Python/PET/acquisition_data.py", line 172, in <module>
main()
File "/home/sirfuser/devel/buildVM/sources/SIRF/examples/Python/PET/acquisition_data.py", line 141, in main
image_array = image.as_array()
File "/home/sirfuser/devel/install/python/sirf/STIR.py", line 611, in as_array
try_calling(pystir.cSTIR_getImageData(self.handle, array.ctypes.data))
File "/home/sirfuser/devel/install/python/sirf/pystir.py", line 306, in cSTIR_getImageData
return _pystir.cSTIR_getImageData(ptr, ptr_data)
TypeError: in method 'cSTIR_getImageData', argument 2 of type 'void *'
sirfuser@vagrant:~/devel/buildVM/sources/SIRF/examples/Python/PET$
same story with float*
:
Traceback (most recent call last):
File "/home/sirfuser/devel/buildVM/sources/SIRF/examples/Python/PET/acquisition_data.py", line 172, in <module>
main()
File "/home/sirfuser/devel/buildVM/sources/SIRF/examples/Python/PET/acquisition_data.py", line 141, in main
image_array = image.as_array()
File "/home/sirfuser/devel/install/python/sirf/STIR.py", line 611, in as_array
try_calling(pystir.cSTIR_getImageData(self.handle, array.ctypes.data))
File "/home/sirfuser/devel/install/python/sirf/pystir.py", line 306, in cSTIR_getImageData
return _pystir.cSTIR_getImageData(ptr, ptr_data)
TypeError: in method 'cSTIR_getImageData', argument 2 of type 'float *'
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmmm. weird. Here's an example that does that https://stackoverflow.com/a/37308401. Anyway, let's leave that for later!
src/xSTIR/cSTIR/cstir.cpp
Outdated
fun.accumulate_sub_Hessian_times_input(output, curr_est, input, subset); | ||
else { | ||
int nsub = fun.get_num_subsets(); | ||
output.fill(0.0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if we're accumulating in SIRF (which I don't think we should), then this fill
is wrong. On the other hand, if we're not accumulating, then you'll need it also in the case subset>=0
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
moved the fill above if
for now - will get rid of it when we switch to multiply_with_Hessian
Thanks, this already looks usable (with a caveat for the "accumulation"). Is this in a state that @gschramm can already try? |
We still have #1251, which is due to the fact that |
@KrisThielemans @evgueni-ovtchinnikov Just implemented a short test script for the PoissonLogL current_estimate = ref_recon.copy()
input_img = acq_data.create_uniform_image(value=1, xy=nxny)
np.random.seed(0)
input_img.fill(np.random.rand(*input_img.shape) * (obj_fun.get_subset_sensitivity(0).as_array() > 0) * current_estimate.max())
hess_out_img = acq_data.create_uniform_image(value=1.0, xy=nxny)
obj_fun.accumulate_Hessian_times_input(current_estimate, input_img, subset=0, out=hess_out_img)
hess_out_img2 = obj_fun.accumulate_Hessian_times_input(current_estimate, input_img, subset=0)
fig, ax = plt.subplots(1, 4, figsize=(16, 4), tight_layout=True)
ax[0].imshow(current_estimate.as_array()[71, :, :], cmap = 'Greys')
ax[1].imshow(input_img.as_array()[71, :, :], cmap = 'Greys')
ax[2].imshow(hess_out_img.as_array()[71, :, :], cmap = 'Greys')
ax[3].imshow(hess_out_img2.as_array()[71, :, :], cmap = 'Greys')
ax[0].set_title('current estimate', fontsize = 'medium')
ax[1].set_title('input', fontsize = 'medium')
ax[2].set_title('hess_out_img', fontsize = 'medium')
ax[3].set_title('hess_out_img2', fontsize = 'medium')
fig.show() Unfortunately, the output of both calls (without and with using the out kwarg), don't seem to work. These results used: |
This is a test with protection data, correct? |
Also use auto more
Indeed. The
|
@gschramm could you try again? |
Could work for listmode as well |
@KrisThielemans I just compared against manually computing the "Hessian multiply" calling the fwd / back projections in SIRF. The result is very close in the foreground (where the current estimate is >> 0), but not super close in the background (where the current estimate is close to 0). Any ideas why that is? hess_out_img = obj_fun.accumulate_Hessian_times_input(current_estimate, input_img, subset=0)
# %%
# calcuate the the Hessian multiply "manually"
acq_model.set_up(acq_data, initial_image)
acq_model.num_subsets = num_subsets
acq_model.subset_num = 0
# get the linear (Ax) part of the Ax + b affine acq. model
lin_acq_model = acq_model.get_linear_acquisition_model()
lin_acq_model.num_subsets = num_subsets
lin_acq_model.subset_num = 0
# for the Hessian "multiply" we need the linear part of the acquisition model applied to the input image
input_img_fwd = lin_acq_model.forward(input_img)
current_estimate_fwd = acq_model.forward(current_estimate)
h = -acq_model.backward(acq_data*input_img_fwd / (current_estimate_fwd*current_estimate_fwd)) The Hessian outputs are shown using a "narrow" color window (top) and a wide color window (bottom) to show the differences in foreground and background. |
hmmm. We try to cancel singularities in the division Of course, ideally there wouldn't be any singularities. Is your background strictly positive? |
that's encouraging. can you try your manual with some handling of 0/0 or so? A bit surprising it didn't generate NaNs then I guess. Easiest is to add an eps to the denominator. I'll flip the sign of the LM Hessian. 😊 |
ah well, this looks like a STIR issue then for the Hessian with projection data. Best to open an issue there. |
@evgueni-ovtchinnikov can you then add |
@gschramm what computation times are you getting? |
|
Using 21 subsets, mMR LM file with 4,332,816 counts and
|
you can use 6.0.0. Just add an extra member (that calls |
Thanks @gschramm would you mind adding the timings in the STIR PR? Ideally also add gradient timings. I'd be interested as well in seeing what it does if |
@KrisThielemans @evgueni-ovtchinnikov is |
@evgueni-ovtchinnikov is working on implementing #1244 (comment). Hopefully this will be done soon. @evgueni-ovtchinnikov if you have trouble, please ask for help. |
@evgueni-ovtchinnikov here's the STIR test I talked about. https://github.com/UCL/STIR/blob/90cff236be35628f447ded4d98a046d5c4e3b316/src/include/stir/recon_buildblock/test/ObjectiveFunctionTests.h#L169 |
see osem_reconstruction.py for a simple regression test for the Hessian multiplication
@evgueni-ovtchinnikov you seem to be pretty close. Can you summarise current situation? |
@KrisThielemans @gschramm I believe this PR can be merged - any objections? |
builds failed at Coveralls step - any idea anyone why all of a sudden? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think there are problems with the fill(0)
. It should NOT be done for the accumulate
function, but has to be done for the multiply
version.
Please batch commits of suggestions (I think via the Files tab), or do them manually of course.
Also, can you add the test_Hessian
to one of the existing test scripts such that it will always run?
Also, please comment out the Coveralls lines |
@evgueni-ovtchinnikov I'm modifying it and putting it in test_ObjectiveFunction.py def test_Hessian(self, subset=-1, eps=1e-3):
"""Checks that grad(x + dx) - grad(x) is close to H(x)*dx
"""
x = self.image
dx = x.clone()
dx *= eps/dx.norm()
dx += eps/2
y = x + dx
gx = self.obj_fun.gradient(x, subset)
gy = self.obj_fun.gradient(y, subset)
dg = gy - gx
Hdx = self.obj_fun.multiply_with_Hessian(x, dx, subset)
norm = dg.norm()
q = (dg - Hdx).norm()/dg.norm()
print('norm of grad(x + dx) - grad(x): %f' % dg.norm())
print('norm of H(x)*dx: %f' % Hdx.norm())
print('relative difference: %f' % q)
numpy.testing.assert_array_equal(q,0) However, I had to add a constant term to the acq_model as otherwise the test fails (essentially because the Hessian is ill-defined otherwise). I'll commit this soon. |
@evgueni-ovtchinnikov I'm now adding a similar test to the priors. (Had to delete a |
what result? |
got distracted... Sorry, I did push the test for the LogLikelihood. Works for me. |
ignore that. The loop in the test is a bit hard to understand... Update coming soon. |
also remove test_Hessian from the objective function
ok. Should be all done now. Can you add a line to CHANGES.md and merge? |
Changes in this pull request
Testing performed
Related issues
Fixes #1244, #1249
Checklist before requesting a review
Contribution Notes
Please read and adhere to the contribution guidelines.
Please tick the following: