// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen // SPDX-License-Identifier: BSD-3-Clause #include "vtkImageMathematics.h" #include "vtkAlgorithmOutput.h" #include "vtkDataArray.h" #include "vtkImageData.h" #include "vtkInformation.h" #include "vtkInformationVector.h" #include "vtkObjectFactory.h" #include "vtkPointData.h" #include "vtkStreamingDemandDrivenPipeline.h" #include #include VTK_ABI_NAMESPACE_BEGIN vtkStandardNewMacro(vtkImageMathematics); //------------------------------------------------------------------------------ vtkImageMathematics::vtkImageMathematics() { this->Operation = VTK_ADD; this->ConstantK = 1.0; this->ConstantC = 0.0; this->DivideByZeroToC = 0; } //------------------------------------------------------------------------------ void vtkImageMathematics::ReplaceNthInputConnection(int idx, vtkAlgorithmOutput* input) { if (idx < 0 || idx >= this->GetNumberOfInputConnections(0)) { vtkErrorMacro("Attempt to replace connection idx " << idx << " of input port " << 0 << ", which has only " << this->GetNumberOfInputConnections(0) << " connections."); return; } if (!input || !input->GetProducer()) { vtkErrorMacro("Attempt to replace connection index " << idx << " for input port " << 0 << " with " << (!input ? "a null input." : "an input with no producer.")); return; } this->SetNthInputConnection(0, idx, input); } //------------------------------------------------------------------------------ // The default vtkImageAlgorithm semantics are that SetInput() puts // each input on a different port, we want all the image inputs to // go on the first port. void vtkImageMathematics::SetInputData(int vtkNotUsed(idx), vtkDataObject* input) { this->AddInputDataInternal(0, input); } //------------------------------------------------------------------------------ // The default vtkImageAlgorithm semantics are that SetInput() puts // each input on a different port, we want all the image inputs to // go on the first port. void vtkImageMathematics::SetInputConnection(int idx, vtkAlgorithmOutput* input) { if (idx > 0) { this->AddInputConnection(0, input); } else { this->Superclass::SetInputConnection(idx, input); } } //------------------------------------------------------------------------------ vtkDataObject* vtkImageMathematics::GetInput(int idx) { if (this->GetNumberOfInputConnections(0) <= idx) { return nullptr; } return vtkImageData::SafeDownCast(this->GetExecutive()->GetInputData(0, idx)); } //------------------------------------------------------------------------------ // The output extent is the intersection. int vtkImageMathematics::RequestInformation(vtkInformation* vtkNotUsed(request), vtkInformationVector** inputVector, vtkInformationVector* outputVector) { // get the info objects vtkInformation* outInfo = outputVector->GetInformationObject(0); int ext[6], unionExt[6]; // Initialize the union. vtkInformation* inInfo0 = inputVector[0]->GetInformationObject(0); inInfo0->Get(vtkStreamingDemandDrivenPipeline::WHOLE_EXTENT(), unionExt); // two input take intersection if (this->Operation == VTK_ADD || this->Operation == VTK_SUBTRACT || this->Operation == VTK_MULTIPLY || this->Operation == VTK_DIVIDE || this->Operation == VTK_MIN || this->Operation == VTK_MAX || this->Operation == VTK_ATAN2) { for (int i = 1; i < this->GetNumberOfInputConnections(0); ++i) { vtkInformation* inInfoI = inputVector[0]->GetInformationObject(i); inInfoI->Get(vtkStreamingDemandDrivenPipeline::WHOLE_EXTENT(), ext); for (int idx = 0; idx < 3; ++idx) { unionExt[idx * 2] = std::min(unionExt[idx * 2], ext[idx * 2]); unionExt[(idx * 2) + 1] = std::max(unionExt[(idx * 2) + 1], ext[(idx * 2) + 1]); } } } outInfo->Set(vtkStreamingDemandDrivenPipeline::WHOLE_EXTENT(), unionExt, 6); return 1; } namespace { //------------------------------------------------------------------------------ template void vtkImageMathematicsClamp(TValue& value, TIvar ivar, vtkImageData* data) { if (ivar < static_cast(data->GetScalarTypeMin())) { value = static_cast(data->GetScalarTypeMin()); } else if (ivar > static_cast(data->GetScalarTypeMax())) { value = static_cast(data->GetScalarTypeMax()); } else { value = static_cast(ivar); } } //------------------------------------------------------------------------------ // This templated function executes the filter for any type of data. // Handles the one input operations template void vtkImageMathematicsExecute1(vtkImageMathematics* self, vtkImageData* in1Data, T* in1Ptr, vtkImageData* outData, T* outPtr, int outExt[6], int id) { int idxR, idxY, idxZ; int maxY, maxZ; vtkIdType inIncX, inIncY, inIncZ; vtkIdType outIncX, outIncY, outIncZ; int rowLength; unsigned long count = 0; unsigned long target; int op = self->GetOperation(); // find the region to loop over rowLength = (outExt[1] - outExt[0] + 1) * in1Data->GetNumberOfScalarComponents(); // What a pain. Maybe I should just make another filter. if (op == VTK_CONJUGATE) { rowLength = (outExt[1] - outExt[0] + 1); } maxY = outExt[3] - outExt[2]; maxZ = outExt[5] - outExt[4]; target = static_cast((maxZ + 1) * (maxY + 1) / 50.0); target++; // Get increments to march through data in1Data->GetContinuousIncrements(outExt, inIncX, inIncY, inIncZ); outData->GetContinuousIncrements(outExt, outIncX, outIncY, outIncZ); int divideByZeroToC = self->GetDivideByZeroToC(); double doubleConstantk = self->GetConstantK(); // Avoid casts by making constants the same type as input/output // Of course they must be clamped to a valid range for the scalar type T constantk, constantc; vtkImageMathematicsClamp(constantk, self->GetConstantK(), in1Data); vtkImageMathematicsClamp(constantc, self->GetConstantC(), in1Data); // Loop through output pixels for (idxZ = 0; idxZ <= maxZ; idxZ++) { for (idxY = 0; idxY <= maxY; idxY++) { if (!id) { if (!(count % target)) { self->UpdateProgress(count / (50.0 * target)); } count++; } for (idxR = 0; idxR < rowLength; idxR++) { // Pixel operation switch (op) { case VTK_INVERT: if (*in1Ptr) { *outPtr = static_cast(1.0 / *in1Ptr); } else { if (divideByZeroToC) { *outPtr = constantc; } else { *outPtr = static_cast(outData->GetScalarTypeMax()); } } break; case VTK_SIN: *outPtr = static_cast(std::sin(static_cast(*in1Ptr))); break; case VTK_COS: *outPtr = static_cast(std::cos(static_cast(*in1Ptr))); break; case VTK_EXP: *outPtr = static_cast(std::exp(static_cast(*in1Ptr))); break; case VTK_LOG: *outPtr = static_cast(std::log(static_cast(*in1Ptr))); break; case VTK_ABS: *outPtr = static_cast(std::abs(static_cast(*in1Ptr))); break; case VTK_SQR: *outPtr = static_cast(*in1Ptr * *in1Ptr); break; case VTK_SQRT: *outPtr = static_cast(std::sqrt(static_cast(*in1Ptr))); break; case VTK_ATAN: *outPtr = static_cast(std::atan(static_cast(*in1Ptr))); break; case VTK_MULTIPLYBYK: *outPtr = static_cast(doubleConstantk * static_cast(*in1Ptr)); break; case VTK_ADDC: *outPtr = constantc + *in1Ptr; break; case VTK_REPLACECBYK: *outPtr = (*in1Ptr == constantc) ? constantk : *in1Ptr; break; case VTK_CONJUGATE: outPtr[0] = in1Ptr[0]; outPtr[1] = static_cast(-1.0 * static_cast(in1Ptr[1])); // Why bother trying to figure out the continuous increments. outPtr++; in1Ptr++; break; } outPtr++; in1Ptr++; } outPtr += outIncY; in1Ptr += inIncY; } outPtr += outIncZ; in1Ptr += inIncZ; } } //------------------------------------------------------------------------------ // This templated function executes the filter for any type of data. // Handles the two input operations template void vtkImageMathematicsExecute2(vtkImageMathematics* self, vtkImageData* inData, T* inPtr, vtkImageData* outData, T* outPtr, int outExt[6], int id) { int idxR, idxY, idxZ; int maxY, maxZ; vtkIdType inIncX, inIncY, inIncZ; vtkIdType outIncX, outIncY, outIncZ; int rowLength; unsigned long count = 0; unsigned long target; int op = self->GetOperation(); int divideByZeroToC = self->GetDivideByZeroToC(); double constantc = self->GetConstantC(); // find the region to loop over rowLength = (outExt[1] - outExt[0] + 1) * inData->GetNumberOfScalarComponents(); // What a pain. Maybe I should just make another filter. if (op == VTK_COMPLEX_MULTIPLY) { rowLength = (outExt[1] - outExt[0] + 1); } maxY = outExt[3] - outExt[2]; maxZ = outExt[5] - outExt[4]; target = static_cast((maxZ + 1) * (maxY + 1) / 50.0); target++; // Get increments to march through data inData->GetContinuousIncrements(outExt, inIncX, inIncY, inIncZ); outData->GetContinuousIncrements(outExt, outIncX, outIncY, outIncZ); // Loop through output pixels for (idxZ = 0; idxZ <= maxZ; idxZ++) { for (idxY = 0; !self->AbortExecute && idxY <= maxY; idxY++) { if (!id) { if (!(count % target)) { self->UpdateProgress(count / (50.0 * target)); } count++; } for (idxR = 0; idxR < rowLength; idxR++) { // Pixel operation switch (op) { case VTK_ADD: *outPtr += *inPtr; break; case VTK_SUBTRACT: *outPtr -= *inPtr; break; case VTK_MULTIPLY: *outPtr *= *inPtr; break; case VTK_DIVIDE: if (*inPtr) { *outPtr /= *inPtr; } else { if (divideByZeroToC) { *outPtr = static_cast(constantc); } else { // *outPtr = (T)(*in1Ptr / 0.00001); *outPtr = static_cast(outData->GetScalarTypeMax()); } } break; case VTK_MIN: *outPtr = std::min(*outPtr, *inPtr); break; case VTK_MAX: *outPtr = std::max(*outPtr, *inPtr); break; case VTK_ATAN2: if (*outPtr == 0.0 && *inPtr == 0.0) { *outPtr = 0; } else { *outPtr = static_cast( std::atan2(static_cast(*outPtr), static_cast(*inPtr))); } break; case VTK_COMPLEX_MULTIPLY: const T prevOut[2] = { outPtr[0], outPtr[1] }; outPtr[0] = prevOut[0] * inPtr[0] - prevOut[1] * inPtr[1]; outPtr[1] = prevOut[1] * inPtr[0] + prevOut[0] * inPtr[1]; // Why bother trying to figure out the continuous increments. ++outPtr; ++inPtr; break; } ++outPtr; ++inPtr; } outPtr += outIncY; inPtr += inIncY; } outPtr += outIncZ; inPtr += inIncZ; } } //------------------------------------------------------------------------------ template void vtkImageMathematicsInitOutput( vtkImageData* inData, T* inPtr, vtkImageData* vtkNotUsed(outData), T* outPtr, int ext[6]) { int idxY, idxZ; int maxY, maxZ; vtkIdType outIncY, outIncZ; int rowLength; int typeSize; T *outPtrZ, *outPtrY; T *inPtrZ, *inPtrY; // This method needs to copy scalars from input to output for the update-extent. vtkDataArray* inArray = inData->GetPointData()->GetScalars(); typeSize = vtkDataArray::GetDataTypeSize(inArray->GetDataType()); outPtrZ = outPtr; inPtrZ = inPtr; // Get increments to march through data vtkIdType increments[3]; increments[0] = inArray->GetNumberOfComponents(); increments[1] = increments[0] * (ext[1] - ext[0] + 1); increments[2] = increments[1] * (ext[3] - ext[2] + 1); outIncY = increments[1]; outIncZ = increments[2]; // Find the region to loop over rowLength = (ext[1] - ext[0] + 1) * inArray->GetNumberOfComponents(); rowLength *= typeSize; maxY = ext[3] - ext[2]; maxZ = ext[5] - ext[4]; // Loop through input pixels for (idxZ = 0; idxZ <= maxZ; idxZ++) { outPtrY = outPtrZ; inPtrY = inPtrZ; for (idxY = 0; idxY <= maxY; idxY++) { memcpy(outPtrY, inPtrY, rowLength); outPtrY += outIncY; inPtrY += outIncY; } outPtrZ += outIncZ; inPtrZ += outIncZ; } } } //------------------------------------------------------------------------------ // This method is passed a input and output datas, and executes the filter // algorithm to fill the output from the inputs. // It just executes a switch statement to call the correct function for // the datas data types. void vtkImageMathematics::ThreadedRequestData(vtkInformation* vtkNotUsed(request), vtkInformationVector** vtkNotUsed(inputVector), vtkInformationVector* vtkNotUsed(outputVector), vtkImageData*** inData, vtkImageData** outData, int outExt[6], int id) { void* outPtr = outData[0]->GetScalarPointerForExtent(outExt); if (this->Operation == VTK_ADD || this->Operation == VTK_SUBTRACT || this->Operation == VTK_MULTIPLY || this->Operation == VTK_DIVIDE || this->Operation == VTK_MIN || this->Operation == VTK_MAX || this->Operation == VTK_ATAN2 || this->Operation == VTK_COMPLEX_MULTIPLY) { for (int i = 0; i < this->GetNumberOfInputConnections(0); ++i) { void* inPtrI = inData[0][i]->GetScalarPointerForExtent(outExt); { if (this->Operation == VTK_COMPLEX_MULTIPLY) { if (inData[0][i]->GetNumberOfScalarComponents() != 2 || inData[0][i]->GetNumberOfScalarComponents() != 2) { vtkErrorMacro("Complex inputs must have two components."); return; } // this filter expects that input is the same type as output. if (inData[0][i]->GetScalarType() != outData[0]->GetScalarType()) { vtkErrorMacro(<< "Execute: input1 ScalarType, " << inData[0][i]->GetScalarType() << ", must match output ScalarType " << outData[0]->GetScalarType()); return; } } if (i == 0) { switch (inData[0][i]->GetScalarType()) { vtkTemplateMacro(vtkImageMathematicsInitOutput(inData[0][i], static_cast(inPtrI), outData[0], static_cast(outPtr), outExt)); default: vtkErrorMacro(<< "InitOutput: Unknown ScalarType"); return; } } else { switch (inData[0][i]->GetScalarType()) { vtkTemplateMacro(vtkImageMathematicsExecute2(this, inData[0][i], static_cast(inPtrI), outData[0], static_cast(outPtr), outExt, id)); default: vtkErrorMacro(<< "Execute: Unknown ScalarType"); return; } } } } } else { // this filter expects that input is the same type as output. if (inData[0][0]->GetScalarType() != outData[0]->GetScalarType()) { vtkErrorMacro(<< "Execute: input ScalarType, " << inData[0][0]->GetScalarType() << ", must match out ScalarType " << outData[0]->GetScalarType()); return; } if (this->Operation == VTK_CONJUGATE) { if (inData[0][0]->GetNumberOfScalarComponents() != 2) { vtkErrorMacro("Complex inputs must have two components."); return; } } void* inPtr = inData[0][0]->GetScalarPointerForExtent(outExt); switch (inData[0][0]->GetScalarType()) { vtkTemplateMacro(vtkImageMathematicsExecute1(this, inData[0][0], static_cast(inPtr), outData[0], static_cast(outPtr), outExt, id)); default: vtkErrorMacro(<< "Execute: Unknown ScalarType"); } } } //------------------------------------------------------------------------------ int vtkImageMathematics::FillInputPortInformation(int port, vtkInformation* info) { info->Set(vtkAlgorithm::INPUT_IS_REPEATABLE(), 1); if (port == 1) { info->Set(vtkAlgorithm::INPUT_IS_OPTIONAL(), 1); } info->Set(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkImageData"); return 1; } //------------------------------------------------------------------------------ void vtkImageMathematics::PrintSelf(ostream& os, vtkIndent indent) { this->Superclass::PrintSelf(os, indent); os << indent << "Operation: " << this->Operation << "\n"; os << indent << "ConstantK: " << this->ConstantK << "\n"; os << indent << "ConstantC: " << this->ConstantC << "\n"; os << indent << "DivideByZeroToC: " << (this->DivideByZeroToC ? "On" : "Off") << "\n"; } VTK_ABI_NAMESPACE_END