-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathDarkFieldAbsorptionRecon3D.java
202 lines (145 loc) · 6.07 KB
/
DarkFieldAbsorptionRecon3D.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
package edu.stanford.rsl.science.darkfield.FlorianDarkField;
import edu.stanford.rsl.conrad.data.numeric.Grid1D;
import edu.stanford.rsl.conrad.data.numeric.Grid2D;
import edu.stanford.rsl.conrad.data.numeric.InterpolationOperators;
import edu.stanford.rsl.conrad.data.numeric.NumericPointwiseOperators;
import edu.stanford.rsl.conrad.numerics.SimpleVector;
import edu.stanford.rsl.conrad.utils.Configuration;
import edu.stanford.rsl.tutorial.filters.RamLakKernel;
public class DarkFieldAbsorptionRecon3D extends DarkFieldTensorGeometry {
private DarkField3DTensorVolume reconAMP;
public DarkField3DTensorVolume getReconAMP() {
return reconAMP;
}
public void setReconAMP(DarkField3DTensorVolume reconAMP) {
this.reconAMP = reconAMP;
}
private DarkField3DTensorVolume myMask;
public DarkField3DTensorVolume getMyMask() {
return myMask;
}
public void setMyMask(DarkField3DTensorVolume myMask) {
this.myMask = myMask;
}
public DarkFieldAbsorptionRecon3D(Configuration config){
// Call super constructor of TensorGeometry
super(config,1);
}
/**
* Reconstructs the 3D Absorption Volume by a given Sinogram
* @param sinogram - DarkField3DSinogram class is used for convenience
* even though the sinogram is a standard absorption sinogram. But can be used for both
* @return - Reconstructed Absorption volume
*/
public DarkField3DTensorVolume reconstructAbsorptionVolume(DarkField3DSinogram sinogram){
// Create RamLak Kernel
RamLakKernel ramLak = new RamLakKernel(maxU_index,deltaU);
// Loop through all projections
for(int curTheta = 0; curTheta < maxTheta_index; curTheta++){
Grid2D projImage = sinogram.getSubGrid(curTheta);
for(int curV = 0; curV < maxV_index; curV++){
// Get Detector row
Grid1D detectorRow = projImage.getSubGrid(curV);
// Apply Ram Lak Filter
ramLak.applyToGrid(detectorRow);
projImage.setSubGrid(curV, detectorRow);
}
sinogram.setSubGrid(curTheta, projImage);
}
// Backprojected filtered sinogram
reconAMP = backProjectAbsorption(sinogram);
return reconAMP;
}
/**
* Creates a mask by binary thresholding of the reconstructed absorption volume
* @param th_lower - lower threshold
* @param th_higher - higher thresholds
* @return - Volume Mask with 1's and 0'2.
* Even though it's an Mask and could be represented as an Grid3D we use
* DarkField3DTensorVolume out of convenience
*/
public DarkField3DTensorVolume createMaskByBinaryThresholding(float th_lower, float th_higher){
if (reconAMP == null){
return null;
}
// Allocate an Absorption volume
// Caution: NumScatterVectors set to 1, as we reconstruct an Absorption Volume
myMask = new DarkField3DTensorVolume(imgSizeX, imgSizeY, imgSizeZ, 1, getSpacing(), getOrigin());
// Loop through all voxel elements
for(int x = 0; x < imgSizeX; x++){
for(int y = 0; y < imgSizeY; y++){
for(int z = 0; z < imgSizeZ; z++){
float val = reconAMP.getAtIndex(x, y, z, 0);
// If value is in bounds set Mask to 1
if(val > th_lower && val < th_higher){
myMask.setAtIndex(x, y, z, 0, 1);
} else{ // if value is out of bounds set Mask to 0
myMask.setAtIndex(x, y, z, 0, 0);
}
} // END z
} // END y
} // END x
return myMask;
}
/**
* Backprojects a sinogram into volume space
* A Parallel Beam is assumed. Also an easy trajectory without any deviations is assumed
* @param sino3D
* @return
*/
public DarkField3DTensorVolume backProjectAbsorption(DarkField3DSinogram sino3D) {
boolean debug = false;
// Create empty 3DDarkField Volume
DarkField3DTensorVolume grid = new DarkField3DTensorVolume(imgSizeX,imgSizeY,imgSizeZ,1,getSpacing(),getOrigin());
// Loop over all projection angles
for (int curTheta = 0; curTheta < maxTheta_index; curTheta++) {
// Calculate current projection angle
double theta = deltaTheta * curTheta;
double cosTheta = Math.cos(theta);
double sinTheta = Math.sin(theta);
if(debug){
// Debug Output: Uncomment if you need to debug the Backprojector
System.out.println("Cur BackProj: " +curTheta +"/" +maxTheta_index
+ " (" +((10000*curTheta/maxTheta_index)/100.0) +"% done.)");
}
// get detector grid
Grid2D detectorImageAtTheta = sino3D.getSubGrid(curTheta);
// Create direction Vector of the detector at given angle Theta
// Remember: Third coordinate is 0
SimpleVector dirU = calculateRotatedVector(cosTheta, sinTheta,0).getAbstractVector();
SimpleVector dirV = calculateRotatedVector(0,0,1f).getAbstractVector();
// Loop through complete volume to do pixel based backprojection
for (int x = 0; x < imgSizeX; x++) {
for (int y = 0; y < imgSizeY; y++) {
for (int z = 0; z < imgSizeZ; z++) {
// compute world coordinate of current pixel
double[] w = grid.indexToPhysical(x, y,z);
// Create current voxel element
SimpleVector voxel = new SimpleVector(w[0], w[1],w[2]);
// Calcualte detector coodinates
SimpleVector orthProj = calcDetectorCoordinates(voxel,dirU,dirV);
// Calculates the subpixel detector coordinate curU
double curU_index = calcU_index(orthProj.getElement(0));
// precalculate detector column
double curV_index = calcV_index(orthProj.getElement(1));
// check detector bounds, continue if out of borders
if ( maxU_index <= curU_index + 1
|| curU_index < 0
|| maxV_index < curV_index + 1
|| curV_index < 0
){
continue; // Do nothing if projected point does not lie on detector
}
// Calculate the interpolated darkField value of the current voxel point
// of the current projection image
float ampValue = InterpolationOperators.interpolateLinear(detectorImageAtTheta,curU_index,curV_index);
grid.addAtIndexDarkfield(x, y, z, 0, ampValue);
} // END LOOP Z
} // END LOOP Y
} // END LOOP X
} // END LOOP ANGLES
// TODO Normalization factors
NumericPointwiseOperators.divideBy(grid, (float) (maxTheta_index / Math.PI));
return grid;
}
}