Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
59faca0
ENH: Add core out-of-core (OOC) architecture for simplnx
joeykleingers May 12, 2026
de2c9d5
COMP: Migrate DREAM3DFileTest compression cases to new DREAM3D loader…
joeykleingers May 12, 2026
ca303db
REF: Replace OOC range-scan-timeout stub with auto-range-computation …
joeykleingers May 19, 2026
c9df34f
ENH: Add SimplnxConfig.hpp.in template for SIMPLNX_USE_OOC macro
joeykleingers May 28, 2026
83e64aa
ENH: Add SIMPLNX_USE_OOC option, source-dir guard, and SimplnxConfig.…
joeykleingers May 28, 2026
c26ffcb
REF: Reach OOC through compile-time SIMPLNX_USE_OOC direct calls
joeykleingers May 28, 2026
090d384
ENH: Add Preferences::removeValue helper
joeykleingers Apr 23, 2026
c836761
ENH: Add --ooc-memory-budget flag parsing and help to nxrunner
joeykleingers Apr 23, 2026
479e2ee
ENH: Wire --ooc-memory-budget through to OOC budget manager
joeykleingers Apr 23, 2026
3e671ff
ENH: Harden --ooc-memory-budget parsing against NaN, inf, and trailin…
joeykleingers Apr 23, 2026
f04efe8
ENH: Include <cmath> explicitly in nxrunner; drop unused <stdexcept>
joeykleingers Apr 23, 2026
e9a4ee2
ENH: Rename nxrunner --ooc-memory-budget flag to --memory-budget
joeykleingers May 12, 2026
1773602
ENH: Register OOC format from DataIOCollection ctor; fix in-core Data…
joeykleingers May 29, 2026
35f39a2
REFACTOR: Rename algorithm files for OOC dispatch preparation
joeykleingers Mar 31, 2026
9018839
PERF: Out-of-core (OOC) optimized algorithms for SimplnxCore and Orie…
joeykleingers Apr 8, 2026
29f746c
FIX: Resolve OOC store format in tests and fix ComputeAvgOrientations…
joeykleingers Apr 14, 2026
ff7d63b
DOCS: Add comprehensive Doxygen and inline documentation for OOC-opti…
joeykleingers Apr 14, 2026
89cc813
COMP: Fix compile error in Windows
imikejackson Apr 17, 2026
bd204f7
PERF: OOC-optimize WritePoleFigure per-element input reads and image …
joeykleingers Apr 20, 2026
39418e0
TEST: Register exemplar archives for OOC-optimized filter tests
joeykleingers Apr 21, 2026
03971d7
PERF: Optimize CropImageGeometry with Z-slab batching for OOC
joeykleingers Apr 22, 2026
2c74178
PERF: Optimize RequireMinimumSizeFeatures memory and I/O patterns
joeykleingers Apr 22, 2026
bc627b8
PERF: Optimize ExtractInternalSurfacesFromTriangleGeometry for OOC
joeykleingers Apr 22, 2026
7c66029
PERF: Optimize ApplyTransformationToGeometry for out-of-core data
joeykleingers Apr 22, 2026
e708e39
PERF: Optimize ComputeTriangleAreas with chunked bulk vertex loads
joeykleingers Apr 22, 2026
383b6b7
PERF: Increase ComputeFeatureSizes chunk size for multi-billion-voxel…
joeykleingers Apr 22, 2026
99d6dc3
DOC: Expand CropImageGeometry algorithm documentation
joeykleingers Apr 22, 2026
c690b77
DOC: Expand RequireMinimumSizeFeatures algorithm documentation
joeykleingers Apr 22, 2026
d38980e
DOC: Add ExtractInternalSurfacesFromTriangleGeometry algorithm section
joeykleingers Apr 22, 2026
fccc1e2
DOC: Add ApplyTransformationToGeometry algorithm section
joeykleingers Apr 22, 2026
8016ac9
DOC: Add ComputeTriangleAreas algorithm section
joeykleingers Apr 22, 2026
97d9e48
DOC: Expand ComputeFeatureSizes algorithm documentation
joeykleingers Apr 22, 2026
e14b2af
BUG: Use double-precision Pi/180 in ComputeGBCDMetricBased
joeykleingers Apr 23, 2026
c97bc35
ENH: Apply PR #1590 to OOC dispatch variants and fix bool-mask bulk I/O
joeykleingers Apr 27, 2026
427e40d
ENH: Unify CreateDataStore and CreateListStore through format resolver
joeykleingers Apr 29, 2026
7d1ce8f
ENH: Register OOC format from DataIOCollection ctor; fix in-core Data…
joeykleingers May 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
92 changes: 92 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,36 @@ option(SIMPLNX_DOWNLOAD_TEST_FILES "Download the test files" ON)
# ------------------------------------------------------------------------------
option(SIMPLNX_WRITE_TEST_OUTPUT "Write unit test output files" OFF)

# ------------------------------------------------------------------------------
# Controls which algorithm paths are exercised by dual-dispatch unit tests.
# 0 (Both) - tests run with forceOoc=false AND forceOoc=true (default)
# 1 (OocOnly) - tests run with forceOoc=true only (use for OOC builds)
# 2 (InCoreOnly) - tests run with forceOoc=false only (quick validation)
# ------------------------------------------------------------------------------
set(SIMPLNX_TEST_ALGORITHM_PATH "0" CACHE STRING "Algorithm paths to test: 0=Both, 1=OocOnly, 2=InCoreOnly")

# ------------------------------------------------------------------------------
# Out-of-core support compile-time switch
# ------------------------------------------------------------------------------
option(SIMPLNX_USE_OOC "Compile out-of-core support into simplnx (requires SIMPLNX_OOC_SOURCE_DIR)" OFF)
set(SIMPLNX_OOC_SOURCE_DIR "" CACHE PATH "Path to the private SimplnxOoc source directory (required when SIMPLNX_USE_OOC=ON)")

if(SIMPLNX_USE_OOC)
if(NOT SIMPLNX_OOC_SOURCE_DIR OR NOT EXISTS "${SIMPLNX_OOC_SOURCE_DIR}")
message(FATAL_ERROR
"SIMPLNX_USE_OOC=ON requires SIMPLNX_OOC_SOURCE_DIR to point at the private SimplnxOoc repo. "
"Set -DSIMPLNX_OOC_SOURCE_DIR=/path/to/SimplnxOoc/SimplnxOoc")
endif()
else()
# With OOC compiled out there are no out-of-core algorithm paths to exercise,
# so pin the test path to InCoreOnly. Forcing it avoids a stale cached 0/1
# (Both/OocOnly) producing tests that can never run.
if(NOT SIMPLNX_TEST_ALGORITHM_PATH STREQUAL "2")
message(STATUS "SIMPLNX_USE_OOC=OFF: forcing SIMPLNX_TEST_ALGORITHM_PATH=2 (InCoreOnly).")
set(SIMPLNX_TEST_ALGORITHM_PATH "2" CACHE STRING "Algorithm paths to test: 0=Both, 1=OocOnly, 2=InCoreOnly" FORCE)
endif()
endif()

# ------------------------------------------------------------------------------
# Is the SimplnxCore Plugin enabled [DEFAULT=ON]
# ------------------------------------------------------------------------------
Expand Down Expand Up @@ -261,6 +291,7 @@ if(SIMPLNX_ENABLE_MULTICORE)
target_link_libraries(simplnx PUBLIC TBB::tbb)
endif()


target_link_libraries(simplnx
PUBLIC
fmt::fmt
Expand Down Expand Up @@ -293,6 +324,11 @@ if(SIMPLNX_ENABLE_LINK_FILESYSTEM)
endif()

set(SIMPLNX_GENERATED_DIR ${simplnx_BINARY_DIR}/generated)

configure_file(
${simplnx_SOURCE_DIR}/cmake/SimplnxConfig.hpp.in
${SIMPLNX_GENERATED_DIR}/simplnx/Common/SimplnxConfig.hpp
@ONLY)
set(SIMPLNX_GENERATED_HEADER_DIR ${simplnx_BINARY_DIR}/generated/simplnx)
set(SIMPLNX_EXPORT_HEADER ${SIMPLNX_GENERATED_HEADER_DIR}/simplnx_export.hpp)

Expand Down Expand Up @@ -351,6 +387,7 @@ set(SIMPLNX_HDRS
${SIMPLNX_SOURCE_DIR}/Common/Constants.hpp
${SIMPLNX_SOURCE_DIR}/Common/DataTypeUtilities.hpp
${SIMPLNX_SOURCE_DIR}/Common/DataVector.hpp
${SIMPLNX_SOURCE_DIR}/Common/Extent.hpp
${SIMPLNX_SOURCE_DIR}/Common/EulerAngle.hpp
${SIMPLNX_SOURCE_DIR}/Common/Filesystem.hpp
${SIMPLNX_SOURCE_DIR}/Common/IteratorUtility.hpp
Expand Down Expand Up @@ -465,6 +502,7 @@ set(SIMPLNX_HDRS
${SIMPLNX_SOURCE_DIR}/DataStructure/DynamicListArray.hpp
${SIMPLNX_SOURCE_DIR}/DataStructure/EmptyDataStore.hpp
${SIMPLNX_SOURCE_DIR}/DataStructure/EmptyListStore.hpp
${SIMPLNX_SOURCE_DIR}/DataStructure/EmptyStringStore.hpp
${SIMPLNX_SOURCE_DIR}/DataStructure/IArray.hpp
${SIMPLNX_SOURCE_DIR}/DataStructure/IDataArray.hpp
${SIMPLNX_SOURCE_DIR}/DataStructure/IDataStore.hpp
Expand Down Expand Up @@ -546,6 +584,7 @@ set(SIMPLNX_HDRS
${SIMPLNX_SOURCE_DIR}/Utilities/DataGroupUtilities.hpp
${SIMPLNX_SOURCE_DIR}/Utilities/DataObjectUtilities.hpp
${SIMPLNX_SOURCE_DIR}/Utilities/DataStoreUtilities.hpp
${SIMPLNX_SOURCE_DIR}/Utilities/AlgorithmDispatch.hpp
${SIMPLNX_SOURCE_DIR}/Utilities/FilePathGenerator.hpp
${SIMPLNX_SOURCE_DIR}/Utilities/ColorTableUtilities.hpp
${SIMPLNX_SOURCE_DIR}/Utilities/FileUtilities.hpp
Expand All @@ -556,6 +595,7 @@ set(SIMPLNX_HDRS
${SIMPLNX_SOURCE_DIR}/Utilities/HistogramUtilities.hpp
${SIMPLNX_SOURCE_DIR}/Utilities/MaskCompareUtilities.hpp
${SIMPLNX_SOURCE_DIR}/Utilities/MemoryUtilities.hpp
${SIMPLNX_SOURCE_DIR}/Utilities/MemoryBudgetManager.hpp
${SIMPLNX_SOURCE_DIR}/Utilities/MessageHelper.hpp
${SIMPLNX_SOURCE_DIR}/Utilities/StringUtilities.hpp
${SIMPLNX_SOURCE_DIR}/Utilities/StringInterpretationUtilities.hpp
Expand All @@ -568,6 +608,7 @@ set(SIMPLNX_HDRS
${SIMPLNX_SOURCE_DIR}/Utilities/SamplingUtils.hpp
${SIMPLNX_SOURCE_DIR}/Utilities/SegmentFeatures.hpp
${SIMPLNX_SOURCE_DIR}/Utilities/TimeUtilities.hpp
${SIMPLNX_SOURCE_DIR}/Utilities/UnionFind.hpp
${SIMPLNX_SOURCE_DIR}/Utilities/TooltipGenerator.hpp
${SIMPLNX_SOURCE_DIR}/Utilities/TooltipRowItem.hpp
${SIMPLNX_SOURCE_DIR}/Utilities/OStreamUtilities.hpp
Expand Down Expand Up @@ -770,6 +811,7 @@ set(SIMPLNX_SRCS
${SIMPLNX_SOURCE_DIR}/Utilities/DataStoreUtilities.cpp
${SIMPLNX_SOURCE_DIR}/Utilities/MaskCompareUtilities.cpp
${SIMPLNX_SOURCE_DIR}/Utilities/MemoryUtilities.cpp
${SIMPLNX_SOURCE_DIR}/Utilities/MemoryBudgetManager.cpp
${SIMPLNX_SOURCE_DIR}/Utilities/MessageHelper.cpp
${SIMPLNX_SOURCE_DIR}/Utilities/IParallelAlgorithm.cpp
${SIMPLNX_SOURCE_DIR}/Utilities/ParallelDataAlgorithm.cpp
Expand Down Expand Up @@ -893,6 +935,49 @@ target_include_directories(simplnx
$<INSTALL_INTERFACE:include>
)

# ------------------------------------------------------------------------------
# Out-of-core sources (SIMPLNX_USE_OOC=ON)
#
# Copy the private SimplnxOoc sources into simplnx's build tree and compile them
# directly into libsimplnx. This keeps the public simplnx source repo free of
# OOC code while letting simplnx core call SimplnxOoc:: functions directly with
# no separate library and no simplnx->SimplnxOoc->simplnx link cycle. The copy
# target lives under SIMPLNX_GENERATED_DIR, which is already on simplnx's PUBLIC
# include path, so `#include "SimplnxOoc/X.hpp"` resolves for every consumer.
# ------------------------------------------------------------------------------
if(SIMPLNX_USE_OOC)
file(GLOB SIMPLNX_OOC_HDRS CONFIGURE_DEPENDS "${SIMPLNX_OOC_SOURCE_DIR}/*.hpp")
file(GLOB SIMPLNX_OOC_SRCS CONFIGURE_DEPENDS "${SIMPLNX_OOC_SOURCE_DIR}/*.cpp")

# The partitioned store (unstructured/poly OOC) is deferred — exclude it from
# the compiled set so the deferred, #if 0'd code never reaches the compiler.
list(FILTER SIMPLNX_OOC_HDRS EXCLUDE REGEX "HDF5PartitionedStore")
list(FILTER SIMPLNX_OOC_SRCS EXCLUDE REGEX "HDF5PartitionedStore")

set(SIMPLNX_OOC_COPY_DIR "${SIMPLNX_GENERATED_DIR}/SimplnxOoc")
set(SIMPLNX_OOC_COPIED_SRCS "")
foreach(_oocSrc ${SIMPLNX_OOC_HDRS} ${SIMPLNX_OOC_SRCS})
get_filename_component(_oocName "${_oocSrc}" NAME)
configure_file("${_oocSrc}" "${SIMPLNX_OOC_COPY_DIR}/${_oocName}" COPYONLY)
if(_oocSrc MATCHES "\\.cpp$")
list(APPEND SIMPLNX_OOC_COPIED_SRCS "${SIMPLNX_OOC_COPY_DIR}/${_oocName}")
endif()
endforeach()

# Generated shim: the OOC code compiles into libsimplnx, so its export macro
# maps onto simplnx's own export macro.
file(WRITE "${SIMPLNX_OOC_COPY_DIR}/SimplnxOoc_export.hpp"
"#pragma once\n#include \"simplnx/simplnx_export.hpp\"\n#define SIMPLNXOOC_EXPORT SIMPLNX_EXPORT\n")

target_sources(simplnx PRIVATE ${SIMPLNX_OOC_COPIED_SRCS})

# The chunked-store .cpp files explicitly instantiate many template classes,
# exceeding the default COFF section limit on MSVC.
if(MSVC)
set_source_files_properties(${SIMPLNX_OOC_COPIED_SRCS} PROPERTIES COMPILE_OPTIONS "/bigobj")
endif()
endif()

cmake_dependent_option(SIMPLNX_DOWNLOAD_TEST_FILES_FIRST "Forces test files to download before simplnx builds" OFF "SIMPLNX_DOWNLOAD_TEST_FILES" OFF)
if(SIMPLNX_DOWNLOAD_TEST_FILES_FIRST)
add_dependencies(simplnx Fetch_Remote_Data_Files)
Expand Down Expand Up @@ -1063,6 +1148,13 @@ if(SIMPLNX_BUILD_TESTS)
find_package(ZLIB REQUIRED)
include(CTest)
add_subdirectory(test)

# Build the private SimplnxOoc test suite against libsimplnx (which carries
# the OOC symbols when SIMPLNX_USE_OOC=ON). This single rule serves both ON
# contexts: standalone simplnx and the simplnx subdirectory inside DREAM3D-NX.
if(SIMPLNX_USE_OOC)
add_subdirectory("${SIMPLNX_OOC_SOURCE_DIR}/test" ${simplnx_BINARY_DIR}/SimplnxOoc_test)
endif()
endif()

if(SIMPLNX_BUILD_PYTHON)
Expand Down
1 change: 1 addition & 0 deletions cmake/Plugin.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,7 @@ function(create_simplnx_plugin_unit_test)
target_compile_definitions(${UNIT_TEST_TARGET}
PRIVATE
SIMPLNX_BUILD_DIR="$<TARGET_FILE_DIR:simplnx_test>"
SIMPLNX_TEST_ALGORITHM_PATH=${SIMPLNX_TEST_ALGORITHM_PATH}
)

target_compile_options(${UNIT_TEST_TARGET}
Expand Down
11 changes: 11 additions & 0 deletions cmake/SimplnxConfig.hpp.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#pragma once

// This header is generated from cmake/SimplnxConfig.hpp.in by configure_file().
// It carries compile-time configuration into every translation unit that links
// simplnx, including MOC, via simplnx's PUBLIC generated include directory.
// Delivering the macro through a header (not per-target compile definitions)
// keeps the value identical across all consumers, which is required for ODR
// safety when the macro gates inline code in public headers.

// Defined (to 1) when out-of-core support is compiled into libsimplnx.
#cmakedefine SIMPLNX_USE_OOC
Original file line number Diff line number Diff line change
Expand Up @@ -855,7 +855,7 @@ Result<OutputActions> DataCheck(const DataStructure& dataStructure, const DataPa
const auto& inputArray = dataStructure.getDataRefAs<IDataArray>(inputArrayPath);
const auto& inputDataStore = inputArray.getIDataStoreRef();

if(!inputArray.getDataFormat().empty())
if(inputArray.getStoreType() == IDataStore::StoreType::OutOfCore)
{
return MakeErrorResult<OutputActions>(Constants::k_OutOfCoreDataNotSupported,
fmt::format("Input Array '{}' utilizes out-of-core data. This is not supported within ITK filters.", inputArrayPath.toString()));
Expand All @@ -877,7 +877,7 @@ Result<detail::ITKFilterFunctorResult_t<FilterCreationFunctorT>> Execute(DataStr

using ResultT = detail::ITKFilterFunctorResult_t<FilterCreationFunctorT>;

if(!inputArray.getDataFormat().empty())
if(inputArray.getStoreType() == IDataStore::StoreType::OutOfCore)
{
return MakeErrorResult(Constants::k_OutOfCoreDataNotSupported, fmt::format("Input Array '{}' utilizes out-of-core data. This is not supported within ITK filters.", inputArrayPath.toString()));
}
Expand Down
44 changes: 2 additions & 42 deletions src/Plugins/ITKImageProcessing/test/ITKTestBase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ std::string ComputeMD5HashTyped(const IDataArray& outputDataArray)
usize arraySize = dataArray.getSize();

MD5 md5;
if(outputDataArray.getDataFormat().empty())
if(outputDataArray.getIDataStoreRef().getStoreType() != IDataStore::StoreType::OutOfCore)
{
const T* dataPtr = dataArray.template getIDataStoreRefAs<DataStore<T>>().data();
md5.update(reinterpret_cast<const uint8*>(dataPtr), arraySize * sizeof(T));
Expand Down Expand Up @@ -135,47 +135,7 @@ namespace ITKTestBase
bool IsArrayInMemory(DataStructure& dataStructure, const DataPath& outputDataPath)
{
const auto& outputDataArray = dataStructure.getDataRefAs<IDataArray>(outputDataPath);
DataType outputDataType = outputDataArray.getDataType();

switch(outputDataType)
{
case DataType::float32: {
return dynamic_cast<const DataArray<float32>&>(outputDataArray).getDataFormat().empty();
}
case DataType::float64: {
return dynamic_cast<const DataArray<float64>&>(outputDataArray).getDataFormat().empty();
}
case DataType::int8: {
return dynamic_cast<const DataArray<int8>&>(outputDataArray).getDataFormat().empty();
}
case DataType::uint8: {
return dynamic_cast<const DataArray<uint8>&>(outputDataArray).getDataFormat().empty();
}
case DataType::int16: {
return dynamic_cast<const DataArray<int16>&>(outputDataArray).getDataFormat().empty();
}
case DataType::uint16: {
return dynamic_cast<const DataArray<uint16>&>(outputDataArray).getDataFormat().empty();
}
case DataType::int32: {
return dynamic_cast<const DataArray<int32>&>(outputDataArray).getDataFormat().empty();
}
case DataType::uint32: {
return dynamic_cast<const DataArray<uint32>&>(outputDataArray).getDataFormat().empty();
}
case DataType::int64: {
return dynamic_cast<const DataArray<int64>&>(outputDataArray).getDataFormat().empty();
}
case DataType::uint64: {
return dynamic_cast<const DataArray<uint64>&>(outputDataArray).getDataFormat().empty();
}
case DataType::boolean: {
[[fallthrough]];
}
default: {
return {};
}
}
return outputDataArray.getIDataStoreRef().getStoreType() != IDataStore::StoreType::OutOfCore;
}
//------------------------------------------------------------------------------
std::string ComputeMd5Hash(DataStructure& dataStructure, const DataPath& outputDataPath)
Expand Down
7 changes: 6 additions & 1 deletion src/Plugins/OrientationAnalysis/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,8 @@ set(filter_algorithms
AlignSectionsMisorientation
AlignSectionsMutualInformation
BadDataNeighborOrientationCheck
BadDataNeighborOrientationCheckScanline
BadDataNeighborOrientationCheckWorklist
CAxisSegmentFeatures
ComputeAvgCAxes
ComputeAvgOrientations
Expand All @@ -186,9 +188,12 @@ set(filter_algorithms
ComputeFZQuaternions
ComputeGBCD
ComputeGBCDMetricBased
ComputeGBCDPoleFigure
ComputeGBCDPoleFigureDirect
ComputeGBCDPoleFigureScanline
ComputeGBPDMetricBased
ComputeIPFColors
ComputeIPFColorsDirect
ComputeIPFColorsScanline
ComputeKernelAvgMisorientations
ComputeMisorientations
ComputeQuaternionConjugate
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,20 @@ In this new structure, what follows is what the created structures represent:
In previous versions a file would have been produced instead. If you wish to recreate this, you can write the Attribute Matrix as a CSV/Text file.


## Algorithm

### In-Core Path

For each pair of adjacent Z-sections, the algorithm computes the misorientation between voxels across the section boundary. It tests candidate X-Y shifts to find the shift that minimizes the total misorientation between the two sections. All voxel comparisons use direct array indexing with `operator[]`.

### Out-of-Core Path

Reads pairs of adjacent Z-slices into local memory buffers using `copyIntoBuffer()`. All misorientation comparisons for a given slice pair operate entirely on the in-memory buffers. This converts what would otherwise be random cross-slice element access into sequential bulk reads, avoiding chunk thrashing when data is stored on disk in compressed chunks.

### Performance

The OOC optimization matters most for large datasets that exceed available RAM. By reading entire Z-slices in bulk rather than accessing individual voxels across slice boundaries, the algorithm avoids repeatedly decompressing the same disk chunks. For in-memory datasets, the two paths produce identical results with negligible overhead difference.

% Auto generated parameter table will be inserted here

## Example Pipelines
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,20 @@ In this new structure, what follows is what the created structures represent:

In previous versions a file would have been produced instead. If you wish to recreate this, you can write the Attribute Matrix as a CSV/Text file.

## Algorithm

### In-Core Path

Aligns Z-sections by maximizing the mutual information of orientation data between adjacent slices. The algorithm segments each slice into temporary features, bins the orientations, and computes joint and marginal histograms to evaluate mutual information at each candidate shift position. All orientation and feature ID data is accessed through direct array indexing with `operator[]`.

### Out-of-Core Path

Reads the orientation and phase data for each pair of adjacent Z-slices into local memory buffers using `copyIntoBuffer()` before computing histograms. This eliminates per-voxel out-of-core reads during the histogram binning and mutual information calculation, replacing them with two sequential bulk reads per slice pair.

### Performance

The OOC optimization matters most for large datasets that exceed available RAM. Histogram computation requires visiting every voxel in both slices multiple times (once per candidate shift), so eliminating per-element OOC access prevents repeated decompression of the same disk chunks. For in-memory datasets, the two paths produce identical results with negligible overhead difference.

% Auto generated parameter table will be inserted here

## References
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,32 @@ since there are no neighbors is the +-Z directions.

Only the *Mask* value defining the cell as *good* or *bad* is changed. No other cell level array is modified.

## Algorithm

The algorithm operates in a multi-level iterative scheme, starting at level 6 (all 6 face-neighbors must agree) and decrementing to the user-specified *Required Number of Neighbors*. At each level, bad voxels are flipped to good if they have at least that many good face-neighbors with matching crystallographic orientation (misorientation below the tolerance). Starting strict and relaxing ensures high-confidence flips happen first, which can cascade to enable additional flips.

### In-Core Path (BadDataNeighborOrientationCheckWorklist)

When all arrays reside in contiguous in-memory storage, the algorithm uses a two-phase worklist approach:

1. **Phase 1 (Initial count)**: A single linear scan counts matching good face-neighbors for every bad voxel, storing the count in a per-voxel array.
2. **Phase 2 (Worklist propagation)**: For each level, a deque is seeded with all bad voxels meeting the threshold. As each voxel is flipped, its still-bad neighbors' counts are incremented. If a neighbor's count now meets the threshold, it is enqueued. This breadth-first flood-fill processes each voxel at most once per level, achieving O(flipped) amortized cost.

### Out-of-Core Path (BadDataNeighborOrientationCheckScanline)

When any of the quaternion, mask, or phase arrays are backed by chunked (OOC) disk storage, the algorithm uses a 3-slice rolling window over the Z axis:

1. Three Z-slices of quaternions, phases, and mask data are maintained in memory (previous, current, next).
2. For each bad voxel in the current slice, the count of matching good face-neighbors is recomputed on-the-fly using the rolling window buffers.
3. If a voxel is flipped, the mask change is written back to the OOC store per-slice via `copyFromBuffer()`.
4. The window shifts forward one Z-slice at a time, with only one new slice loaded per step.

This approach trades recomputation (no persistent neighbor-count array) for strictly sequential I/O that avoids the random-access chunk thrashing that would occur with the worklist variant's BFS pattern.

### Performance

The in-core worklist variant is significantly faster for datasets that fit in RAM because each voxel is processed at most once per level (O(flipped) cost vs. O(N * passes) for the scanline variant). The OOC scanline variant is slower in absolute terms but avoids catastrophic performance degradation on disk-backed datasets where the worklist's random access pattern would trigger continuous chunk load/evict cycles. Memory usage is O(N) for the worklist variant vs. O(3 * sliceSize) for the scanline variant.

## Example Data

| Example Input Image | Example Output Image |
Expand Down
Loading
Loading