|
3 | 3 | #include "OrientationAnalysisTestUtils.hpp" |
4 | 4 |
|
5 | 5 | #include "simplnx/Core/Application.hpp" |
| 6 | +#include "simplnx/DataStructure/AttributeMatrix.hpp" |
| 7 | +#include "simplnx/DataStructure/Geometry/ImageGeom.hpp" |
6 | 8 | #include "simplnx/Parameters/ArraySelectionParameter.hpp" |
7 | 9 | #include "simplnx/Parameters/BoolParameter.hpp" |
8 | 10 | #include "simplnx/Parameters/Dream3dImportParameter.hpp" |
9 | 11 | #include "simplnx/Parameters/GeometrySelectionParameter.hpp" |
| 12 | +#include "simplnx/Parameters/MultiArraySelectionParameter.hpp" |
10 | 13 | #include "simplnx/UnitTest/UnitTestCommon.hpp" |
| 14 | +#include "simplnx/Utilities/AlgorithmDispatch.hpp" |
| 15 | +#include "simplnx/Utilities/DataStoreUtilities.hpp" |
11 | 16 |
|
12 | 17 | #include <catch2/catch.hpp> |
13 | 18 |
|
| 19 | +#include <cmath> |
14 | 20 | #include <filesystem> |
15 | 21 |
|
16 | 22 | namespace fs = std::filesystem; |
@@ -38,6 +44,8 @@ using namespace nx::core::UnitTest; |
38 | 44 | TEST_CASE("OrientationAnalysis::NeighborOrientationCorrelationFilter: Small IN100 Pipeline", "[OrientationAnalysis][NeighborOrientationCorrelationFilter]") |
39 | 45 | { |
40 | 46 | UnitTest::LoadPlugins(); |
| 47 | + // 1 Z-slice of quats (largest array): 189*201*4*4 = 607824 bytes |
| 48 | + const UnitTest::PreferencesSentinel prefsSentinel("HDF5-OOC", 600000, true); |
41 | 49 |
|
42 | 50 | const nx::core::UnitTest::TestFileSentinel testDataSentinel(nx::core::unit_test::k_TestFilesDir, "neighbor_orientation_correlation.tar.gz", "neighbor_orientation_correlation.dream3d"); |
43 | 51 |
|
@@ -194,3 +202,165 @@ TEST_CASE("OrientationAnalysis::NeighborOrientationCorrelationFilter: Small IN10 |
194 | 202 |
|
195 | 203 | UnitTest::CheckArraysInheritTupleDims(dataStructure, SmallIn100::k_TupleCheckIgnoredPaths); |
196 | 204 | } |
| 205 | + |
| 206 | +namespace |
| 207 | +{ |
| 208 | +const std::string k_GeomName("Image Geometry"); |
| 209 | +const std::string k_CellDataName("Cell Data"); |
| 210 | + |
| 211 | +const DataPath k_GeomPath({k_GeomName}); |
| 212 | +const DataPath k_CellDataPath = k_GeomPath.createChildPath(k_CellDataName); |
| 213 | +const DataPath k_CIPath = k_CellDataPath.createChildPath("Confidence Index"); |
| 214 | +const DataPath k_QuatsPath = k_CellDataPath.createChildPath("Quats"); |
| 215 | +const DataPath k_PhasesPath = k_CellDataPath.createChildPath("Phases"); |
| 216 | +const DataPath k_CrystalStructuresPath = k_GeomPath.createChildPath("Ensemble Data").createChildPath("CrystalStructures"); |
| 217 | + |
| 218 | +void BuildTestData(DataStructure& dataStructure, usize dimX, usize dimY, usize dimZ, usize blockSize) |
| 219 | +{ |
| 220 | + const ShapeType cellTupleShape = {dimZ, dimY, dimX}; |
| 221 | + const usize sliceSize = dimX * dimY; |
| 222 | + |
| 223 | + auto* imageGeom = ImageGeom::Create(dataStructure, k_GeomName); |
| 224 | + imageGeom->setDimensions({dimX, dimY, dimZ}); |
| 225 | + imageGeom->setSpacing({1.0f, 1.0f, 1.0f}); |
| 226 | + imageGeom->setOrigin({0.0f, 0.0f, 0.0f}); |
| 227 | + |
| 228 | + auto* cellAM = AttributeMatrix::Create(dataStructure, k_CellDataName, cellTupleShape, imageGeom->getId()); |
| 229 | + imageGeom->setCellData(*cellAM); |
| 230 | + |
| 231 | + auto quatsDataStore = DataStoreUtilities::CreateDataStore<float32>(cellTupleShape, {4}, IDataAction::Mode::Execute); |
| 232 | + auto* quatsArray = DataArray<float32>::Create(dataStructure, "Quats", quatsDataStore, cellAM->getId()); |
| 233 | + auto& quatsStore = quatsArray->getDataStoreRef(); |
| 234 | + |
| 235 | + auto phasesDataStore = DataStoreUtilities::CreateDataStore<int32>(cellTupleShape, {1}, IDataAction::Mode::Execute); |
| 236 | + auto* phasesArray = DataArray<int32>::Create(dataStructure, "Phases", phasesDataStore, cellAM->getId()); |
| 237 | + auto& phasesStore = phasesArray->getDataStoreRef(); |
| 238 | + |
| 239 | + auto ciDataStore = DataStoreUtilities::CreateDataStore<float32>(cellTupleShape, {1}, IDataAction::Mode::Execute); |
| 240 | + auto* ciArray = DataArray<float32>::Create(dataStructure, "Confidence Index", ciDataStore, cellAM->getId()); |
| 241 | + auto& ciStore = ciArray->getDataStoreRef(); |
| 242 | + |
| 243 | + const usize blocksPerDimX = dimX / blockSize; |
| 244 | + const usize blocksPerDimY = dimY / blockSize; |
| 245 | + |
| 246 | + std::vector<float32> quatsBuf(sliceSize * 4); |
| 247 | + std::vector<int32> phasesBuf(sliceSize); |
| 248 | + std::vector<float32> ciBuf(sliceSize); |
| 249 | + |
| 250 | + for(usize z = 0; z < dimZ; z++) |
| 251 | + { |
| 252 | + for(usize y = 0; y < dimY; y++) |
| 253 | + { |
| 254 | + for(usize x = 0; x < dimX; x++) |
| 255 | + { |
| 256 | + const usize inSlice = y * dimX + x; |
| 257 | + phasesBuf[inSlice] = 1; |
| 258 | + |
| 259 | + usize bx = x / blockSize; |
| 260 | + usize by = y / blockSize; |
| 261 | + usize bz = z / blockSize; |
| 262 | + float32 angle = static_cast<float32>(bz * blocksPerDimY * blocksPerDimX + by * blocksPerDimX + bx) * 0.1f; |
| 263 | + float32 sinHalf = std::sin(angle * 0.5f); |
| 264 | + float32 cosHalf = std::cos(angle * 0.5f); |
| 265 | + |
| 266 | + const usize qIdx = inSlice * 4; |
| 267 | + quatsBuf[qIdx] = cosHalf; |
| 268 | + quatsBuf[qIdx + 1] = sinHalf * 0.577350269f; // 1/sqrt(3) |
| 269 | + quatsBuf[qIdx + 2] = sinHalf * 0.577350269f; |
| 270 | + quatsBuf[qIdx + 3] = sinHalf * 0.577350269f; |
| 271 | + |
| 272 | + bool isBoundary = (x % blockSize == 0) || (y % blockSize == 0) || (z % blockSize == 0); |
| 273 | + bool isNoisy = ((x * 7 + y * 13 + z * 29) % 10 == 0); |
| 274 | + ciBuf[inSlice] = (isBoundary || isNoisy) ? 0.05f : 0.9f; |
| 275 | + } |
| 276 | + } |
| 277 | + const usize zOffset = z * sliceSize; |
| 278 | + quatsStore.copyFromBuffer(zOffset * 4, nonstd::span<const float32>(quatsBuf.data(), sliceSize * 4)); |
| 279 | + phasesStore.copyFromBuffer(zOffset, nonstd::span<const int32>(phasesBuf.data(), sliceSize)); |
| 280 | + ciStore.copyFromBuffer(zOffset, nonstd::span<const float32>(ciBuf.data(), sliceSize)); |
| 281 | + } |
| 282 | + |
| 283 | + // Ensemble data — small enough for per-element writes |
| 284 | + auto* ensembleAM = AttributeMatrix::Create(dataStructure, "Ensemble Data", {2}, imageGeom->getId()); |
| 285 | + auto crystalStructuresDataStore = DataStoreUtilities::CreateDataStore<uint32>({2}, {1}, IDataAction::Mode::Execute); |
| 286 | + auto* crystalStructuresArray = DataArray<uint32>::Create(dataStructure, "CrystalStructures", crystalStructuresDataStore, ensembleAM->getId()); |
| 287 | + std::array<uint32, 2> csData = {999, 1}; // Unknown, Cubic-High (m-3m) |
| 288 | + crystalStructuresArray->getDataStoreRef().copyFromBuffer(0, nonstd::span<const uint32>(csData.data(), 2)); |
| 289 | +} |
| 290 | +} // namespace |
| 291 | + |
| 292 | +TEST_CASE("OrientationAnalysis::NeighborOrientationCorrelationFilter: Generate Test Data", "[OrientationAnalysis][NeighborOrientationCorrelationFilter][.GenerateTestData]") |
| 293 | +{ |
| 294 | + const auto outputDir = fs::path(unit_test::k_BinaryTestOutputDir.view()) / "generated_test_data" / "neighbor_orientation_correlation"; |
| 295 | + fs::create_directories(outputDir); |
| 296 | + |
| 297 | + // Large input data (200x200x200, blockSize=25) |
| 298 | + { |
| 299 | + DataStructure buildDS; |
| 300 | + BuildTestData(buildDS, 200, 200, 200, 25); |
| 301 | + UnitTest::WriteTestDataStructure(buildDS, outputDir / "large_input.dream3d"); |
| 302 | + fmt::print("Generated large input: {}\n", (outputDir / "large_input.dream3d").string()); |
| 303 | + } |
| 304 | +} |
| 305 | + |
| 306 | +TEST_CASE("OrientationAnalysis::NeighborOrientationCorrelationFilter: 200x200x200 Large OOC", "[OrientationAnalysis][NeighborOrientationCorrelationFilter]") |
| 307 | +{ |
| 308 | + UnitTest::LoadPlugins(); |
| 309 | + bool forceOocAlgo = GENERATE(false, true); |
| 310 | + const nx::core::ForceOocAlgorithmGuard guard(forceOocAlgo); |
| 311 | + // 200x200x200, Quats (float32, 4-comp) => 200*200*4*4 = 640,000 bytes/slice |
| 312 | + const UnitTest::PreferencesSentinel prefsSentinel("HDF5-OOC", 640000, true); |
| 313 | + |
| 314 | + DYNAMIC_SECTION("forceOoc: " << forceOocAlgo) |
| 315 | + { |
| 316 | + constexpr usize k_Dim = 200; |
| 317 | + constexpr usize k_Block = 25; |
| 318 | + |
| 319 | + DataStructure dataStructure; |
| 320 | + BuildTestData(dataStructure, k_Dim, k_Dim, k_Dim, k_Block); |
| 321 | + |
| 322 | + const NeighborOrientationCorrelationFilter filter; |
| 323 | + Arguments args; |
| 324 | + args.insertOrAssign(NeighborOrientationCorrelationFilter::k_ImageGeometryPath_Key, std::make_any<DataPath>(k_GeomPath)); |
| 325 | + args.insertOrAssign(NeighborOrientationCorrelationFilter::k_MinConfidence_Key, std::make_any<float32>(0.2f)); |
| 326 | + args.insertOrAssign(NeighborOrientationCorrelationFilter::k_MisorientationTolerance_Key, std::make_any<float32>(5.0f)); |
| 327 | + args.insertOrAssign(NeighborOrientationCorrelationFilter::k_Level_Key, std::make_any<int32>(2)); |
| 328 | + args.insertOrAssign(NeighborOrientationCorrelationFilter::k_CorrelationArrayPath_Key, std::make_any<DataPath>(k_CIPath)); |
| 329 | + args.insertOrAssign(NeighborOrientationCorrelationFilter::k_CellPhasesArrayPath_Key, std::make_any<DataPath>(k_PhasesPath)); |
| 330 | + args.insertOrAssign(NeighborOrientationCorrelationFilter::k_QuatsArrayPath_Key, std::make_any<DataPath>(k_QuatsPath)); |
| 331 | + args.insertOrAssign(NeighborOrientationCorrelationFilter::k_CrystalStructuresArrayPath_Key, std::make_any<DataPath>(k_CrystalStructuresPath)); |
| 332 | + args.insertOrAssign(NeighborOrientationCorrelationFilter::k_IgnoredDataArrayPaths_Key, std::make_any<MultiArraySelectionParameter::ValueType>(MultiArraySelectionParameter::ValueType{})); |
| 333 | + |
| 334 | + auto preflightResult = filter.preflight(dataStructure, args); |
| 335 | + SIMPLNX_RESULT_REQUIRE_VALID(preflightResult.outputActions); |
| 336 | + |
| 337 | + auto executeResult = filter.execute(dataStructure, args); |
| 338 | + SIMPLNX_RESULT_REQUIRE_VALID(executeResult.result); |
| 339 | + |
| 340 | + // Some low-CI voxels should have been modified — use Z-slice batched reads for OOC efficiency |
| 341 | + const auto& ciAfter = dataStructure.getDataRefAs<Float32Array>(k_CIPath).getDataStoreRef(); |
| 342 | + const usize sliceSize = k_Dim * k_Dim; |
| 343 | + std::vector<float32> ciBuf(sliceSize); |
| 344 | + usize modifiedCount = 0; |
| 345 | + for(usize z = 0; z < k_Dim; z++) |
| 346 | + { |
| 347 | + ciAfter.copyIntoBuffer(z * sliceSize, nonstd::span<float32>(ciBuf.data(), sliceSize)); |
| 348 | + for(usize y = 0; y < k_Dim; y++) |
| 349 | + { |
| 350 | + for(usize x = 0; x < k_Dim; x++) |
| 351 | + { |
| 352 | + const usize inSlice = y * k_Dim + x; |
| 353 | + bool wasBoundary = (x % k_Block == 0) || (y % k_Block == 0) || (z % k_Block == 0); |
| 354 | + bool wasNoisy = ((x * 7 + y * 13 + z * 29) % 10 == 0); |
| 355 | + if((wasBoundary || wasNoisy) && ciBuf[inSlice] != 0.05f) |
| 356 | + { |
| 357 | + modifiedCount++; |
| 358 | + } |
| 359 | + } |
| 360 | + } |
| 361 | + } |
| 362 | + REQUIRE(modifiedCount > 0); |
| 363 | + |
| 364 | + UnitTest::CheckArraysInheritTupleDims(dataStructure); |
| 365 | + } |
| 366 | +} |
0 commit comments