@@ -516,9 +516,9 @@ void reconstruction_from_radon(padded_matrix& image,
516
516
By the Fourier slice theorem,
517
517
radon_hat(theta, ksi) = f_hat(ksi*sin(theta), ksi*cos(theta))
518
518
where f_hat is the (continuous) 2D Fourier transform of f, i.e.,
519
- f_hat(k_y, k_x ) :=
520
- integral integral (f(y,x)*exp(-1i*2*M_PI*(k_y *y + k_x *x))) dx dy.
521
- y real x real
519
+ f_hat(kappa_y, kappa_x ) :=
520
+ integral integral (f(y,x)*exp(-1i*2*M_PI*(kappa_y *y + kappa_x *x))) dx dy.
521
+ y real x real
522
522
523
523
Considering ksi = r / S for integers r in [0, q/2], one has
524
524
radon_hat(theta(i), r / S)
@@ -527,7 +527,7 @@ void reconstruction_from_radon(padded_matrix& image,
527
527
which may be approximated as (midpoint rule)
528
528
radon_hat(theta(i), r / S)
529
529
~ \sum_j (R[i][j]*exp(-1i*2*M_PI*(r/S)*v(j))) * (S/q), for j=0,...,q-1
530
- = exp(1i*M_PI*r*(1.0 - 1.0/q)) * (S/q) * DFT(R[i][0:q[ ; r), (eq. 1a)
530
+ = exp(1i*M_PI*r*(1.0 - 1.0/q)) * (S/q) * DFT(R[i][0:q[; r), (eq. 1a)
531
531
where DFT(R[i][0:q[; r) represents the r-th value of the forward DFT of
532
532
the i-th row of q discrete values in R.
533
533
Note: radon_hat(theta(i) + M_PI, r / S) = conj(radon_hat(theta(i), r / S))
@@ -542,7 +542,7 @@ void reconstruction_from_radon(padded_matrix& image,
542
542
// Distances must be set for batched transforms. For real in-place DFTs with
543
543
// unit stride, the distance in forward domain (wherein elements are real)
544
544
// must be twice the distance in backward domain (wherein elements are
545
- // complex). Therefore, padding is requied in forward domain (as accounted
545
+ // complex). Therefore, padding is required in forward domain (as accounted
546
546
// for by the padded_matrix structure)
547
547
radon_dft.set_value (dft_ns::config_param::FWD_DISTANCE, R.ldw ());
548
548
radon_dft.set_value (dft_ns::config_param::BWD_DISTANCE, R.ldw () / 2 );
@@ -559,128 +559,109 @@ void reconstruction_from_radon(padded_matrix& image,
559
559
Note that values of i in [p, 2*p) are known too, as the complex conjugates
560
560
of their (i-p) counterparts (albeit not stored explicitly).
561
561
562
- The reconstructed image is based off a version of the spectrum f_hat that
563
- is truncated to wave vectors no larger than 0.5*q/S in magnitude, e.g.,
564
- g_hat(ksi_y, ksi_x) =
565
- f_hat(ksi_y, ksi_x)*heaviside(0.5*q/S - sqrt(ksi_y^2 + ksi_x^2)),
566
- where heaviside(x) := {0, 0.5, 1} if {x < 0.0, x == 0, x > 0.0},
567
- respectively.
568
-
569
- While g(y, x) is not strictly equal to f(y, x) pointwise in general, g
570
- converges towards f in a weaker sense (e.g., in L2 norm) as q gets large if
571
- f is bounded and has compact support. Since samples of g are desired at
572
- specific points, i.e., the (centers of the) q x q pixels of the
573
- reconstructed S-by-S image, let
574
- g_s(y, x) = g(y, x) * 2d_dirac_comb(y, x; S/q),
575
- where 2d_dirac_comb(y, x; alpha) is defined as
576
- \sum_j \sum_i 2d_dirac(y-alpha*j, x-alpha*i), {i,j} = -infty,...,+infty.
577
-
578
- By the convolution theorem, the spectrum of g_s is
579
- g_s_hat(ksi_y, ksi_x) = (eq. 2)
580
- (q^2/S^2)*convolution[g_hat(., .), 2d_dirac_comb(., .; q/S)](ksi_y, ksi_x),
581
- which is periodic with period q/S along ksi_y and ksi_x. Sampling g_s_hat at
582
- wave vectors of components multiple of 1.0/S, i.e., considering
583
- f_tilde_hat(ksi_y, ksi_x) = (eq. 3)
584
- g_s_hat(ksi_y, ksi_x) * 2d_dirac_comb(ksi_y, ksi_x; 1.0/S)
585
- effectively corresponds to f_tilde(y, x) being a periodic replication of
586
- (S^2)*g_s(y, x) with period S along y and x (assuming that values of
587
- g_s(y, x) may be ignored for {(y, x): max(|y|, |x|) > 0.5*S}).
588
-
589
- Developing the (continuous) 2D inverse Fourier transform of f_tilde_hat
590
- (see eq. 3), one has
591
- f_tilde(y, x) = \sum_i \sum_j 2d_dirac(y-i*(S/q), x-j*(S/q)) G[i%q][j%q],
592
- {i,j} = -infty,...,+infty
593
- where G[i][j] (0 <= i < q. 0 <= j < q) is the (i,j) value of the 2D backward
594
- DFT of
595
- G_S_HAT[m][n] = g_s_hat(mm(m)/S, nn(n)/S), 0 <= m < q, 0 <= n < q
596
- where 0 <= |xx(x)| <= q/2 and mod(x, q) == mod(xx(x), q) with 'x' being
597
- either 'm' or 'n'. Explicitly, for 0 <= m < q, 0 <= n < q, let
598
- abs_S_ksi = sycl::hypot(mm(n), nn(n));
599
- theta = sycl::atan2(mm(n), nn(n));
600
- then (see eq. 2)
601
- G_S_HAT[m][n]=0.0, if abs_S_ksi > 0.5*q;
602
- G_S_HAT[m][n]=f_hat(mm(m)/S, nn(n)/S), if round(abs_S_ksi) < 0.5*q;
603
- G_S_HAT[m][n]=0.5*f_hat(mm(m)/S, nn(n)/S), if round(abs_S_ksi) = 0.5*q,
604
- and fmod(theta, 0.5*M_PI) != 0.0;
605
- G_S_HAT[m][n]=real_part(f_hat(mm(m)/S, nn(n)/S)),
606
- if round(abs_S_ksi) = 0.5*q,
607
- and fmod(theta, 0.5*M_PI) = 0.0.
608
-
609
- Identifying f_tilde(y, x) as the periodic replication of (S^2)*g_s(y, x)
610
- with period S along y and x, one concludes that
611
- g(i*(S/q), j*(S/q)) = G[i%q][j%q] / S^2 (eq. 4)
612
- for integers i and j s.t. |i| <= q/2 and |j| <= q/2 (assuming that values of
613
- g_s(y, x) may be ignored for {(y, x): max(|y|, |x|) > 0.5*S}).
614
-
615
- The required values of G_S_HAT[m][n] are interpolated below from the known
562
+ The reconstructed image approaches (an elementary cell of) the periodic
563
+ replication of f of period S along y and x, i.e., of
564
+ +\infty +\infty
565
+ \psi(y, x) = \sum \sum f(y - n_y*S, x - n_x*S).
566
+ n_y=-\infty n_x=-\infty
567
+ Given its periodic nature, \psi(y, x) can be expressed as the Fourier series
568
+ +\infty +\infty
569
+ \sum \sum \psi_hat[k_y, k_x]*exp(1i*2*M_PI*(k_y*y+k_x*x)/S),
570
+ k_y=-\infty k_x=-\infty
571
+ wherein
572
+ \psi_hat[k_y, k_x] = (1/S^2)*f_hat(k_y/S, k_x/S).
573
+
574
+ Given that f_hat(k_y/S, k_x/S) is unknown for hypot(k_x, k_y) > q/2, the
575
+ reconstruction function g approximates \psi as its truncated Fourier series,
576
+ i.e.,
577
+ (S^2)*g(y,x) = \sum f_hat(k_y/S, k_x/S)*exp(1i*2*M_PI*(k_y*y+k_x*x)/S).
578
+ [k_y,k_x],
579
+ hypot(k_y, k_x) <= q/2
580
+
581
+ Sampling the values of g at points (i*S/q, j*S/q) (0 <= i < q and 0 <= j < q,
582
+ i and j are integers), one has
583
+ G[i,j] = (1/S^2)*iDFT(G_HAT; i,j) (eq. 2)
584
+ where G_HAT is a set of qxq (complex) values G_HAT[m][n] (0 <= m < q and
585
+ 0 <= n < q, m and n are integers) such that
586
+ G_HAT[m][n] = 0.0, if hypot(mm, nn) > 0.5*q;
587
+ G_HAT[m][n] = f_hat(mm/S, nn/S) if hypot(mm, nn) < 0.5*q
588
+ or if (hypot(mm, nn) == 0.5*q
589
+ && mm != 0 && nn != 0)
590
+ G_HAT[m][n] = 2*real_part(f_hat(mm/S, nn/S)), if (hypot(mm, nn) == 0.5*q
591
+ && (mm != 0 || nn != 0)).
592
+ where the notation "xx" represents an integer such that
593
+ 0 <= |xx| <= q/2 and mod(x, q) == mod(xx, q)
594
+ ('x' being either 'm' or 'n' above).
595
+
596
+ The required values of G_HAT[m][n] are interpolated below from the known
616
597
values stored in R (using eq. 1b).
617
- Note that G_S_HAT [q - m][q - n] = conj(G_S_HAT [m][n]) given that
618
- f_hat(-ksi_y, -ksi_x) = conj(f_hat(ksi_y, ksi_x)). This is consistent with
619
- the requirements for a well-defined backward real 2D DFT, and the values of
620
- G_S_HAT [m][n] do not need to be set explicitly for n > q/2.
598
+ Note that G_HAT [q - m][q - n] = conj(G_HAT [m][n]) by construction, given
599
+ that f_hat(-ksi_y, -ksi_x) = conj(f_hat(ksi_y, ksi_x)). This is consistent
600
+ with the requirements for a well-defined backward real 2D DFT. Therefore, the
601
+ values of G_HAT [m][n] do not need to be set/stored explicitly for n > q/2.
621
602
*/
622
603
std::cout << " \t Step 2 - Interpolating spectrum from polar to "
623
604
<< " cartesian grid" << std::endl;
624
605
auto interp_ev = image.q .submit ([&](sycl::handler &cgh) {
625
606
cgh.depends_on (compute_radon_hat);
626
607
const complex_t *R_data_c = reinterpret_cast <complex_t *>(R.data );
627
- complex_t *G_S_HAT = reinterpret_cast <complex_t *>(image.data );
608
+ complex_t *G_HAT = reinterpret_cast <complex_t *>(image.data );
628
609
const int R_data_c_ldw = R.ldw ()/2 ; // in complex values
629
- const int G_S_HAT_ldw = image.ldw ()/2 ; // in complex values
610
+ const int G_HAT_ldw = image.ldw ()/2 ; // in complex values
630
611
cgh.parallel_for <class interpolateKernelClass >(
631
612
sycl::range<2 >(q, q/2 + 1 ),
632
613
[=](sycl::item<2 > item) {
633
614
const int m = item.get_id (0 );
634
615
const int n = item.get_id (1 );
635
616
const int mm = m - (2 *m > q ? q : 0 );
636
617
const int nn = n; // nn(n) == n since n never exceeds q/2
637
- const double abs_S_ksi = sycl::hypot (double (mm), double (nn));
618
+ const double abs_k = sycl::hypot (double (mm), double (nn));
638
619
const double theta = (mm == 0 && nn == 0 ) ?
639
620
0.0 : sycl::atan2 (double (mm), double (nn));
640
621
// theta in [-0.5*M_PI, +0.5*M_PI]
641
- complex_t G_S_HAT_mn = complex_t (0.0 , 0.0 );
642
- if (abs_S_ksi <= 0.5 *q) {
643
- const int r = static_cast <int >(sycl::round (abs_S_ksi ));
622
+ complex_t G_HAT_mn = complex_t (0.0 , 0.0 );
623
+ if (abs_k <= 0.5 *q) {
624
+ const int r = static_cast <int >(sycl::round (abs_k ));
644
625
const int i =
645
626
static_cast <int >(sycl::round (((theta + 0.5 *M_PI)/M_PI)*p));
646
627
// if i < 0 or i >= p (e.g., theta = 0.5*M_PI corresponds to
647
628
// i == p), the value is mapped to the complex conjugate of
648
629
// R_data_c[(i%p) * R_data_c_ldw + r] (e.g.,
649
630
// theta = -0.5*M_PI if i == p).
650
- complex_t f_hat_mm_nn =
631
+ // Approximated values of
632
+ // f_hat((r/S)*sin(theta(i)), (r/S)*cos(theta(i))):
633
+ complex_t f_hat_value =
651
634
R_data_c[(i% p) * R_data_c_ldw + r]*
652
635
complex_t (sycl::cos (M_PI*r*(1.0 - 1.0 /q)),
653
636
sycl::sin (M_PI*r*(1.0 - 1.0 /q))); // see eq. 1b
654
637
if (i%(2 *p) >= p)
655
- f_hat_mm_nn = std::conj (f_hat_mm_nn);
656
-
657
- G_S_HAT_mn = f_hat_mm_nn;
658
- if (2 *r == q) {
659
- if (nn == 0 || mm == 0 ) // |theta| = 0.0 or 0.5*M_PI
660
- G_S_HAT_mn.imag (0.0 );
661
- else
662
- G_S_HAT_mn *= 0.5 ;
638
+ f_hat_value = std::conj (f_hat_value);
639
+
640
+ G_HAT_mn = f_hat_value;
641
+ if (2 *r == q && (nn == 0 || mm == 0 )) {
642
+ G_HAT_mn *= 2.0 ;
643
+ G_HAT_mn.imag (0.0 );
663
644
}
664
645
// For a more convenient representation of the reconstructed
665
646
// image, shift the target reference frame so that the
666
647
// center of the reconstructed image is located at pixel
667
648
// of indices (q/2, q/2):
668
- G_S_HAT_mn *= complex_t (sycl::cos (-2.0 *M_PI*m*(q/2 )/q),
669
- sycl::sin (-2.0 *M_PI*m*(q/2 )/q));
649
+ G_HAT_mn *= complex_t (sycl::cos (-2.0 *M_PI*m*(q/2 )/q),
650
+ sycl::sin (-2.0 *M_PI*m*(q/2 )/q));
670
651
// RHS is ((-1)^m, 0) is q is even
671
- G_S_HAT_mn *= complex_t (sycl::cos (-2.0 *M_PI*n*(q/2 )/q),
672
- sycl::sin (-2.0 *M_PI*n*(q/2 )/q));
652
+ G_HAT_mn *= complex_t (sycl::cos (-2.0 *M_PI*n*(q/2 )/q),
653
+ sycl::sin (-2.0 *M_PI*n*(q/2 )/q));
673
654
// RHS is ((-1)^n, 0) is q is even
674
655
}
675
- G_S_HAT [m * G_S_HAT_ldw + n] = G_S_HAT_mn ;
656
+ G_HAT [m * G_HAT_ldw + n] = G_HAT_mn ;
676
657
});
677
658
});
678
659
std::cout << " \t Step 3 - In-place backward real 2D DFT of size "
679
660
<< q << " x" << q << std::endl;
680
661
real_descriptor_t q_by_q_real_dft ({q, q});
681
662
// Default strides are set by default for in-place DFTs (consistently with
682
663
// the implementation of padded_matrix)
683
- // Scaling factor for backward DFT (see eq. 4 )
664
+ // Scaling factor for backward DFT (see eq. 2 )
684
665
q_by_q_real_dft.set_value (dft_ns::config_param::BACKWARD_SCALE, 1.0 / (S*S));
685
666
q_by_q_real_dft.commit (image.q );
686
667
auto compute_g_values =
@@ -693,7 +674,7 @@ void reconstruction_from_radon(padded_matrix& image,
693
674
// original image. The mean error over an area A is defined as
694
675
// (1.0/area(A)) integral |f_reconstruction - f_original| dA
695
676
// A
696
- // where f_reconstruction (resp. f_original) is the piecewise contant function
677
+ // where f_reconstruction (resp. f_original) is the piecewise constant function
697
678
// of the gray-scale intensity in the reconstructed (resp. original) image.
698
679
// Note: f_reconstruction and f_original are considered equal to 0.0 outside
699
680
// the support of the original image.
0 commit comments