@@ -17,16 +17,18 @@ constexpr double default_S_to_D = 1.0;
17
17
static const std::string default_original_bmpname = " input.bmp" ;
18
18
static const std::string default_radon_bmpname = " radon.bmp" ;
19
19
static const std::string default_restored_bmpname = " restored.bmp" ;
20
+ static const std::string default_errors_bmpname = " errors.bmp" ;
20
21
#ifdef _WIN64
21
22
static const std::string program_name = " compute_tomography.exe" ;
22
23
#else
23
24
static const std::string program_name = " compute_tomography" ;
24
25
#endif
26
+ constexpr double arbitrary_error_threshold = 0.1 ;
25
27
static const std::string usage_info =
26
28
" \n \
27
29
Usage:\n \
28
30
======\n \
29
- " + program_name + " p q S_to_D in radon_out restored_out\n \
31
+ " + program_name + " p q S_to_D in radon_out restored_out err_out \n \
30
32
Inputs:\n \
31
33
-------\n \
32
34
p - number of projection directions considered for the Radon\n \
@@ -56,6 +58,14 @@ static const std::string usage_info =
56
58
gray-scaled image representing the image reconstructed\n\
57
59
from the Radon transform data points.\n\
58
60
\"" + default_restored_bmpname + "\" is used by default.\n\
61
+ err_out - name of a 24-bit uncompressed bitmap image file storing a\n\
62
+ gray-scaled image representing the pixel-wise error in the\n\
63
+ restored_out file. This file is created only if the mean\n\
64
+ global error is found to exceed "
65
+ + std::to_string(100.0 *arbitrary_error_threshold) +
66
+ "% of the maximum\n\
67
+ gray-scale value in the original image.\n\
68
+ \"" + default_errors_bmpname + "\" is considered by default.\n\
59
69
";
60
70
61
71
#include < cmath>
@@ -661,6 +671,139 @@ void reconstruction_from_radon(padded_matrix& image,
661
671
compute_g_values.wait ();
662
672
}
663
673
674
+ // Routine computing the mean global error and pixel-wise mean local errors in
675
+ // the reconstructed image, when compared to the (gray-scale-converted)
676
+ // original image. The mean error over an area A is defined as
677
+ // (1.0/area(A)) integral |f_reconstruction - f_original| dA
678
+ // A
679
+ // where f_reconstruction (resp. f_original) is the piecewise contant function
680
+ // of the gray-scale intensity in the reconstructed (resp. original) image.
681
+ // Note: f_reconstruction and f_original are considered equal to 0.0 outside
682
+ // the support of the original image.
683
+ //
684
+ // Inputs:
685
+ // -------
686
+ // S: scanning width used when sampling the Radon transform;
687
+ // original: padded_matrix containing the original input image's pixel
688
+ // values converted to gray-scale values;
689
+ // reconstruction: padded_matrix containing the reconstructed image's pixel
690
+ // gray-scale values.
691
+ // Output:
692
+ // -------
693
+ // errors: padded_matrix of the same size as reconstruction, containing
694
+ // the pixel-wise mean local errors in the pixels of the
695
+ // reconstructed image.
696
+ // Returns:
697
+ // --------
698
+ // The mean global error.
699
+ double compute_errors (padded_matrix& errors,
700
+ double S,
701
+ const padded_matrix& original,
702
+ const padded_matrix& reconstruction) {
703
+ const int q = reconstruction.h ;
704
+ if (q <= 0 || q != reconstruction.w )
705
+ die (" invalid reconstruction considered for evaluating mean local errors" );
706
+ errors.allocate (q, q);
707
+ if (!errors.data )
708
+ die (" cannot allocate memory for mean local errors" );
709
+ // the reconstructed image is an S-by-S image of q x q pixels
710
+ const double pixel_length = S/q;
711
+ const double max_overlap = sycl::min (pixel_length, in_pix_len);
712
+ // dimensions of the original image:
713
+ const double hh = in_pix_len*original.h ;
714
+ const double ww = in_pix_len*original.w ;
715
+
716
+ // helper routines to compute the mean local error in pixel (i,j) of the
717
+ // reconstructed image
718
+ const int supremum_io = original.h - 1 ;
719
+ const int supremum_jo = original.w - 1 ;
720
+ auto get_original_index = [=](double y, double x) {
721
+ int io = static_cast <int >(sycl::floor ((0.5 *hh + y) / in_pix_len));
722
+ int jo = static_cast <int >(sycl::floor ((0.5 *ww + x) / in_pix_len));
723
+ io = sycl::max (0 , sycl::min (io, supremum_io));
724
+ jo = sycl::max (0 , sycl::min (jo, supremum_jo));
725
+ return std::pair<int , int >(io, jo);
726
+ };
727
+ const double * reconstruction_data = reconstruction.data ;
728
+ const double * original_data = original.data ;
729
+ const int reconstruction_ldw = reconstruction.ldw ();
730
+ const int original_ldw = original.ldw ();
731
+ auto compute_mean_error_in_pixel = [=](int i, int j) {
732
+ // Notes:
733
+ // - pixel of index (i,j) in the reconstructed image covers the area of
734
+ // points (y, x) such that
735
+ // |y - (i - q/2)*pixel_length| <= 0.5*pixel_length
736
+ // |x - (j - q/2)*pixel_length| <= 0.5*pixel_length
737
+ // - pixel of index (io,jo) in the original image covers the area of
738
+ // points (y, x) such that
739
+ // io*in_pix_len <= y + 0.5*hh <= (io+1)*in_pix_len
740
+ // jo*in_pix_len <= x + 0.5*ww <= (jo+1)*in_pix_len
741
+ // in a common orthonormal reference frame centered on either image
742
+ const double y_min = (i - q/2 - 0.5 )*pixel_length;
743
+ const double x_min = (j - q/2 - 0.5 )*pixel_length;
744
+ const double y_max = (i - q/2 + 0.5 )*pixel_length;
745
+ const double x_max = (j - q/2 + 0.5 )*pixel_length;
746
+ if (y_min > 0.5 *hh || y_max < -0.5 *hh ||
747
+ x_min > 0.5 *ww || x_max < -0.5 *ww) {
748
+ // out of scope of relevance, no intersection with the original
749
+ // image
750
+ return 0.0 ;
751
+ }
752
+ // find corresponding range of pixels in original image
753
+ const auto io_jo_min = get_original_index (y_min, x_min);
754
+ const auto io_jo_max = get_original_index (y_max, x_max);
755
+ const double got = reconstruction_data[i*reconstruction_ldw + j];
756
+ double mean_local_error = 0.0 ;
757
+ // parse original image's pixels (io, jo) having a non-empty
758
+ // intersection with the reconstructed image's pixel (i, j)
759
+ for (int io = io_jo_min.first ; io <= io_jo_max.first ; io++) {
760
+ const double yo_min = -0.5 *hh + io*in_pix_len;
761
+ const double yo_max = -0.5 *hh + (io + 1 )*in_pix_len;
762
+ double overlap_y =
763
+ sycl::min (y_max, yo_max) - sycl::max (y_min, yo_min);
764
+ overlap_y = sycl::max (0.0 , sycl::min (overlap_y, max_overlap));
765
+ for (int jo = io_jo_min.second ; jo <= io_jo_max.second ; jo++) {
766
+ const double xo_min = -0.5 *ww + jo*in_pix_len;
767
+ const double xo_max = -0.5 *ww + (jo + 1 )*in_pix_len;
768
+ double overlap_x =
769
+ sycl::min (x_max, xo_max) - sycl::max (x_min, xo_min);
770
+ overlap_x = sycl::max (0.0 , sycl::min (overlap_x, max_overlap));
771
+ const double exp = original_data[io*original_ldw + jo];
772
+ mean_local_error +=
773
+ sycl::fabs (exp - got)*overlap_y*overlap_x;
774
+ }
775
+ }
776
+ mean_local_error /= (pixel_length*pixel_length);
777
+ return mean_local_error;
778
+ };
779
+ // compute the mean local errors and the mean global error
780
+ double mean_error = 0.0 ;
781
+ double * L1_error = (double *)sycl::malloc_device (sizeof (double ), errors.q );
782
+ if (!L1_error)
783
+ die (" cannot allocate memory for mean global error" );
784
+ auto init_ev = errors.q .copy (&mean_error, L1_error, 1 );
785
+ auto compute_errors_ev = errors.q .submit ([&](sycl::handler &cgh) {
786
+ cgh.depends_on (init_ev);
787
+ auto global_integral = sycl::reduction (L1_error, sycl::plus<>());
788
+ double *local_error_data = errors.data ;
789
+ const int local_error_ldw = errors.ldw ();
790
+ cgh.parallel_for <class compute_errors_class >(
791
+ sycl::range<2 >(q, q), global_integral,
792
+ [=](sycl::item<2 > item, auto & sum) {
793
+ const int i = item.get_id (0 );
794
+ const int j = item.get_id (1 );
795
+ const double mean_err_ij = compute_mean_error_in_pixel (i, j);
796
+ local_error_data[i*local_error_ldw + j] = mean_err_ij;
797
+ sum += mean_err_ij * pixel_length * pixel_length;
798
+ }
799
+ );
800
+ });
801
+ errors.q .copy (L1_error, &mean_error, 1 , compute_errors_ev).wait ();
802
+ sycl::free (L1_error, errors.q );
803
+ mean_error /= (hh*ww);
804
+ return mean_error;
805
+ }
806
+
664
807
int main (int argc, char **argv) {
665
808
const int p = argc > 1 ? std::atoi (argv[1 ]) :
666
809
default_p;
@@ -674,8 +817,10 @@ int main(int argc, char **argv) {
674
817
default_radon_bmpname;
675
818
const std::string restored_bmpname = argc > 6 ? argv[6 ] :
676
819
default_restored_bmpname;
820
+ const std::string errors_bmpname = argc > 7 ? argv[7 ] :
821
+ default_errors_bmpname;
677
822
// validate numerical input arguments
678
- if (argc > 7 || p <= 0 || q <= 0 || S_to_D < 1.0 ) {
823
+ if (argc > 8 || p <= 0 || q <= 0 || S_to_D < 1.0 ) {
679
824
die (" invalid usage.\n " + usage_info);
680
825
}
681
826
@@ -714,6 +859,36 @@ int main(int argc, char **argv) {
714
859
reconstruction_from_radon (reconstruction, radon_image, S, radon_ev);
715
860
std::cout << " Saving restored image in " << restored_bmpname << std::endl;
716
861
bmp_write (restored_bmpname, reconstruction);
862
+ // evaluate the mean error, pixel by pixel in the reconstructed image
863
+ padded_matrix errors (main_queue);
864
+ const double mean_error = compute_errors (errors, S, original, reconstruction);
865
+ std::cout << " The mean error in the reconstruction image is " << mean_error
866
+ << std::endl;
867
+ if (mean_error / max_input_value > arbitrary_error_threshold) {
868
+ std::cerr << " The mean error exceeds the (arbitrarily-chosen) "
869
+ << " threshold of " << 100.0 *arbitrary_error_threshold
870
+ << " % of the original image's maximum gray-scale value"
871
+ << std::endl;
872
+ if (std::fabs (p * S_to_D - q) > 0.2 *std::max (p*S_to_D, double (q))) {
873
+ std::cerr << " It is recommended to use values of p and q such that "
874
+ << " p*S_to_D and q are commensurate." << std::endl;
875
+ }
876
+ else if (S / q > 2.0 *in_pix_len) {
877
+ std::cerr << " Consider increasing q (to "
878
+ << std::ceil (S/(2.0 *in_pix_len)) << " or more) "
879
+ << " to alleviate blurring in the reconstructed image."
880
+ << std::endl;
881
+ }
882
+ else {
883
+ std::cerr << " Consider increasing S_to_D and q proportionally to "
884
+ << " one another to reduce interpolation errors."
885
+ << std::endl;
886
+ }
887
+ std::cerr << " Saving local errors in " << errors_bmpname
888
+ << std::endl << std::flush;
889
+ bmp_write (errors_bmpname, errors);
890
+ return EXIT_FAILURE;
891
+ }
717
892
718
893
return EXIT_SUCCESS;
719
894
}
0 commit comments