// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen // SPDX-License-Identifier: BSD-3-Clause #include "vtkImageMapToWindowLevelColors.h" #include "vtkDataArray.h" #include "vtkImageData.h" #include "vtkInformation.h" #include "vtkInformationVector.h" #include "vtkObjectFactory.h" #include "vtkPointData.h" #include "vtkScalarsToColors.h" #include VTK_ABI_NAMESPACE_BEGIN vtkStandardNewMacro(vtkImageMapToWindowLevelColors); // Constructor sets default values vtkImageMapToWindowLevelColors::vtkImageMapToWindowLevelColors() { this->Window = 255; this->Level = 127.5; } vtkImageMapToWindowLevelColors::~vtkImageMapToWindowLevelColors() = default; //------------------------------------------------------------------------------ // This method checks to see if we can simply reference the input data int vtkImageMapToWindowLevelColors::RequestData( vtkInformation* request, vtkInformationVector** inputVector, vtkInformationVector* outputVector) { vtkInformation* outInfo = outputVector->GetInformationObject(0); vtkInformation* inInfo = inputVector[0]->GetInformationObject(0); vtkImageData* outData = vtkImageData::SafeDownCast(outInfo->Get(vtkDataObject::DATA_OBJECT())); vtkImageData* inData = vtkImageData::SafeDownCast(inInfo->Get(vtkDataObject::DATA_OBJECT())); // If LookupTable is null and window / level produces no change, // then just pass the data if (this->LookupTable == nullptr && (inData->GetScalarType() == VTK_UNSIGNED_CHAR && this->Window == 255 && this->Level == 127.5)) { vtkDebugMacro("ExecuteData: LookupTable not set, " "Window / Level at default, " "passing input to output."); outData->SetExtent(inData->GetExtent()); outData->GetPointData()->PassData(inData->GetPointData()); this->DataWasPassed = 1; } else // normal behaviour - skip up a level since we don't want to // call the superclasses ExecuteData - it would pass the data if there // is no lookup table even if there is a window / level - wrong // behavior. { if (this->DataWasPassed) { outData->GetPointData()->SetScalars(nullptr); this->DataWasPassed = 0; } // NOLINTNEXTLINE(bugprone-parent-virtual-call) return this->vtkThreadedImageAlgorithm::RequestData(request, inputVector, outputVector); } return 1; } //------------------------------------------------------------------------------ int vtkImageMapToWindowLevelColors::RequestInformation(vtkInformation* vtkNotUsed(request), vtkInformationVector** inputVector, vtkInformationVector* outputVector) { vtkInformation* inInfo = inputVector[0]->GetInformationObject(0); vtkInformation* outInfo = outputVector->GetInformationObject(0); vtkInformation* inScalarInfo = vtkDataObject::GetActiveFieldInformation( inInfo, vtkDataObject::FIELD_ASSOCIATION_POINTS, vtkDataSetAttributes::SCALARS); if (!inScalarInfo) { vtkErrorMacro("Missing scalar field on input information!"); return 0; } // If LookupTable is null and window / level produces no change, // then the data will be passed if (this->LookupTable == nullptr && (inScalarInfo->Get(vtkDataObject::FIELD_ARRAY_TYPE()) == VTK_UNSIGNED_CHAR && this->Window == 255 && this->Level == 127.5)) { if (inScalarInfo->Get(vtkDataObject::FIELD_ARRAY_TYPE()) != VTK_UNSIGNED_CHAR) { vtkErrorMacro( "ExecuteInformation: No LookupTable was set and input data is not VTK_UNSIGNED_CHAR!"); } else { // no lookup table, pass the input if it was VTK_UNSIGNED_CHAR vtkDataObject::SetPointDataActiveScalarInfo( outInfo, VTK_UNSIGNED_CHAR, inScalarInfo->Get(vtkDataObject::FIELD_NUMBER_OF_COMPONENTS())); } } else // the lookup table was set or window / level produces a change { int numComponents = 4; switch (this->OutputFormat) { case VTK_RGBA: numComponents = 4; break; case VTK_RGB: numComponents = 3; break; case VTK_LUMINANCE_ALPHA: numComponents = 2; break; case VTK_LUMINANCE: numComponents = 1; break; default: vtkErrorMacro("ExecuteInformation: Unrecognized color format."); break; } vtkDataObject::SetPointDataActiveScalarInfo(outInfo, VTK_UNSIGNED_CHAR, numComponents); } return 1; } /* * This templated routine calculates effective lower and upper limits * for a window of values of type T, lower and upper. */ template void vtkImageMapToWindowLevelClamps(vtkImageData* data, double w, double l, T& lower, T& upper, unsigned char& lower_val, unsigned char& upper_val) { double f_lower, f_upper, f_lower_val, f_upper_val; double adjustedLower, adjustedUpper; double range[2]; data->GetPointData()->GetScalars()->GetDataTypeRange(range); f_lower = l - fabs(w) / 2.0; f_upper = f_lower + fabs(w); // Set the correct lower value if (f_lower <= range[1]) { if (f_lower >= range[0]) { lower = static_cast(f_lower); adjustedLower = f_lower; } else { lower = static_cast(range[0]); adjustedLower = range[0]; } } else { lower = static_cast(range[1]); adjustedLower = range[1]; } // Set the correct upper value if (f_upper >= range[0]) { if (f_upper <= range[1]) { upper = static_cast(f_upper); adjustedUpper = f_upper; } else { upper = static_cast(range[1]); adjustedUpper = range[1]; } } else { upper = static_cast(range[0]); adjustedUpper = range[0]; } // now compute the lower and upper values if (w > 0.0) { f_lower_val = 255.0 * (adjustedLower - f_lower) / w; f_upper_val = 255.0 * (adjustedUpper - f_lower) / w; } else if (w < 0.0) { f_lower_val = 255.0 + 255.0 * (adjustedLower - f_lower) / w; f_upper_val = 255.0 + 255.0 * (adjustedUpper - f_lower) / w; } else { f_lower_val = 0.0; f_upper_val = 255.0; } if (f_upper_val > 255) { upper_val = 255; } else if (f_upper_val < 0) { upper_val = 0; } else { upper_val = static_cast(f_upper_val); } if (f_lower_val > 255) { lower_val = 255; } else if (f_lower_val < 0) { lower_val = 0; } else { lower_val = static_cast(f_lower_val); } } //------------------------------------------------------------------------------ // This non-templated function executes the filter for any type of data. template void vtkImageMapToWindowLevelColorsExecute(vtkImageMapToWindowLevelColors* self, vtkImageData* inData, T* inPtr, vtkImageData* outData, unsigned char* outPtr, int outExt[6], int id) { int idxX, idxY, idxZ; int extX, extY, extZ; vtkIdType inIncX, inIncY, inIncZ; vtkIdType outIncX, outIncY, outIncZ; unsigned long count = 0; unsigned long target; int dataType = inData->GetScalarType(); int numberOfComponents, numberOfOutputComponents, outputFormat; int rowLength; vtkScalarsToColors* lookupTable = self->GetLookupTable(); unsigned char* outPtr1; T* inPtr1; unsigned char* optr; T* iptr; double shift = self->GetWindow() / 2.0 - self->GetLevel(); double scale = 255.0 / self->GetWindow(); T lower, upper; unsigned char lower_val, upper_val, result_val; unsigned short ushort_val; vtkImageMapToWindowLevelClamps( inData, self->GetWindow(), self->GetLevel(), lower, upper, lower_val, upper_val); // find the region to loop over extX = outExt[1] - outExt[0] + 1; extY = outExt[3] - outExt[2] + 1; extZ = outExt[5] - outExt[4] + 1; target = static_cast(extZ * extY / 50.0); target++; // Get increments to march through data inData->GetContinuousIncrements(outExt, inIncX, inIncY, inIncZ); outData->GetContinuousIncrements(outExt, outIncX, outIncY, outIncZ); numberOfComponents = inData->GetNumberOfScalarComponents(); numberOfOutputComponents = outData->GetNumberOfScalarComponents(); outputFormat = self->GetOutputFormat(); rowLength = extX * numberOfComponents; // Loop through output pixels outPtr1 = outPtr; inPtr1 = inPtr + self->GetActiveComponent(); for (idxZ = 0; idxZ < extZ; idxZ++) { for (idxY = 0; !self->AbortExecute && idxY < extY; idxY++) { if (!id) { if (!(count % target)) { self->UpdateProgress(count / (50.0 * target)); } count++; } iptr = inPtr1; optr = outPtr1; if (lookupTable) { lookupTable->MapScalarsThroughTable2( inPtr1, outPtr1, dataType, extX, numberOfComponents, outputFormat); for (idxX = 0; idxX < extX; idxX++) { if (*iptr <= lower) { ushort_val = lower_val; } else if (*iptr >= upper) { ushort_val = upper_val; } else { ushort_val = static_cast((*iptr + shift) * scale); } *optr = static_cast((*optr * ushort_val) >> 8); switch (outputFormat) { case VTK_RGBA: *(optr + 1) = static_cast((*(optr + 1) * ushort_val) >> 8); *(optr + 2) = static_cast((*(optr + 2) * ushort_val) >> 8); *(optr + 3) = 255; break; case VTK_RGB: *(optr + 1) = static_cast((*(optr + 1) * ushort_val) >> 8); *(optr + 2) = static_cast((*(optr + 2) * ushort_val) >> 8); break; case VTK_LUMINANCE_ALPHA: *(optr + 1) = 255; break; } iptr += numberOfComponents; optr += numberOfOutputComponents; } } else { for (idxX = 0; idxX < extX; idxX++) { if (*iptr <= lower) { result_val = lower_val; } else if (*iptr >= upper) { result_val = upper_val; } else { result_val = static_cast((*iptr + shift) * scale); } *optr = result_val; switch (outputFormat) { case VTK_RGBA: *(optr + 1) = result_val; *(optr + 2) = result_val; *(optr + 3) = 255; break; case VTK_RGB: *(optr + 1) = result_val; *(optr + 2) = result_val; break; case VTK_LUMINANCE_ALPHA: *(optr + 1) = 255; break; } iptr += numberOfComponents; optr += numberOfOutputComponents; } } outPtr1 += outIncY + extX * numberOfOutputComponents; inPtr1 += inIncY + rowLength; } outPtr1 += outIncZ; inPtr1 += inIncZ; } } //------------------------------------------------------------------------------ // This method is passed a input and output data, and executes the filter // algorithm to fill the output from the input. void vtkImageMapToWindowLevelColors::ThreadedRequestData(vtkInformation* vtkNotUsed(request), vtkInformationVector** vtkNotUsed(inputVector), vtkInformationVector* vtkNotUsed(outputVector), vtkImageData*** inData, vtkImageData** outData, int outExt[6], int id) { void* inPtr = inData[0][0]->GetScalarPointerForExtent(outExt); void* outPtr = outData[0]->GetScalarPointerForExtent(outExt); switch (inData[0][0]->GetScalarType()) { vtkTemplateMacro(vtkImageMapToWindowLevelColorsExecute(this, inData[0][0], static_cast(inPtr), outData[0], static_cast(outPtr), outExt, id)); default: vtkErrorMacro(<< "Execute: Unknown ScalarType"); return; } } void vtkImageMapToWindowLevelColors::PrintSelf(ostream& os, vtkIndent indent) { this->Superclass::PrintSelf(os, indent); os << indent << "Window: " << this->Window << endl; os << indent << "Level: " << this->Level << endl; } VTK_ABI_NAMESPACE_END