Open kouximei opened 3 years ago
hi! check out the development branch. i just updated the constants to use torch.double
instead of the default torch.float
. the difference between pytorch_radon and scikit's radon should be minimal now. let me know if it works for you :)
Thanks a lot for your commit! It works very well for forward and back projection!
BTW, I compared pytorch_radon dev branch and scikit's radon's hann filtered backprojection. I'm using torch.double and the mean absolute difference between them is still around 1e-6. I checked through the codes and I'm not sure if this is still a precision loss any more. Did you implement the hann back projection in a different way from scikit?
P.S. That's not a big difference, but I'm just very curious why that happened, because everything seems to be using torch.double now.
Hm, might be related to ramp_filter
in filters.py. I am going to check this
ok, it is only partly related to ramp_filter
: the variable image_filter
should be torch.double
. but another quite large error comes from torch.irfft
. these are the errors to scikit's hann iradon for the different combinations of (r)fft and i(r)fft:
fourier | inverse fourier | error |
---|---|---|
fft | ifft | 4.2482147420681154e-11 |
rfft | ifft | 4.2482147117965245e-11 |
fft | irfft | 2.697666179063214e-06 |
rfft | irfft | 2.697666179063532e-06 |
i can't explain this behavior but i will use the combination of rfft/ifft and commit it shortly
I see! Thanks for locating this ifft problem, it totally slipped my mind... Maybe that's due to the the problem described here.https://github.com/pytorch/pytorch/issues/19111, but I'm not exactly sure. I guess we don't need to worry about that.
I notice another strange thing. Right now, in the test_radon.py, the phantom is first filled with zeros, and then the values of its center part are changed to 0.5. And it worked very well. However, instead of first filling it with zeros, if I first filled it with ones, or any values other than zero, and then changed the center part to 0.5, then the back projection's pixel at [0,0] alone differs a lot from the back projection from scikit (around 1e-3). The rest of the pixels differ very little from scikit (about 1e-14). I'm a beginner on the radon transform and don't quite understand the reason behind this. Do you know what's happening here? Many thanks!
Actually, when I replace the testing phantom with any other real CT images, I can observe the same phenomena: the single pixel at [0,0] has a large difference from scikit, the rest pixels are the same.
I've tested more phantoms on this problem: It seems that if I set the first two rows or the first two columns to zeros, then no matter how I changed the rest of the pixels of the phantom, the resulting back projections will be the same as scikit (difference within 1e-14). But I still don't figure out why is that...
I see! Thanks for locating this ifft problem, it totally slipped my mind... Maybe that's due to the the problem described here.pytorch/pytorch#19111, but I'm not exactly sure. I guess we don't need to worry about that.
I also thought so first, but the problem there was that he used onesided=True
and didn't specify the shape in irfft
. However, when using onesided=False
(as I did), ifft
and irfft
should have basically the same output (I got a difference of 1e-14 using torch.double
). So something else must go wrong.
then the back projection's pixel at [0,0] alone differs a lot from the back projection from scikit (around 1e-3). The rest of the pixels differ very little from scikit (about 1e-14).
Can you show me your code?
Here's my code. Basically all I did is changing the background of the phantom from zero to a non-zero number.
from pytorch_radon import Radon, IRadon import torch import skimage.transform import numpy as np import sys
np.set_printoptions(threshold=sys.maxsize, precision=20) torch.set_printoptions(precision=10) device = torch.device('cuda:4')
img = torch.ones(1, 1, 256, 256, dtype=torch.double) * 1 img[:, :, 120:130, 120:130] = 0.5 img = img.to(device).requiresgrad(True) img_np = np.float64(np.squeeze(img.detach().cpu().numpy()))
theta = torch.arange(180, dtype=torch.double).to(device).requiresgrad(True) theta_np = np.float64(theta.detach().cpu().numpy())
r = Radon(img.shape[2], theta, False, dtype=torch.double, device=device).to(device) sino = r(img) sino_np_torch = np.squeeze(sino.detach().cpu().numpy()) sino_np_scipy = skimage.transform.radon(img_np, theta=theta_np, circle=False)
ir = IRadon(img.shape[2], theta, False, dtype=torch.double, use_filter=None, device=device).to(device) reco = ir(sino) reco_np_torch = np.squeeze(reco.detach().cpu().numpy()) reco_np_scipy = skimage.transform.iradon(sino_np_scipy, theta=theta_np, filter=None, circle=False)
print('comparing sinogram') print('sino max difference ' + str(np.max(np.abs(sino_np_scipy - sino_np_torch)))) print('sino mean difference ' + str(np.mean(np.abs(sino_np_scipy - sino_np_torch)))) print()
print('comparing reconstruction') print('reco max difference except pixel at index [0, 0] ' + str(np.max(np.abs(reco_np_scipy - reco_np_torch).flatten('F')[1:]))) print('reco mean difference except pixel at index [0, 0] ' + str(np.mean(np.abs(reco_np_scipy - reco_np_torch).flatten('F')[1:]))) print('index of pixel with large error ' + str(np.argwhere(np.abs(reco_np_scipy - reco_np_torch) > 1e-12))) print('error of pixel at index [0, 0]: ' + str((reco_np_scipy - reco_np_torch)[0, 0]))
I think I found out where the errors come from. The problem is related to the grids that are created and used for the interpolation (both radon
and iradon
). The center of rotation in scikit's implementation is shape//2
, pytorch uses the actual center of the image (with subpixel accuracy). This means scikit produces a small error if the shape of the image is even. However, it is possible that pytorch_radon produces another error because of align_corners=True
in grid_sample
and affine_grid
. Setting it to True
doesn't make sense to me mathematically but it produces smaller reconstruction errors than setting it to False
, at the moment. I will check this again.
align_corners=True
is correct. For Radon
, there is actually no difference but in IRadon
, the grids that are created for grid_sample
assume the centers of the extreme pixels to be -1
and 1
, which is the definition of align_corners=True
.
I see! Thanks a lot! Could you please push the changes to the repo?
All the changes are now in the master branch :)
However, it is possible that pytorch_radon produces another error because of align_corners=True in grid_sample and affine_grid. Setting it to True doesn't make sense to me mathematically but it produces smaller reconstruction errors than setting it to False, at the moment. I will check this again.
align_corners=True is correct. For Radon, there is actually no difference but in IRadon, the grids that are created for grid_sample assume the centers of the extreme pixels to be -1 and 1, which is the definition of align_corners=True.
I'm actually a bit confused here. Because Pytorch_radon has been using align_corners=True since the beginning, right? So basically that the difference at pixel [0, 0] between torch and scikit is due to the different implementation of the center of rotation, and the scikit is inaccurate because it uses shape//2
instead of the actual center, am I understanding this correctly?
I think so, yes. I just noticed something else. In your code above, try to change theta
to not include 135
and the difference at [0, 0]
will be around 1e-13
. In fact, including 135
(or 135+k*180
) in theta
creates this big difference at [0, 0]
. Set theta = [135, 135]
(twice to avoid dividing by 0
) to get the error or set theta
to another value (except 135+k*180
) to get around 1e-13
. I still assume that this is somehow related to shape//2
in scikit.
Hi! Many thanks for this repo! It is very helpful! I'm wondering if there is any difference with scikit-image's radon? For instance, if I run the following code, I would get a small difference between pytorch_radon and scikit-image's radon (around 1e-6 ~ 1e-8) . Is this because these two radon functions are implemented in different ways? Or is this because there's precision loss in torch? If this difference is due to precision loss, do you know of any ways to fix that? Thank you!
Follow up: I just fixed this precision issue, just need to change two lines of the utils.py like this.