// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen // SPDX-License-Identifier: BSD-3-Clause #include "vtkDataArray.h" #include "vtkImageData.h" #include "vtkImageImport.h" #include "vtkImageInterpolator.h" #include "vtkMathUtilities.h" #include "vtkMatrix3x3.h" #include "vtkNew.h" #include "vtkPointData.h" #include // Test functions namespace { // Print ": (a, b, c) != (x, y, z)" template void PrintError(const char* text, const T* x, const T* y, int size) { std::cout << text << " "; const T* v = x; for (int j = 0; j < 2; ++j) { if (j != 0) { std::cout << " != "; } const char* delim = ""; std::cout << "("; for (int i = 0; i < size; ++i) { std::cout << delim << v[i]; delim = ", "; } std::cout << ")"; v = y; } std::cout << std::endl; } // Compare vectors with a small tolerance template bool CompareVectorFuzzy(const char* text, const T* x, const T* y, int size, T tol) { for (int i = 0; i < size; ++i) { if (!vtkMathUtilities::FuzzyCompare(x[i], y[i], tol)) { PrintError(text, x, y, size); return false; } } return true; } bool TestImageNoDirection() { std::cout << "Testing with no image direction:" << std::endl; // basic image information constexpr double origin[3] = { 5.1234165, -12.09375, 0.857643 }; constexpr double spacing[3] = { 5.0, 2.0, 3.0 }; int extent[6] = { 0, 19, 0, 49, 0, 29 }; // the input image vtkNew input; input->SetSpacing(spacing); input->SetOrigin(origin); input->SetExtent(extent); input->AllocateScalars(VTK_UNSIGNED_CHAR, 3); // set the data values to index times spacing for testing vtkDataArray* pixels = input->GetPointData()->GetScalars(); vtkIdType idx = 0; for (int k = extent[4]; k <= extent[5]; ++k) { for (int j = extent[2]; j <= extent[3]; ++j) { for (int i = extent[0]; i <= extent[1]; ++i) { pixels->SetTuple3(idx++, i * spacing[0], j * spacing[1], k * spacing[2]); } } } vtkNew interpolator; interpolator->Initialize(input); interpolator->SetInterpolationModeToLinear(); interpolator->SetOutValue(255); bool success = true; double points[20][3] = { // random points within image bounds { 80.6616917, 16.2843800, 58.7851199 }, { 62.5605082, 47.4324900, 51.7575806 }, { 90.4047476, -5.3094740, 9.7738484 }, { 87.2008769, 14.7361779, 45.2379583 }, { 44.8079918, 33.3200863, 87.2547544 }, { 11.4262999, 53.5768517, 87.0814281 }, { 64.8173899, 2.8272480, 45.1864982 }, { 90.5444488, 22.7720092, 70.7924360 }, { 97.1644100, 66.8605872, 54.7898647 }, { 44.9673259, 60.1069581, 44.9178906 }, // some out-of-bounds points { 88.9490496, 55.7590724, 89.1766919 }, { 103.9643647, 50.7450499, 79.7674468 }, { 97.7313546, 34.6269689, 88.6995403 }, { 101.9604439, 46.7481909, 67.1910364 }, { 77.4775538, 84.4625972, -1.9124923 }, { 103.5592146, 82.3749221, 10.9448482 }, { 102.0220226, 3.3229726, 38.7948792 }, { 49.7995352, 72.2975327, 0.6357556 }, { 40.6762731, 57.7901959, 88.4700388 }, { 0.8091914, -5.9155755, 39.3323177 }, }; for (int i = 0; i < 20; ++i) { // expected value if point is out of bounds double expectedValue[3] = { 255.0, 255.0, 255.0 }; if (i < 10) { // expected interpolated value if point is within bounds expectedValue[0] = points[i][0] - origin[0]; expectedValue[1] = points[i][1] - origin[1]; expectedValue[2] = points[i][2] - origin[2]; } double value[3]; bool inBounds = interpolator->Interpolate(points[i], value); success &= CompareVectorFuzzy("Value:", value, expectedValue, 3, 1e-4); if (!inBounds && i < 10) { std::cout << "Point " << i << " incorrectly marked out-of-bounds\n"; success = false; } else if (inBounds && i >= 10) { std::cout << "Point " << i << " incorrectly marked in-bounds\n"; success = false; } } if (success) { std::cout << "Success!" << std::endl; } return success; } bool TestImageWithDirection() { std::cout << "Testing with image direction:" << std::endl; // basic image information constexpr double origin[3] = { 5.1234165, -12.09375, 0.857643 }; constexpr double direction[9] = { // 3x3 matrix -0.5618556200580342, -0.34610201570703625, 0.7513532171573689, // 1st row 0.826672975724893, -0.2014274000446417, 0.525393941454797, // 2nd row -0.030496777130583796, 0.9163189385987733, 0.39928629997767906 // 3rd row }; constexpr double spacing[3] = { 5.0, 2.0, 3.0 }; int extent[6] = { 0, 19, 0, 49, 0, 29 }; // the direction matrix vtkNew matrix; matrix->DeepCopy(direction); vtkNew matrixInverse; vtkMatrix3x3::Invert(matrix, matrixInverse); // the input image vtkNew input; input->SetSpacing(spacing); input->SetDirectionMatrix(matrix); input->SetOrigin(origin); input->SetExtent(extent); input->AllocateScalars(VTK_FLOAT, 3); // set the data values to index times spacing for testing vtkDataArray* pixels = input->GetPointData()->GetScalars(); vtkIdType idx = 0; for (int k = extent[4]; k <= extent[5]; ++k) { for (int j = extent[2]; j <= extent[3]; ++j) { for (int i = extent[0]; i <= extent[1]; ++i) { pixels->SetTuple3(idx++, i * spacing[0], j * spacing[1], k * spacing[2]); } } } vtkNew interpolator; interpolator->Initialize(input); interpolator->SetInterpolationModeToLinear(); bool success = true; double points[20][3] = { // random points within image bounds { 2.8462919, -8.5164647, 12.7463881 }, { 9.9924921, 72.6033040, 61.1140928 }, { -31.8801448, 59.3476671, 43.7503173 }, { 39.0528309, 15.6922992, 109.4925365 }, { -60.8563933, 50.6743971, 92.7543887 }, { -39.5351161, 64.8472584, 89.8646631 }, { -26.7609181, 33.8420170, 31.1569328 }, { -18.8116334, 76.8430662, 79.6248037 }, { 17.6741224, 57.6621854, 94.5859167 }, { -11.5610305, 90.3415364, 61.0285089 }, // some out-of-bounds points { 34.5208733, 66.5281775, 62.0378498 }, { 39.0771521, 12.3222314, 61.3746313 }, { 60.0838912, 46.9133954, 44.1492156 }, { -22.9413822, 72.5683548, 116.1745177 }, { -0.5906894, 91.1229086, 94.1050301 }, { -22.6683535, 37.8556764, 110.2892534 }, { -8.0201237, 93.3770987, 17.0129858 }, { 35.5092073, 50.1582868, 80.5899676 }, { 29.7921164, 30.2326700, 17.7492048 }, { -21.4728238, 90.5506128, 54.2677831 }, }; for (int i = 0; i < 20; ++i) { // expected value if point is out of bounds double expectedValue[3] = { 0.0, 0.0, 0.0 }; if (i < 10) { // expected interpolated value if point is in bounds, // pixel values are equal to image coordinate with no // orientation (direction) or offset (origin) expectedValue[0] = points[i][0] - origin[0]; expectedValue[1] = points[i][1] - origin[1]; expectedValue[2] = points[i][2] - origin[2]; matrixInverse->MultiplyPoint(expectedValue, expectedValue); } double value[3]; bool inBounds = interpolator->Interpolate(points[i], value); success &= CompareVectorFuzzy("Value:", value, expectedValue, 3, 1e-4); if (!inBounds && i < 10) { std::cout << "Point " << i << " incorrectly marked out-of-bounds\n"; success = false; } else if (inBounds && i >= 10) { std::cout << "Point " << i << " incorrectly marked in-bounds\n"; success = false; } } if (success) { std::cout << "Success!" << std::endl; } return success; } bool TestInterpolateRow() { std::cout << "Test interpolating a row of pixels:" << std::endl; // basic image information constexpr double origin[3] = { 0.0, 0.0, 0.0 }; constexpr double spacing[3] = { 1.0, 1.0, 1.0 }; int extent[6] = { 0, 1, 2, 3, 0, 9 }; // the input image vtkNew input; input->SetSpacing(spacing); input->SetOrigin(origin); input->SetExtent(extent); input->AllocateScalars(VTK_FLOAT, 3); // set the data values to index times spacing for testing vtkDataArray* pixels = input->GetPointData()->GetScalars(); vtkIdType idx = 0; for (int k = extent[4]; k <= extent[5]; ++k) { for (int j = extent[2]; j <= extent[3]; ++j) { for (int i = extent[0]; i <= extent[1]; ++i) { pixels->SetTuple3(idx++, i * spacing[0], j * spacing[1], k * spacing[2]); } } } constexpr double matrix[16] = { // matrix exchanges x and z 0.0, 0.0, 0.25, 0.0, // 0.0, 0.25, 0.0, 0.0, // 0.25, 0.0, 0.0, 0.0, // 0.0, 0.0, 0.0, 1.0, // }; constexpr int outExtent[6] = { -2, 37, 0, 15, 2, 2 }; vtkNew interpolator; interpolator->SetInterpolationModeToCubic(); interpolator->Initialize(input); double interpolatedData[114]; int checkExtent[6]; vtkInterpolationWeights* weights; interpolator->PrecomputeWeightsForExtent(matrix, outExtent, checkExtent, weights); interpolator->InterpolateRow(weights, -1, 7, 2, interpolatedData, 38); interpolator->FreePrecomputedWeights(weights); bool success = true; constexpr int expectedCheck[6] = { 0, 36, 8, 12, 2, 2 }; constexpr double expectedData[114] = { // 3 components per pixel 0.500000, 1.929688, -0.070312, // 0.500000, 1.929688, 0.000000, // 0.500000, 1.929688, 0.179688, // 0.500000, 1.929688, 0.437500, // 0.500000, 1.929688, 0.726562, // 0.500000, 1.929688, 1.000000, // 0.500000, 1.929688, 1.250000, // 0.500000, 1.929688, 1.500000, // 0.500000, 1.929688, 1.750000, // 0.500000, 1.929688, 2.000000, // 0.500000, 1.929688, 2.250000, // 0.500000, 1.929688, 2.500000, // 0.500000, 1.929688, 2.750000, // 0.500000, 1.929688, 3.000000, // 0.500000, 1.929688, 3.250000, // 0.500000, 1.929688, 3.500000, // 0.500000, 1.929688, 3.750000, // 0.500000, 1.929688, 4.000000, // 0.500000, 1.929688, 4.250000, // 0.500000, 1.929688, 4.500000, // 0.500000, 1.929688, 4.750000, // 0.500000, 1.929688, 5.000000, // 0.500000, 1.929688, 5.250000, // 0.500000, 1.929688, 5.500000, // 0.500000, 1.929688, 5.750000, // 0.500000, 1.929688, 6.000000, // 0.500000, 1.929688, 6.250000, // 0.500000, 1.929688, 6.500000, // 0.500000, 1.929688, 6.750000, // 0.500000, 1.929688, 7.000000, // 0.500000, 1.929688, 7.250000, // 0.500000, 1.929688, 7.500000, // 0.500000, 1.929688, 7.750000, // 0.500000, 1.929688, 8.000000, // 0.500000, 1.929688, 8.273438, // 0.500000, 1.929688, 8.562500, // 0.500000, 1.929688, 8.820312, // 0.500000, 1.929688, 9.000000, // }; std::cout << "CheckExtent:"; for (int i = 0; i < 6; i++) { std::cout << " " << checkExtent[i]; } // check extent should be input extent divided by matrix if (checkExtent[0] != expectedCheck[0] || checkExtent[1] != expectedCheck[1] || checkExtent[2] != expectedCheck[2] || checkExtent[3] != expectedCheck[3] || checkExtent[4] != expectedCheck[4] || checkExtent[5] != expectedCheck[5]) { std::cout << " !="; for (int i = 0; i < 6; i++) { std::cout << " " << expectedCheck[i]; } success = false; } std::cout << std::endl; std::ios_base::fmtflags savedFlags = std::cout.flags(); std::cout << "OutputData:" << std::endl; std::cout << std::fixed << std::setprecision(6); for (int i = 0; i < 38; i++) { std::cout << " " // << interpolatedData[3 * i] << ", " // << interpolatedData[3 * i + 1] << ", " // << interpolatedData[3 * i + 2] << ","; // bool different = false; for (int j = 0; j < 3; j++) { if (!vtkMathUtilities::FuzzyCompare( interpolatedData[3 * i + j], expectedData[3 * i + j], 1e-6)) { different = true; break; } } if (different) { std::cout << " // expected: " // << expectedData[3 * i] << ", " // << expectedData[3 * i + 1] << ", " // << expectedData[3 * i + 2]; // success = false; } std::cout << std::endl; } std::cout.flags(savedFlags); if (success) { std::cout << "Success!" << std::endl; } return success; } } // Driver Function int ImageInterpolator(int, char*[]) { bool success = true; success &= TestImageNoDirection(); success &= TestImageWithDirection(); success &= TestInterpolateRow(); return success ? EXIT_SUCCESS : EXIT_FAILURE; }