LEAP icon indicating copy to clipboard operation
LEAP copied to clipboard

Help me: Offsetscan

Open ProjX0 opened this issue 1 year ago • 33 comments

Thank you for sharing the excellent toolkit.

I am currently interested in offset scan and am trying it with the data I have. However, I seem to have made a mistake, and the reconstruction isn’t working well.

Could you possibly help me if I share the geometry information and data with you?

Thank you very much.

ProjX0 avatar May 13 '24 07:05 ProjX0

Sure. Please also send a picture of one of the projections.

kylechampley avatar May 13 '24 14:05 kylechampley

I guess that your right_side offscan mechanism has some issue. I tested your "d23_offscan.py" which shows some strange reconed slice and like attached one. I just change the +100 offset by -100 as like centerCols = 0.5*(numCols-1)-100 and also change numCols by 512 too. Please, check it by changing the offset value by -100 and the numCols by 512. right_offscan

hws203 avatar May 13 '24 23:05 hws203

2024-05-14 09;33;24

For reference, the images should look like the one above after reconstruction. I am currently trying to achieve this using LEAP, but it appears that I am making some mistakes, as the images are not coming out as intended.

image

ProjX0 avatar May 14 '24 00:05 ProjX0

Thank you @hws203 for pointing out that bug. The cause for this was quite minor and was easy to fix. Currently the bug fix is in my development branch champley_dev. This branch will be merged into the main branch and a new LEAP release will come out later this week. You can either use this new code now or there is a workaround if you want to just work with the current version. The work-around is just add the following command after you set the reconstruction volume parameters: leapct.set_diameterFOV(leapct.get_voxelWidth()*leapct.get_numX()) The issue you noticed was caused by LEAP calculating the field of view incorrectly. Thus the above line forces LEAP to not restrict the field of view, so you can see the whole thing.

OK, now for @ProjX0 specific issue. Could you post your whole LEAP script? Above you have only shown a portion of it. One question I have is where the "shift_distance" value came from. I did not see this in the geometry file you posted. Also, why do you shift centerCol and centerRow by this same amount? I also don't understand the parameter names in that file like p0.x, p0.y, phi, etc. Can you explain what these mean?

kylechampley avatar May 14 '24 11:05 kylechampley

In our half-beam geometry, the centerline of the source is positioned 38.139 mm from the center of the detector.

p0.x, p0.y: coordinates of the center of the image on the detector. Phi (φ): rotation angle of the detector relative to the x-axis. Eta (η): rotation angle of the detector relative to the y-axis. Theta (θ): rotation angle of the gantry.

The parameters in the geometry.txt file are currently fixed for testing with SOD = 410 mm and SDD = 660 mm, and the other variables have not been used.

Here is the whole LEAP script: image image

ProjX0 avatar May 14 '24 13:05 ProjX0

Thanks for the code. I was able to make some improvements. I fixed some more small bugs in the code, so please use the code in the champley_dev branch until I merge in into the main branch this weekend. The main two things I changes in the processing were:

  1. Change the direction of the rotation. Instead of an angular range of 360.0, just put -360.0.

  2. Crop off the border of the projection images which contain bad pixels. With the binned data, I did it like this: g = leapct.crop_projections([2, leapct.get_numRows()-3], [2, leapct.get_numCols()-3], g)

With those changes, here is what I got. image

With those changes, it is still not a great result, but I noticed in the geometry file you posted that from projection-to-projection there seemed to be a lot of variation in the geometry. You'll need to use LEAP's "modular-beam" geometry and specify all those parameters. I did not know how to interpret all those parameters and information from some of the measured projections seemed to be missing from this file, so I couldn't try this myself.

kylechampley avatar May 15 '24 13:05 kylechampley

Oh, one more thing. Strictly speaking, this data cannot be properly reconstructed with the "offset scan" strategy because many of the projections are truncated on both sides. Offset scan implies that the projections are only truncated on one side. Yes, this is true for most of your projections, but there are enough projections that violate this, so you'll still get some artifacts. Regardless, I think the next thing you need to do is use the modular-beam geometry as I discussed in my last post.

kylechampley avatar May 15 '24 13:05 kylechampley

I first downloaded the code from the champley_dev branch, deleted the existing version, and reinstalled it.

I then made the changes as you suggested:

  1. Change the direction of the rotation. Instead of an angular range of 360.0, just put -360.0. leapct.set_conebeam(numAngles-start_index, numRows, numCols, pixelSize, pixelSize, centerRow, centerCol, leapct.setAngleArray(numAngles-start_index, -360.0), 410, 660)

However, I am still encountering problems. Is there something I missed? 2024-05-16 08;38;56

For reference, the whole LEAP script is as follows: image image

If this works, I will thoroughly examine the projection data and geometry information, and then attempt it using the modular-beam geometry. Thank you.

ProjX0 avatar May 15 '24 23:05 ProjX0

I apologize, but I entered the start_index incorrectly. With start_index = 47, the measured projection matches the information in geometry.txt. Can these parameters be applied to LEAP's "modular-beam" geometry?

ProjX0 avatar May 16 '24 05:05 ProjX0

Oh, I forgot. I had to modify the detector shift amounts. I used the following: shift_distance_col = -47.138 # mm shift_distance_row = -38.139 # mm

I am also concerned that you are still using an older version of LEAP. To check the LEAP version run the following command: leapct.about() It should say version 1.11

kylechampley avatar May 16 '24 13:05 kylechampley

The versions were different. Everything is working fine now that I matched the version to 1.11. Thank you very much. By the way, could you please explain how you estimated shift_distance_col and shift_distance_row?

ProjX0 avatar May 16 '24 14:05 ProjX0

Good question. If your data were NOT truncated, I would have just used this algorithm: find_centerCol

Unfortunately for offset scan data this algorithm does not work properly yet. For centerRow, I just looked at the projections and saw something that looked like a rotation stage at the bottom of the images. It looked like the projections were tangent to this flat surface, so I just flipped the sign of the value you gave me which approximately put this parameter in the right spot. Good image quality is achievable if this parameter is close to the true value, but it does not have to be perfect.

For centerCol, I estimated this parameter by performing several single-slice reconstructions with different values. Then I just choose the one that looks best. For this strategy, I just added a new algorithm in LEAP (again, in the champley_dev branch) to do this automatically. Here is how to run it: from preprocessing_algorithms import * centerCol = leapct.get_centerCol() centerCols = np.array(range(-5,5+1))+centerCol print(centerCols) f_stack = parameter_sweep(leapct, g, 'centerCol', centerCols) leapct.display(f_stack)

The preprocessing_algorithms.py file is in the utils directory.

kylechampley avatar May 16 '24 15:05 kylechampley

Oh, wait, I just realized that centerCol and centerRow can be calculated from your geometry file like this: centerCol = p0.x/pitch centerRow = numRows - 1 - p0.y/pitch

Once I used these parameters, I got even better results.

kylechampley avatar May 16 '24 15:05 kylechampley

thank you very much. It's working well now. However, I discovered one more unexpected result during my attempt.

In binning mode, when using leapct.set_default_volume(), the reconstruction works well. It also works fine when using leapct.set_volume(750, 750, 450, 0.2, 0.2).

However, when not using binning mode (using full data), the images are different when using leapct.set_default_volume() and leapct.set_volume(750, 750, 450, 0.2, 0.2). image

When using leapct.set_default_volume(), the image comes out well, but when using leapct.set_volume, it looks as if the FBP filter is not applied.

The whole code is as follows: image

ProjX0 avatar May 17 '24 05:05 ProjX0

I actually got a cudaMalloc error when I tried this. Turns out this was caused by LEAP trying to allocate data with a negative number of elements. I fixed this bug and pushed the changes.

OK, so the real issue here is that you need to do the following: leapct.set_volume(750, 750, 450, 0.2, 0.2, 0.0, 0.0, -47.274899)

Note that the z=0 plane is defined by the centerRow parameter. Since your centerRow parameter is far from numRows/2, your field of view is not centered around zero, it is centered around z=-47.274899, so you need to shift your volume to be centered in the field of view. One way to automate this is the following leapct.set_default_volume() leapct.set_volume(750, 750, 450, 0.2, 0.2, 0.0, 0.0, leapct.get_offsetZ())

And one more thing. To get rid of that bright ring around your reconstruction, add the following line to your script: leapct.set_truncatedScan(True)

kylechampley avatar May 17 '24 14:05 kylechampley

Thanks @ProjX0 and @hws203 for this discussion. It has helped me find and fix several bugs in LEAP, making this toolbox better. If you find LEAP useful, please consider giving it a "Star" by clicking the star icon on the LEAP main page. Stars increase the visibility of LEAP, which brings in more users, which increases the chance of people reporting bugs such as yourselves, which helps improve LEAP for myself and everyone else who uses it.

kylechampley avatar May 17 '24 16:05 kylechampley

Of course. I clicked "Star" because it is such a useful toolkit.

You mentioned two methods regarding the offset Z as follows:

  1. leapct.set_volume(750, 750, 450, 0.2, 0.2, 0.0, 0.0, -47.274899)
  2. leapct.set_default_volume() leapct.set_volume(750, 750, 450, 0.2, 0.2, 0.0, 0.0, leapct.get_offsetZ())

I have entered these(1 or 2) into the code, but the output still appears blurry, as shown in the image below. image

I changed only the offset Z setting in the whole code that I shared just before, as you suggested.

P.S. The method to solve the truncation artifact, leapct.set_truncatedScan(True), works well. Thank you.

ProjX0 avatar May 19 '24 09:05 ProjX0

Are you using the latest code? I just fixed this bug about 48 hours ago. You can just pull from the main branch because I merged all changes to main. Also I recommend cropping the data like this:

if bin_mode == True: g = leapct.crop_projections([3,numRows-4], [3,numCols-4], g) else: g = leapct.crop_projections([6,numRows-7], [6,numCols-7], g)

kylechampley avatar May 19 '24 13:05 kylechampley

I have checked your v1.11 version about the offscan issue. And I could see that there is still a FOV limit issue at the case of left-side offscan with the modification of "centerCols = 0.5*(numCols-1)-100" .

As you recommend if I add the leapct.set_diameterFOV(leapct.get_voxelWidth()*leapct.get_numX()), then there is no such issue.

But I hope that it will work as like righ-side offscan example.

I added star mark at your main page too.

Best regards.

hws203 avatar May 19 '24 23:05 hws203

@kylechampley Yes, I am using the latest code. Just in case, I downloaded the main code again and reinstalled it. I also tried cropping the data as you suggested, but the issue is still occurring. (It only occurs when bin_mode is false)

The whole code is as follows:

image

ProjX0 avatar May 19 '24 23:05 ProjX0

Another issue is when using iterative reconstruction techniques in offset scans, the resulting image shows artifacts in the center as shown below. Is there a way to resolve this artifact?

image

ProjX0 avatar May 20 '24 06:05 ProjX0

Thanks for your patience. I found and fixed another bug that hopefully solves your issue. Apparently, I forgot to account for the extra memory used by texture memory and I was running out of memory. Now I account for this and the data is chunking into smaller pieces. The change just got pushed to the champley_dev branch; it should say version 1.12. Please check this out and let me know if the issue is resolved. By the way, what GPU are you using?

Now let's talk about iterative reconstruction. That ring appears there because you are not converged; you need more iterations. I can tell because your result looks blurry. If you start with an FBP reconstruction this ring artifact won't be so strong because it'll be closer to convergence.

Also notice that the ring is the boundary of the edge of your detector as projected into the volume. Inside the ring you have 360 degrees of projections and outside you have less than 360 degrees. One way to mitigate these artifacts quicker with iterative reconstruction is to use an algorithm that accounts for this variable range in angular coverage. SART, SIRT, and ASDPOCS due this naturally. If you want to use LS, WLS, RLS, or RWLS make sure you add this argument: preconditioner='SQS'

Finally, I would like to remark about other issues with iterative reconstruction. Iterative reconstruction can outperform FBP only when you have an accurate model. If your geometry specification is off by too much or you have a lot of beam hardening, scatter, etc., iterative reconstruction might actually give a poorer result due to overfitting. I would focus on getting all your geometry specifications into LEAP before doing iterative reconstruction. You will also have issues with iterative reconstruction because of truncation artifacts. To avoid these, make sure your reconstruction is reconstructing the whole object. This includes turning off the windowing like this: leapct.set_diameterFOV(2.0*leapct.get_voxelWidth()*leapct.get_numX())

Notice I added a 2.0 in the above calculation which will remove all the windowing.

Good luck!

kylechampley avatar May 20 '24 15:05 kylechampley

I have checked the version 1.12 which included in charmley-dev branch, and I can see the FOV-limit issue of off-scan has been cleared now. My GPU is RTX-3090TI with 24GB memory.

hws203 avatar May 20 '24 21:05 hws203

@kylechampley First issue I am using an RTX 4090 with a single GPU. In version 1.12, I tested the FBP reconstruction with "bin_mode = false", and the results are shown below.

image

The image blurring still occurred with the specific FOV settings, but when the FOV was set as on the right, the image blurring did not occur.

Second issue I tested the recommendations you gave previously for reducing ring artifacts and achieving better iterative reconstruction results. The main modifications and additions to the code are as follows.

bin_mode = True ... leapct.set_diameterFOV(2.0*leapct.get_voxelWidth()*leapct.get_numX())

f = leapct.FBP(g) filters = filterSequence(2e2) filters.append(TV(leapct, delta=0.01 / 100.0, p=1.0)) filters.append(histogramSparsity(leapct, mus=[0.0, 0.02], weight=0.00001)) leapct.RWLS(g, f, iteration_number, filters, preconditioner='SQS')

I used the RWLS reconstruction method and compared the number of iterations at 5, 10, and 50.

image

As you can see from the results, the ring artifact inside became more pronounced as the number of iterations increased. And when using other reconstruction methods (SART, ASD-POCS), the ring artifacts are even more pronounced compared to the RWLS method.

I think this seems to be a very normal tendency as you mentioned above. So, rather than increasing the number of iterations related to reconstruction, it might be necessary to use another process to reduce the ring artifacts.

ProjX0 avatar May 21 '24 04:05 ProjX0

@kylechampley. I found the almost same issue which named as "ramp-filter missing issue" by me. As you can see below image, after doFBP, the recon slice shows very blurred one as like there is no ramp-filer adapted during back-propagation. blurred_one

And I attached my test script too. For your side test, I shared my image files(https://drive.google.com/file/d/1KvSFzIEzhvaSmuOSkTGAsPNymWqwTm0I/view?usp=sharing). Please check this case. From Jeff.

hws203 avatar May 21 '24 05:05 hws203

script1 script2

hws203 avatar May 21 '24 05:05 hws203

I just cannot reproduce those blurry results that you guys are getting. I've tried both of your data sets on two different computers (Windows with 2 1080Ti GPUs and Linux with 2 4090 GPUs). All I can say is run the leapct.about() command and make sure it says version 1.12. I know you said you checked this, but I am at a loss as to the cause of those blurry results.

@hws203 I don't understand why you included the modular-beam stuff in that script. I skipped all of that. The reconstruction didn't look great, but it definitely didn't look blurry like what you posted. There is likely something wrong with the geometry specification. Anyway, here is what I got. image

@ProjX0 I would not include the histogram sparsity regularizer. This filter is only for severely ill-posed reconstructions like limited angle or few-view. In addition, the values you give it do not agree with the LAC values in your object, so it will likely give very strange results with the parameters you specified. If I were you, I would focus on getting all your geometry parameters correct before I started working on iterative reconstruction. From the looks of your geometry file, your system has a fair amount of variability from projection to projection.

Oh, and be careful when you call them "ring artifacts". I agree that the artifact is a ring, but "ring artifacts" is a specific term in CT that is caused by detector pixel-to-pixel gain variations that cause several ring artifacts in reconstructions.

kylechampley avatar May 21 '24 13:05 kylechampley

Thank you so much for the detailed and great feedback.

As you pointed out in https://github.com/LLNL/LEAP/issues/35#issuecomment-2115606743, when centerCol and centerRow are not the center, where should I make changes in the following modularbeam code?

T_phi = 2.0np.pi/float(numAngles) for n in range(numAngles): phi = nT_phi-0.5*np.pi

sourcePositions[n,0] = sod*np.cos(phi)
sourcePositions[n,1] = sod*np.sin(phi)

moduleCenters[n,0] = (sod-sdd)*np.cos(phi)
moduleCenters[n,1] = (sod-sdd)*np.sin(phi)

rowVectors[n,2] = 1.0

colVectors[n,0] = -np.sin(phi)
colVectors[n,1] = np.cos(phi)

leapct.set_modularbeam(numAngles, numRows, numCols, pixelSize, pixelSize, sourcePositions, moduleCenters, rowVectors, colVectors)

ProjX0 avatar May 21 '24 13:05 ProjX0

I recommend starting with a cone-beam specification, converting it to modular-beam, and then tweaking the geometry specification to match your geometry. Like this:

leapct.set_conebeam(...) leapct.convert_to_modularbeam() ...

See demo 25 for an example.

You can also utilize scipy for making your rotation matrices. For example: from scipy.spatial.transform import Rotation as R A = R.from_euler("xyz", [1.0, 2.0, 3.0], degrees=True).as_matrix()

kylechampley avatar May 21 '24 15:05 kylechampley

I found the key function for making this blurry result, that is the custom volume setting function which is 'leapct.set_volume()'. If I use leapct.set_default_volume() function, then there is no such blurry result. I can not understand why this happens with leapct.set_volume() function. Please explain this reason.

Below is the reconed slice with leapct.set_default_volume(). default_volume Below is my new script. script_02

hws203 avatar May 21 '24 21:05 hws203