Skip to content

Commit b2d9a8a

Browse files
committed
[oneMKL samples][computed tomography] Adding verification to the sample.
1 parent 8ed9d8d commit b2d9a8a

File tree

3 files changed

+179
-4
lines changed

3 files changed

+179
-4
lines changed

Libraries/oneMKL/computed_tomography/GNUmakefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,6 @@ computed_tomography: computed_tomography.cpp
1515
icpx $< -fsycl -o $@ $(DPCPP_OPTS)
1616

1717
clean:
18-
-rm -f computed_tomography radon.bmp restored.bmp
18+
-rm -f computed_tomography radon.bmp restored.bmp errors.bmp
1919

2020
.PHONY: clean run all

Libraries/oneMKL/computed_tomography/computed_tomography.cpp

Lines changed: 177 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,18 @@ constexpr double default_S_to_D = 1.0;
1717
static const std::string default_original_bmpname = "input.bmp";
1818
static const std::string default_radon_bmpname = "radon.bmp";
1919
static const std::string default_restored_bmpname = "restored.bmp";
20+
static const std::string default_errors_bmpname = "errors.bmp";
2021
#ifdef _WIN64
2122
static const std::string program_name = "compute_tomography.exe";
2223
#else
2324
static const std::string program_name = "compute_tomography";
2425
#endif
26+
constexpr double arbitrary_error_threshold = 0.1;
2527
static const std::string usage_info =
2628
"\n\
2729
Usage:\n\
2830
======\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\
3032
Inputs:\n\
3133
-------\n\
3234
p - number of projection directions considered for the Radon\n\
@@ -56,6 +58,14 @@ static const std::string usage_info =
5658
gray-scaled image representing the image reconstructed\n\
5759
from the Radon transform data points.\n\
5860
\"" + 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\
5969
";
6070

6171
#include <cmath>
@@ -661,6 +671,139 @@ void reconstruction_from_radon(padded_matrix& image,
661671
compute_g_values.wait();
662672
}
663673

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+
664807
int main(int argc, char **argv) {
665808
const int p = argc > 1 ? std::atoi(argv[1]) :
666809
default_p;
@@ -674,8 +817,10 @@ int main(int argc, char **argv) {
674817
default_radon_bmpname;
675818
const std::string restored_bmpname = argc > 6 ? argv[6] :
676819
default_restored_bmpname;
820+
const std::string errors_bmpname = argc > 7 ? argv[7] :
821+
default_errors_bmpname;
677822
// 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) {
679824
die("invalid usage.\n" + usage_info);
680825
}
681826

@@ -714,6 +859,36 @@ int main(int argc, char **argv) {
714859
reconstruction_from_radon(reconstruction, radon_image, S, radon_ev);
715860
std::cout << "Saving restored image in " << restored_bmpname << std::endl;
716861
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+
}
717892

718893
return EXIT_SUCCESS;
719894
}

Libraries/oneMKL/computed_tomography/makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,6 @@ computed_tomography.exe: computed_tomography.cpp
1313
icx-cl -fsycl computed_tomography.cpp /Fecomputed_tomography.exe $(DPCPP_OPTS)
1414

1515
clean:
16-
del /q computed_tomography.exe computed_tomography.exp computed_tomography.lib radon.bmp restored.bmp
16+
del /q computed_tomography.exe computed_tomography.exp computed_tomography.lib radon.bmp restored.bmp errors.bmp
1717

1818
pseudo: clean run all

0 commit comments

Comments
 (0)