summaryrefslogtreecommitdiffstats
path: root/Wrappers/Python/ccpi/astra/processors/FBP.py
blob: adf27eb3cfd3f34d985308899ff23df5d25e9464 (plain)
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
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
from ccpi.framework import DataProcessor, ImageGeometry, AcquisitionGeometry
from ccpi.astra.utils import convert_geometry_to_astra
import astra
import numpy as np
import warnings

class FBP(DataProcessor):
    
    '''Filtered Back Projection is a reconstructor
    
    Input: Volume Geometry
           Sinogram Geometry
           Filter_type
           Device = 'cpu' or 'gpu'. For 2D cases we have the option cpu/gpu 
                             For 3D cases we have the option of gpu
                             
    Cases:  1) Parallel 3D FBP:  using 2D FBP per slice ( CPU/GPU )
    
    
    Example:  FBP(ig, ag, 'ram-lak', device='cpu')
              FBP.set_input(sinogram)
              reconstruction = FBP.get_ouput()
              
    Filters: 'ram-lak', 'shepp-logan', 'cosine', 'hamming', 'hann', 'none', 'tukey', 
             'lanczos', 'triangular', 'gaussian', 'barlett-hann', 'blackman', 'nuttall', 
             'blackman-harris', 'blackman-nuttall', 'flat-top', 'kaiser', 'parzen', 
             'projection', 'sinogram', 'rprojection', 'rsinogram'.              
    
    
                         
    Output: ImageData                             

    
    '''
    
    def __init__(self, volume_geometry, 
                       sinogram_geometry, 
                       device = 'cpu', 
                       filter_type = 'ram-lak', 
                       **kwargs): 
        

        if sinogram_geometry.dimension == '3D':
            
            
            # For 3D FBP and parallel goemetry, we perform 2D FBP per slice
            # Therofore, we need vol_geom2D, porj_geom2D attributes to setup in the DataProcessor.
            # There are both CPU/GPU options for this case
            
            if sinogram_geometry.geom_type=='parallel':
                
                # By default, we select GPU device
                super(FBP, self).__init__(volume_geometry = volume_geometry, 
                                     sinogram_geometry = sinogram_geometry,
                                     device = 'gpu', proj_id = None,
                                     vol_geom2D = None, proj_geom2D = None,
                                     filter_type = filter_type)
                          
                # we need a 2D image and acquisition geometry
                ig2D = ImageGeometry(voxel_num_x = self.volume_geometry.voxel_num_x,
                                          voxel_num_y = self.volume_geometry.voxel_num_y,
                                          voxel_size_x = self.volume_geometry.voxel_size_x,
                                           voxel_size_y = self.volume_geometry.voxel_size_y)
            
                ag2D = AcquisitionGeometry(geom_type = self.sinogram_geometry.geom_type,
                                                dimension = '2D',
                                                angles = self.sinogram_geometry.angles,
                                                pixel_num_h=self.sinogram_geometry.pixel_num_h,
                                                pixel_size_h=self.sinogram_geometry.pixel_size_h)
                     
                # Convert to Astra Geometries                    
                self.vol_geom2D, self.proj_geom2D = convert_geometry_to_astra(ig2D,ag2D)                
                
                if self.device == 'gpu':
                    self.set_projector(astra.create_projector('cuda', self.proj_geom2D, self.vol_geom2D) ) 
                else:
                    self.set_projector(astra.create_projector('line', self.proj_geom2D, self.vol_geom2D) )                   
                
                
            elif sinogram_geometry.geom_type=='cone':
                 
                # For 3D cone we do not need  ASTRA proj_id
                super(FBP, self).__init__(volume_geometry = volume_geometry, 
                     sinogram_geometry = sinogram_geometry,
                     device = 'gpu',
                     filter_type = 'ram-lak')   
                
                #Raise an error if the user select a filter other than ram-lak
                if self.filter_type !='ram-lak':
                    raise NotImplementedError('Currently in astra, FDK has only ram-lak available')                   
        else:
            
            
            # Setup for 2D case
            super(FBP, self).__init__(volume_geometry = volume_geometry, 
                                      sinogram_geometry = sinogram_geometry,
                                      device = device, proj_id = None,
                                      filter_type = filter_type)
            
            # Raise an error if the user select a filter other than ram-lak, for 2D case
            if self.filter_type !='ram-lak' and self.device == 'cpu':
                raise NotImplementedError('Currently in astra, 2D FBP is using only the ram-lak filter, switch to gpu for other filters')
            
            if (self.sinogram_geometry.geom_type == 'cone' and self.device == 'gpu') and self.sinogram_geometry.channels>=1:
                warnings.warn("For FanFlat geometry there is a mismatch between CPU & GPU filtered back projection. Currently, only CPU case provides a reasonable result.") 
            
            self.set_ImageGeometry(volume_geometry)
            self.set_AcquisitionGeometry(sinogram_geometry)
            
            # Set up ASTRA Volume and projection geometry, not to be stored in self
            vol_geom, proj_geom = convert_geometry_to_astra(self.volume_geometry,
                                                            self.sinogram_geometry)            
                
            # Here we define proj_id that will be used for ASTRA
            # ASTRA projector, to be stored
            if self.device == 'cpu':
                
                # Note that 'line' only one option            
                if self.sinogram_geometry.geom_type == 'parallel':
                    self.set_projector(astra.create_projector('line', proj_geom, vol_geom) )
                    
                elif self.sinogram_geometry.geom_type == 'cone':
                    self.set_projector(astra.create_projector('line_fanflat', proj_geom, vol_geom) )
                    
                else:
                    NotImplemented 
                    
            elif self.device == 'gpu':
                            
                self.set_projector(astra.create_projector('cuda', proj_geom, vol_geom) )
                
            else:
                NotImplemented            
            
                                                            
                                  
    def check_input(self, dataset):
        
        if self.sinogram_geometry.dimension == '2D':
            if dataset.number_of_dimensions == 2 or dataset.geometry.channels>1:
                return True
            else:
                raise ValueError("Expected input dimensions is 2, got {0}"\
                 .format(dataset.number_of_dimensions))  
                
        elif self.sinogram_geometry.dimension=='3D':  
            if dataset.number_of_dimensions == 3 or dataset.geometry.channels>1:
                return True
            else:
                raise ValueError("Expected input dimensions is 3, got {0}"\
                 .format(dataset.number_of_dimensions))  
        
    def set_projector(self, proj_id):
        self.proj_id = proj_id
        
    def set_ImageGeometry(self, volume_geometry):
        self.volume_geometry = volume_geometry
        
    def set_AcquisitionGeometry(self, sinogram_geometry):
        self.sinogram_geometry = sinogram_geometry
    
    def set_filter(self, filter_type):
        self.filter_type = filter_type
        
    def process(self):
           
        # Get DATA
        DATA = self.get_input()
        
        # Allocate FBP reconstruction
        IM = self.volume_geometry.allocate()
        
        # Set up ASTRA Volume and projection geometry, not to be stored in self
        vol_geom, proj_geom = convert_geometry_to_astra(self.volume_geometry,
                                                        self.sinogram_geometry)
        # Get number of channels
        num_chan = DATA.geometry.channels
        
        #######################################################################
        #######################################################################
        #######################################################################
        
        # Run FBP for one channel data
        if  num_chan == 1:
            
            if self.sinogram_geometry.dimension == '2D':         
                
                # Create a data object for the reconstruction and to hold sinogram for 2D
                rec_id = astra.data2d.create( '-vol', vol_geom)
                sinogram_id = astra.data2d.create('-sino', proj_geom, DATA.as_array())
                
                # ASTRA configuration for reconstruction algorithm
                if self.device == 'cpu':
                    cfg = astra.astra_dict('FBP')
                    cfg['ProjectorId'] = self.proj_id
                else:
                    cfg = astra.astra_dict('FBP_CUDA')
                cfg['ReconstructionDataId'] = rec_id
                cfg['ProjectionDataId'] = sinogram_id    
                cfg['FilterType'] = self.filter_type
                alg_id = astra.algorithm.create(cfg)
                astra.algorithm.run(alg_id)
                
                # Get the result
                IM.array = astra.data2d.get(rec_id)
                astra.data2d.delete(rec_id)
                astra.data2d.delete(sinogram_id)
                astra.algorithm.delete(alg_id)
                 
                if self.sinogram_geometry.geom_type == 'parallel':                                       
                    if self.device == 'cpu':
                        return IM / (self.volume_geometry.voxel_size_x**2)
                    else:
                        scaling = self.volume_geometry.voxel_size_x
                        return scaling * IM       
                else:
                    if self.device == 'cpu':
                        return IM / (self.volume_geometry.voxel_size_x**2)
                    else:
                        return IM
                    
                            
                
            elif self.sinogram_geometry.dimension == '3D':
                
                # Here we use 2D geometries to run FBP for parallel geometry for each slice
                if self.sinogram_geometry.geom_type == 'parallel':
                                        
                    rec_id = astra.data2d.create( '-vol', self.vol_geom2D)
                    
                    for i in range(DATA.shape[0]):
                                                                                
                        sinogram_id = astra.data2d.create('-sino', self.proj_geom2D, DATA.as_array()[i])
                        
                        cfg = astra.astra_dict('FBP_CUDA')                        
                        cfg['ReconstructionDataId'] = rec_id
                        cfg['ProjectionDataId'] = sinogram_id
                        cfg['FilterType'] = self.filter_type
                        alg_id = astra.algorithm.create(cfg)
                        astra.algorithm.run(alg_id)
                        
                        # since we run gpu we need to rescale and flip it
                        IM.array[i] = np.flip(astra.data2d.get(rec_id) *  self.volume_geometry.voxel_size_x,0)  
                    
                        astra.algorithm.delete(alg_id)
                        astra.data2d.delete(sinogram_id)                     
                    
                    astra.data2d.delete(rec_id)
                    
                    return IM                        
                        
                elif self.sinogram_geometry.geom_type == 'cone':
                    
                    rec_id = astra.data3d.create('-vol', vol_geom)
                    sinogram_id = astra.data3d.create('-sino', proj_geom, DATA.as_array())
                    
                    cfg = astra.astra_dict('FDK_CUDA')
                    cfg['ReconstructionDataId'] = rec_id
                    cfg['ProjectionDataId'] = sinogram_id
                    alg_id = astra.algorithm.create(cfg)
                    astra.algorithm.run(alg_id)            
                    IM.array = astra.data3d.get(rec_id)
                    astra.data3d.delete(rec_id)
                    astra.data3d.delete(sinogram_id)                    
                    astra.algorithm.delete(alg_id)
                    return  IM/(self.volume_geometry.voxel_size_x**4)
        
        #######################################################################
        #######################################################################
        #######################################################################
        
        # Here we preform FBP for each channel for the 2D/3D cases
        else:

            if self.sinogram_geometry.dimension == '2D':
                                                             
                if self.device == 'cpu':
                    cfg = astra.astra_dict('FBP')
                    cfg['ProjectorId'] = self.proj_id
                else:
                    cfg = astra.astra_dict('FBP_CUDA')
                cfg['FilterType'] = self.filter_type
                
                for i in range(num_chan):
                     
                    sinogram_id = astra.data2d.create('-sino', proj_geom, DATA.as_array()[i])
                    rec_id = astra.data2d.create( '-vol', vol_geom)
                    
                    cfg['ReconstructionDataId'] = rec_id
                    cfg['ProjectionDataId'] = sinogram_id    
                    
                    alg_id = astra.algorithm.create(cfg)
                    astra.algorithm.run(alg_id)
                                                      
                    # Get the result
                    IM.array[i] =  astra.data2d.get(rec_id)
                    
                    
                    if self.device == 'cpu':
                        IM.array[i] /= (self.volume_geometry.voxel_size_x**2)
                    else:
                        IM.array[i] *= self.volume_geometry.voxel_size_x 
                return IM        
               
            elif self.sinogram_geometry.dimension == '3D':
                
                if self.sinogram_geometry.geom_type == 'parallel':
                    
                    cfg = astra.astra_dict('FBP_CUDA')
                    cfg['FilterType'] = self.filter_type                    
                    
                    for i in range(num_chan):
                        
                        for j in range(DATA.shape[1]):
                                                        
                            sinogram_id = astra.data2d.create('-sino', self.proj_geom2D, DATA.as_array()[i,j])
                            rec_id = astra.data2d.create( '-vol', self.vol_geom2D)
                            
                            cfg['ReconstructionDataId'] = rec_id
                            cfg['ProjectionDataId'] = sinogram_id    
                    
                            alg_id = astra.algorithm.create(cfg)
                            astra.algorithm.run(alg_id)
                                                      
                            # Get the result
                            IM.array[i,j] =  np.flip(astra.data2d.get(rec_id) *  self.volume_geometry.voxel_size_x,0)
                            
                            astra.algorithm.delete(alg_id)
                            astra.data2d.delete(rec_id)
                            astra.data2d.delete(sinogram_id)   
                            
                    return IM
                    
                elif self.sinogram_geometry.geom_type == 'cone':
                    
                    for i in range(num_chan):
                        
                        rec_id = astra.data3d.create('-vol', vol_geom)
                        sinogram_id = astra.data3d.create('-sino', proj_geom, DATA.as_array()[i])
                        
                        cfg = astra.astra_dict('FDK_CUDA')
                        cfg['ReconstructionDataId'] = rec_id
                        cfg['ProjectionDataId'] = sinogram_id
                        alg_id = astra.algorithm.create(cfg)
                        astra.algorithm.run(alg_id)            
                        IM.array[i] = astra.data3d.get(rec_id) /  self.volume_geometry.voxel_size_x ** 4
                        
                    astra.data3d.delete(rec_id)
                    astra.data3d.delete(sinogram_id)                    
                    astra.algorithm.delete(alg_id)
                        
                    return IM