CASToR  3.0
Tomographic Reconstruction (PET/SPECT/CT)
iProjectorJoseph.cc
Go to the documentation of this file.
1 /*
2 This file is part of CASToR.
3 
4  CASToR is free software: you can redistribute it and/or modify it under the
5  terms of the GNU General Public License as published by the Free Software
6  Foundation, either version 3 of the License, or (at your option) any later
7  version.
8 
9  CASToR is distributed in the hope that it will be useful, but WITHOUT ANY
10  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11  FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
12  details.
13 
14  You should have received a copy of the GNU General Public License along with
15  CASToR (in file GNU_GPL.TXT). If not, see <http://www.gnu.org/licenses/>.
16 
17 Copyright 2017-2019 all CASToR contributors listed below:
18 
19  --> Didier BENOIT, Claude COMTAT, Marina FILIPOVIC, Thibaut MERLIN, Mael MILLARDET, Simon STUTE, Valentin VIELZEUF
20 
21 This is CASToR version 3.0.
22 */
23 
30 #include "iProjectorJoseph.hh"
31 #include "sOutputManager.hh"
32 
33 #include <cmath>
34 #ifdef _WIN32
35 // Avoid compilation errors due to mix up between std::min()/max() and
36 // min max macros
37 #undef min
38 #undef max
39 #endif
40 
41 // =====================================================================
42 // ---------------------------------------------------------------------
43 // ---------------------------------------------------------------------
44 // =====================================================================
45 
47 {
48  // This projector is not compatible with SPECT attenuation correction because
49  // the voxels contributing to the line are not strictly ordered with respect to
50  // their distance to point2 (due to interpolations at each plane crossed)
52  // This projector is compatible with compression as it works only with the
53  // cartesian coordinates of 2 end points of a line
55  // Default pointers and parameters
56  mp_boundariesX = nullptr;
57  mp_boundariesY = nullptr;
58  mp_boundariesZ = nullptr;
59  mp_maskPad = nullptr;
60  m_toleranceX = 0.;
61  m_toleranceY = 0.;
62  m_toleranceZ = 0.;
63 }
64 
65 // =====================================================================
66 // ---------------------------------------------------------------------
67 // ---------------------------------------------------------------------
68 // =====================================================================
69 
71 {
72  if ( mp_boundariesX )
73  {
74  delete[] mp_boundariesX;
75  mp_boundariesX = nullptr;
76  }
77 
78  if ( mp_boundariesY )
79  {
80  delete[] mp_boundariesY;
81  mp_boundariesY = nullptr;
82  }
83 
84  if ( mp_boundariesZ )
85  {
86  delete[] mp_boundariesZ;
87  mp_boundariesZ = nullptr;
88  }
89 
90  if ( mp_maskPad )
91  {
92  delete[] mp_maskPad;
93  mp_maskPad = nullptr;
94  }
95 }
96 
97 // =====================================================================
98 // ---------------------------------------------------------------------
99 // ---------------------------------------------------------------------
100 // =====================================================================
101 
102 int iProjectorJoseph::ReadConfigurationFile(const string& a_configurationFile)
103 {
104  // No options for joseph
105  ;
106  // Normal end
107  return 0;
108 }
109 
110 // =====================================================================
111 // ---------------------------------------------------------------------
112 // ---------------------------------------------------------------------
113 // =====================================================================
114 
115 int iProjectorJoseph::ReadOptionsList(const string& a_optionsList)
116 {
117  // No options for joseph
118  ;
119  // Normal end
120  return 0;
121 }
122 
123 // =====================================================================
124 // ---------------------------------------------------------------------
125 // ---------------------------------------------------------------------
126 // =====================================================================
127 
129 {
130  cout << "This projector is a line projector that uses linear interpolation between pixels." << endl;
131  cout << "It is implemented from the following published paper:" << endl;
132  cout << "P. M. Joseph, \"An improved algorithm for reprojecting rays through pixel images\", IEEE Trans. Med. Imaging, vol. 1, pp. 192-6, 1982." << endl;
133 }
134 
135 // =====================================================================
136 // ---------------------------------------------------------------------
137 // ---------------------------------------------------------------------
138 // =====================================================================
139 
141 {
142  // Nothing to check for this projector
143  ;
144  // Normal end
145  return 0;
146 }
147 
148 // =====================================================================
149 // ---------------------------------------------------------------------
150 // ---------------------------------------------------------------------
151 // =====================================================================
152 
154 {
155  // Verbose
156  if (m_verbose>=2) Cout("iProjectorJoseph::InitializeSpecific() -> Use Joseph projector" << endl);
157 
158  // Allocate and compute boundaries (grid through voxel centers)
159  mp_boundariesX = new HPFLTNB[ mp_nbVox[ 0 ] + 2 ];
160  for( INTNB i = 0; i < mp_nbVox[ 0 ] + 2; ++i ) mp_boundariesX[ i ] = -((HPFLTNB)(mp_halfFOV[ 0 ])) + ((HPFLTNB)(mp_sizeVox[ 0 ])) * ( ((HPFLTNB)i) - 0.5 );
161  mp_boundariesY = new HPFLTNB[ mp_nbVox[ 1 ] + 2 ];
162  for( INTNB i = 0; i < mp_nbVox[ 1 ] + 2; ++i ) mp_boundariesY[ i ] = -((HPFLTNB)(mp_halfFOV[ 1 ])) + ((HPFLTNB)(mp_sizeVox[ 1 ])) * ( ((HPFLTNB)i) - 0.5 );
163  mp_boundariesZ = new HPFLTNB[ mp_nbVox[ 2 ] + 2 ];
164  for( INTNB i = 0; i < mp_nbVox[ 2 ] + 2; ++i ) mp_boundariesZ[ i ] = -((HPFLTNB)(mp_halfFOV[ 2 ])) + ((HPFLTNB)(mp_sizeVox[ 2 ])) * ( ((HPFLTNB)i) - 0.5 );
165 
166  // Allocating the mask buffer for the padded image space
167  INTNB nElts = ( mp_nbVox[ 0 ] + 2 ) * ( mp_nbVox[ 1 ] + 2 ) * ( mp_nbVox[ 2 ] + 2 );
168  mp_maskPad = new uint8_t[ nElts ];
169  ::memset( mp_maskPad, 0, sizeof( uint8_t ) * nElts );
170  for( INTNB k = 1; k < mp_nbVox[ 2 ] + 1; ++k )
171  {
172  for( INTNB j = 1; j < mp_nbVox[ 1 ] + 1; ++j )
173  {
174  for( INTNB i = 1; i < mp_nbVox[ 0 ] + 1; ++i )
175  {
176  mp_maskPad[ i + j * ( ( mp_nbVox[ 0 ] + 2 ) ) + k * ( mp_nbVox[ 0 ] + 2 ) * ( mp_nbVox[ 1 ] + 2 ) ] = 1;
177  }
178  }
179  }
180 
181  // Set the tolerance with respect to voxel sizes in each dimensions
182  HPFLTNB tolerance_factor = 1.e-4;
183  m_toleranceX = ((HPFLTNB)(mp_sizeVox[0])) * tolerance_factor;
184  m_toleranceY = ((HPFLTNB)(mp_sizeVox[1])) * tolerance_factor;
185  m_toleranceZ = ((HPFLTNB)(mp_sizeVox[2])) * tolerance_factor;
186 
187  // Setting the bounds
188  m_boundX = (-mp_halfFOV[ 0 ]) - mp_sizeVox[ 0 ] * 0.5;
189  m_boundY = (-mp_halfFOV[ 1 ]) - mp_sizeVox[ 1 ] * 0.5;
190  m_boundZ = (-mp_halfFOV[ 2 ]) - mp_sizeVox[ 2 ] * 0.5;
191 
192  // Normal end
193  return 0;
194 }
195 
196 // =====================================================================
197 // ---------------------------------------------------------------------
198 // ---------------------------------------------------------------------
199 // =====================================================================
200 
202 {
203  // Find the maximum number of voxels along a given dimension
204  INTNB max_nb_voxels_in_dimension = mp_ImageDimensionsAndQuantification->GetNbVoxX();
205  if (mp_ImageDimensionsAndQuantification->GetNbVoxY()>max_nb_voxels_in_dimension) max_nb_voxels_in_dimension = mp_ImageDimensionsAndQuantification->GetNbVoxY();
206  if (mp_ImageDimensionsAndQuantification->GetNbVoxZ()>max_nb_voxels_in_dimension) max_nb_voxels_in_dimension = mp_ImageDimensionsAndQuantification->GetNbVoxZ();
207  // We should have at most 4 voxels in a given plane, so multiply by 4
208  max_nb_voxels_in_dimension *= 4;
209  // Return the value
210  return max_nb_voxels_in_dimension;
211 }
212 
213 // =====================================================================
214 // ---------------------------------------------------------------------
215 // ---------------------------------------------------------------------
216 // =====================================================================
217 
218 int iProjectorJoseph::ProjectWithoutTOF(int a_direction, oProjectionLine* ap_ProjectionLine )
219 {
220  #ifdef CASTOR_DEBUG
221  if (!m_initialized)
222  {
223  Cerr("***** iProjectorJoseph::ProjectWithoutTOF() -> Called while not initialized !" << endl);
224  Exit(EXIT_DEBUG);
225  }
226  #endif
227 
228  #ifdef CASTOR_VERBOSE
229  if (m_verbose>=10)
230  {
231  string direction = "";
232  if (a_direction==FORWARD) direction = "forward";
233  else direction = "backward";
234  Cout("iProjectorJoseph::Project without TOF -> Project line '" << ap_ProjectionLine << "' in " << direction << " direction" << endl);
235  }
236  #endif
237 
238  // Get event positions
239  FLTNB* event1 = ap_ProjectionLine->GetPosition1();
240  FLTNB* event2 = ap_ProjectionLine->GetPosition2();
241 
242  // Distance between point event1 and event2
243  HPFLTNB const r[ 3 ] = {
244  event2[ 0 ] - event1[ 0 ],
245  event2[ 1 ] - event1[ 1 ],
246  event2[ 2 ] - event1[ 2 ]
247  };
248 
249  // Square of r
250  HPFLTNB const r2[ 3 ] = {
251  r[ 0 ] * r[ 0 ],
252  r[ 1 ] * r[ 1 ],
253  r[ 2 ] * r[ 2 ]
254  };
255 
256  // Find the first and last intersecting plane in X using the parametric
257  // values alphaMin and alphaMax
258  HPFLTNB alphaMin = 0.0, alphaMax = 1.0;
259 
260  // Variables for Joseph
261  HPFLTNB pos[ 3 ] = { 0.0, 0.0, 0.0 }; // Position of point in image space
262  HPFLTNB wfl[ 2 ] = { 0.0, 0.0 }; // Weight floor
263  HPFLTNB wcl[ 2 ] = { 0.0, 0.0 }; // Weight ceil
264  HPFLTNB w[ 4 ] = { 0.0, 0.0, 0.0, 0.0 }; // Interpolation weight
265  int16_t index[ 2 ] = { 0, 0 }; // index in the image space
266  int32_t finalIdx = 0; // final index
267  int8_t limitX1 = 1; int8_t limitX2 = 1;
268  int8_t limitY1 = 1; int8_t limitY2 = 1;
269  int8_t limitZ1 = 1; int8_t limitZ2 = 1;
270 
271  // Condition on the largest component of r
272  if( ::fabs( r[ 0 ] ) > ::fabs( r[ 1 ] ) )
273  {
274  // Computing the parametric values
275  // For the X-axis
276  // We stay in the limit of the image space
277  HPFLTNB const alphaX_0 = ( -mp_halfFOV[ 0 ] - event1[ 0 ] ) / r[ 0 ];
278  HPFLTNB const alphaX_1 = ( mp_halfFOV[ 0 ] - event1[ 0 ] ) / r[ 0 ];
279  alphaMin = std::max( alphaMin, std::min( alphaX_0, alphaX_1 ) );
280  alphaMax = std::min( alphaMax, std::max( alphaX_0, alphaX_1 ) );
281 
282  // For the Y-axis
283  // We introduce 1 voxel size around Y-axis
284  // Make a shift on first and last plane (like an offset)
285  if( r[ 1 ] != 0.0 )
286  {
287  HPFLTNB const alphaY_0 = ( -mp_halfFOV[ 1 ] - mp_sizeVox[ 1 ] * 0.5 - event1[ 1 ] )
288  / r[ 1 ];
289  HPFLTNB const alphaY_1 = ( mp_halfFOV[ 1 ] + mp_sizeVox[ 1 ] * 0.5 - event1[ 1 ] )
290  / r[ 1 ];
291  alphaMin = std::max( alphaMin, std::min( alphaY_0, alphaY_1 ) );
292  alphaMax = std::min( alphaMax, std::max( alphaY_0, alphaY_1 ) );
293  }
294 
295  // For the Z-axis
296  // We introduce 1 voxel size around Z-axis
297  // Make a shift on first and last plane (like an offset)
298  if( r[ 2 ] != 0.0 )
299  {
300  HPFLTNB const alphaZ_0 = ( -mp_halfFOV[ 2 ] - mp_sizeVox[ 2 ] * 0.5 - event1[ 2 ] )
301  / r[ 2 ];
302  HPFLTNB const alphaZ_1 = ( mp_halfFOV[ 2 ] + mp_sizeVox[ 2 ] * 0.5 - event1[ 2 ] )
303  / r[ 2 ];
304  alphaMin = std::max( alphaMin, std::min( alphaZ_0, alphaZ_1 ) );
305  alphaMax = std::min( alphaMax, std::max( alphaZ_0, alphaZ_1 ) );
306  }
307 
308  if( alphaMax <= alphaMin ) return 0;
309 
310  if( r[ 1 ] == 0.0 ) if( event1[ 1 ] < -mp_halfFOV[ 1 ] || event1[ 1 ] > mp_halfFOV[ 1 ] ) return 0;
311  if( r[ 2 ] == 0.0 ) if( event1[ 2 ] < -mp_halfFOV[ 2 ] || event1[ 2 ] > mp_halfFOV[ 2 ] ) return 0;
312 
313  // Computing weight of normalization
314  HPFLTNB const factor( ::fabs( r[ 0 ] )
315  / ::sqrt( r2[ 0 ] + r2[ 1 ] + r2[ 2 ] ) );
316  HPFLTNB const weight( mp_sizeVox[ 0 ] / factor );
317 
318  // Computing the increment
319  HPFLTNB const ri[ 3 ] = {
320  1.0, // r[ 0 ] / r[ 0 ]
321  r[ 1 ] / r[ 0 ],
322  r[ 2 ] / r[ 0 ]
323  };
324 
325  // Computing the first and the last plane
326  int iMin = 0, iMax = 0;
327  if( r[ 0 ] > 0.0 )
328  {
329  iMin = ::ceil( mp_nbVox[ 0 ] - m_toleranceX - ( mp_halfFOV[ 0 ] - alphaMin * r[ 0 ] - event1[ 0 ] ) / mp_sizeVox[ 0 ] );
330  iMax = ::floor( m_toleranceX + ( event1[ 0 ] + alphaMax * r[ 0 ] - (-mp_halfFOV[ 0 ]) ) / mp_sizeVox[ 0 ] );
331  }
332  else if( r[ 0 ] < 0.0 )
333  {
334  iMin = ::ceil( mp_nbVox[ 0 ] - m_toleranceX - ( mp_halfFOV[ 0 ] - alphaMax * r[ 0 ] - event1[ 0 ] ) / mp_sizeVox[ 0 ] );
335  iMax = ::floor( m_toleranceX + ( event1[ 0 ] + alphaMin * r[ 0 ] - (-mp_halfFOV[ 0 ]) ) / mp_sizeVox[ 0 ] );
336  }
337 
338  // Computing an offset to get the correct position in plane
339  HPFLTNB const offset = -mp_halfFOV[ 0 ] + ( mp_sizeVox[ 0 ] * 0.5 ) - event1[ 0 ];
340 
341  // Loop over all the crossing planes
342  for( int i = iMin; i < iMax; ++i )
343  {
344  // Computing position on crossed plane in term of grid spacing
345  HPFLTNB const step = offset + i * mp_sizeVox[ 0 ];
346  pos[ 1 ] = event1[ 1 ] + step * ri[ 1 ];
347  pos[ 2 ] = event1[ 2 ] + step * ri[ 2 ];
348 
349  // Find the index in the image
350  index[ 0 ] = ::floor( m_toleranceY + ( pos[ 1 ] - m_boundY ) / mp_sizeVox[ 1 ] );
351  index[ 1 ] = ::floor( m_toleranceZ + ( pos[ 2 ] - m_boundZ ) / mp_sizeVox[ 2 ] );
352 
353  // Storing values and indices
354  finalIdx = i + ( index[ 0 ] - 1 ) * mp_nbVox[ 0 ] + ( index[ 1 ] - 1 ) * m_nbVoxXY;
355 
356  // Bilinear interpolation component (using floor)
357  wfl[ 0 ] = pos[ 1 ] - ( m_boundY + index[ 0 ] * mp_sizeVox[ 1 ] );
358  wfl[ 1 ] = pos[ 2 ] - ( m_boundZ + index[ 1 ] * mp_sizeVox[ 2 ] );
359  wfl[ 0 ] /= mp_sizeVox[ 1 ];
360  wfl[ 1 ] /= mp_sizeVox[ 2 ];
361 
362  // Bilinear interpolation component (using ceil)
363  wcl[ 0 ] = 1.0 - wfl[ 0 ];
364  wcl[ 1 ] = 1.0 - wfl[ 1 ];
365 
366  // Final coefficients
367  w[ 0 ] = wcl[ 0 ] * wcl[ 1 ];
368  w[ 1 ] = wcl[ 0 ] * wfl[ 1 ];
369  w[ 2 ] = wfl[ 0 ] * wfl[ 1 ];
370  w[ 3 ] = wfl[ 0 ] * wcl[ 1 ];
371 
372  // Check Y bounds
373  if( index[ 0 ] <= 0 ) limitY1 = 0;
374  if( index[ 0 ] >= mp_nbVox[ 1 ] ) limitY2 = 0;
375 
376  // Check Z bounds
377  if( index[ 1 ] <= 0 ) limitZ1 = 0;
378  if( index[ 1 ] >= mp_nbVox[ 2 ] ) limitZ2 = 0;
379 
380  if (!m_hasMask || (limitY1 && limitZ1 && index[ 0 ] <= mp_nbVox[ 1 ] && index[ 1 ] <= mp_nbVox[ 2 ] && mp_mask[finalIdx]))
381  {
382  ap_ProjectionLine->AddVoxel(a_direction, finalIdx * limitY1 * limitZ1, w[ 0 ] * weight * limitY1 * limitZ1);
383  ap_ProjectionLine->AddVoxel(a_direction, ( finalIdx + m_nbVoxXY ) * limitY1 * limitZ2, w[ 1 ] * weight * limitY1 * limitZ2);
384  ap_ProjectionLine->AddVoxel(a_direction, ( finalIdx + mp_nbVox[ 0 ] + m_nbVoxXY ) * limitY2 * limitZ2, w[ 2 ] * weight * limitY2 * limitZ2);
385  ap_ProjectionLine->AddVoxel(a_direction, ( finalIdx + mp_nbVox[ 0 ] ) * limitY2 * limitZ1, w[ 3 ] * weight * limitY2 * limitZ1);
386  }
387  limitY1 = 1; limitY2 = 1;
388  limitZ1 = 1; limitZ2 = 1;
389  }
390  }
391  else
392  {
393  // Computing the parametric values
394  // For the X-axis
395  // We introduce 1 voxel size around Y-axis
396  if( r[ 0 ] != 0.0 )
397  {
398  HPFLTNB const alphaX_0 = ( -mp_halfFOV[ 0 ] - mp_sizeVox[ 0 ] * 0.5 - event1[ 0 ] )
399  / r[ 0 ];
400  HPFLTNB const alphaX_1 = ( mp_halfFOV[ 0 ] + mp_sizeVox[ 0 ] * 0.5 - event1[ 0 ] )
401  / r[ 0 ];
402  alphaMin = std::max( alphaMin, std::min( alphaX_0, alphaX_1 ) );
403  alphaMax = std::min( alphaMax, std::max( alphaX_0, alphaX_1 ) );
404  }
405 
406  // For the Y-axis
407  // Make a shift on first and last plane (like an offset)
408  // We stay in the limit of the image space
409  HPFLTNB const alphaY_0 = ( -mp_halfFOV[ 1 ] - event1[ 1 ] ) / r[ 1 ];
410  HPFLTNB const alphaY_1 = ( mp_halfFOV[ 1 ] - event1[ 1 ] ) / r[ 1 ];
411  alphaMin = std::max( alphaMin, std::min( alphaY_0, alphaY_1 ) );
412  alphaMax = std::min( alphaMax, std::max( alphaY_0, alphaY_1 ) );
413 
414  // For the Z-axis
415  // We introduce 1 voxel size around Z-axis
416  // Make a shift on first and last plane (like an offset)
417  if( r[ 2 ] != 0.0 )
418  {
419  HPFLTNB const alphaZ_0 = ( -mp_halfFOV[ 2 ] - mp_sizeVox[ 2 ] * 0.5 - event1[ 2 ] )
420  / r[ 2 ];
421  HPFLTNB const alphaZ_1 = ( mp_halfFOV[ 2 ] + mp_sizeVox[ 2 ] * 0.5 - event1[ 2 ] )
422  / r[ 2 ];
423  alphaMin = std::max( alphaMin, std::min( alphaZ_0, alphaZ_1 ) );
424  alphaMax = std::min( alphaMax, std::max( alphaZ_0, alphaZ_1 ) );
425  }
426 
427  if( alphaMax <= alphaMin ) return 0;
428 
429  if( r[ 0 ] == 0.0 ) if( event1[ 0 ] < -mp_halfFOV[ 0 ] || event1[ 0 ] > mp_halfFOV[ 0 ] ) return 0;
430  if( r[ 2 ] == 0.0 ) if( event1[ 2 ] < -mp_halfFOV[ 2 ] || event1[ 2 ] > mp_halfFOV[ 2 ] ) return 0;
431 
432  // Computing weight of normalization
433  HPFLTNB const factor( ::fabs( r[ 1 ] ) / ::sqrt( r2[ 0 ] + r2[ 1 ] + r2[ 2 ] ) );
434  HPFLTNB const weight( mp_sizeVox[ 1 ] / factor );
435 
436  // Computing the increment
437  HPFLTNB const ri[ 3 ] = {
438  r[ 0 ] / r[ 1 ],
439  1.0, // r[ 1 ] / r[ 1 ]
440  r[ 2 ] / r[ 1 ]
441  };
442 
443  // Computing the first and the last plane
444  int jMin = 0, jMax = 0;
445  if( r[ 1 ] > 0.0 )
446  {
447  jMin = ::ceil( mp_nbVox[ 1 ] - m_toleranceY - ( mp_halfFOV[ 1 ] - alphaMin * r[ 1 ] - event1[ 1 ] ) / mp_sizeVox[ 1 ] );
448  jMax = ::floor( m_toleranceY + ( event1[ 1 ] + alphaMax * r[ 1 ] - (-mp_halfFOV[ 1 ]) ) / mp_sizeVox[ 1 ] );
449  }
450  else if( r[ 1 ] < 0.0 )
451  {
452  jMin = ::ceil( mp_nbVox[ 1 ] - m_toleranceY - ( mp_halfFOV[ 1 ] - alphaMax * r[ 1 ] - event1[ 1 ] ) / mp_sizeVox[ 1 ] );
453  jMax = ::floor( m_toleranceY + ( event1[ 1 ] + alphaMin * r[ 1 ] - (-mp_halfFOV[ 1 ]) ) / mp_sizeVox[ 1 ] );
454  }
455 
456  // Computing an offset to get the correct position in plane
457  HPFLTNB const offset = -mp_halfFOV[ 1 ] + ( mp_sizeVox[ 1 ] * 0.5 ) - event1[ 1 ];
458 
459  // Loop over all the crossing planes
460  for( int j = jMin; j < jMax; ++j )
461  {
462  // Computing position on crossed plane in term of grid spacing
463  HPFLTNB const step = offset + j * mp_sizeVox[ 1 ];
464  pos[ 0 ] = event1[ 0 ] + step * ri[ 0 ];
465  pos[ 2 ] = event1[ 2 ] + step * ri[ 2 ];
466 
467  // Find the index in the image
468  index[ 0 ] = ::floor( m_toleranceX + ( pos[ 0 ] - m_boundX ) / mp_sizeVox[ 0 ] );
469  index[ 1 ] = ::floor( m_toleranceZ + ( pos[ 2 ] - m_boundZ ) / mp_sizeVox[ 2 ] );
470 
471  // Storing values and indices
472  finalIdx = ( index[ 0 ] - 1 ) + j * mp_nbVox[ 0 ] + ( index[ 1 ] - 1 ) * m_nbVoxXY;
473 
474  // Bilinear interpolation component (using floor)
475  wfl[ 0 ] = pos[ 0 ] - ( m_boundX + index[ 0 ] * mp_sizeVox[ 0 ] );
476  wfl[ 1 ] = pos[ 2 ] - ( m_boundZ + index[ 1 ] * mp_sizeVox[ 2 ] );
477  wfl[ 0 ] /= mp_sizeVox[ 0 ];
478  wfl[ 1 ] /= mp_sizeVox[ 2 ];
479 
480  // Bilinear interpolation component (using ceil)
481  wcl[ 0 ] = 1.0 - wfl[ 0 ];
482  wcl[ 1 ] = 1.0 - wfl[ 1 ];
483 
484  // Final coefficients
485  w[ 0 ] = wcl[ 0 ] * wcl[ 1 ];
486  w[ 1 ] = wcl[ 0 ] * wfl[ 1 ];
487  w[ 2 ] = wfl[ 0 ] * wfl[ 1 ];
488  w[ 3 ] = wfl[ 0 ] * wcl[ 1 ];
489 
490  // Check X bounds
491  if( index[ 0 ] <= 0 ) limitX1 = 0;
492  if( index[ 0 ] >= mp_nbVox[ 0 ] ) limitX2 = 0;
493 
494  // Check Z bounds
495  if( index[ 1 ] <= 0 ) limitZ1 = 0;
496  if( index[ 1 ] >= mp_nbVox[ 2 ] ) limitZ2 = 0;
497 
498  if (!m_hasMask || (limitX1 && limitZ1 && index[ 0 ] <= mp_nbVox[ 0 ] && index[ 1 ] <= mp_nbVox[ 2 ] && mp_mask[finalIdx]))
499  {
500  ap_ProjectionLine->AddVoxel(a_direction, finalIdx * limitX1 * limitZ1, w[ 0 ] * weight * limitX1 * limitZ1);
501  ap_ProjectionLine->AddVoxel(a_direction, ( finalIdx + m_nbVoxXY ) * limitX1 * limitZ2, w[ 1 ] * weight * limitX1 * limitZ2);
502  ap_ProjectionLine->AddVoxel(a_direction, ( finalIdx + 1 + m_nbVoxXY ) * limitX2 * limitZ2, w[ 2 ] * weight * limitX2 * limitZ2);
503  ap_ProjectionLine->AddVoxel(a_direction, ( finalIdx + 1 ) * limitX2 * limitZ1, w[ 3 ] * weight * limitX2 * limitZ1);
504  }
505 
506  limitX1 = 1; limitX2 = 1;
507  limitZ1 = 1; limitZ2 = 1;
508  }
509  }
510 
511  return 0;
512 }
513 
514 // =====================================================================
515 // ---------------------------------------------------------------------
516 // ---------------------------------------------------------------------
517 // =====================================================================
518 
519 int iProjectorJoseph::ProjectTOFListmode(int a_direction, oProjectionLine* ap_ProjectionLine)
520 {
521 
522  #ifdef CASTOR_DEBUG
523  if (!m_initialized)
524  {
525  Cerr("***** iProjectorJoseph::ProjectTOFListmode() -> Called while not initialized !" << endl);
526  Exit(EXIT_DEBUG);
527  }
528  #endif
529 
530  #ifdef CASTOR_VERBOSE
531  if (m_verbose>=10)
532  {
533  string direction = "";
534  if (a_direction==FORWARD) direction = "forward";
535  else direction = "backward";
536  Cout("iProjectorJoseph::Project with TOF measurement -> Project line '" << ap_ProjectionLine << "' in " << direction << " direction" << endl);
537  }
538  #endif
539 
540  // Get event positions
541  FLTNB* event1 = ap_ProjectionLine->GetPosition1();
542  FLTNB* event2 = ap_ProjectionLine->GetPosition2();
543 
544  // Distance between point event1 and event2
545  HPFLTNB const r[ 3 ] = {
546  event2[ 0 ] - event1[ 0 ],
547  event2[ 1 ] - event1[ 1 ],
548  event2[ 2 ] - event1[ 2 ]
549  };
550 
551 /*
552  // Square of r
553  HPFLTNB const r2[ 3 ] = {
554  r[ 0 ] * r[ 0 ],
555  r[ 1 ] * r[ 1 ],
556  r[ 2 ] * r[ 2 ]
557  };
558 */
559 
560  // LOR length
561  HPFLTNB lor_length = ap_ProjectionLine->GetLength();
562 
563  // Get TOF info
564  FLTNB tof_measurement = ap_ProjectionLine->GetTOFMeasurementInPs();
565 
566  // convert delta time into delta length
567  HPFLTNB tof_delta = tof_measurement * SPEED_OF_LIGHT_IN_MM_PER_PS * 0.5;
568 
569  // TOF standard deviation and truncation
571  HPFLTNB tof_sigma_sqrt2 = sqrt(2.)*tof_sigma;
572  HPFLTNB tof_half_span = 0.;
573 
575  else if (m_TOFBinProperProcessingFlag) tof_half_span = tof_sigma * m_TOFNbSigmas + 0.5 * m_TOFBinSizeInMm;
576  else tof_half_span = tof_sigma * m_TOFNbSigmas;
577 
578  // distance between the first event1 and the TOF measurement
579  HPFLTNB lor_tof_center = lor_length * 0.5 + tof_delta;
580 
581  // coordinates of the lower edge of the first voxel falling inside the truncated TOF distribution centered at the TOF measurement
582  HPFLTNB tof_edge_low[] = {0,0,0};
583  // coordinate of the upper edge of the last voxel falling inside the truncated TOF distribution centered at the TOF measurement
584  HPFLTNB tof_edge_high[] = {0,0,0};
585  HPFLTNB tof_center;
586  INTNB tof_index;
587 
588  // low/high voxel edges (in absolute coordinates) for truncated TOF
589  for (int ax=0;ax<3;ax++)
590  {
591  // absolute coordinate along each axis of the center of the TOF distribution
592  tof_center = event1[ax] + lor_tof_center * r[ax] / lor_length;
593 
594  // absolute coordinate along each axis of the lowest voxel edge spanned by the TOF distribution, limited by the lowest FOV edge
595  tof_edge_low[ax] = tof_center - tof_half_span * fabs(r[ax]) / lor_length;
596  tof_index = max( (INTNB)::floor( (tof_edge_low[ax] - (-mp_halfFOV[ax])) / mp_sizeVox[ax] ), (INTNB)0);
597  // if low TOF edge above the highest FOV edge, return empty line
598  if (tof_index>mp_nbVox[ax]-1) return 0;
599  tof_edge_low[ax] = (HPFLTNB)tof_index * mp_sizeVox[ax] - mp_halfFOV[ax];
600 
601  // absolute coordinate along each axis of the highest voxel edge spanned by the TOF distribution, limited by the highest FOV edge
602  tof_edge_high[ax] = tof_center + tof_half_span * fabs(r[ax]) / lor_length;
603  tof_index = min( (INTNB)::floor( (tof_edge_high[ax] - (-mp_halfFOV[ax])) / mp_sizeVox[ax] ), mp_nbVox[ax]-1);
604  // if high TOF edge below the lowest FOV edge, return empty line
605  if (tof_index<0) return 0;
606  tof_edge_high[ax] = (HPFLTNB)(tof_index+1) * mp_sizeVox[ax] - mp_halfFOV[ax];
607  }
608 
609  // Find the first and last intersecting plane in X using the parametric
610  // values alphaMin and alphaMax
611  HPFLTNB alphaMin = 0.0, alphaMax = 1.0;
612 
613  // Variables for Joseph
614  HPFLTNB pos[ 3 ] = { 0.0, 0.0, 0.0 }; // Position of point in image space
615  HPFLTNB wfl[ 2 ] = { 0.0, 0.0 }; // Weight floor
616  HPFLTNB wcl[ 2 ] = { 0.0, 0.0 }; // Weight ceil
617  HPFLTNB w[ 4 ] = { 0.0, 0.0, 0.0, 0.0 }; // Interpolation weight
618  int16_t index[ 2 ] = { 0, 0 }; // index in the image space
619  int32_t finalIdx = 0; // final index
620  int8_t limitX1 = 1; int8_t limitX2 = 1;
621  int8_t limitY1 = 1; int8_t limitY2 = 1;
622  int8_t limitZ1 = 1; int8_t limitZ2 = 1;
623 
624  // Condition on the largest component of r
625  if( ::fabs( r[ 0 ] ) > ::fabs( r[ 1 ] ) )
626  {
627  // Computing the parametric values
628  // For the X-axis
629  // We stay in the limit of the image space
630  HPFLTNB const alphaX_0 = ( tof_edge_low[ 0 ] - event1[ 0 ] ) / r[ 0 ];
631  HPFLTNB const alphaX_1 = ( tof_edge_high[ 0 ] - event1[ 0 ] ) / r[ 0 ];
632  alphaMin = std::max( alphaMin, std::min( alphaX_0, alphaX_1 ) );
633  alphaMax = std::min( alphaMax, std::max( alphaX_0, alphaX_1 ) );
634 
635  // For the Y-axis
636  // We introduce 1 voxel size around Y-axis
637  // Make a shift on first and last plane (like an offset)
638  if( r[ 1 ] != 0.0 )
639  {
640  HPFLTNB const alphaY_0 = ( tof_edge_low[ 1 ] - mp_sizeVox[ 1 ] * 0.5 - event1[ 1 ] )
641  / r[ 1 ];
642  HPFLTNB const alphaY_1 = ( tof_edge_high[ 1 ] + mp_sizeVox[ 1 ] * 0.5 - event1[ 1 ] )
643  / r[ 1 ];
644  alphaMin = std::max( alphaMin, std::min( alphaY_0, alphaY_1 ) );
645  alphaMax = std::min( alphaMax, std::max( alphaY_0, alphaY_1 ) );
646  }
647 
648  // For the Z-axis
649  // We introduce 1 voxel size around Z-axis
650  // Make a shift on first and last plane (like an offset)
651  if( r[ 2 ] != 0.0 )
652  {
653  HPFLTNB const alphaZ_0 = ( tof_edge_low[ 2 ] - mp_sizeVox[ 2 ] * 0.5 - event1[ 2 ] )
654  / r[ 2 ];
655  HPFLTNB const alphaZ_1 = ( tof_edge_high[ 2 ] + mp_sizeVox[ 2 ] * 0.5 - event1[ 2 ] )
656  / r[ 2 ];
657  alphaMin = std::max( alphaMin, std::min( alphaZ_0, alphaZ_1 ) );
658  alphaMax = std::min( alphaMax, std::max( alphaZ_0, alphaZ_1 ) );
659  }
660 
661  if( alphaMax <= alphaMin ) return 0;
662 
663  if( r[ 1 ] == 0.0 ) if( event1[ 1 ] < -mp_halfFOV[ 1 ] || event1[ 1 ] > mp_halfFOV[ 1 ] ) return 0;
664  if( r[ 2 ] == 0.0 ) if( event1[ 2 ] < -mp_halfFOV[ 2 ] || event1[ 2 ] > mp_halfFOV[ 2 ] ) return 0;
665 
666  // Computing weight of normalization
667  HPFLTNB const factor( ::fabs( r[ 0 ] ) / lor_length );
668  HPFLTNB const factor_for_tof( lor_length / r[ 0 ]) ;
669  HPFLTNB const weight( mp_sizeVox[ 0 ] / factor );
670 
671  // Computing the increment
672  HPFLTNB const ri[ 3 ] = {
673  1.0, // r[ 0 ] / r[ 0 ]
674  r[ 1 ] / r[ 0 ],
675  r[ 2 ] / r[ 0 ]
676  };
677 
678  // Computing the first and the last plane
679  int iMin = 0, iMax = 0;
680  if( r[ 0 ] > 0.0 )
681  {
682  iMin = ::ceil( mp_nbVox[ 0 ] - m_toleranceX - ( mp_halfFOV[ 0 ] - alphaMin * r[ 0 ] - event1[ 0 ] ) / mp_sizeVox[ 0 ] );
683  iMax = ::floor( m_toleranceX + ( event1[ 0 ] + alphaMax * r[ 0 ] - (-mp_halfFOV[ 0 ]) ) / mp_sizeVox[ 0 ] );
684  }
685  else if( r[ 0 ] < 0.0 )
686  {
687  iMin = ::ceil( mp_nbVox[ 0 ] - m_toleranceX - ( mp_halfFOV[ 0 ] - alphaMax * r[ 0 ] - event1[ 0 ] ) / mp_sizeVox[ 0 ] );
688  iMax = ::floor( m_toleranceX + ( event1[ 0 ] + alphaMin * r[ 0 ] - (-mp_halfFOV[ 0 ]) ) / mp_sizeVox[ 0 ] );
689  }
690 
691  // Computing an offset to get the correct position in plane
692  HPFLTNB const offset = -mp_halfFOV[ 0 ] + ( mp_sizeVox[ 0 ] * 0.5 ) - event1[ 0 ];
693 
695  //HPFLTNB previous_edge_erf = erf( ( ( -mp_halfFOV[ 0 ] + iMin * mp_sizeVox[ 0 ] - event1[ 0 ] ) * factor_for_tof - lor_tof_center) / tof_sigma_sqrt2 );
696  //HPFLTNB next_edge_erf = 0.;
697  HPFLTNB tof_weight = 0.;
698 
699  // Loop over all the crossing planes
700  for( int i = iMin; i < iMax; ++i )
701  {
702  // Computing position on crossed plane in term of grid spacing
703  HPFLTNB const step = offset + i * mp_sizeVox[ 0 ];
704  pos[ 1 ] = event1[ 1 ] + step * ri[ 1 ];
705  pos[ 2 ] = event1[ 2 ] + step * ri[ 2 ];
706 
707  // Find the index in the image
708  index[ 0 ] = ::floor( m_toleranceY + ( pos[ 1 ] - m_boundY ) / mp_sizeVox[ 1 ] );
709  index[ 1 ] = ::floor( m_toleranceZ + ( pos[ 2 ] - m_boundZ ) / mp_sizeVox[ 2 ] );
710 
711  // Bilinear interpolation component (using floor)
712  wfl[ 0 ] = pos[ 1 ] - ( m_boundY + index[ 0 ] * mp_sizeVox[ 1 ] );
713  wfl[ 1 ] = pos[ 2 ] - ( m_boundZ + index[ 1 ] * mp_sizeVox[ 2 ] );
714  wfl[ 0 ] /= mp_sizeVox[ 1 ];
715  wfl[ 1 ] /= mp_sizeVox[ 2 ];
716 
717  // Bilinear interpolation component (using ceil)
718  wcl[ 0 ] = 1.0 - wfl[ 0 ];
719  wcl[ 1 ] = 1.0 - wfl[ 1 ];
720 
721  // Final coefficients
722  w[ 0 ] = wcl[ 0 ] * wcl[ 1 ];
723  w[ 1 ] = wcl[ 0 ] * wfl[ 1 ];
724  w[ 2 ] = wfl[ 0 ] * wfl[ 1 ];
725  w[ 3 ] = wfl[ 0 ] * wcl[ 1 ];
726 
727  // Check Y bounds
728  if( index[ 0 ] <= 0 ) limitY1 = 0;
729  if( index[ 0 ] >= mp_nbVox[ 1 ] ) limitY2 = 0;
730 
731  // Check Z bounds
732  if( index[ 1 ] <= 0 ) limitZ1 = 0;
733  if( index[ 1 ] >= mp_nbVox[ 2 ] ) limitZ2 = 0;
734 
735  // Storing values and indices
736  finalIdx = i + ( index[ 0 ] - 1 ) * mp_nbVox[ 0 ] + ( index[ 1 ] - 1 ) * m_nbVoxXY;
737 
738  tof_weight = 0.;
740  {
741  // fetch the value of the precomputed TOF weighting function (centered at the TOF measurement)
742  // nearest to the projection of the voxel center onto the LOR
743  INTNB temp = m_TOFWeightingFcnNbSamples/2 + (INTNB)round( ( step * factor_for_tof - lor_tof_center ) * m_TOFPrecomputedSamplingFactor );
744  if (temp>=0 && temp<m_TOFWeightingFcnNbSamples) tof_weight = mp_TOFWeightingFcn[temp];
745  }
746  else
747  {
749  //next_edge_erf = erf( ( ( step + mp_sizeVox[ 0 ] * 0.5 ) * factor_for_tof - lor_tof_center ) / tof_sigma_sqrt2 );
751  //tof_weight = 0.5 * ::fabs(previous_edge_erf - next_edge_erf) ;
753  //previous_edge_erf = next_edge_erf;
754 
756  {
757  // integration between the edges of the current quantization TOF bin (centered at the TOF measurement)
758  // of the Gaussian centered at the projection of the voxel center onto the LOR
759  // normalized by the size of the bin so that the integral of the final TOF weighting function remains 1
760  HPFLTNB temp_erf = std::min(tof_half_span, std::max(-tof_half_span, lor_tof_center - m_TOFBinSizeInMm/2. - step * factor_for_tof));
761  HPFLTNB prev_erf = erf(temp_erf/tof_sigma_sqrt2);
762  temp_erf = std::min(tof_half_span, std::max(-tof_half_span, lor_tof_center + m_TOFBinSizeInMm/2. - step * factor_for_tof));
763  HPFLTNB new_erf = erf(temp_erf/tof_sigma_sqrt2);
764  tof_weight = 0.5 * fabs(new_erf - prev_erf) / m_TOFBinSizeInMm;
765  }
766  else
767  {
768  // value of the normalized Gaussian (centered at the TOF measurement)
769  // at the projection of the voxel center onto the LOR
770  HPFLTNB temp = ( step * factor_for_tof - lor_tof_center ) / tof_sigma;
771  tof_weight = exp(- 0.5 * temp * temp ) * m_TOFGaussianNormCoef;
772  }
773  }
774 
775  // check if the main voxel is relevant
776  if (!m_hasMask || (limitY1 && limitZ1 && index[ 0 ] <= mp_nbVox[ 1 ] && index[ 1 ] <= mp_nbVox[ 2 ] && mp_mask[finalIdx]))
777  {
778  ap_ProjectionLine->AddVoxel(a_direction, finalIdx * limitY1 * limitZ1, w[ 0 ] * weight * tof_weight * limitY1 * limitZ1);
779  ap_ProjectionLine->AddVoxel(a_direction, ( finalIdx + m_nbVoxXY ) * limitY1 * limitZ2, w[ 1 ] * weight * tof_weight * limitY1 * limitZ2);
780  ap_ProjectionLine->AddVoxel(a_direction, ( finalIdx + mp_nbVox[ 0 ] + m_nbVoxXY ) * limitY2 * limitZ2, w[ 2 ] * weight * tof_weight * limitY2 * limitZ2);
781  ap_ProjectionLine->AddVoxel(a_direction, ( finalIdx + mp_nbVox[ 0 ] ) * limitY2 * limitZ1, w[ 3 ] * weight * tof_weight * limitY2 * limitZ1);
782  }
783 
784  limitY1 = 1.0; limitY2 = 1.0;
785  limitZ1 = 1.0; limitZ2 = 1.0;
786  }
787  }
788  else
789  {
790  // Computing the parametric values
791  // For the X-axis
792  // We introduce 1 voxel size around Y-axis
793  if( r[ 0 ] != 0.0 )
794  {
795  HPFLTNB const alphaX_0 = ( tof_edge_low[ 0 ] - mp_sizeVox[ 0 ] * 0.5 - event1[ 0 ] )
796  / r[ 0 ];
797  HPFLTNB const alphaX_1 = ( tof_edge_high[ 0 ] + mp_sizeVox[ 0 ] * 0.5 - event1[ 0 ] )
798  / r[ 0 ];
799  alphaMin = std::max( alphaMin, std::min( alphaX_0, alphaX_1 ) );
800  alphaMax = std::min( alphaMax, std::max( alphaX_0, alphaX_1 ) );
801  }
802 
803  // For the Y-axis
804  // Make a shift on first and last plane (like an offset)
805  // We stay in the limit of the image space
806  HPFLTNB const alphaY_0 = ( tof_edge_low[ 1 ] - event1[ 1 ] ) / r[ 1 ];
807  HPFLTNB const alphaY_1 = ( tof_edge_high[ 1 ] - event1[ 1 ] ) / r[ 1 ];
808  alphaMin = std::max( alphaMin, std::min( alphaY_0, alphaY_1 ) );
809  alphaMax = std::min( alphaMax, std::max( alphaY_0, alphaY_1 ) );
810 
811  // For the Z-axis
812  // We introduce 1 voxel size around Z-axis
813  // Make a shift on first and last plane (like an offset)
814  if( r[ 2 ] != 0.0 )
815  {
816  HPFLTNB const alphaZ_0 = ( tof_edge_low[ 2 ] - mp_sizeVox[ 2 ] * 0.5 - event1[ 2 ] )
817  / r[ 2 ];
818  HPFLTNB const alphaZ_1 = ( tof_edge_high[ 2 ] + mp_sizeVox[ 2 ] * 0.5 - event1[ 2 ] )
819  / r[ 2 ];
820  alphaMin = std::max( alphaMin, std::min( alphaZ_0, alphaZ_1 ) );
821  alphaMax = std::min( alphaMax, std::max( alphaZ_0, alphaZ_1 ) );
822  }
823 
824  if( alphaMax <= alphaMin ) return 0;
825 
826  if( r[ 0 ] == 0.0 ) if( event1[ 0 ] < -mp_halfFOV[ 0 ] || event1[ 0 ] > mp_halfFOV[ 0 ] ) return 0;
827  if( r[ 2 ] == 0.0 ) if( event1[ 2 ] < -mp_halfFOV[ 2 ] || event1[ 2 ] > mp_halfFOV[ 2 ] ) return 0;
828 
829  // Computing weight of normalization
830  HPFLTNB const factor( ::fabs( r[ 1 ] ) / lor_length );
831  HPFLTNB const factor_for_tof( lor_length / r[ 1 ] );
832  HPFLTNB const weight( mp_sizeVox[ 1 ] / factor );
833 
834  // Computing the increment
835  HPFLTNB const ri[ 3 ] = {
836  r[ 0 ] / r[ 1 ],
837  1.0, // r[ 1 ] / r[ 1 ]
838  r[ 2 ] / r[ 1 ]
839  };
840 
841  // Computing the first and the last plane
842  int jMin = 0, jMax = 0;
843  if( r[ 1 ] > 0.0 )
844  {
845  jMin = ::ceil( mp_nbVox[ 1 ] - m_toleranceY - ( mp_halfFOV[ 1 ] - alphaMin * r[ 1 ] - event1[ 1 ] ) / mp_sizeVox[ 1 ] );
846  jMax = ::floor( m_toleranceY + ( event1[ 1 ] + alphaMax * r[ 1 ] - (-mp_halfFOV[ 1 ]) ) / mp_sizeVox[ 1 ] );
847  }
848  else if( r[ 1 ] < 0.0 )
849  {
850  jMin = ::ceil( mp_nbVox[ 1 ] - m_toleranceY - ( mp_halfFOV[ 1 ] - alphaMax * r[ 1 ] - event1[ 1 ] ) / mp_sizeVox[ 1 ] );
851  jMax = ::floor( m_toleranceY + ( event1[ 1 ] + alphaMin * r[ 1 ] - (-mp_halfFOV[ 1 ]) ) / mp_sizeVox[ 1 ] );
852  }
853 
855  //HPFLTNB previous_edge_erf = erf( ( ( -mp_halfFOV[ 1 ] + jMin * mp_sizeVox[ 1 ] - event1[ 1 ]) * factor_for_tof - lor_tof_center) / tof_sigma_sqrt2 );
856  //HPFLTNB next_edge_erf = 0.;
857  HPFLTNB tof_weight = 0.;
858 
859  // Computing an offset to get the correct position in plane
860  HPFLTNB const offset = -mp_halfFOV[ 1 ] + ( mp_sizeVox[ 1 ] * 0.5 ) - event1[ 1 ];
861 
862  // Loop over all the crossing planes
863  for( int j = jMin; j < jMax; ++j )
864  {
865  // Computing position on crossed plane in term of grid spacing
866  HPFLTNB const step = offset + j * mp_sizeVox[ 1 ];
867  pos[ 0 ] = event1[ 0 ] + step * ri[ 0 ];
868  pos[ 2 ] = event1[ 2 ] + step * ri[ 2 ];
869 
870  // Find the index in the image
871  index[ 0 ] = ::floor( m_toleranceX + ( pos[ 0 ] - m_boundX ) / mp_sizeVox[ 0 ] );
872  index[ 1 ] = ::floor( m_toleranceZ + ( pos[ 2 ] - m_boundZ ) / mp_sizeVox[ 2 ] );
873 
874  // Bilinear interpolation component (using floor)
875  wfl[ 0 ] = pos[ 0 ] - ( m_boundX + index[ 0 ] * mp_sizeVox[ 0 ] );
876  wfl[ 1 ] = pos[ 2 ] - ( m_boundZ + index[ 1 ] * mp_sizeVox[ 2 ] );
877  wfl[ 0 ] /= mp_sizeVox[ 0 ];
878  wfl[ 1 ] /= mp_sizeVox[ 2 ];
879 
880  // Bilinear interpolation component (using ceil)
881  wcl[ 0 ] = 1.0 - wfl[ 0 ];
882  wcl[ 1 ] = 1.0 - wfl[ 1 ];
883 
884  // Final coefficients
885  w[ 0 ] = wcl[ 0 ] * wcl[ 1 ];
886  w[ 1 ] = wcl[ 0 ] * wfl[ 1 ];
887  w[ 2 ] = wfl[ 0 ] * wfl[ 1 ];
888  w[ 3 ] = wfl[ 0 ] * wcl[ 1 ];
889 
890  // Check X bounds
891  if( index[ 0 ] <= 0 ) limitX1 = 0;
892  if( index[ 0 ] >= mp_nbVox[ 0 ] ) limitX2 = 0;
893 
894  // Check Z bounds
895  if( index[ 1 ] <= 0 ) limitZ1 = 0;
896  if( index[ 1 ] >= mp_nbVox[ 2 ] ) limitZ2 = 0;
897 
898  // Storing values and indices
899  finalIdx = ( index[ 0 ] - 1 ) + j * mp_nbVox[ 0 ] + ( index[ 1 ] - 1 ) * m_nbVoxXY;
900 
901  tof_weight = 0.;
903  {
904  // fetch the value of the precomputed TOF weighting function (centered at the TOF measurement)
905  // nearest to the projection of the voxel center onto the LOR
906  INTNB temp = m_TOFWeightingFcnNbSamples/2 + (INTNB)round( ( step * factor_for_tof - lor_tof_center) * m_TOFPrecomputedSamplingFactor );
907  if (temp>=0 && temp<m_TOFWeightingFcnNbSamples) tof_weight = mp_TOFWeightingFcn[temp];
908  }
909  else
910  {
912  //next_edge_erf = erf( ( ( step + mp_sizeVox[ 1 ] * 0.5 ) * factor_for_tof - lor_tof_center ) / tof_sigma_sqrt2 );
914  //tof_weight = 0.5 * ::fabs(previous_edge_erf - next_edge_erf) ;
916  //previous_edge_erf = next_edge_erf;
917 
919  {
920  // integration between the edges of the current quantization TOF bin (centered at the TOF measurement)
921  // of the Gaussian centered at the projection of the voxel center onto the LOR
922  // normalized by the size of the bin so that the integral of the final TOF weighting function remains 1
923  HPFLTNB temp_erf = std::min(tof_half_span, std::max(-tof_half_span, lor_tof_center - m_TOFBinSizeInMm/2. - step * factor_for_tof));
924  HPFLTNB prev_erf = erf(temp_erf/tof_sigma_sqrt2);
925  temp_erf = std::min(tof_half_span, std::max(-tof_half_span, lor_tof_center + m_TOFBinSizeInMm/2. - step * factor_for_tof));
926  HPFLTNB new_erf = erf(temp_erf/tof_sigma_sqrt2);
927  tof_weight = 0.5 * fabs(new_erf - prev_erf) / m_TOFBinSizeInMm;
928  }
929  else
930  {
931  // value of the normalized Gaussian (centered at the TOF measurement)
932  // at the projection of the voxel center onto the LOR
933  HPFLTNB temp = ( step * factor_for_tof - lor_tof_center ) / tof_sigma;
934  tof_weight = exp(- 0.5 * temp * temp ) * m_TOFGaussianNormCoef;
935  }
936  }
937 
938  // check if the main voxel is relevant
939  if (!m_hasMask || (limitX1 && limitZ1 && index[ 0 ] <= mp_nbVox[ 0 ] && index[ 1 ] <= mp_nbVox[ 2 ] && mp_mask[finalIdx]))
940  {
941  ap_ProjectionLine->AddVoxel(a_direction, finalIdx * limitX1 * limitZ1, w[ 0 ] * weight * tof_weight * limitX1 * limitZ1);
942  ap_ProjectionLine->AddVoxel(a_direction, ( finalIdx + m_nbVoxXY ) * limitX1 * limitZ2, w[ 1 ] * weight * tof_weight * limitX1 * limitZ2);
943  ap_ProjectionLine->AddVoxel(a_direction, ( finalIdx + 1 + m_nbVoxXY ) * limitX2 * limitZ2, w[ 2 ] * weight * tof_weight * limitX2 * limitZ2);
944  ap_ProjectionLine->AddVoxel(a_direction, ( finalIdx + 1 ) * limitX2 * limitZ1, w[ 3 ] * weight * tof_weight * limitX2 * limitZ1);
945  }
946 
947  limitX1 = 1; limitX2 = 1;
948  limitZ1 = 1; limitZ2 = 1;
949  }
950  }
951  return 0;
952 }
953 
954 // =====================================================================
955 // ---------------------------------------------------------------------
956 // ---------------------------------------------------------------------
957 // =====================================================================
958 
959 int iProjectorJoseph::ProjectTOFHistogram(int a_direction, oProjectionLine* ap_ProjectionLine)
960 {
961  #ifdef CASTOR_DEBUG
962  if (!m_initialized)
963  {
964  Cerr("***** iProjectorJoseph::ProjectTOFHistogram() -> Called while not initialized !" << endl);
965  Exit(EXIT_DEBUG);
966  }
967  #endif
968 
969  #ifdef CASTOR_VERBOSE
970  if (m_verbose>=10)
971  {
972  string direction = "";
973  if (a_direction==FORWARD) direction = "forward";
974  else direction = "backward";
975  Cout("iProjectorJoseph::Project with TOF bins -> Project line '" << ap_ProjectionLine << "' in " << direction << " direction" << endl);
976  }
977  #endif
978 
979  // Get event positions
980  FLTNB* event1 = ap_ProjectionLine->GetPosition1();
981  FLTNB* event2 = ap_ProjectionLine->GetPosition2();
982 
983  // Distance between point event1 and event2
984  HPFLTNB const r[ 3 ] = {
985  event2[ 0 ] - event1[ 0 ],
986  event2[ 1 ] - event1[ 1 ],
987  event2[ 2 ] - event1[ 2 ]
988  };
989 
990  // LOR length
991  HPFLTNB lor_length = ap_ProjectionLine->GetLength();
992  HPFLTNB lor_length_half = lor_length * 0.5;
993 
994  // Get TOF info
995  INTNB tof_nb_bins = ap_ProjectionLine->GetNbTOFBins();
996  INTNB tof_half_nb_bins = tof_nb_bins/2;
997 
998  // TOF Gaussian standard deviation and truncation
1000  HPFLTNB tof_sigma_sqrt2 = sqrt(2.)*tof_sigma;
1001  HPFLTNB tof_half_span = 0.;
1003  else tof_half_span = tof_sigma * m_TOFNbSigmas;
1004 
1005  // if integration of the Gaussian over the TOF bin, need for help variables to save calls to erf
1006  HPFLTNB prev_erf = 0., new_erf = 0.;
1007 
1008  // minimum and maximum TOF bins, the whole span
1009  INTNB tof_bin_last = tof_half_nb_bins;
1010  INTNB tof_bin_first = -tof_half_nb_bins;
1011 
1012  // distance between the first event1 and the center of a TOF bin along the LOR
1013  HPFLTNB lor_tof_center = 0.;
1014  // the sum of all TOF bin weights for a voxel
1015  //HPFLTNB tof_norm_coef = 0.;
1016 
1017  // the first and the last relevant TOF bins for a voxel
1018  INTNB tof_bin_first_for_voxel = 0, tof_bin_last_for_voxel = 0;
1019 
1020 /*
1021  // Square of r
1022  HPFLTNB const r2[ 3 ] = {
1023  r[ 0 ] * r[ 0 ],
1024  r[ 1 ] * r[ 1 ],
1025  r[ 2 ] * r[ 2 ]
1026  };
1027 */
1028 
1029  // Find the first and last intersecting plane in X using the parametric
1030  // values alphaMin and alphaMax
1031  HPFLTNB alphaMin = 0.0, alphaMax = 1.0;
1032 
1033  // Variables for Joseph
1034  HPFLTNB pos[ 3 ] = { 0.0, 0.0, 0.0 }; // Position of point in image space
1035  HPFLTNB wfl[ 2 ] = { 0.0, 0.0 }; // Weight floor
1036  HPFLTNB wcl[ 2 ] = { 0.0, 0.0 }; // Weight ceil
1037  HPFLTNB w[ 4 ] = { 0.0, 0.0, 0.0, 0.0 }; // Interpolation weight
1038  int16_t index[ 2 ] = { 0, 0 }; // index in the image space
1039  int32_t finalIdx = 0; // final index
1040  int8_t limitX1 = 1; int8_t limitX2 = 1;
1041  int8_t limitY1 = 1; int8_t limitY2 = 1;
1042  int8_t limitZ1 = 1; int8_t limitZ2 = 1;
1043 
1044  // Condition on the largest component of r
1045  if( ::fabs( r[ 0 ] ) > ::fabs( r[ 1 ] ) )
1046  {
1047  // Computing the parametric values
1048  // For the X-axis
1049  // We stay in the limit of the image space
1050  HPFLTNB const alphaX_0 = ( -mp_halfFOV[ 0 ] - event1[ 0 ] ) / r[ 0 ];
1051  HPFLTNB const alphaX_1 = ( mp_halfFOV[ 0 ] - event1[ 0 ] ) / r[ 0 ];
1052  alphaMin = std::max( alphaMin, std::min( alphaX_0, alphaX_1 ) );
1053  alphaMax = std::min( alphaMax, std::max( alphaX_0, alphaX_1 ) );
1054 
1055  // For the Y-axis
1056  // We introduce 1 voxel size around Y-axis
1057  // Make a shift on first and last plane (like an offset)
1058  if( r[ 1 ] != 0.0 )
1059  {
1060  HPFLTNB const alphaY_0 = ( -mp_halfFOV[ 1 ] - mp_sizeVox[ 1 ] * 0.5 - event1[ 1 ] )
1061  / r[ 1 ];
1062  HPFLTNB const alphaY_1 = ( mp_halfFOV[ 1 ] + mp_sizeVox[ 1 ] * 0.5 - event1[ 1 ] )
1063  / r[ 1 ];
1064  alphaMin = std::max( alphaMin, std::min( alphaY_0, alphaY_1 ) );
1065  alphaMax = std::min( alphaMax, std::max( alphaY_0, alphaY_1 ) );
1066  }
1067 
1068  // For the Z-axis
1069  // We introduce 1 voxel size around Z-axis
1070  // Make a shift on first and last plane (like an offset)
1071  if( r[ 2 ] != 0.0 )
1072  {
1073  HPFLTNB const alphaZ_0 = ( -mp_halfFOV[ 2 ] - mp_sizeVox[ 2 ] * 0.5 - event1[ 2 ] )
1074  / r[ 2 ];
1075  HPFLTNB const alphaZ_1 = ( mp_halfFOV[ 2 ] + mp_sizeVox[ 2 ] * 0.5 - event1[ 2 ] )
1076  / r[ 2 ];
1077  alphaMin = std::max( alphaMin, std::min( alphaZ_0, alphaZ_1 ) );
1078  alphaMax = std::min( alphaMax, std::max( alphaZ_0, alphaZ_1 ) );
1079  }
1080 
1081  if( alphaMax <= alphaMin ) return 0;
1082 
1083  if( r[ 1 ] == 0.0 ) if( event1[ 1 ] < -mp_halfFOV[ 1 ] || event1[ 1 ] > mp_halfFOV[ 1 ] ) return 0;
1084  if( r[ 2 ] == 0.0 ) if( event1[ 2 ] < -mp_halfFOV[ 2 ] || event1[ 2 ] > mp_halfFOV[ 2 ] ) return 0;
1085 
1086  // temporary storage for TOF bin weights
1087  // allocation after potential returns
1088  HPFLTNB* tof_weights_temp = new HPFLTNB[tof_nb_bins];
1089  for (INTNB tof_bin=0; tof_bin<tof_nb_bins; tof_bin++) tof_weights_temp[tof_bin] = 0.;
1090 
1091  // Computing weight of normalization
1092  HPFLTNB const factor( ::fabs( r[ 0 ] ) / lor_length );
1093  HPFLTNB const factor_for_tof( lor_length / r[ 0 ] );
1094  HPFLTNB const weight( mp_sizeVox[ 0 ] / factor );
1095 
1096  // Computing the increment
1097  HPFLTNB const ri[ 3 ] = {
1098  1.0, // r[ 0 ] / r[ 0 ]
1099  r[ 1 ] / r[ 0 ],
1100  r[ 2 ] / r[ 0 ]
1101  };
1102 
1103  // Computing the first and the last plane
1104  int iMin = 0, iMax = 0;
1105  if( r[ 0 ] > 0.0 )
1106  {
1107  iMin = ::ceil( mp_nbVox[ 0 ] - m_toleranceX - ( mp_halfFOV[ 0 ] - alphaMin * r[ 0 ] - event1[ 0 ] ) / mp_sizeVox[ 0 ] );
1108  iMax = ::floor( m_toleranceX + ( event1[ 0 ] + alphaMax * r[ 0 ] - (-mp_halfFOV[ 0 ]) ) / mp_sizeVox[ 0 ] );
1109  }
1110  else if( r[ 0 ] < 0.0 )
1111  {
1112  iMin = ::ceil( mp_nbVox[ 0 ] - m_toleranceX - ( mp_halfFOV[ 0 ] - alphaMax * r[ 0 ] - event1[ 0 ] ) / mp_sizeVox[ 0 ] );
1113  iMax = ::floor( m_toleranceX + ( event1[ 0 ] + alphaMin * r[ 0 ] - (-mp_halfFOV[ 0 ]) ) / mp_sizeVox[ 0 ] );
1114  }
1115 
1116  // Computing an offset to get the correct position in plane
1117  HPFLTNB const offset = -mp_halfFOV[ 0 ] + ( mp_sizeVox[ 0 ] * 0.5 ) - event1[ 0 ];
1118 
1119  // Loop over all the crossing planes
1120  for( int i = iMin; i < iMax; ++i )
1121  {
1122  // Computing position on crossed plane in term of grid spacing
1123  HPFLTNB const step = offset + i * mp_sizeVox[ 0 ];
1124  pos[ 1 ] = event1[ 1 ] + step * ri[ 1 ];
1125  pos[ 2 ] = event1[ 2 ] + step * ri[ 2 ];
1126 
1127  // Find the index in the image
1128  index[ 0 ] = ::floor( m_toleranceY + ( pos[ 1 ] - m_boundY ) / mp_sizeVox[ 1 ] );
1129  index[ 1 ] = ::floor( m_toleranceZ + ( pos[ 2 ] - m_boundZ ) / mp_sizeVox[ 2 ] );
1130 
1131  finalIdx = i + ( index[ 0 ] - 1 ) * mp_nbVox[ 0 ] + ( index[ 1 ] - 1 ) * m_nbVoxXY;
1132 
1133  // check if the main voxel is relevant
1134  if (m_hasMask && index[ 0 ] > 0 && index[ 1 ] > 0 && index[ 0 ] <= mp_nbVox[ 1 ] && index[ 1 ] <= mp_nbVox[ 2 ] && !mp_mask[finalIdx]) continue;
1135 
1136  // Compute the first and the last relevant TOF bin for this voxel
1138  {
1139  // taking the TOF bins reached by the truncated Gaussian centered at the voxel center projection
1140  tof_bin_first_for_voxel = max(tof_bin_first , (INTNB)(floor( (step * factor_for_tof - tof_half_span - lor_length_half - m_TOFBinSizeInMm/2.) / m_TOFBinSizeInMm )));
1141  tof_bin_last_for_voxel = min(tof_bin_last , (INTNB)(ceil( (step * factor_for_tof + tof_half_span - lor_length_half + m_TOFBinSizeInMm/2.) / m_TOFBinSizeInMm )));
1142  }
1143  else
1144  {
1145  // taking the TOF bins whose TOF weighting function, centered at the bin center, reaches the voxel center projection
1146  tof_bin_first_for_voxel = max(tof_bin_first , (INTNB)(ceil( (step * factor_for_tof - tof_half_span - lor_length_half ) / m_TOFBinSizeInMm )));
1147  tof_bin_last_for_voxel = min(tof_bin_last , (INTNB)(floor( (step * factor_for_tof + tof_half_span - lor_length_half) / m_TOFBinSizeInMm )));
1148  }
1149 
1150  lor_tof_center = lor_length_half + tof_bin_first_for_voxel * m_TOFBinSizeInMm;
1151 
1152  // shift tof bin indices from -/+ to 0:nbBins range
1153  tof_bin_first_for_voxel += tof_half_nb_bins;
1154  tof_bin_last_for_voxel += tof_half_nb_bins;
1155 
1156  // initialization of help variables for reducing calls to erf
1158  {
1159  // bound the integration to the Gaussian truncation
1160  HPFLTNB temp = std::min(tof_half_span,std::max(-tof_half_span, lor_tof_center - m_TOFBinSizeInMm/2. - step * factor_for_tof));
1161  prev_erf = erf(temp/tof_sigma_sqrt2);
1162  }
1163 
1164  // compute the TOF bin weights for the current voxel for all relevant TOF bins
1165  //tof_norm_coef = 0.;
1166  for (INTNB tof_bin=tof_bin_first_for_voxel; tof_bin<=tof_bin_last_for_voxel; tof_bin++)
1167  {
1169  {
1170  // fetch the value of the precomputed TOF weighting function (centered at the TOF bin center)
1171  // nearest to the projection of the voxel center onto the LOR
1172  INTNB temp = m_TOFWeightingFcnNbSamples/2 + (INTNB)round( (step * factor_for_tof - lor_tof_center) * m_TOFPrecomputedSamplingFactor);
1173  if (temp>=0 && temp<m_TOFWeightingFcnNbSamples) tof_weights_temp[tof_bin] = mp_TOFWeightingFcn[temp];
1174  // add the weight to the sum
1175  //tof_norm_coef += tof_weights_temp[tof_bin];
1176  // update TOF bin center along the LOR for the next TOF bin
1177  lor_tof_center += m_TOFBinSizeInMm;
1178  }
1179  else
1180  {
1182  {
1183  // integration between the edges of the current TOF bin of the Gaussian centered at the projection of the voxel center onto the LOR
1184  // reuse of integration from the previous bin to save calls to erf
1185  // bound the integration to the Gaussian truncation
1186  HPFLTNB temp = std::min(tof_half_span,std::max(-tof_half_span, lor_tof_center + m_TOFBinSizeInMm/2. - step * factor_for_tof));
1187  new_erf = erf(temp/tof_sigma_sqrt2);
1188  tof_weights_temp[tof_bin] = 0.5 * fabs(new_erf - prev_erf);
1189  // add the weight to the sum
1190  //tof_norm_coef += tof_weights_temp[tof_bin];
1191  prev_erf = new_erf;
1192  // update TOF bin center along the LOR for the next TOF bin
1193  lor_tof_center += m_TOFBinSizeInMm;
1194  }
1195  else
1196  {
1197  // TOF weight = TOF bin size * value of the normalized Gaussian (centered at the TOF bin center) at the projection of the voxel center onto the LOR
1198  HPFLTNB temp = ( step * factor_for_tof - lor_tof_center) / tof_sigma;
1199  // save the weight temporarily
1200  tof_weights_temp[tof_bin] = exp(- 0.5 * temp * temp ) * m_TOFGaussianNormCoef * m_TOFBinSizeInMm;
1201  // add the weight to the sum
1202  //tof_norm_coef += tof_weights_temp[tof_bin];
1203  // update TOF bin center along the LOR for the next TOF bin
1204  lor_tof_center += m_TOFBinSizeInMm;
1205  }
1206  }
1207  }
1208 
1209  // Bilinear interpolation component (using floor)
1210  wfl[ 0 ] = pos[ 1 ] - ( m_boundY + index[ 0 ] * mp_sizeVox[ 1 ] );
1211  wfl[ 1 ] = pos[ 2 ] - ( m_boundZ + index[ 1 ] * mp_sizeVox[ 2 ] );
1212  wfl[ 0 ] /= mp_sizeVox[ 1 ];
1213  wfl[ 1 ] /= mp_sizeVox[ 2 ];
1214 
1215  // Bilinear interpolation component (using ceil)
1216  wcl[ 0 ] = 1.0 - wfl[ 0 ];
1217  wcl[ 1 ] = 1.0 - wfl[ 1 ];
1218 
1219  // Final coefficients
1220  w[ 0 ] = wcl[ 0 ] * wcl[ 1 ];
1221  w[ 1 ] = wcl[ 0 ] * wfl[ 1 ];
1222  w[ 2 ] = wfl[ 0 ] * wfl[ 1 ];
1223  w[ 3 ] = wfl[ 0 ] * wcl[ 1 ];
1224 
1225  // Check Y bounds
1226  if( index[ 0 ] <= 0 ) limitY1 = 0;
1227  if( index[ 0 ] >= mp_nbVox[ 1 ] ) limitY2 = 0;
1228 
1229  // Check Z bounds
1230  if( index[ 1 ] <= 0 ) limitZ1 = 0;
1231  if( index[ 1 ] >= mp_nbVox[ 2 ] ) limitZ2 = 0;
1232 /*
1233  if (tof_norm_coef>0.)
1234  {
1235  // first normalize TOF bin weights so that they sum to 1
1236  for (INTNB tof_bin=tof_bin_first_for_voxel; tof_bin<=tof_bin_last_for_voxel; tof_bin++) tof_weights_temp[tof_bin] /= tof_norm_coef;
1237  }
1238 */
1239  // compute and write the final TOF bin projection coefficients for current voxels
1240  ap_ProjectionLine->AddVoxelAllTOFBins(a_direction, finalIdx * limitY1 * limitZ1, w[ 0 ] * weight * limitY1 * limitZ1, tof_weights_temp, tof_bin_first_for_voxel, tof_bin_last_for_voxel);
1241  ap_ProjectionLine->AddVoxelAllTOFBins(a_direction, ( finalIdx + m_nbVoxXY ) * limitY1 * limitZ2, w[ 1 ] * weight * limitY1 * limitZ2 , tof_weights_temp, tof_bin_first_for_voxel, tof_bin_last_for_voxel);
1242  ap_ProjectionLine->AddVoxelAllTOFBins(a_direction, ( finalIdx + mp_nbVox[ 0 ] + m_nbVoxXY ) * limitY2 * limitZ2, w[ 2 ] * weight * limitY2 * limitZ2 , tof_weights_temp, tof_bin_first_for_voxel, tof_bin_last_for_voxel);
1243  ap_ProjectionLine->AddVoxelAllTOFBins(a_direction, ( finalIdx + mp_nbVox[ 0 ] ) * limitY2 * limitZ1, w[ 3 ] * weight * limitY2 * limitZ1 , tof_weights_temp, tof_bin_first_for_voxel, tof_bin_last_for_voxel);
1244 
1245 
1246  limitY1 = 1.0; limitY2 = 1.0;
1247  limitZ1 = 1.0; limitZ2 = 1.0;
1248  }
1249  delete[] tof_weights_temp;
1250  }
1251  else
1252  {
1253  // Computing the parametric values
1254  // For the X-axis
1255  // We introduce 1 voxel size around Y-axis
1256  if( r[ 0 ] != 0.0 )
1257  {
1258  HPFLTNB const alphaX_0 = ( -mp_halfFOV[ 0 ] - mp_sizeVox[ 0 ] * 0.5 - event1[ 0 ] )
1259  / r[ 0 ];
1260  HPFLTNB const alphaX_1 = ( mp_halfFOV[ 0 ] + mp_sizeVox[ 0 ] * 0.5 - event1[ 0 ] )
1261  / r[ 0 ];
1262  alphaMin = std::max( alphaMin, std::min( alphaX_0, alphaX_1 ) );
1263  alphaMax = std::min( alphaMax, std::max( alphaX_0, alphaX_1 ) );
1264  }
1265 
1266  // For the Y-axis
1267  // Make a shift on first and last plane (like an offset)
1268  // We stay in the limit of the image space
1269  HPFLTNB const alphaY_0 = ( -mp_halfFOV[ 1 ] - event1[ 1 ] ) / r[ 1 ];
1270  HPFLTNB const alphaY_1 = ( mp_halfFOV[ 1 ] - event1[ 1 ] ) / r[ 1 ];
1271  alphaMin = std::max( alphaMin, std::min( alphaY_0, alphaY_1 ) );
1272  alphaMax = std::min( alphaMax, std::max( alphaY_0, alphaY_1 ) );
1273 
1274  // For the Z-axis
1275  // We introduce 1 voxel size around Z-axis
1276  // Make a shift on first and last plane (like an offset)
1277  if( r[ 2 ] != 0.0 )
1278  {
1279  HPFLTNB const alphaZ_0 = ( -mp_halfFOV[ 2 ] - mp_sizeVox[ 2 ] * 0.5 - event1[ 2 ] )
1280  / r[ 2 ];
1281  HPFLTNB const alphaZ_1 = ( mp_halfFOV[ 2 ] + mp_sizeVox[ 2 ] * 0.5 - event1[ 2 ] )
1282  / r[ 2 ];
1283  alphaMin = std::max( alphaMin, std::min( alphaZ_0, alphaZ_1 ) );
1284  alphaMax = std::min( alphaMax, std::max( alphaZ_0, alphaZ_1 ) );
1285  }
1286 
1287  if( alphaMax <= alphaMin ) return 0;
1288 
1289  if( r[ 0 ] == 0.0 ) if( event1[ 0 ] < -mp_halfFOV[ 0 ] || event1[ 0 ] > mp_halfFOV[ 0 ] ) return 0;
1290  if( r[ 2 ] == 0.0 ) if( event1[ 2 ] < -mp_halfFOV[ 2 ] || event1[ 2 ] > mp_halfFOV[ 2 ] ) return 0;
1291 
1292  // temporary storage for TOF bins Gaussian integrals over the current voxel, allocation after potential returns
1293  HPFLTNB* tof_weights_temp = new HPFLTNB[tof_nb_bins];
1294  for (INTNB tof_bin=0; tof_bin<tof_nb_bins; tof_bin++) tof_weights_temp[tof_bin] = 0.;
1295 
1296  // Computing weight of normalization
1297  HPFLTNB const factor( ::fabs( r[ 1 ] ) / lor_length );
1298  HPFLTNB const factor_for_tof( lor_length / r[ 1 ] );
1299  HPFLTNB const weight( mp_sizeVox[ 1 ] / factor );
1300 
1301  // Computing the increment
1302  HPFLTNB const ri[ 3 ] = {
1303  r[ 0 ] / r[ 1 ],
1304  1.0, // r[ 1 ] / r[ 1 ]
1305  r[ 2 ] / r[ 1 ]
1306  };
1307 
1308  // Computing the first and the last plane
1309  int jMin = 0, jMax = 0;
1310  if( r[ 1 ] > 0.0 )
1311  {
1312  jMin = ::ceil( mp_nbVox[ 1 ] - m_toleranceY - ( mp_halfFOV[ 1 ] - alphaMin * r[ 1 ] - event1[ 1 ] ) / mp_sizeVox[ 1 ] );
1313  jMax = ::floor( m_toleranceY + ( event1[ 1 ] + alphaMax * r[ 1 ] - (-mp_halfFOV[ 1 ]) ) / mp_sizeVox[ 1 ] );
1314  }
1315  else if( r[ 1 ] < 0.0 )
1316  {
1317  jMin = ::ceil( mp_nbVox[ 1 ] - m_toleranceY - ( mp_halfFOV[ 1 ] - alphaMax * r[ 1 ] - event1[ 1 ] ) / mp_sizeVox[ 1 ] );
1318  jMax = ::floor( m_toleranceY + ( event1[ 1 ] + alphaMin * r[ 1 ] - (-mp_halfFOV[ 1 ]) ) / mp_sizeVox[ 1 ] );
1319  }
1320 
1321  // Computing an offset to get the correct position in plane
1322  HPFLTNB const offset = -mp_halfFOV[ 1 ] + ( mp_sizeVox[ 1 ] * 0.5 ) - event1[ 1 ];
1323 
1324  // Loop over all the crossing planes
1325  for( int j = jMin; j < jMax; ++j )
1326  {
1327  // Computing position on crossed plane in term of grid spacing
1328  HPFLTNB const step = offset + j * mp_sizeVox[ 1 ];
1329  pos[ 0 ] = event1[ 0 ] + step * ri[ 0 ];
1330  pos[ 2 ] = event1[ 2 ] + step * ri[ 2 ];
1331 
1332  // Find the index in the image
1333  index[ 0 ] = ::floor( m_toleranceX + ( pos[ 0 ] - m_boundX ) / mp_sizeVox[ 0 ] );
1334  index[ 1 ] = ::floor( m_toleranceZ + ( pos[ 2 ] - m_boundZ ) / mp_sizeVox[ 2 ] );
1335 
1336  finalIdx = ( index[ 0 ] - 1 ) + j * mp_nbVox[ 0 ] + ( index[ 1 ] - 1 ) * m_nbVoxXY;
1337 
1338  // check if the main voxel is relevant
1339  if (m_hasMask && (index[ 0 ] > 0 && index[ 1 ] > 0 && index[ 0 ] <= mp_nbVox[ 0 ] && index[ 1 ] <= mp_nbVox[ 2 ]) && !mp_mask[finalIdx]) continue;
1340 
1341  // Compute the first and the last relevant TOF bin for this voxel
1343  {
1344  // taking the TOF bins reached by the truncated Gaussian centered at the voxel center projection
1345  tof_bin_first_for_voxel = max(tof_bin_first , (INTNB)(floor( (step * factor_for_tof - tof_half_span - lor_length_half - m_TOFBinSizeInMm/2.) / m_TOFBinSizeInMm )));
1346  tof_bin_last_for_voxel = min(tof_bin_last , (INTNB)(ceil( (step * factor_for_tof + tof_half_span - lor_length_half + m_TOFBinSizeInMm/2.) / m_TOFBinSizeInMm )));
1347  }
1348  else
1349  {
1350  // taking the TOF bins whose TOF uncertainty function (centered at the bin center) reaches the voxel center projection
1351  tof_bin_first_for_voxel = max(tof_bin_first , (INTNB)(ceil( (step * factor_for_tof - tof_half_span - lor_length_half ) / m_TOFBinSizeInMm )));
1352  tof_bin_last_for_voxel = min(tof_bin_last , (INTNB)(floor( (step * factor_for_tof + tof_half_span - lor_length_half) / m_TOFBinSizeInMm )));
1353  }
1354 
1355  lor_tof_center = lor_length_half + tof_bin_first_for_voxel * m_TOFBinSizeInMm;
1356 
1357  // if min/max TOF bins for this voxel do not fall into the total available TOF bins range, skip
1358  if(tof_bin_first_for_voxel>tof_bin_last || tof_bin_last_for_voxel<tof_bin_first) continue;
1359 
1360  // shift tof bin indices from -/+ to 0:nbBins range
1361  tof_bin_first_for_voxel += tof_half_nb_bins;
1362  tof_bin_last_for_voxel += tof_half_nb_bins;
1363 
1364  // initialization of help variables for reducing calls to erf
1366  {
1367  // bound the integration to the Gaussian truncation
1368  HPFLTNB temp = std::min(tof_half_span,std::max(-tof_half_span, lor_tof_center - m_TOFBinSizeInMm/2. - step * factor_for_tof));
1369  prev_erf = erf(temp/tof_sigma_sqrt2);
1370  }
1371 
1372  // compute TOF bin weights for the current voxel for all relevant TOF bins
1373  //tof_norm_coef = 0.;
1374  for (INTNB tof_bin=tof_bin_first_for_voxel; tof_bin<=tof_bin_last_for_voxel; tof_bin++)
1375  {
1377  {
1378  // fetch the value of the precomputed TOF weighting function (centered at the TOF bin center)
1379  // nearest to the projection of the voxel center onto the LOR
1380  INTNB temp = m_TOFWeightingFcnNbSamples/2 + (INTNB)round( (step * factor_for_tof - lor_tof_center) * m_TOFPrecomputedSamplingFactor );
1381  if (temp>=0 && temp<m_TOFWeightingFcnNbSamples) tof_weights_temp[tof_bin] = mp_TOFWeightingFcn[temp];
1382  // add the weight to the sum for normalization
1383  //tof_norm_coef += tof_weights_temp[tof_bin];
1384  // update TOF center along the LOR for the next TOF bin
1385  lor_tof_center += m_TOFBinSizeInMm;
1386  }
1387  else
1388  {
1390  {
1391  // integration between the edges of the current TOF bin of the Gaussian centered at the projection of the voxel center onto the LOR
1392  // reuse of integration from the previous bin to save calls to erf
1393  // bound the integration to the Gaussian truncation
1394  HPFLTNB temp = std::min(tof_half_span,std::max(-tof_half_span, lor_tof_center + m_TOFBinSizeInMm/2. - step * factor_for_tof));
1395  new_erf = erf(temp/tof_sigma_sqrt2);
1396  tof_weights_temp[tof_bin] = 0.5 * fabs(new_erf - prev_erf);
1397  // add the weight to the sum for normalization
1398  //tof_norm_coef += tof_weights_temp[tof_bin];
1399  prev_erf = new_erf;
1400  // update TOF center along the LOR for the next TOF bin
1401  lor_tof_center += m_TOFBinSizeInMm;
1402  }
1403  else
1404  {
1405  // TOF weight = TOF bin size * value of the normalized Gaussian (centered at the TOF bin center) at the projection of the voxel center onto the LOR
1406  HPFLTNB temp = ( step * factor_for_tof - lor_tof_center) / tof_sigma;
1407  // save the weight temporarily
1408  tof_weights_temp[tof_bin] = exp(- 0.5 * temp * temp ) * m_TOFGaussianNormCoef * m_TOFBinSizeInMm;
1409  // add the weight to the sum
1410  //tof_norm_coef += tof_weights_temp[tof_bin];
1411  // update TOF bin center along the LOR for the next TOF bin
1412  lor_tof_center += m_TOFBinSizeInMm;
1413  }
1414  }
1415  }
1416 
1417  // Bilinear interpolation component (using floor)
1418  wfl[ 0 ] = pos[ 0 ] - ( m_boundX + index[ 0 ] * mp_sizeVox[ 0 ] );
1419  wfl[ 1 ] = pos[ 2 ] - ( m_boundZ + index[ 1 ] * mp_sizeVox[ 2 ] );
1420  wfl[ 0 ] /= mp_sizeVox[ 0 ];
1421  wfl[ 1 ] /= mp_sizeVox[ 2 ];
1422 
1423  // Bilinear interpolation component (using ceil)
1424  wcl[ 0 ] = 1.0 - wfl[ 0 ];
1425  wcl[ 1 ] = 1.0 - wfl[ 1 ];
1426 
1427  // Final coefficients
1428  w[ 0 ] = wcl[ 0 ] * wcl[ 1 ];
1429  w[ 1 ] = wcl[ 0 ] * wfl[ 1 ];
1430  w[ 2 ] = wfl[ 0 ] * wfl[ 1 ];
1431  w[ 3 ] = wfl[ 0 ] * wcl[ 1 ];
1432 
1433  // Check X bounds
1434  if( index[ 0 ] <= 0 ) limitX1 = 0;
1435  if( index[ 0 ] >= mp_nbVox[ 0 ] ) limitX2 = 0;
1436 
1437  // Check Z bounds
1438  if( index[ 1 ] <= 0 ) limitZ1 = 0;
1439  if( index[ 1 ] >= mp_nbVox[ 2 ] ) limitZ2 = 0;
1440 
1441  // Storing values and indices
1442 /*
1443  if (tof_norm_coef>0.)
1444  {
1445  // first normalize TOF bin weights so that they sum to 1
1446  for (INTNB tof_bin=tof_bin_first_for_voxel; tof_bin<=tof_bin_last_for_voxel; tof_bin++) tof_weights_temp[tof_bin] /= tof_norm_coef;
1447  }
1448 */
1449  // compute and write the final TOF bin projection coefficients for current voxels
1450  ap_ProjectionLine->AddVoxelAllTOFBins(a_direction, finalIdx * limitX1 * limitZ1, w[ 0 ] * weight * limitX1 * limitZ1, tof_weights_temp, tof_bin_first_for_voxel, tof_bin_last_for_voxel);
1451  ap_ProjectionLine->AddVoxelAllTOFBins(a_direction, ( finalIdx + m_nbVoxXY ) * limitX1 * limitZ2, w[ 1 ] * weight * limitX1 * limitZ2, tof_weights_temp, tof_bin_first_for_voxel, tof_bin_last_for_voxel);
1452  ap_ProjectionLine->AddVoxelAllTOFBins(a_direction, ( finalIdx + 1 + m_nbVoxXY ) * limitX2 * limitZ2, w[ 2 ] * weight * limitX2 * limitZ2, tof_weights_temp, tof_bin_first_for_voxel, tof_bin_last_for_voxel);
1453  ap_ProjectionLine->AddVoxelAllTOFBins(a_direction, ( finalIdx + 1 ) * limitX2 * limitZ1, w[ 3 ] * weight * limitX2 * limitZ1, tof_weights_temp, tof_bin_first_for_voxel, tof_bin_last_for_voxel);
1454 
1455  limitX1 = 1; limitX2 = 1;
1456  limitZ1 = 1; limitZ2 = 1;
1457  }
1458  delete[] tof_weights_temp;
1459  }
1460 
1461  return 0;
1462 }
1463 
1464 // =====================================================================
1465 // ---------------------------------------------------------------------
1466 // ---------------------------------------------------------------------
1467 // =====================================================================
bool m_compatibleWithSPECTAttenuationCorrection
Definition: vProjector.hh:407
FLTNB mp_sizeVox[3]
Definition: vProjector.hh:378
FLTNB m_TOFNbSigmas
Definition: vProjector.hh:389
INTNB m_nbVoxXY
Definition: vProjector.hh:380
INTNB mp_nbVox[3]
Definition: vProjector.hh:379
#define FLTNB
Definition: gVariables.hh:81
#define SPEED_OF_LIGHT_IN_MM_PER_PS
Definition: gVariables.hh:106
FLTNB m_TOFGaussianNormCoef
Definition: vProjector.hh:397
int InitializeSpecific()
This function is used to initialize specific stuff to the child projector.
void ShowHelpSpecific()
A function used to show help about the child module.
void AddVoxelAllTOFBins(int a_direction, INTNB a_voxelIndex, FLTNB a_voxelWeight, HPFLTNB *a_tofWeights, INTNB a_tofBinFirst, INTNB a_tofBinLast)
Add a voxel contribution to the line for all relevant TOF bins.
#define TWO_SQRT_TWO_LN_2
Definition: gVariables.hh:100
oImageDimensionsAndQuantification * mp_ImageDimensionsAndQuantification
Definition: vProjector.hh:384
#define HPFLTNB
Definition: gVariables.hh:83
FLTNB GetTOFMeasurementInPs()
This function is used to get the TOF measurement in ps.
This class is designed to generically described any on-the-fly projector.
Definition: vProjector.hh:75
int ReadConfigurationFile(const string &a_configurationFile)
A function used to read options from a configuration file.
bool m_TOFWeightingFcnPrecomputedFlag
Definition: vProjector.hh:391
bool m_TOFBinProperProcessingFlag
Definition: vProjector.hh:392
HPFLTNB * mp_TOFWeightingFcn
Definition: vProjector.hh:393
FLTNB GetLength()
This function is used to get the length of the line.
int ReadOptionsList(const string &a_optionsList)
A function used to read options from a list of options.
int CheckSpecificParameters()
A private function used to check the parameters settings specific to the child projector.
void Exit(int code)
~iProjectorJoseph()
The destructor of iProjectorJoseph.
int ProjectTOFListmode(int a_direction, oProjectionLine *ap_ProjectionLine)
A function to project with TOF continuous information.
#define Cerr(MESSAGE)
FLTNB * GetPosition1()
This function is used to get the pointer to the mp_position1 (3-values tab).
bool m_initialized
Definition: vProjector.hh:416
bool * mp_mask
Definition: vProjector.hh:400
void AddVoxel(int a_direction, INTNB a_voxelIndice, FLTNB a_voxelWeight)
This function is used to add a voxel contribution to the line, assuming TOF bin 0 (i...
FLTNB m_TOFResolutionInMm
Definition: vProjector.hh:396
INTNB EstimateMaxNumberOfVoxelsPerLine()
This function is used to compute and provide an estimate of the maximum number of voxels that could c...
bool m_hasMask
Definition: vProjector.hh:401
Declaration of class iProjectorJoseph.
#define INTNB
Definition: gVariables.hh:92
This class is designed to manage and store system matrix elements associated to a vEvent...
int ProjectWithoutTOF(int a_direction, oProjectionLine *ap_ProjectionLine)
A function to project without TOF.
FLTNB m_TOFPrecomputedSamplingFactor
Definition: vProjector.hh:398
FLTNB m_TOFBinSizeInMm
Definition: vProjector.hh:394
Declaration of class sOutputManager.
int ProjectTOFHistogram(int a_direction, oProjectionLine *ap_ProjectionLine)
A function to project with TOF binned information.
int GetNbTOFBins()
This function is used to get the number of TOF bins in use.
FLTNB mp_halfFOV[3]
Definition: vProjector.hh:381
#define EXIT_DEBUG
Definition: gVariables.hh:97
FLTNB * GetPosition2()
This function is used to get the pointer to the mp_position2 (3-values tab).
INTNB GetNbVoxX()
Get the number of voxels along the X axis.
INTNB GetNbVoxZ()
Get the number of voxels along the Z axis.
#define FORWARD
#define Cout(MESSAGE)
iProjectorJoseph()
The constructor of iProjectorJoseph.
bool m_compatibleWithCompression
Definition: vProjector.hh:410
INTNB GetNbVoxY()
Get the number of voxels along the Y axis.
INTNB m_TOFWeightingFcnNbSamples
Definition: vProjector.hh:390