/*========================================================================= Program: Visualization Toolkit Module: vtkFiniteDifferenceGradientEstimator.cxx Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen All rights reserved. See Copyright.txt or http://www.kitware.com/Copyright.htm for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the above copyright notice for more information. =========================================================================*/ #include "vtkFiniteDifferenceGradientEstimator.h" #include "vtkCharArray.h" #include "vtkDirectionEncoder.h" #include "vtkDoubleArray.h" #include "vtkFloatArray.h" #include "vtkImageData.h" #include "vtkIntArray.h" #include "vtkLongArray.h" #include "vtkMultiThreader.h" #include "vtkObjectFactory.h" #include "vtkPointData.h" #include "vtkShortArray.h" #include "vtkUnsignedCharArray.h" #include "vtkUnsignedIntArray.h" #include "vtkUnsignedLongArray.h" #include "vtkUnsignedShortArray.h" #include vtkStandardNewMacro(vtkFiniteDifferenceGradientEstimator); // This is the templated function that actually computes the EncodedNormal // and the GradientMagnitude template void vtkComputeGradients( vtkFiniteDifferenceGradientEstimator* estimator, T* data_ptr, int thread_id, int thread_count) { vtkIdType xstep, ystep, zstep; int x, y, z; vtkIdType offset; int x_start, x_limit; int y_start, y_limit; int z_start, z_limit; int useClip; int* clip; T* dptr; unsigned char* gptr; unsigned short* nptr; float n[3], t; float gvalue; float zeroNormalThreshold; int useBounds; int bounds[6]; int size[3]; float aspect[3]; int xlow, xhigh; float scale, bias; int computeGradientMagnitudes; vtkDirectionEncoder* direction_encoder; int zeroPad; estimator->GetInputSize(size); estimator->GetInputAspect(aspect); computeGradientMagnitudes = estimator->GetComputeGradientMagnitudes(); scale = estimator->GetGradientMagnitudeScale(); bias = estimator->GetGradientMagnitudeBias(); zeroPad = estimator->GetZeroPad(); // adjust the aspect aspect[0] = aspect[0] * 2.0 * estimator->SampleSpacingInVoxels; aspect[1] = aspect[1] * 2.0 * estimator->SampleSpacingInVoxels; aspect[2] = aspect[2] * 2.0 * estimator->SampleSpacingInVoxels; // Compute steps through the volume in x, y, and z xstep = 1; ystep = size[0]; zstep = ystep * size[1]; // Multiply by the spacing used for normal estimation xstep *= estimator->SampleSpacingInVoxels; ystep *= estimator->SampleSpacingInVoxels; zstep *= estimator->SampleSpacingInVoxels; // Get the length at or below which normals are considered to // be "zero" zeroNormalThreshold = estimator->GetZeroNormalThreshold(); useBounds = estimator->GetBoundsClip(); // Compute an offset based on the thread_id. The volume will // be broken into large slabs (thread_count slabs). For this thread // we need to access the correct slab. Also compute the z plane that // this slab starts on, and the z limit of this slab (one past the // end of the slab) if (useBounds) { estimator->GetBounds(bounds); x_start = bounds[0]; x_limit = bounds[1] + 1; y_start = bounds[2]; y_limit = bounds[3] + 1; z_start = static_cast( (thread_id / static_cast(thread_count)) * (bounds[5] - bounds[4] + 1)) + bounds[4]; z_limit = static_cast(((thread_id + 1) / static_cast(thread_count)) * (bounds[5] - bounds[4] + 1)) + bounds[4]; } else { x_start = 0; x_limit = size[0]; y_start = 0; y_limit = size[1]; z_start = static_cast((thread_id / static_cast(thread_count)) * size[2]); z_limit = static_cast(((thread_id + 1) / static_cast(thread_count)) * size[2]); } // Do final error checking on limits - make sure they are all within bounds // of the scalar input x_start = (x_start < 0) ? (0) : (x_start); y_start = (y_start < 0) ? (0) : (y_start); z_start = (z_start < 0) ? (0) : (z_start); x_limit = (x_limit > size[0]) ? (size[0]) : (x_limit); y_limit = (y_limit > size[1]) ? (size[1]) : (y_limit); z_limit = (z_limit > size[2]) ? (size[2]) : (z_limit); direction_encoder = estimator->GetDirectionEncoder(); useClip = estimator->GetUseCylinderClip(); clip = estimator->GetCircleLimits(); // Loop through all the data and compute the encoded normal and // gradient magnitude for each scalar location for (z = z_start; z < z_limit; z++) { for (y = y_start; y < y_limit; y++) { if (useClip) { xlow = ((clip[2 * y]) > x_start) ? (clip[2 * y]) : (x_start); xhigh = ((clip[2 * y + 1] + 1) < x_limit) ? (clip[2 * y + 1] + 1) : (x_limit); } else { xlow = x_start; xhigh = x_limit; } offset = z * zstep + y * ystep + xlow; // Set some pointers dptr = data_ptr + offset; nptr = estimator->EncodedNormals + offset; gptr = estimator->GradientMagnitudes + offset; for (x = xlow; x < xhigh; x++) { // Use a central difference method if possible, // otherwise use a forward or backward difference if // we are on the edge // Compute the X component if (x < estimator->SampleSpacingInVoxels) { if (zeroPad) { n[0] = -(static_cast(*(dptr + xstep))); } else { n[0] = 2.0 * (static_cast(*(dptr)) - static_cast(*(dptr + xstep))); } } else if (x >= size[0] - estimator->SampleSpacingInVoxels) { if (zeroPad) { n[0] = static_cast(*(dptr - xstep)); } else { n[0] = 2.0 * (static_cast(*(dptr - xstep)) - static_cast(*(dptr))); } } else { n[0] = static_cast(*(dptr - xstep)) - static_cast(*(dptr + xstep)); } // Compute the Y component if (y < estimator->SampleSpacingInVoxels) { if (zeroPad) { n[1] = -static_cast(*(dptr + ystep)); } else { n[1] = 2.0 * (static_cast(*(dptr)) - static_cast(*(dptr + ystep))); } } else if (y >= size[1] - estimator->SampleSpacingInVoxels) { if (zeroPad) { n[1] = static_cast(*(dptr - ystep)); } else { n[1] = 2.0 * (static_cast(*(dptr - ystep)) - static_cast(*(dptr))); } } else { n[1] = static_cast(*(dptr - ystep)) - static_cast(*(dptr + ystep)); } // Compute the Z component if (z < estimator->SampleSpacingInVoxels) { if (zeroPad) { n[2] = -static_cast(*(dptr + zstep)); } else { n[2] = 2.0 * (static_cast(*(dptr)) - static_cast(*(dptr + zstep))); } } else if (z >= size[2] - estimator->SampleSpacingInVoxels) { if (zeroPad) { n[2] = static_cast(*(dptr - zstep)); } else { n[2] = 2.0 * (static_cast(*(dptr - zstep)) - static_cast(*(dptr))); } } else { n[2] = static_cast(*(dptr - zstep)) - static_cast(*(dptr + zstep)); } // Take care of the aspect ratio of the data // Scaling in the vtkVolume is isotropic, so this is the // only place we have to worry about non-isotropic scaling. n[0] /= aspect[0]; n[1] /= aspect[1]; n[2] /= aspect[2]; // Compute the gradient magnitude t = sqrt(static_cast(n[0] * n[0] + n[1] * n[1] + n[2] * n[2])); if (computeGradientMagnitudes) { // Encode this into an 8 bit value gvalue = (t + bias) * scale; if (gvalue < 0.0) { *gptr = 0; } else if (gvalue > 255.0) { *gptr = 255; } else { *gptr = static_cast(gvalue); } gptr++; } // Normalize the gradient direction if (t > zeroNormalThreshold) { n[0] /= t; n[1] /= t; n[2] /= t; } else { n[0] = n[1] = n[2] = 0.0; } // Convert the gradient direction into an encoded index value *nptr = direction_encoder->GetEncodedDirection(n); nptr++; dptr++; } } } } // Construct a vtkFiniteDifferenceGradientEstimator vtkFiniteDifferenceGradientEstimator::vtkFiniteDifferenceGradientEstimator() { this->SampleSpacingInVoxels = 1; } // Destruct a vtkFiniteDifferenceGradientEstimator - free up any memory used vtkFiniteDifferenceGradientEstimator::~vtkFiniteDifferenceGradientEstimator() = default; static VTK_THREAD_RETURN_TYPE vtkSwitchOnDataType(void* arg) { vtkFiniteDifferenceGradientEstimator* estimator; int thread_count; int thread_id; vtkDataArray* scalars; thread_id = ((vtkMultiThreader::ThreadInfo*)(arg))->ThreadID; thread_count = ((vtkMultiThreader::ThreadInfo*)(arg))->NumberOfThreads; estimator = (vtkFiniteDifferenceGradientEstimator*)(((vtkMultiThreader::ThreadInfo*)(arg))->UserData); scalars = estimator->InputData->GetPointData()->GetScalars(); if (scalars == nullptr) { return VTK_THREAD_RETURN_VALUE; } // Find the data type of the Input and call the correct // templated function to actually compute the normals and magnitudes switch (scalars->GetDataType()) { vtkTemplateMacro(vtkComputeGradients( estimator, static_cast(scalars->GetVoidPointer(0)), thread_id, thread_count)); default: vtkGenericWarningMacro("unable to encode scalar type!"); } return VTK_THREAD_RETURN_VALUE; } // This method is used to compute the encoded normal and the // magnitude of the gradient for each voxel location in the // Input. void vtkFiniteDifferenceGradientEstimator::UpdateNormals() { vtkDebugMacro(<< "Updating Normals!"); this->Threader->SetNumberOfThreads(this->NumberOfThreads); this->Threader->SetSingleMethod(vtkSwitchOnDataType, this); this->Threader->SingleMethodExecute(); } // Print the vtkFiniteDifferenceGradientEstimator void vtkFiniteDifferenceGradientEstimator::PrintSelf(ostream& os, vtkIndent indent) { this->Superclass::PrintSelf(os, indent); os << indent << "Sample spacing in voxels: " << this->SampleSpacingInVoxels << endl; }