Skip to content

Commit 19b009c

Browse files
authored
ENH: ITKImportImageStack - Add option to convert images to grayscale 'on-the-fly'. (BlueQuartzSoftware#832)
Signed-off-by: Michael Jackson <[email protected]>
1 parent 7c434ae commit 19b009c

File tree

6 files changed

+275
-39
lines changed

6 files changed

+275
-39
lines changed
Loading
Loading
Loading

src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Filters/ITKImportImageStack.cpp

Lines changed: 128 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include "ITKImageProcessing/Common/ITKArrayHelper.hpp"
44
#include "ITKImageProcessing/Filters/ITKImageReader.hpp"
55

6+
#include "simplnx/Common/TypesUtility.hpp"
67
#include "simplnx/Core/Application.hpp"
78
#include "simplnx/DataStructure/DataPath.hpp"
89
#include "simplnx/DataStructure/Geometry/ImageGeom.hpp"
@@ -57,6 +58,8 @@ const ChoicesParameter::ValueType k_FlipAboutYAxis = 2;
5758
const Uuid k_SimplnxCorePluginId = *Uuid::FromString("05cc618b-781f-4ac0-b9ac-43f26ce1854f");
5859
const Uuid k_RotateSampleRefFrameFilterId = *Uuid::FromString("d2451dc1-a5a1-4ac2-a64d-7991669dcffc");
5960
const FilterHandle k_RotateSampleRefFrameFilterHandle(k_RotateSampleRefFrameFilterId, k_SimplnxCorePluginId);
61+
const Uuid k_ColorToGrayScaleFilterId = *Uuid::FromString("d938a2aa-fee2-4db9-aa2f-2c34a9736580");
62+
const FilterHandle k_ColorToGrayScaleFilterHandle(k_ColorToGrayScaleFilterId, k_SimplnxCorePluginId);
6063

6164
// Make sure we can instantiate the RotateSampleRefFrame Filter
6265
std::unique_ptr<IFilter> CreateRotateSampleRefFrameFilter()
@@ -132,7 +135,8 @@ namespace cxITKImportImageStack
132135
{
133136
template <class T>
134137
Result<> ReadImageStack(DataStructure& dataStructure, const DataPath& imageGeomPath, const std::string& cellDataName, const DataPath& imageDataPath, const std::vector<std::string>& files,
135-
ChoicesParameter::ValueType transformType, const IFilter::MessageHandler& messageHandler, const std::atomic_bool& shouldCancel)
138+
ChoicesParameter::ValueType transformType, bool convertToGrayscale, VectorFloat32Parameter::ValueType luminosityValues, const IFilter::MessageHandler& messageHandler,
139+
const std::atomic_bool& shouldCancel)
136140
{
137141
auto& imageGeom = dataStructure.getDataRefAs<ImageGeom>(imageGeomPath);
138142

@@ -147,13 +151,21 @@ Result<> ReadImageStack(DataStructure& dataStructure, const DataPath& imageGeomP
147151
// Variables for the progress Reporting
148152
usize slice = 0;
149153

154+
auto* filterListPtr = Application::Instance()->getFilterList();
155+
156+
if(convertToGrayscale && !filterListPtr->containsPlugin(k_SimplnxCorePluginId))
157+
{
158+
return MakeErrorResult(-18542, "SimplnxCore was not instantiated in this instance, so color to grayscale is not a valid option.");
159+
}
160+
auto grayScaleFilter = filterListPtr->createFilter(k_ColorToGrayScaleFilterHandle);
161+
Result<> outputResult = {};
162+
150163
// Loop over all the files importing them one by one and copying the data into the data array
151164
for(const auto& filePath : files)
152165
{
153166
messageHandler(IFilter::Message::Type::Info, fmt::format("Importing: {}", filePath));
154167

155168
DataStructure importedDataStructure;
156-
157169
{
158170
// Create a sub-filter to read each image, although for preflight we are going to read the first image in the
159171
// list and hope the rest are correct.
@@ -171,6 +183,50 @@ Result<> ReadImageStack(DataStructure& dataStructure, const DataPath& imageGeomP
171183
return executeResult.result;
172184
}
173185
}
186+
187+
// ======================= Convert to GrayScale Section ===================
188+
bool validInputForGrayScaleConversion = importedDataStructure.getDataRefAs<IDataArray>(imageDataPath).getDataType() == DataType::uint8;
189+
if(convertToGrayscale && validInputForGrayScaleConversion && nullptr != grayScaleFilter.get())
190+
{
191+
192+
// This same filter was used to preflight so as long as nothing changes on disk this really should work....
193+
Arguments colorToGrayscaleArgs;
194+
colorToGrayscaleArgs.insertOrAssign("conversion_algorithm", std::make_any<ChoicesParameter::ValueType>(0));
195+
colorToGrayscaleArgs.insertOrAssign("color_weights", std::make_any<VectorFloat32Parameter::ValueType>(luminosityValues));
196+
colorToGrayscaleArgs.insertOrAssign("input_data_array_vector", std::make_any<std::vector<DataPath>>(std::vector<DataPath>{imageDataPath}));
197+
colorToGrayscaleArgs.insertOrAssign("output_array_prefix", std::make_any<std::string>("gray"));
198+
199+
// Run grayscale filter and process results and messages
200+
auto result = grayScaleFilter->execute(importedDataStructure, colorToGrayscaleArgs).result;
201+
if(result.invalid())
202+
{
203+
return result;
204+
}
205+
206+
// deletion of non-grayscale array
207+
DataObject::IdType id;
208+
{ // scoped for safety since this reference will be nonexistent in a moment
209+
auto& oldArray = importedDataStructure.getDataRefAs<IDataArray>(imageDataPath);
210+
id = oldArray.getId();
211+
}
212+
importedDataStructure.removeData(id);
213+
214+
// rename grayscale array to reflect original
215+
{
216+
auto& gray = importedDataStructure.getDataRefAs<IDataArray>(imageDataPath.getParent().createChildPath("gray" + imageDataPath.getTargetName()));
217+
if(!gray.canRename(imageDataPath.getTargetName()))
218+
{
219+
return MakeErrorResult(-64543, fmt::format("Unable to rename the internal grayscale array to {}", imageDataPath.getTargetName()));
220+
}
221+
gray.rename(imageDataPath.getTargetName());
222+
}
223+
}
224+
else if(convertToGrayscale && !validInputForGrayScaleConversion)
225+
{
226+
outputResult.warnings().emplace_back(Warning{
227+
-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())});
228+
}
229+
174230
// Check the ImageGeometry of the imported Image matches the destination
175231
const auto& importedImageGeom = importedDataStructure.getDataRefAs<ImageGeom>(imageGeomPath);
176232
SizeVec3 importedDims = importedImageGeom.getDimensions();
@@ -206,11 +262,11 @@ Result<> ReadImageStack(DataStructure& dataStructure, const DataPath& imageGeomP
206262
// Check to see if the filter got canceled.
207263
if(shouldCancel)
208264
{
209-
return {};
265+
return outputResult;
210266
}
211267
}
212268

213-
return {};
269+
return outputResult;
214270
}
215271
} // namespace cxITKImportImageStack
216272

@@ -256,6 +312,10 @@ Parameters ITKImportImageStack::parameters() const
256312
params.insert(std::make_unique<VectorFloat32Parameter>(k_Spacing_Key, "Spacing", "The spacing of the 3D volume", std::vector<float32>{1.0F, 1.0F, 1.0F}, std::vector<std::string>{"X", "y", "Z"}));
257313
params.insertLinkableParameter(std::make_unique<ChoicesParameter>(k_ImageTransformChoice_Key, "Optional Slice Operations",
258314
"Operation that is performed on each slice. 0=None, 1=Flip about X, 2=Flip about Y", 0, k_SliceOperationChoices));
315+
params.insertLinkableParameter(
316+
std::make_unique<BoolParameter>(k_ConvertToGrayScale_Key, "Convert To GrayScale", "The filter will show an error if the images are already in grayscale format", false));
317+
params.insert(std::make_unique<VectorFloat32Parameter>(k_ColorWeights_Key, "Color Weighting", "RGB weights for the grayscale conversion using the luminosity algorithm.",
318+
std::vector<float32>{0.2125f, 0.7154f, 0.0721f}, std::vector<std::string>({"Red", "Green", "Blue"})));
259319

260320
params.insertSeparator(Parameters::Separator{"File List"});
261321
params.insert(
@@ -266,6 +326,8 @@ Parameters ITKImportImageStack::parameters() const
266326
params.insert(std::make_unique<DataObjectNameParameter>(k_CellDataName_Key, "Cell Data Name", "The name of the created cell attribute matrix", ImageGeom::k_CellDataName));
267327
params.insert(std::make_unique<DataObjectNameParameter>(k_ImageDataArrayPath_Key, "Created Image Data", "The path to the created image data array", "ImageData"));
268328

329+
params.linkParameters(k_ConvertToGrayScale_Key, k_ColorWeights_Key, true);
330+
269331
return params;
270332
}
271333

@@ -283,11 +345,17 @@ IFilter::PreflightResult ITKImportImageStack::preflightImpl(const DataStructure&
283345
auto origin = filterArgs.value<VectorFloat32Parameter::ValueType>(k_Origin_Key);
284346
auto spacing = filterArgs.value<VectorFloat32Parameter::ValueType>(k_Spacing_Key);
285347
auto imageGeomPath = filterArgs.value<DataPath>(k_ImageGeometryPath_Key);
286-
auto imageDataName = filterArgs.value<DataObjectNameParameter::ValueType>(k_ImageDataArrayPath_Key);
348+
auto pImageDataArrayNameValue = filterArgs.value<DataObjectNameParameter::ValueType>(k_ImageDataArrayPath_Key);
287349
auto cellDataName = filterArgs.value<DataObjectNameParameter::ValueType>(k_CellDataName_Key);
288350
auto imageTransformValue = filterArgs.value<ChoicesParameter::ValueType>(k_ImageTransformChoice_Key);
351+
auto pConvertToGrayScaleValue = filterArgs.value<bool>(k_ConvertToGrayScale_Key);
352+
auto pColorWeightsValue = filterArgs.value<VectorFloat32Parameter::ValueType>(k_ColorWeights_Key);
289353

290-
const DataPath imageDataPath = imageGeomPath.createChildPath(cellDataName).createChildPath(imageDataName);
354+
PreflightResult preflightResult;
355+
nx::core::Result<OutputActions> resultOutputActions = {};
356+
std::vector<PreflightValue> preflightUpdatedValues;
357+
358+
const DataPath imageDataPath = imageGeomPath.createChildPath(cellDataName).createChildPath(pImageDataArrayNameValue);
291359

292360
if(imageTransformValue != k_NoImageTransform)
293361
{
@@ -314,7 +382,7 @@ IFilter::PreflightResult ITKImportImageStack::preflightImpl(const DataStructure&
314382
imageReaderArgs.insertOrAssign(ITKImageReader::k_FileName_Key, std::make_any<fs::path>(files.at(0)));
315383

316384
const ITKImageReader imageReader;
317-
PreflightResult imageReaderResult = imageReader.preflight(dataStructure, imageReaderArgs, messageHandler);
385+
PreflightResult imageReaderResult = imageReader.preflight(dataStructure, imageReaderArgs, messageHandler, shouldCancel);
318386
if(imageReaderResult.outputActions.invalid())
319387
{
320388
return imageReaderResult;
@@ -326,15 +394,15 @@ IFilter::PreflightResult ITKImportImageStack::preflightImpl(const DataStructure&
326394
const auto* createImageGeomActionPtr = dynamic_cast<const CreateImageGeometryAction*>(action0Ptr);
327395
if(createImageGeomActionPtr == nullptr)
328396
{
329-
throw std::runtime_error("ITKImportImageStack: Expected CreateImageGeometryAction at index 0");
397+
throw std::runtime_error(fmt::format("{}: Expected CreateImageGeometryAction at index 0", this->humanName()));
330398
}
331399

332400
// The second action should be the array creation
333401
const IDataAction* action1Ptr = imageReaderResult.outputActions.value().actions.at(1).get();
334402
const auto* createArrayActionPtr = dynamic_cast<const CreateArrayAction*>(action1Ptr);
335403
if(createArrayActionPtr == nullptr)
336404
{
337-
throw std::runtime_error("ITKImportImageStack: Expected CreateArrayAction at index 1");
405+
throw std::runtime_error(fmt::format("{}: Expected CreateArrayAction at index 1", this->humanName()));
338406
}
339407

340408
// X Y Z
@@ -344,11 +412,36 @@ IFilter::PreflightResult ITKImportImageStack::preflightImpl(const DataStructure&
344412
// Z Y X
345413
const std::vector<usize> arrayDims(dims.crbegin(), dims.crend());
346414

347-
OutputActions outputActions;
348-
outputActions.appendAction(std::make_unique<CreateImageGeometryAction>(std::move(imageGeomPath), std::move(dims), std::move(origin), std::move(spacing), cellDataName));
349-
outputActions.appendAction(std::make_unique<CreateArrayAction>(createArrayActionPtr->type(), arrayDims, createArrayActionPtr->componentDims(), imageDataPath));
415+
// OutputActions outputActions;
416+
resultOutputActions.value().appendAction(std::make_unique<CreateImageGeometryAction>(std::move(imageGeomPath), std::move(dims), std::move(origin), std::move(spacing), cellDataName));
417+
418+
if(createArrayActionPtr->type() != DataType::uint8 && pConvertToGrayScaleValue)
419+
{
420+
return MakePreflightErrorResult(-23504, fmt::format("The input DataType is {} which cannot be converted to grayscale. Please turn off the 'Convert To Grayscale' option.",
421+
nx::core::DataTypeToString(createArrayActionPtr->type())));
422+
}
423+
424+
if(pConvertToGrayScaleValue)
425+
{
426+
auto* filterListPtr = Application::Instance()->getFilterList();
427+
if(!filterListPtr->containsPlugin(k_SimplnxCorePluginId))
428+
{
429+
return MakePreflightErrorResult(-23501, "Color to GrayScale conversion is disabled because the 'SimplnxCore' plugin was not loaded.");
430+
}
431+
auto grayScaleFilter = filterListPtr->createFilter(k_ColorToGrayScaleFilterHandle);
432+
if(nullptr == grayScaleFilter.get())
433+
{
434+
return MakePreflightErrorResult(-23502, "Color to GrayScale conversion is disabled because the 'Color to GrayScale' filter is missing from the SimplnxCore plugin.");
435+
}
436+
resultOutputActions.value().appendAction(std::make_unique<CreateArrayAction>(createArrayActionPtr->type(), arrayDims, std::vector<size_t>{1ULL}, imageDataPath));
437+
}
438+
else
439+
{
440+
resultOutputActions.value().appendAction(std::make_unique<CreateArrayAction>(createArrayActionPtr->type(), arrayDims, createArrayActionPtr->componentDims(), imageDataPath));
441+
}
350442

351-
return {std::move(outputActions)};
443+
// Return both the resultOutputActions and the preflightUpdatedValues via std::move()
444+
return {std::move(resultOutputActions), std::move(preflightUpdatedValues)};
352445
}
353446

354447
//------------------------------------------------------------------------------
@@ -362,6 +455,8 @@ Result<> ITKImportImageStack::executeImpl(DataStructure& dataStructure, const Ar
362455
auto imageDataName = filterArgs.value<DataObjectNameParameter::ValueType>(k_ImageDataArrayPath_Key);
363456
auto cellDataName = filterArgs.value<DataObjectNameParameter::ValueType>(k_CellDataName_Key);
364457
auto imageTransformValue = filterArgs.value<ChoicesParameter::ValueType>(k_ImageTransformChoice_Key);
458+
auto convertToGrayScaleValue = filterArgs.value<bool>(k_ConvertToGrayScale_Key);
459+
auto colorWeightsValue = filterArgs.value<VectorFloat32Parameter::ValueType>(k_ColorWeights_Key);
365460

366461
const DataPath imageDataPath = imageGeomPath.createChildPath(cellDataName).createChildPath(imageDataName);
367462

@@ -384,43 +479,53 @@ Result<> ITKImportImageStack::executeImpl(DataStructure& dataStructure, const Ar
384479
switch(*numericType)
385480
{
386481
case NumericType::uint8: {
387-
readResult = cxITKImportImageStack::ReadImageStack<uint8>(dataStructure, imageGeomPath, cellDataName, imageDataPath, files, imageTransformValue, messageHandler, shouldCancel);
482+
readResult = cxITKImportImageStack::ReadImageStack<uint8>(dataStructure, imageGeomPath, cellDataName, imageDataPath, files, imageTransformValue, convertToGrayScaleValue, colorWeightsValue,
483+
messageHandler, shouldCancel);
388484
break;
389485
}
390486
case NumericType::int8: {
391-
readResult = cxITKImportImageStack::ReadImageStack<int8>(dataStructure, imageGeomPath, cellDataName, imageDataPath, files, imageTransformValue, messageHandler, shouldCancel);
487+
readResult = cxITKImportImageStack::ReadImageStack<int8>(dataStructure, imageGeomPath, cellDataName, imageDataPath, files, imageTransformValue, convertToGrayScaleValue, colorWeightsValue,
488+
messageHandler, shouldCancel);
392489
break;
393490
}
394491
case NumericType::uint16: {
395-
readResult = cxITKImportImageStack::ReadImageStack<uint16>(dataStructure, imageGeomPath, cellDataName, imageDataPath, files, imageTransformValue, messageHandler, shouldCancel);
492+
readResult = cxITKImportImageStack::ReadImageStack<uint16>(dataStructure, imageGeomPath, cellDataName, imageDataPath, files, imageTransformValue, convertToGrayScaleValue, colorWeightsValue,
493+
messageHandler, shouldCancel);
396494
break;
397495
}
398496
case NumericType::int16: {
399-
readResult = cxITKImportImageStack::ReadImageStack<int16>(dataStructure, imageGeomPath, cellDataName, imageDataPath, files, imageTransformValue, messageHandler, shouldCancel);
497+
readResult = cxITKImportImageStack::ReadImageStack<int16>(dataStructure, imageGeomPath, cellDataName, imageDataPath, files, imageTransformValue, convertToGrayScaleValue, colorWeightsValue,
498+
messageHandler, shouldCancel);
400499
break;
401500
}
402501
case NumericType::uint32: {
403-
readResult = cxITKImportImageStack::ReadImageStack<uint32>(dataStructure, imageGeomPath, cellDataName, imageDataPath, files, imageTransformValue, messageHandler, shouldCancel);
502+
readResult = cxITKImportImageStack::ReadImageStack<uint32>(dataStructure, imageGeomPath, cellDataName, imageDataPath, files, imageTransformValue, convertToGrayScaleValue, colorWeightsValue,
503+
messageHandler, shouldCancel);
404504
break;
405505
}
406506
case NumericType::int32: {
407-
readResult = cxITKImportImageStack::ReadImageStack<int32>(dataStructure, imageGeomPath, cellDataName, imageDataPath, files, imageTransformValue, messageHandler, shouldCancel);
507+
readResult = cxITKImportImageStack::ReadImageStack<int32>(dataStructure, imageGeomPath, cellDataName, imageDataPath, files, imageTransformValue, convertToGrayScaleValue, colorWeightsValue,
508+
messageHandler, shouldCancel);
408509
break;
409510
}
410511
case NumericType::uint64: {
411-
readResult = cxITKImportImageStack::ReadImageStack<uint64>(dataStructure, imageGeomPath, cellDataName, imageDataPath, files, imageTransformValue, messageHandler, shouldCancel);
512+
readResult = cxITKImportImageStack::ReadImageStack<uint64>(dataStructure, imageGeomPath, cellDataName, imageDataPath, files, imageTransformValue, convertToGrayScaleValue, colorWeightsValue,
513+
messageHandler, shouldCancel);
412514
break;
413515
}
414516
case NumericType::int64: {
415-
readResult = cxITKImportImageStack::ReadImageStack<int64>(dataStructure, imageGeomPath, cellDataName, imageDataPath, files, imageTransformValue, messageHandler, shouldCancel);
517+
readResult = cxITKImportImageStack::ReadImageStack<int64>(dataStructure, imageGeomPath, cellDataName, imageDataPath, files, imageTransformValue, convertToGrayScaleValue, colorWeightsValue,
518+
messageHandler, shouldCancel);
416519
break;
417520
}
418521
case NumericType::float32: {
419-
readResult = cxITKImportImageStack::ReadImageStack<float32>(dataStructure, imageGeomPath, cellDataName, imageDataPath, files, imageTransformValue, messageHandler, shouldCancel);
522+
readResult = cxITKImportImageStack::ReadImageStack<float32>(dataStructure, imageGeomPath, cellDataName, imageDataPath, files, imageTransformValue, convertToGrayScaleValue, colorWeightsValue,
523+
messageHandler, shouldCancel);
420524
break;
421525
}
422526
case NumericType::float64: {
423-
readResult = cxITKImportImageStack::ReadImageStack<float64>(dataStructure, imageGeomPath, cellDataName, imageDataPath, files, imageTransformValue, messageHandler, shouldCancel);
527+
readResult = cxITKImportImageStack::ReadImageStack<float64>(dataStructure, imageGeomPath, cellDataName, imageDataPath, files, imageTransformValue, convertToGrayScaleValue, colorWeightsValue,
528+
messageHandler, shouldCancel);
424529
break;
425530
}
426531
default: {

src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Filters/ITKImportImageStack.hpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ class ITKIMAGEPROCESSING_EXPORT ITKImportImageStack : public IFilter
3131
static inline constexpr StringLiteral k_ImageDataArrayPath_Key = "image_data_array_path";
3232
static inline constexpr StringLiteral k_CellDataName_Key = "cell_data_name";
3333
static inline constexpr StringLiteral k_ImageTransformChoice_Key = "image_transform_choice";
34-
34+
static inline constexpr StringLiteral k_ConvertToGrayScale_Key = "convert_to_gray_scale";
35+
static inline constexpr StringLiteral k_ColorWeights_Key = "color_weights";
3536
/**
3637
* @brief Reads SIMPL json and converts it simplnx Arguments.
3738
* @param json

0 commit comments

Comments
 (0)