diff --git a/src/Plugins/ITKImageProcessing/data/ImageStack/rgb_0.png b/src/Plugins/ITKImageProcessing/data/ImageStack/rgb_0.png new file mode 100644 index 0000000000..6641d2e7ff Binary files /dev/null and b/src/Plugins/ITKImageProcessing/data/ImageStack/rgb_0.png differ diff --git a/src/Plugins/ITKImageProcessing/data/ImageStack/rgb_1.png b/src/Plugins/ITKImageProcessing/data/ImageStack/rgb_1.png new file mode 100644 index 0000000000..13ed8c8ae2 Binary files /dev/null and b/src/Plugins/ITKImageProcessing/data/ImageStack/rgb_1.png differ diff --git a/src/Plugins/ITKImageProcessing/data/ImageStack/rgb_2.png b/src/Plugins/ITKImageProcessing/data/ImageStack/rgb_2.png new file mode 100644 index 0000000000..05cc4f9335 Binary files /dev/null and b/src/Plugins/ITKImageProcessing/data/ImageStack/rgb_2.png differ diff --git a/src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Filters/ITKImportImageStack.cpp b/src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Filters/ITKImportImageStack.cpp index bd694bdaa4..ca69c7b742 100644 --- a/src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Filters/ITKImportImageStack.cpp +++ b/src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Filters/ITKImportImageStack.cpp @@ -3,6 +3,7 @@ #include "ITKImageProcessing/Common/ITKArrayHelper.hpp" #include "ITKImageProcessing/Filters/ITKImageReader.hpp" +#include "simplnx/Common/TypesUtility.hpp" #include "simplnx/Core/Application.hpp" #include "simplnx/DataStructure/DataPath.hpp" #include "simplnx/DataStructure/Geometry/ImageGeom.hpp" @@ -57,6 +58,8 @@ const ChoicesParameter::ValueType k_FlipAboutYAxis = 2; const Uuid k_SimplnxCorePluginId = *Uuid::FromString("05cc618b-781f-4ac0-b9ac-43f26ce1854f"); const Uuid k_RotateSampleRefFrameFilterId = *Uuid::FromString("d2451dc1-a5a1-4ac2-a64d-7991669dcffc"); const FilterHandle k_RotateSampleRefFrameFilterHandle(k_RotateSampleRefFrameFilterId, k_SimplnxCorePluginId); +const Uuid k_ColorToGrayScaleFilterId = *Uuid::FromString("d938a2aa-fee2-4db9-aa2f-2c34a9736580"); +const FilterHandle k_ColorToGrayScaleFilterHandle(k_ColorToGrayScaleFilterId, k_SimplnxCorePluginId); // Make sure we can instantiate the RotateSampleRefFrame Filter std::unique_ptr CreateRotateSampleRefFrameFilter() @@ -132,7 +135,8 @@ namespace cxITKImportImageStack { template Result<> ReadImageStack(DataStructure& dataStructure, const DataPath& imageGeomPath, const std::string& cellDataName, const DataPath& imageDataPath, const std::vector& files, - ChoicesParameter::ValueType transformType, const IFilter::MessageHandler& messageHandler, const std::atomic_bool& shouldCancel) + ChoicesParameter::ValueType transformType, bool convertToGrayscale, VectorFloat32Parameter::ValueType luminosityValues, const IFilter::MessageHandler& messageHandler, + const std::atomic_bool& shouldCancel) { auto& imageGeom = dataStructure.getDataRefAs(imageGeomPath); @@ -147,13 +151,21 @@ Result<> ReadImageStack(DataStructure& dataStructure, const DataPath& imageGeomP // Variables for the progress Reporting usize slice = 0; + auto* filterListPtr = Application::Instance()->getFilterList(); + + if(convertToGrayscale && !filterListPtr->containsPlugin(k_SimplnxCorePluginId)) + { + return MakeErrorResult(-18542, "SimplnxCore was not instantiated in this instance, so color to grayscale is not a valid option."); + } + auto grayScaleFilter = filterListPtr->createFilter(k_ColorToGrayScaleFilterHandle); + Result<> outputResult = {}; + // Loop over all the files importing them one by one and copying the data into the data array for(const auto& filePath : files) { messageHandler(IFilter::Message::Type::Info, fmt::format("Importing: {}", filePath)); DataStructure importedDataStructure; - { // Create a sub-filter to read each image, although for preflight we are going to read the first image in the // list and hope the rest are correct. @@ -171,6 +183,50 @@ Result<> ReadImageStack(DataStructure& dataStructure, const DataPath& imageGeomP return executeResult.result; } } + + // ======================= Convert to GrayScale Section =================== + bool validInputForGrayScaleConversion = importedDataStructure.getDataRefAs(imageDataPath).getDataType() == DataType::uint8; + if(convertToGrayscale && validInputForGrayScaleConversion && nullptr != grayScaleFilter.get()) + { + + // This same filter was used to preflight so as long as nothing changes on disk this really should work.... + Arguments colorToGrayscaleArgs; + colorToGrayscaleArgs.insertOrAssign("conversion_algorithm", std::make_any(0)); + colorToGrayscaleArgs.insertOrAssign("color_weights", std::make_any(luminosityValues)); + colorToGrayscaleArgs.insertOrAssign("input_data_array_vector", std::make_any>(std::vector{imageDataPath})); + colorToGrayscaleArgs.insertOrAssign("output_array_prefix", std::make_any("gray")); + + // Run grayscale filter and process results and messages + auto result = grayScaleFilter->execute(importedDataStructure, colorToGrayscaleArgs).result; + if(result.invalid()) + { + return result; + } + + // deletion of non-grayscale array + DataObject::IdType id; + { // scoped for safety since this reference will be nonexistent in a moment + auto& oldArray = importedDataStructure.getDataRefAs(imageDataPath); + id = oldArray.getId(); + } + importedDataStructure.removeData(id); + + // rename grayscale array to reflect original + { + auto& gray = importedDataStructure.getDataRefAs(imageDataPath.getParent().createChildPath("gray" + imageDataPath.getTargetName())); + if(!gray.canRename(imageDataPath.getTargetName())) + { + return MakeErrorResult(-64543, fmt::format("Unable to rename the internal grayscale array to {}", imageDataPath.getTargetName())); + } + gray.rename(imageDataPath.getTargetName()); + } + } + else if(convertToGrayscale && !validInputForGrayScaleConversion) + { + outputResult.warnings().emplace_back(Warning{ + -74320, fmt::format("The array ({}) resulting from reading the input image file is not a UInt8Array. The input image will not be converted to grayscale.", imageDataPath.getTargetName())}); + } + // Check the ImageGeometry of the imported Image matches the destination const auto& importedImageGeom = importedDataStructure.getDataRefAs(imageGeomPath); SizeVec3 importedDims = importedImageGeom.getDimensions(); @@ -206,11 +262,11 @@ Result<> ReadImageStack(DataStructure& dataStructure, const DataPath& imageGeomP // Check to see if the filter got canceled. if(shouldCancel) { - return {}; + return outputResult; } } - return {}; + return outputResult; } } // namespace cxITKImportImageStack @@ -256,6 +312,10 @@ Parameters ITKImportImageStack::parameters() const params.insert(std::make_unique(k_Spacing_Key, "Spacing", "The spacing of the 3D volume", std::vector{1.0F, 1.0F, 1.0F}, std::vector{"X", "y", "Z"})); params.insertLinkableParameter(std::make_unique(k_ImageTransformChoice_Key, "Optional Slice Operations", "Operation that is performed on each slice. 0=None, 1=Flip about X, 2=Flip about Y", 0, k_SliceOperationChoices)); + params.insertLinkableParameter( + std::make_unique(k_ConvertToGrayScale_Key, "Convert To GrayScale", "The filter will show an error if the images are already in grayscale format", false)); + params.insert(std::make_unique(k_ColorWeights_Key, "Color Weighting", "RGB weights for the grayscale conversion using the luminosity algorithm.", + std::vector{0.2125f, 0.7154f, 0.0721f}, std::vector({"Red", "Green", "Blue"}))); params.insertSeparator(Parameters::Separator{"File List"}); params.insert( @@ -266,6 +326,8 @@ Parameters ITKImportImageStack::parameters() const params.insert(std::make_unique(k_CellDataName_Key, "Cell Data Name", "The name of the created cell attribute matrix", ImageGeom::k_CellDataName)); params.insert(std::make_unique(k_ImageDataArrayPath_Key, "Created Image Data", "The path to the created image data array", "ImageData")); + params.linkParameters(k_ConvertToGrayScale_Key, k_ColorWeights_Key, true); + return params; } @@ -283,11 +345,17 @@ IFilter::PreflightResult ITKImportImageStack::preflightImpl(const DataStructure& auto origin = filterArgs.value(k_Origin_Key); auto spacing = filterArgs.value(k_Spacing_Key); auto imageGeomPath = filterArgs.value(k_ImageGeometryPath_Key); - auto imageDataName = filterArgs.value(k_ImageDataArrayPath_Key); + auto pImageDataArrayNameValue = filterArgs.value(k_ImageDataArrayPath_Key); auto cellDataName = filterArgs.value(k_CellDataName_Key); auto imageTransformValue = filterArgs.value(k_ImageTransformChoice_Key); + auto pConvertToGrayScaleValue = filterArgs.value(k_ConvertToGrayScale_Key); + auto pColorWeightsValue = filterArgs.value(k_ColorWeights_Key); - const DataPath imageDataPath = imageGeomPath.createChildPath(cellDataName).createChildPath(imageDataName); + PreflightResult preflightResult; + nx::core::Result resultOutputActions = {}; + std::vector preflightUpdatedValues; + + const DataPath imageDataPath = imageGeomPath.createChildPath(cellDataName).createChildPath(pImageDataArrayNameValue); if(imageTransformValue != k_NoImageTransform) { @@ -314,7 +382,7 @@ IFilter::PreflightResult ITKImportImageStack::preflightImpl(const DataStructure& imageReaderArgs.insertOrAssign(ITKImageReader::k_FileName_Key, std::make_any(files.at(0))); const ITKImageReader imageReader; - PreflightResult imageReaderResult = imageReader.preflight(dataStructure, imageReaderArgs, messageHandler); + PreflightResult imageReaderResult = imageReader.preflight(dataStructure, imageReaderArgs, messageHandler, shouldCancel); if(imageReaderResult.outputActions.invalid()) { return imageReaderResult; @@ -326,7 +394,7 @@ IFilter::PreflightResult ITKImportImageStack::preflightImpl(const DataStructure& const auto* createImageGeomActionPtr = dynamic_cast(action0Ptr); if(createImageGeomActionPtr == nullptr) { - throw std::runtime_error("ITKImportImageStack: Expected CreateImageGeometryAction at index 0"); + throw std::runtime_error(fmt::format("{}: Expected CreateImageGeometryAction at index 0", this->humanName())); } // The second action should be the array creation @@ -334,7 +402,7 @@ IFilter::PreflightResult ITKImportImageStack::preflightImpl(const DataStructure& const auto* createArrayActionPtr = dynamic_cast(action1Ptr); if(createArrayActionPtr == nullptr) { - throw std::runtime_error("ITKImportImageStack: Expected CreateArrayAction at index 1"); + throw std::runtime_error(fmt::format("{}: Expected CreateArrayAction at index 1", this->humanName())); } // X Y Z @@ -344,11 +412,36 @@ IFilter::PreflightResult ITKImportImageStack::preflightImpl(const DataStructure& // Z Y X const std::vector arrayDims(dims.crbegin(), dims.crend()); - OutputActions outputActions; - outputActions.appendAction(std::make_unique(std::move(imageGeomPath), std::move(dims), std::move(origin), std::move(spacing), cellDataName)); - outputActions.appendAction(std::make_unique(createArrayActionPtr->type(), arrayDims, createArrayActionPtr->componentDims(), imageDataPath)); + // OutputActions outputActions; + resultOutputActions.value().appendAction(std::make_unique(std::move(imageGeomPath), std::move(dims), std::move(origin), std::move(spacing), cellDataName)); + + if(createArrayActionPtr->type() != DataType::uint8 && pConvertToGrayScaleValue) + { + return MakePreflightErrorResult(-23504, fmt::format("The input DataType is {} which cannot be converted to grayscale. Please turn off the 'Convert To Grayscale' option.", + nx::core::DataTypeToString(createArrayActionPtr->type()))); + } + + if(pConvertToGrayScaleValue) + { + auto* filterListPtr = Application::Instance()->getFilterList(); + if(!filterListPtr->containsPlugin(k_SimplnxCorePluginId)) + { + return MakePreflightErrorResult(-23501, "Color to GrayScale conversion is disabled because the 'SimplnxCore' plugin was not loaded."); + } + auto grayScaleFilter = filterListPtr->createFilter(k_ColorToGrayScaleFilterHandle); + if(nullptr == grayScaleFilter.get()) + { + return MakePreflightErrorResult(-23502, "Color to GrayScale conversion is disabled because the 'Color to GrayScale' filter is missing from the SimplnxCore plugin."); + } + resultOutputActions.value().appendAction(std::make_unique(createArrayActionPtr->type(), arrayDims, std::vector{1ULL}, imageDataPath)); + } + else + { + resultOutputActions.value().appendAction(std::make_unique(createArrayActionPtr->type(), arrayDims, createArrayActionPtr->componentDims(), imageDataPath)); + } - return {std::move(outputActions)}; + // Return both the resultOutputActions and the preflightUpdatedValues via std::move() + return {std::move(resultOutputActions), std::move(preflightUpdatedValues)}; } //------------------------------------------------------------------------------ @@ -362,6 +455,8 @@ Result<> ITKImportImageStack::executeImpl(DataStructure& dataStructure, const Ar auto imageDataName = filterArgs.value(k_ImageDataArrayPath_Key); auto cellDataName = filterArgs.value(k_CellDataName_Key); auto imageTransformValue = filterArgs.value(k_ImageTransformChoice_Key); + auto convertToGrayScaleValue = filterArgs.value(k_ConvertToGrayScale_Key); + auto colorWeightsValue = filterArgs.value(k_ColorWeights_Key); const DataPath imageDataPath = imageGeomPath.createChildPath(cellDataName).createChildPath(imageDataName); @@ -384,43 +479,53 @@ Result<> ITKImportImageStack::executeImpl(DataStructure& dataStructure, const Ar switch(*numericType) { case NumericType::uint8: { - readResult = cxITKImportImageStack::ReadImageStack(dataStructure, imageGeomPath, cellDataName, imageDataPath, files, imageTransformValue, messageHandler, shouldCancel); + readResult = cxITKImportImageStack::ReadImageStack(dataStructure, imageGeomPath, cellDataName, imageDataPath, files, imageTransformValue, convertToGrayScaleValue, colorWeightsValue, + messageHandler, shouldCancel); break; } case NumericType::int8: { - readResult = cxITKImportImageStack::ReadImageStack(dataStructure, imageGeomPath, cellDataName, imageDataPath, files, imageTransformValue, messageHandler, shouldCancel); + readResult = cxITKImportImageStack::ReadImageStack(dataStructure, imageGeomPath, cellDataName, imageDataPath, files, imageTransformValue, convertToGrayScaleValue, colorWeightsValue, + messageHandler, shouldCancel); break; } case NumericType::uint16: { - readResult = cxITKImportImageStack::ReadImageStack(dataStructure, imageGeomPath, cellDataName, imageDataPath, files, imageTransformValue, messageHandler, shouldCancel); + readResult = cxITKImportImageStack::ReadImageStack(dataStructure, imageGeomPath, cellDataName, imageDataPath, files, imageTransformValue, convertToGrayScaleValue, colorWeightsValue, + messageHandler, shouldCancel); break; } case NumericType::int16: { - readResult = cxITKImportImageStack::ReadImageStack(dataStructure, imageGeomPath, cellDataName, imageDataPath, files, imageTransformValue, messageHandler, shouldCancel); + readResult = cxITKImportImageStack::ReadImageStack(dataStructure, imageGeomPath, cellDataName, imageDataPath, files, imageTransformValue, convertToGrayScaleValue, colorWeightsValue, + messageHandler, shouldCancel); break; } case NumericType::uint32: { - readResult = cxITKImportImageStack::ReadImageStack(dataStructure, imageGeomPath, cellDataName, imageDataPath, files, imageTransformValue, messageHandler, shouldCancel); + readResult = cxITKImportImageStack::ReadImageStack(dataStructure, imageGeomPath, cellDataName, imageDataPath, files, imageTransformValue, convertToGrayScaleValue, colorWeightsValue, + messageHandler, shouldCancel); break; } case NumericType::int32: { - readResult = cxITKImportImageStack::ReadImageStack(dataStructure, imageGeomPath, cellDataName, imageDataPath, files, imageTransformValue, messageHandler, shouldCancel); + readResult = cxITKImportImageStack::ReadImageStack(dataStructure, imageGeomPath, cellDataName, imageDataPath, files, imageTransformValue, convertToGrayScaleValue, colorWeightsValue, + messageHandler, shouldCancel); break; } case NumericType::uint64: { - readResult = cxITKImportImageStack::ReadImageStack(dataStructure, imageGeomPath, cellDataName, imageDataPath, files, imageTransformValue, messageHandler, shouldCancel); + readResult = cxITKImportImageStack::ReadImageStack(dataStructure, imageGeomPath, cellDataName, imageDataPath, files, imageTransformValue, convertToGrayScaleValue, colorWeightsValue, + messageHandler, shouldCancel); break; } case NumericType::int64: { - readResult = cxITKImportImageStack::ReadImageStack(dataStructure, imageGeomPath, cellDataName, imageDataPath, files, imageTransformValue, messageHandler, shouldCancel); + readResult = cxITKImportImageStack::ReadImageStack(dataStructure, imageGeomPath, cellDataName, imageDataPath, files, imageTransformValue, convertToGrayScaleValue, colorWeightsValue, + messageHandler, shouldCancel); break; } case NumericType::float32: { - readResult = cxITKImportImageStack::ReadImageStack(dataStructure, imageGeomPath, cellDataName, imageDataPath, files, imageTransformValue, messageHandler, shouldCancel); + readResult = cxITKImportImageStack::ReadImageStack(dataStructure, imageGeomPath, cellDataName, imageDataPath, files, imageTransformValue, convertToGrayScaleValue, colorWeightsValue, + messageHandler, shouldCancel); break; } case NumericType::float64: { - readResult = cxITKImportImageStack::ReadImageStack(dataStructure, imageGeomPath, cellDataName, imageDataPath, files, imageTransformValue, messageHandler, shouldCancel); + readResult = cxITKImportImageStack::ReadImageStack(dataStructure, imageGeomPath, cellDataName, imageDataPath, files, imageTransformValue, convertToGrayScaleValue, colorWeightsValue, + messageHandler, shouldCancel); break; } default: { diff --git a/src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Filters/ITKImportImageStack.hpp b/src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Filters/ITKImportImageStack.hpp index 0f0b56fc6b..7db20aa7c1 100644 --- a/src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Filters/ITKImportImageStack.hpp +++ b/src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Filters/ITKImportImageStack.hpp @@ -31,7 +31,8 @@ class ITKIMAGEPROCESSING_EXPORT ITKImportImageStack : public IFilter static inline constexpr StringLiteral k_ImageDataArrayPath_Key = "image_data_array_path"; static inline constexpr StringLiteral k_CellDataName_Key = "cell_data_name"; static inline constexpr StringLiteral k_ImageTransformChoice_Key = "image_transform_choice"; - + static inline constexpr StringLiteral k_ConvertToGrayScale_Key = "convert_to_gray_scale"; + static inline constexpr StringLiteral k_ColorWeights_Key = "color_weights"; /** * @brief Reads SIMPL json and converts it simplnx Arguments. * @param json diff --git a/src/Plugins/ITKImageProcessing/test/ITKImportImageStackTest.cpp b/src/Plugins/ITKImageProcessing/test/ITKImportImageStackTest.cpp index 1b383e8c94..03e572971f 100644 --- a/src/Plugins/ITKImageProcessing/test/ITKImportImageStackTest.cpp +++ b/src/Plugins/ITKImageProcessing/test/ITKImportImageStackTest.cpp @@ -14,6 +14,7 @@ #include using namespace nx::core; +using namespace nx::core::UnitTest; namespace fs = std::filesystem; @@ -46,8 +47,8 @@ void ExecuteImportImageStackXY(DataStructure& dataStructure, const std::string& { // Filter needs RotateSampleRefFrameFilter to run Application::GetOrCreateInstance()->loadPlugins(unit_test::k_BuildDir.view(), true); - auto* filterList = nx::core::Application::Instance()->getFilterList(); - REQUIRE(filterList != nullptr); + auto* filterListPtr = nx::core::Application::Instance()->getFilterList(); + REQUIRE(filterListPtr != nullptr); // Define Shared parameters std::vector k_Origin = {0.0f, 0.0f, 0.0f}; @@ -69,7 +70,7 @@ void ExecuteImportImageStackXY(DataStructure& dataStructure, const std::string& // Run generated X flip { - auto importImageStackFilter = filterList->createFilter(::k_ImportImageStackFilterHandle); + auto importImageStackFilter = filterListPtr->createFilter(::k_ImportImageStackFilterHandle); REQUIRE(nullptr != importImageStackFilter); Arguments args; @@ -89,7 +90,7 @@ void ExecuteImportImageStackXY(DataStructure& dataStructure, const std::string& // Run generated Y flip { - auto importImageStackFilter = filterList->createFilter(::k_ImportImageStackFilterHandle); + auto importImageStackFilter = filterListPtr->createFilter(::k_ImportImageStackFilterHandle); REQUIRE(nullptr != importImageStackFilter); Arguments args; @@ -169,7 +170,7 @@ TEST_CASE("ITKImageProcessing::ITKImportImageStack: NoInput", "[ITKImageProcessi Arguments args; auto preflightResult = filter.preflight(dataStructure, args); - SIMPLNX_RESULT_REQUIRE_INVALID(preflightResult.outputActions); + SIMPLNX_RESULT_REQUIRE_INVALID(preflightResult.outputActions) } TEST_CASE("ITKImageProcessing::ITKImportImageStack: NoImageGeometry", "[ITKImageProcessing][ITKImportImageStack]") @@ -185,7 +186,7 @@ TEST_CASE("ITKImageProcessing::ITKImportImageStack: NoImageGeometry", "[ITKImage args.insertOrAssign(ITKImportImageStack::k_InputFileListInfo_Key, std::make_any(fileListInfo)); auto preflightResult = filter.preflight(dataStructure, args); - SIMPLNX_RESULT_REQUIRE_INVALID(preflightResult.outputActions); + SIMPLNX_RESULT_REQUIRE_INVALID(preflightResult.outputActions) } TEST_CASE("ITKImageProcessing::ITKImportImageStack: NoFiles", "[ITKImageProcessing][ITKImportImageStack]") @@ -209,7 +210,7 @@ TEST_CASE("ITKImageProcessing::ITKImportImageStack: NoFiles", "[ITKImageProcessi args.insertOrAssign(ITKImportImageStack::k_ImageGeometryPath_Key, std::make_any(k_ImageGeomPath)); auto preflightResult = filter.preflight(dataStructure, args); - SIMPLNX_RESULT_REQUIRE_INVALID(preflightResult.outputActions); + SIMPLNX_RESULT_REQUIRE_INVALID(preflightResult.outputActions) } TEST_CASE("ITKImageProcessing::ITKImportImageStack: FileDoesNotExist", "[ITKImageProcessing][ITKImportImageStack]") @@ -233,11 +234,14 @@ TEST_CASE("ITKImageProcessing::ITKImportImageStack: FileDoesNotExist", "[ITKImag args.insertOrAssign(ITKImportImageStack::k_ImageGeometryPath_Key, std::make_any(k_ImageGeomPath)); auto preflightResult = filter.preflight(dataStructure, args); - SIMPLNX_RESULT_REQUIRE_INVALID(preflightResult.outputActions); + SIMPLNX_RESULT_REQUIRE_INVALID(preflightResult.outputActions) } TEST_CASE("ITKImageProcessing::ITKImportImageStack: CompareImage", "[ITKImageProcessing][ITKImportImageStack]") { + auto app = Application::GetOrCreateInstance(); + app->loadPlugins(unit_test::k_BuildDir.view()); + ITKImportImageStack filter; DataStructure dataStructure; Arguments args; @@ -267,12 +271,12 @@ TEST_CASE("ITKImageProcessing::ITKImportImageStack: CompareImage", "[ITKImagePro auto executeResult = filter.execute(dataStructure, args); SIMPLNX_RESULT_REQUIRE_VALID(executeResult.result) - const auto* imageGeom = dataStructure.getDataAs(k_ImageGeomPath); - REQUIRE(imageGeom != nullptr); + const auto* imageGeomPtr = dataStructure.getDataAs(k_ImageGeomPath); + REQUIRE(imageGeomPtr != nullptr); - SizeVec3 imageDims = imageGeom->getDimensions(); - FloatVec3 imageOrigin = imageGeom->getOrigin(); - FloatVec3 imageSpacing = imageGeom->getSpacing(); + SizeVec3 imageDims = imageGeomPtr->getDimensions(); + FloatVec3 imageOrigin = imageGeomPtr->getOrigin(); + FloatVec3 imageSpacing = imageGeomPtr->getSpacing(); std::array dims = {524, 390, 3}; @@ -288,8 +292,8 @@ TEST_CASE("ITKImageProcessing::ITKImportImageStack: CompareImage", "[ITKImagePro REQUIRE(imageSpacing[1] == Approx(spacing[1])); REQUIRE(imageSpacing[2] == Approx(spacing[2])); - const auto* imageData = dataStructure.getDataAs(k_ImageDataPath); - REQUIRE(imageData != nullptr); + const auto* imageDataPtr = dataStructure.getDataAs(k_ImageDataPath); + REQUIRE(imageDataPtr != nullptr); const std::string md5Hash = ITKTestBase::ComputeMd5Hash(dataStructure, k_ImageDataPath); REQUIRE(md5Hash == "2620b39f0dcaa866602c2591353116a4"); @@ -382,3 +386,129 @@ TEST_CASE("ITKImageProcessing::ITKImportImageStack: Flipped Image Odd-Odd X/Y", // Compare against exemplars ::CompareXYFlippedGeometries(dataStructure); } + +TEST_CASE("ITKImageProcessing::ITKImportImageStack: RGB_To_Grayscale", "[ITKImageProcessing][ITKImportImageStack]") +{ + auto app = Application::GetOrCreateInstance(); + app->loadPlugins(unit_test::k_BuildDir.view()); + + ITKImportImageStack filter; + DataStructure dataStructure; + Arguments args; + + GeneratedFileListParameter::ValueType fileListInfo; + fileListInfo.inputPath = k_ImageStackDir; + fileListInfo.startIndex = 0; + fileListInfo.endIndex = 2; + fileListInfo.incrementIndex = 1; + fileListInfo.fileExtension = ".png"; + fileListInfo.filePrefix = "rgb_"; + fileListInfo.fileSuffix = ""; + fileListInfo.paddingDigits = 1; + fileListInfo.ordering = GeneratedFileListParameter::Ordering::LowToHigh; + + std::vector origin = {1.0f, 4.0f, 8.0f}; + std::vector spacing = {0.3f, 0.2f, 0.9f}; + + args.insertOrAssign(ITKImportImageStack::k_InputFileListInfo_Key, std::make_any(fileListInfo)); + args.insertOrAssign(ITKImportImageStack::k_Origin_Key, std::make_any>(origin)); + args.insertOrAssign(ITKImportImageStack::k_Spacing_Key, std::make_any>(spacing)); + args.insertOrAssign(ITKImportImageStack::k_ImageGeometryPath_Key, std::make_any(k_ImageGeomPath)); + args.insertOrAssign(ITKImportImageStack::k_ConvertToGrayScale_Key, std::make_any(true)); + + auto preflightResult = filter.preflight(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_VALID(preflightResult.outputActions) + + auto executeResult = filter.execute(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_VALID(executeResult.result) + + const auto* imageGeomPtr = dataStructure.getDataAs(k_ImageGeomPath); + REQUIRE(imageGeomPtr != nullptr); + + SizeVec3 imageDims = imageGeomPtr->getDimensions(); + FloatVec3 imageOrigin = imageGeomPtr->getOrigin(); + FloatVec3 imageSpacing = imageGeomPtr->getSpacing(); + + std::array dims = {524, 390, 3}; + + REQUIRE(imageDims[0] == dims[0]); + REQUIRE(imageDims[1] == dims[1]); + REQUIRE(imageDims[2] == dims[2]); + + REQUIRE(imageOrigin[0] == Approx(origin[0])); + REQUIRE(imageOrigin[1] == Approx(origin[1])); + REQUIRE(imageOrigin[2] == Approx(origin[2])); + + REQUIRE(imageSpacing[0] == Approx(spacing[0])); + REQUIRE(imageSpacing[1] == Approx(spacing[1])); + REQUIRE(imageSpacing[2] == Approx(spacing[2])); + + const auto* imageDataPtr = dataStructure.getDataAs(k_ImageDataPath); + REQUIRE(imageDataPtr != nullptr); + + const std::string md5Hash = ITKTestBase::ComputeMd5Hash(dataStructure, k_ImageDataPath); + REQUIRE(md5Hash == "2620b39f0dcaa866602c2591353116a4"); +} + +TEST_CASE("ITKImageProcessing::ITKImportImageStack: RGB", "[ITKImageProcessing][ITKImportImageStack]") +{ + auto app = Application::GetOrCreateInstance(); + app->loadPlugins(unit_test::k_BuildDir.view()); + + ITKImportImageStack filter; + DataStructure dataStructure; + Arguments args; + + GeneratedFileListParameter::ValueType fileListInfo; + fileListInfo.inputPath = k_ImageStackDir; + fileListInfo.startIndex = 0; + fileListInfo.endIndex = 2; + fileListInfo.incrementIndex = 1; + fileListInfo.fileExtension = ".png"; + fileListInfo.filePrefix = "rgb_"; + fileListInfo.fileSuffix = ""; + fileListInfo.paddingDigits = 1; + fileListInfo.ordering = GeneratedFileListParameter::Ordering::LowToHigh; + + std::vector origin = {1.0f, 4.0f, 8.0f}; + std::vector spacing = {0.3f, 0.2f, 0.9f}; + + args.insertOrAssign(ITKImportImageStack::k_InputFileListInfo_Key, std::make_any(fileListInfo)); + args.insertOrAssign(ITKImportImageStack::k_Origin_Key, std::make_any>(origin)); + args.insertOrAssign(ITKImportImageStack::k_Spacing_Key, std::make_any>(spacing)); + args.insertOrAssign(ITKImportImageStack::k_ImageGeometryPath_Key, std::make_any(k_ImageGeomPath)); + args.insertOrAssign(ITKImportImageStack::k_ConvertToGrayScale_Key, std::make_any(false)); + + auto preflightResult = filter.preflight(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_VALID(preflightResult.outputActions) + + auto executeResult = filter.execute(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_VALID(executeResult.result) + + const auto* imageGeomPtr = dataStructure.getDataAs(k_ImageGeomPath); + REQUIRE(imageGeomPtr != nullptr); + + SizeVec3 imageDims = imageGeomPtr->getDimensions(); + FloatVec3 imageOrigin = imageGeomPtr->getOrigin(); + FloatVec3 imageSpacing = imageGeomPtr->getSpacing(); + + std::array dims = {524, 390, 3}; + + REQUIRE(imageDims[0] == dims[0]); + REQUIRE(imageDims[1] == dims[1]); + REQUIRE(imageDims[2] == dims[2]); + + REQUIRE(imageOrigin[0] == Approx(origin[0])); + REQUIRE(imageOrigin[1] == Approx(origin[1])); + REQUIRE(imageOrigin[2] == Approx(origin[2])); + + REQUIRE(imageSpacing[0] == Approx(spacing[0])); + REQUIRE(imageSpacing[1] == Approx(spacing[1])); + REQUIRE(imageSpacing[2] == Approx(spacing[2])); + + const auto* imageDataPtr = dataStructure.getDataAs(k_ImageDataPath); + REQUIRE(imageDataPtr != nullptr); + + const std::string md5Hash = ITKTestBase::ComputeMd5Hash(dataStructure, k_ImageDataPath); + REQUIRE(md5Hash == "8b0b0393d6779156c88544bc4d75d3fc"); +}