|
| 1 | +#include "MTRSimFilter.hpp" |
| 2 | + |
| 3 | +#include "MTRSim/Filters/Algorithms/MTRSim.hpp" |
| 4 | + |
| 5 | +#include "simplnx/Common/DataTypeUtilities.hpp" |
| 6 | +#include "simplnx/DataStructure/DataArray.hpp" |
| 7 | +#include "simplnx/DataStructure/DataPath.hpp" |
| 8 | +#include "simplnx/Filter/Actions/CreateArrayAction.hpp" |
| 9 | +#include "simplnx/Filter/Actions/CreateImageGeometryAction.hpp" |
| 10 | +#include "simplnx/Parameters/BoolParameter.hpp" |
| 11 | +#include "simplnx/Parameters/DataGroupCreationParameter.hpp" |
| 12 | +#include "simplnx/Parameters/DataObjectNameParameter.hpp" |
| 13 | +#include "simplnx/Parameters/DynamicTableParameter.hpp" |
| 14 | +#include "simplnx/Parameters/GeometrySelectionParameter.hpp" |
| 15 | +#include "simplnx/Parameters/MultiArraySelectionParameter.hpp" |
| 16 | +#include "simplnx/Parameters/NumberParameter.hpp" |
| 17 | +#include "simplnx/Parameters/VectorParameter.hpp" |
| 18 | +#include "simplnx/Parameters/util/DynamicTableInfo.hpp" |
| 19 | + |
| 20 | +#include <fmt/format.h> |
| 21 | + |
| 22 | +#include <chrono> |
| 23 | +#include <cmath> |
| 24 | +#include <random> |
| 25 | +#include <vector> |
| 26 | + |
| 27 | +using namespace nx::core; |
| 28 | + |
| 29 | +namespace nx::core |
| 30 | +{ |
| 31 | +//------------------------------------------------------------------------------ |
| 32 | +std::string MTRSimFilter::name() const |
| 33 | +{ |
| 34 | + return FilterTraits<MTRSimFilter>::name.str(); |
| 35 | +} |
| 36 | + |
| 37 | +//------------------------------------------------------------------------------ |
| 38 | +std::string MTRSimFilter::className() const |
| 39 | +{ |
| 40 | + return FilterTraits<MTRSimFilter>::className; |
| 41 | +} |
| 42 | + |
| 43 | +//------------------------------------------------------------------------------ |
| 44 | +Uuid MTRSimFilter::uuid() const |
| 45 | +{ |
| 46 | + return FilterTraits<MTRSimFilter>::uuid; |
| 47 | +} |
| 48 | + |
| 49 | +//------------------------------------------------------------------------------ |
| 50 | +std::string MTRSimFilter::humanName() const |
| 51 | +{ |
| 52 | + return "Generate Synthetic Microtexture"; |
| 53 | +} |
| 54 | + |
| 55 | +//------------------------------------------------------------------------------ |
| 56 | +std::vector<std::string> MTRSimFilter::defaultTags() const |
| 57 | +{ |
| 58 | + return {className(), "MTRSim", "Synthetic", "Microtexture", "Generate"}; |
| 59 | +} |
| 60 | + |
| 61 | +//------------------------------------------------------------------------------ |
| 62 | +Parameters MTRSimFilter::parameters() const |
| 63 | +{ |
| 64 | + Parameters params; |
| 65 | + |
| 66 | + params.insertSeparator(Parameters::Separator{"Input ODF"}); |
| 67 | + params.insert(std::make_unique<GeometrySelectionParameter>(k_InputOdfGeometry_Key, "Input ODF Geometry", "Image Geometry holding the ODF (from the Read/Compute ODF filters).", DataPath{}, |
| 68 | + GeometrySelectionParameter::AllowedTypes{IGeometry::Type::Image})); |
| 69 | + params.insert(std::make_unique<MultiArraySelectionParameter>(k_OdfComponentArrays_Key, "ODF Component Arrays", "Ordered list of per-component ODF cell arrays. Order maps to Volume Fraction columns.", |
| 70 | + MultiArraySelectionParameter::ValueType{}, MultiArraySelectionParameter::AllowedTypes{IArray::ArrayType::DataArray}, GetAllNumericTypes(), |
| 71 | + MultiArraySelectionParameter::AllowedComponentShapes{{1}})); |
| 72 | + |
| 73 | + params.insertSeparator(Parameters::Separator{"Simulation Parameters"}); |
| 74 | + { |
| 75 | + DynamicTableInfo vfInfo; |
| 76 | + vfInfo.setRowsInfo(DynamicTableInfo::StaticVectorInfo(1)); |
| 77 | + vfInfo.setColsInfo(DynamicTableInfo::DynamicVectorInfo(1, 3, "Comp {}")); |
| 78 | + params.insert(std::make_unique<DynamicTableParameter>(k_VolumeFractions_Key, "Volume Fraction", "One value per ODF component; must match the component count and sum to 1.0.", vfInfo)); |
| 79 | + } |
| 80 | + { |
| 81 | + DynamicTableInfo thetaInfo; |
| 82 | + thetaInfo.setRowsInfo(DynamicTableInfo::DynamicVectorInfo(1, 2, "Gaussian {}")); |
| 83 | + thetaInfo.setColsInfo(DynamicTableInfo::StaticVectorInfo(DynamicTableInfo::HeadersListType{"theta_x", "theta_y", "theta_z"})); |
| 84 | + params.insert(std::make_unique<DynamicTableParameter>(k_ThetaList_Key, "Theta List", |
| 85 | + "Correlation lengths [theta_x, theta_y, theta_z] per latent Gaussian. Needs >= (components - 1) rows. Same length unit as Physical " |
| 86 | + "Size/Spacing.", |
| 87 | + thetaInfo)); |
| 88 | + } |
| 89 | + params.insert(std::make_unique<VectorFloat32Parameter>(k_PhysicalSize_Key, "Physical Size (microns)", "Domain extent X,Y,Z.", std::vector<float32>{38.1f, 12.7f, 0.0f}, |
| 90 | + std::vector<std::string>{"X", "Y", "Z"})); |
| 91 | + params.insert(std::make_unique<VectorFloat32Parameter>(k_PhysicalSpacing_Key, "Physical Spacing (microns)", "Voxel spacing X,Y,Z.", std::vector<float32>{0.02f, 0.02f, 0.02f}, |
| 92 | + std::vector<std::string>{"X", "Y", "Z"})); |
| 93 | + |
| 94 | + params.insertSeparator(Parameters::Separator{"Random Number Seed Parameters"}); |
| 95 | + params.insertLinkableParameter(std::make_unique<BoolParameter>(k_UseSeed_Key, "Use Seed for Random Generation", "When true the user can supply a fixed seed.", false)); |
| 96 | + params.insert(std::make_unique<NumberParameter<uint64>>(k_SeedValue_Key, "Seed Value", "The seed fed into the random generator.", std::mt19937::default_seed)); |
| 97 | + params.insert(std::make_unique<DataObjectNameParameter>(k_SeedArrayName_Key, "Stored Seed Value Array Name", "Top-level array recording the seed used.", "MTRSim SeedValue")); |
| 98 | + |
| 99 | + params.insertSeparator(Parameters::Separator{"Outputs"}); |
| 100 | + params.insertLinkableParameter( |
| 101 | + std::make_unique<BoolParameter>(k_GeneratePolarColoring_Key, "Generate Polar Coloring", "Create a 3-component UInt8 RGB array using the MATLAB polar color mapping.", false)); |
| 102 | + params.insert(std::make_unique<DataGroupCreationParameter>(k_OutputGeometry_Key, "Output Image Geometry", "Path of the new microstructure Image Geometry.", DataPath({"MTR Microstructure"}))); |
| 103 | + params.insert(std::make_unique<DataObjectNameParameter>(k_CellAttrMatName_Key, "Cell Attribute Matrix Name", "Name of the created cell AttributeMatrix.", "Cell Data")); |
| 104 | + params.insert(std::make_unique<DataObjectNameParameter>(k_MtrIdsArrayName_Key, "MTR Ids Array Name", "Int32 per-voxel MTR component id (1-based).", "MTRIds")); |
| 105 | + params.insert(std::make_unique<DataObjectNameParameter>(k_EulersArrayName_Key, "Euler Angles Array Name", "Float32 3-component Bunge Euler angles [rad].", "Eulers")); |
| 106 | + params.insert(std::make_unique<DataObjectNameParameter>(k_PolarColorsArrayName_Key, "Polar Colors Array Name", "UInt8 3-component RGB polar coloring.", "Polar Colors")); |
| 107 | + |
| 108 | + params.linkParameters(k_UseSeed_Key, k_SeedValue_Key, true); |
| 109 | + params.linkParameters(k_GeneratePolarColoring_Key, k_PolarColorsArrayName_Key, true); |
| 110 | + |
| 111 | + return params; |
| 112 | +} |
| 113 | + |
| 114 | +//------------------------------------------------------------------------------ |
| 115 | +IFilter::VersionType MTRSimFilter::parametersVersion() const |
| 116 | +{ |
| 117 | + return 1; |
| 118 | +} |
| 119 | + |
| 120 | +//------------------------------------------------------------------------------ |
| 121 | +IFilter::UniquePointer MTRSimFilter::clone() const |
| 122 | +{ |
| 123 | + return std::make_unique<MTRSimFilter>(); |
| 124 | +} |
| 125 | + |
| 126 | +//------------------------------------------------------------------------------ |
| 127 | +IFilter::PreflightResult MTRSimFilter::preflightImpl(const DataStructure& dataStructure, const Arguments& filterArgs, const MessageHandler& messageHandler, const std::atomic_bool& shouldCancel, |
| 128 | + const ExecutionContext& executionContext) const |
| 129 | +{ |
| 130 | + auto pOdfArrays = filterArgs.value<MultiArraySelectionParameter::ValueType>(k_OdfComponentArrays_Key); |
| 131 | + auto pVolumeFractions = filterArgs.value<DynamicTableParameter::ValueType>(k_VolumeFractions_Key); |
| 132 | + auto pThetaList = filterArgs.value<DynamicTableParameter::ValueType>(k_ThetaList_Key); |
| 133 | + auto pSize = filterArgs.value<std::vector<float32>>(k_PhysicalSize_Key); |
| 134 | + auto pSpacing = filterArgs.value<std::vector<float32>>(k_PhysicalSpacing_Key); |
| 135 | + auto pGenPolar = filterArgs.value<bool>(k_GeneratePolarColoring_Key); |
| 136 | + auto pOutGeomPath = filterArgs.value<DataPath>(k_OutputGeometry_Key); |
| 137 | + auto pCellAttrMatName = filterArgs.value<std::string>(k_CellAttrMatName_Key); |
| 138 | + auto pMtrIdsName = filterArgs.value<std::string>(k_MtrIdsArrayName_Key); |
| 139 | + auto pEulersName = filterArgs.value<std::string>(k_EulersArrayName_Key); |
| 140 | + auto pPolarName = filterArgs.value<std::string>(k_PolarColorsArrayName_Key); |
| 141 | + auto pSeedArrayName = filterArgs.value<std::string>(k_SeedArrayName_Key); |
| 142 | + |
| 143 | + nx::core::Result<OutputActions> resultOutputActions; |
| 144 | + std::vector<PreflightValue> preflightUpdatedValues; |
| 145 | + |
| 146 | + const usize numComponents = pOdfArrays.size(); |
| 147 | + if(numComponents < 2) |
| 148 | + { |
| 149 | + return {MakeErrorResult<OutputActions>(-13001, "MTRSim requires at least 2 ODF component arrays.")}; |
| 150 | + } |
| 151 | + if(pVolumeFractions.size() != 1 || pVolumeFractions[0].size() != numComponents) |
| 152 | + { |
| 153 | + return {MakeErrorResult<OutputActions>(-13002, fmt::format("Volume Fraction must be 1 row x {} columns (one per ODF component).", numComponents))}; |
| 154 | + } |
| 155 | + double vfSum = 0.0; |
| 156 | + for(double v : pVolumeFractions[0]) |
| 157 | + { |
| 158 | + vfSum += v; |
| 159 | + } |
| 160 | + if(std::abs(vfSum - 1.0) > 1.0e-3) |
| 161 | + { |
| 162 | + return {MakeErrorResult<OutputActions>(-13003, fmt::format("Volume Fraction values must sum to 1.0 (got {:.4f}).", vfSum))}; |
| 163 | + } |
| 164 | + if(pThetaList.size() < numComponents - 1) |
| 165 | + { |
| 166 | + return {MakeErrorResult<OutputActions>(-13004, fmt::format("Theta List needs at least {} rows (components - 1).", numComponents - 1))}; |
| 167 | + } |
| 168 | + for(const auto& row : pThetaList) |
| 169 | + { |
| 170 | + if(row.size() != 3) |
| 171 | + { |
| 172 | + return {MakeErrorResult<OutputActions>(-13005, "Each Theta List row must have exactly 3 columns.")}; |
| 173 | + } |
| 174 | + } |
| 175 | + |
| 176 | + const auto dim = [](float len, float sp) { return static_cast<usize>(std::max(std::lround(len / sp), 1L)); }; |
| 177 | + const usize nx = dim(pSize[0], pSpacing[0]); |
| 178 | + const usize ny = dim(pSize[1], pSpacing[1]); |
| 179 | + const usize nz = (pSize[2] <= 0.0f) ? 1 : dim(pSize[2], pSpacing[2]); |
| 180 | + |
| 181 | + const std::vector<usize> imageGeomDimsXYZ = {nx, ny, nz}; |
| 182 | + const std::vector<float32> origin = {0.0f, 0.0f, 0.0f}; |
| 183 | + const std::vector<float32> spacingXYZ = {pSpacing[0], pSpacing[1], pSpacing[2]}; |
| 184 | + const std::vector<usize> tupleShapeZYX = {nz, ny, nx}; |
| 185 | + |
| 186 | + resultOutputActions.value().appendAction(std::make_unique<CreateImageGeometryAction>(pOutGeomPath, imageGeomDimsXYZ, origin, spacingXYZ, pCellAttrMatName)); |
| 187 | + |
| 188 | + const DataPath cellAttrMatPath = pOutGeomPath.createChildPath(pCellAttrMatName); |
| 189 | + resultOutputActions.value().appendAction(std::make_unique<CreateArrayAction>(DataType::int32, tupleShapeZYX, std::vector<usize>{1}, cellAttrMatPath.createChildPath(pMtrIdsName))); |
| 190 | + resultOutputActions.value().appendAction(std::make_unique<CreateArrayAction>(DataType::float32, tupleShapeZYX, std::vector<usize>{3}, cellAttrMatPath.createChildPath(pEulersName))); |
| 191 | + if(pGenPolar) |
| 192 | + { |
| 193 | + resultOutputActions.value().appendAction(std::make_unique<CreateArrayAction>(DataType::uint8, tupleShapeZYX, std::vector<usize>{3}, cellAttrMatPath.createChildPath(pPolarName))); |
| 194 | + } |
| 195 | + resultOutputActions.value().appendAction(std::make_unique<CreateArrayAction>(DataType::uint64, std::vector<usize>{1}, std::vector<usize>{1}, DataPath({pSeedArrayName}))); |
| 196 | + |
| 197 | + preflightUpdatedValues.push_back({"Output Grid (X, Y, Z)", fmt::format("{} x {} x {}", nx, ny, nz)}); |
| 198 | + preflightUpdatedValues.push_back({"Number of ODF Components", std::to_string(numComponents)}); |
| 199 | + |
| 200 | + return {std::move(resultOutputActions), std::move(preflightUpdatedValues)}; |
| 201 | +} |
| 202 | + |
| 203 | +//------------------------------------------------------------------------------ |
| 204 | +Result<> MTRSimFilter::executeImpl(DataStructure& dataStructure, const Arguments& filterArgs, const PipelineFilter* pipelineNode, const MessageHandler& messageHandler, const std::atomic_bool& shouldCancel, |
| 205 | + const ExecutionContext& executionContext) const |
| 206 | +{ |
| 207 | + MTRSimInputValues inputValues; |
| 208 | + inputValues.inputOdfGeometryPath = filterArgs.value<DataPath>(k_InputOdfGeometry_Key); |
| 209 | + inputValues.odfComponentPaths = filterArgs.value<MultiArraySelectionParameter::ValueType>(k_OdfComponentArrays_Key); |
| 210 | + inputValues.volumeFractions = filterArgs.value<DynamicTableParameter::ValueType>(k_VolumeFractions_Key); |
| 211 | + inputValues.thetaList = filterArgs.value<DynamicTableParameter::ValueType>(k_ThetaList_Key); |
| 212 | + inputValues.physicalSize = filterArgs.value<std::vector<float32>>(k_PhysicalSize_Key); |
| 213 | + inputValues.physicalSpacing = filterArgs.value<std::vector<float32>>(k_PhysicalSpacing_Key); |
| 214 | + inputValues.generatePolarColoring = filterArgs.value<bool>(k_GeneratePolarColoring_Key); |
| 215 | + inputValues.outputGeometryPath = filterArgs.value<DataPath>(k_OutputGeometry_Key); |
| 216 | + inputValues.cellAttrMatName = filterArgs.value<std::string>(k_CellAttrMatName_Key); |
| 217 | + inputValues.mtrIdsArrayName = filterArgs.value<std::string>(k_MtrIdsArrayName_Key); |
| 218 | + inputValues.eulersArrayName = filterArgs.value<std::string>(k_EulersArrayName_Key); |
| 219 | + inputValues.polarColorsArrayName = filterArgs.value<std::string>(k_PolarColorsArrayName_Key); |
| 220 | + |
| 221 | + uint64 seed = filterArgs.value<uint64>(k_SeedValue_Key); |
| 222 | + if(!filterArgs.value<bool>(k_UseSeed_Key)) |
| 223 | + { |
| 224 | + seed = static_cast<uint64>(std::chrono::steady_clock::now().time_since_epoch().count()); |
| 225 | + } |
| 226 | + dataStructure.getDataRefAs<UInt64Array>(DataPath({filterArgs.value<std::string>(k_SeedArrayName_Key)}))[0] = seed; |
| 227 | + inputValues.seed = seed; |
| 228 | + |
| 229 | + return MTRSim(dataStructure, messageHandler, shouldCancel, &inputValues)(); |
| 230 | +} |
| 231 | + |
| 232 | +//------------------------------------------------------------------------------ |
| 233 | +Result<Arguments> MTRSimFilter::FromSIMPLJson(const nlohmann::json& json) |
| 234 | +{ |
| 235 | + Arguments args = MTRSimFilter().getDefaultArguments(); |
| 236 | + |
| 237 | + std::vector<Result<>> results; |
| 238 | + |
| 239 | + /* This is a NEW filter and has no SIMPL (DREAM3D v6) equivalent. */ |
| 240 | + |
| 241 | + Result<> conversionResult = MergeResults(std::move(results)); |
| 242 | + |
| 243 | + return ConvertResultTo<Arguments>(std::move(conversionResult), std::move(args)); |
| 244 | +} |
| 245 | + |
| 246 | +} // namespace nx::core |
0 commit comments