vtk9/Rendering/Volume/vtkFiniteDifferenceGradient...

366 lines
11 KiB
C++

/*=========================================================================
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 <cmath>
vtkStandardNewMacro(vtkFiniteDifferenceGradientEstimator);
// This is the templated function that actually computes the EncodedNormal
// and the GradientMagnitude
template <class T>
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<int>(
(thread_id / static_cast<float>(thread_count)) * (bounds[5] - bounds[4] + 1)) +
bounds[4];
z_limit = static_cast<int>(((thread_id + 1) / static_cast<float>(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<int>((thread_id / static_cast<float>(thread_count)) * size[2]);
z_limit = static_cast<int>(((thread_id + 1) / static_cast<float>(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<float>(*(dptr + xstep)));
}
else
{
n[0] = 2.0 * (static_cast<float>(*(dptr)) - static_cast<float>(*(dptr + xstep)));
}
}
else if (x >= size[0] - estimator->SampleSpacingInVoxels)
{
if (zeroPad)
{
n[0] = static_cast<float>(*(dptr - xstep));
}
else
{
n[0] = 2.0 * (static_cast<float>(*(dptr - xstep)) - static_cast<float>(*(dptr)));
}
}
else
{
n[0] = static_cast<float>(*(dptr - xstep)) - static_cast<float>(*(dptr + xstep));
}
// Compute the Y component
if (y < estimator->SampleSpacingInVoxels)
{
if (zeroPad)
{
n[1] = -static_cast<float>(*(dptr + ystep));
}
else
{
n[1] = 2.0 * (static_cast<float>(*(dptr)) - static_cast<float>(*(dptr + ystep)));
}
}
else if (y >= size[1] - estimator->SampleSpacingInVoxels)
{
if (zeroPad)
{
n[1] = static_cast<float>(*(dptr - ystep));
}
else
{
n[1] = 2.0 * (static_cast<float>(*(dptr - ystep)) - static_cast<float>(*(dptr)));
}
}
else
{
n[1] = static_cast<float>(*(dptr - ystep)) - static_cast<float>(*(dptr + ystep));
}
// Compute the Z component
if (z < estimator->SampleSpacingInVoxels)
{
if (zeroPad)
{
n[2] = -static_cast<float>(*(dptr + zstep));
}
else
{
n[2] = 2.0 * (static_cast<float>(*(dptr)) - static_cast<float>(*(dptr + zstep)));
}
}
else if (z >= size[2] - estimator->SampleSpacingInVoxels)
{
if (zeroPad)
{
n[2] = static_cast<float>(*(dptr - zstep));
}
else
{
n[2] = 2.0 * (static_cast<float>(*(dptr - zstep)) - static_cast<float>(*(dptr)));
}
}
else
{
n[2] = static_cast<float>(*(dptr - zstep)) - static_cast<float>(*(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<double>(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<unsigned char>(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<VTK_TT*>(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;
}