1 /*=========================================================================
2
3 Program: Visualization Toolkit
4 Module: vtkImageGaussianSmooth.cxx
5
6 Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
7 All rights reserved.
8 See Copyright.txt or http://www.kitware.com/Copyright.htm for details.
9
10 This software is distributed WITHOUT ANY WARRANTY; without even
11 the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
12 PURPOSE. See the above copyright notice for more information.
13
14 =========================================================================*/
15 #include "vtkImageGaussianSmooth.h"
16
17 #include "vtkImageData.h"
18 #include "vtkInformation.h"
19 #include "vtkInformationVector.h"
20 #include "vtkObjectFactory.h"
21 #include "vtkStreamingDemandDrivenPipeline.h"
22
23 #include <cmath>
24
25 vtkStandardNewMacro(vtkImageGaussianSmooth);
26
27 //------------------------------------------------------------------------------
vtkImageGaussianSmooth()28 vtkImageGaussianSmooth::vtkImageGaussianSmooth()
29 {
30 this->Dimensionality = 3; // note: this overrides Standard deviation.
31 this->StandardDeviations[0] = 2.0;
32 this->StandardDeviations[1] = 2.0;
33 this->StandardDeviations[2] = 2.0;
34 this->RadiusFactors[0] = 1.5;
35 this->RadiusFactors[1] = 1.5;
36 this->RadiusFactors[2] = 1.5;
37 }
38
39 //------------------------------------------------------------------------------
40 vtkImageGaussianSmooth::~vtkImageGaussianSmooth() = default;
41
42 //------------------------------------------------------------------------------
PrintSelf(ostream & os,vtkIndent indent)43 void vtkImageGaussianSmooth::PrintSelf(ostream& os, vtkIndent indent)
44 {
45 this->Superclass::PrintSelf(os, indent);
46
47 // int idx;
48
49 // os << indent << "BoundaryRescale: " << this->BoundaryRescale << "\n";
50
51 os << indent << "Dimensionality: " << this->Dimensionality << "\n";
52
53 os << indent << "RadiusFactors: ( " << this->RadiusFactors[0] << ", " << this->RadiusFactors[1]
54 << ", " << this->RadiusFactors[2] << " )\n";
55
56 os << indent << "StandardDeviations: ( " << this->StandardDeviations[0] << ", "
57 << this->StandardDeviations[1] << ", " << this->StandardDeviations[2] << " )\n";
58 }
59
60 //------------------------------------------------------------------------------
ComputeKernel(double * kernel,int min,int max,double std)61 void vtkImageGaussianSmooth::ComputeKernel(double* kernel, int min, int max, double std)
62 {
63 int x;
64 double sum;
65
66 // handle special case
67 if (std == 0.0)
68 {
69 kernel[0] = 1.0;
70 return;
71 }
72
73 // fill in kernel
74 sum = 0.0;
75 for (x = min; x <= max; ++x)
76 {
77 sum += kernel[x - min] = exp(-(static_cast<double>(x * x)) / (std * std * 2.0));
78 }
79
80 // normalize
81 for (x = min; x <= max; ++x)
82 {
83 kernel[x - min] /= sum;
84 }
85 }
86
87 //------------------------------------------------------------------------------
RequestUpdateExtent(vtkInformation * vtkNotUsed (request),vtkInformationVector ** inputVector,vtkInformationVector * outputVector)88 int vtkImageGaussianSmooth::RequestUpdateExtent(vtkInformation* vtkNotUsed(request),
89 vtkInformationVector** inputVector, vtkInformationVector* outputVector)
90 {
91 // get the info objects
92 vtkInformation* outInfo = outputVector->GetInformationObject(0);
93 vtkInformation* inInfo = inputVector[0]->GetInformationObject(0);
94
95 int wholeExtent[6], inExt[6];
96
97 outInfo->Get(vtkStreamingDemandDrivenPipeline::UPDATE_EXTENT(), inExt);
98
99 // Expand filtered axes
100 inInfo->Get(vtkStreamingDemandDrivenPipeline::WHOLE_EXTENT(), wholeExtent);
101
102 this->InternalRequestUpdateExtent(inExt, wholeExtent);
103
104 inInfo->Set(vtkStreamingDemandDrivenPipeline::UPDATE_EXTENT(), inExt, 6);
105
106 return 1;
107 }
108
109 //------------------------------------------------------------------------------
InternalRequestUpdateExtent(int * inExt,int * wholeExtent)110 void vtkImageGaussianSmooth::InternalRequestUpdateExtent(int* inExt, int* wholeExtent)
111 {
112 int idx, radius;
113
114 // Expand filtered axes
115 for (idx = 0; idx < this->Dimensionality; ++idx)
116 {
117 radius = static_cast<int>(this->StandardDeviations[idx] * this->RadiusFactors[idx]);
118 inExt[idx * 2] -= radius;
119 if (inExt[idx * 2] < wholeExtent[idx * 2])
120 {
121 inExt[idx * 2] = wholeExtent[idx * 2];
122 }
123
124 inExt[idx * 2 + 1] += radius;
125 if (inExt[idx * 2 + 1] > wholeExtent[idx * 2 + 1])
126 {
127 inExt[idx * 2 + 1] = wholeExtent[idx * 2 + 1];
128 }
129 }
130 }
131
132 //------------------------------------------------------------------------------
133 // For a given position along the convolution axis, this method loops over
134 // all other axes, and performs the convolution. Boundary conditions handled
135 // previously.
136 template <class T>
vtkImageGaussianSmoothExecute(vtkImageGaussianSmooth * self,int axis,double * kernel,int kernelSize,vtkImageData * inData,T * inPtrC,vtkImageData * outData,int outExt[6],T * outPtrC,int * pcycle,int target,int * pcount,int total)137 void vtkImageGaussianSmoothExecute(vtkImageGaussianSmooth* self, int axis, double* kernel,
138 int kernelSize, vtkImageData* inData, T* inPtrC, vtkImageData* outData, int outExt[6], T* outPtrC,
139 int* pcycle, int target, int* pcount, int total)
140 {
141 int maxC, max0 = 0, max1 = 0;
142 int idxC, idx0, idx1, idxK;
143 vtkIdType inIncs[3], outIncs[3];
144 vtkIdType inInc0 = 0, inInc1 = 0, inIncK, outInc0 = 0, outInc1 = 0;
145 T *outPtr1, *outPtr0;
146 T *inPtr1, *inPtr0, *inPtrK;
147 double *ptrK, sum;
148
149 // I am counting on the fact that tight loops (component on outside)
150 // is more important than cache misses from shuffled access.
151
152 // Do the correct shuffling of the axes (increments, extents)
153 inData->GetIncrements(inIncs);
154 outData->GetIncrements(outIncs);
155 inIncK = inIncs[axis];
156 maxC = outData->GetNumberOfScalarComponents();
157 switch (axis)
158 {
159 case 0:
160 inInc0 = inIncs[1];
161 inInc1 = inIncs[2];
162 outInc0 = outIncs[1];
163 outInc1 = outIncs[2];
164 max0 = outExt[3] - outExt[2] + 1;
165 max1 = outExt[5] - outExt[4] + 1;
166 break;
167 case 1:
168 inInc0 = inIncs[0];
169 inInc1 = inIncs[2];
170 outInc0 = outIncs[0];
171 outInc1 = outIncs[2];
172 max0 = outExt[1] - outExt[0] + 1;
173 max1 = outExt[5] - outExt[4] + 1;
174 break;
175 case 2:
176 inInc0 = inIncs[0];
177 inInc1 = inIncs[1];
178 outInc0 = outIncs[0];
179 outInc1 = outIncs[1];
180 max0 = outExt[1] - outExt[0] + 1;
181 max1 = outExt[3] - outExt[2] + 1;
182 break;
183 }
184
185 for (idxC = 0; idxC < maxC; ++idxC)
186 {
187 inPtr1 = inPtrC;
188 outPtr1 = outPtrC;
189 for (idx1 = 0; !self->AbortExecute && idx1 < max1; ++idx1)
190 {
191 inPtr0 = inPtr1;
192 outPtr0 = outPtr1;
193 for (idx0 = 0; idx0 < max0; ++idx0)
194 {
195 inPtrK = inPtr0;
196 ptrK = kernel;
197 sum = 0.0;
198 // too bad this short loop has to be the inner most loop
199 for (idxK = 0; idxK < kernelSize; ++idxK)
200 {
201 sum += *ptrK * static_cast<double>(*inPtrK);
202 ++ptrK;
203 inPtrK += inIncK;
204 }
205 *outPtr0 = static_cast<T>(sum);
206 inPtr0 += inInc0;
207 outPtr0 += outInc0;
208 }
209 inPtr1 += inInc1;
210 outPtr1 += outInc1;
211 // we finished a row ... do we update ???
212 if (total)
213 { // yes this is the main thread
214 *pcycle += max0;
215 if (*pcycle > target)
216 { // yes
217 *pcycle -= target;
218 *pcount += target;
219 self->UpdateProgress(static_cast<double>(*pcount) / static_cast<double>(total));
220 // fprintf(stderr, "count: %d, total: %d, progress: %f\n",
221 //*pcount, total, (double)(*pcount) / (double)total);
222 }
223 }
224 }
225
226 ++inPtrC;
227 ++outPtrC;
228 }
229 }
230
231 //------------------------------------------------------------------------------
232 template <class T>
vtkImageGaussianSmoothGetTypeSize(T *)233 size_t vtkImageGaussianSmoothGetTypeSize(T*)
234 {
235 return sizeof(T);
236 }
237
238 //------------------------------------------------------------------------------
239 // This method convolves over one axis. It loops over the convolved axis,
240 // and handles boundary conditions.
ExecuteAxis(int axis,vtkImageData * inData,int inExt[6],vtkImageData * outData,int outExt[6],int * pcycle,int target,int * pcount,int total,vtkInformation * inInfo)241 void vtkImageGaussianSmooth::ExecuteAxis(int axis, vtkImageData* inData, int inExt[6],
242 vtkImageData* outData, int outExt[6], int* pcycle, int target, int* pcount, int total,
243 vtkInformation* inInfo)
244 {
245 int idxA, max;
246 int wholeExtent[6], wholeMax, wholeMin;
247 double* kernel;
248 // previousClip and currentClip rembers that the previous was not clipped
249 // keeps from recomputing kernels for center pixels.
250 int kernelSize = 0;
251 int kernelLeftClip, kernelRightClip;
252 int previousClipped, currentClipped;
253 int radius, size;
254 void* inPtr;
255 void* outPtr;
256 int coords[3];
257 vtkIdType outIncs[3], outIncA;
258
259 // Get the correct starting pointer of the output
260 outPtr = outData->GetScalarPointerForExtent(outExt);
261 outData->GetIncrements(outIncs);
262 outIncA = outIncs[axis];
263
264 // trick to account for the scalar type of the output(used to be only float)
265 switch (outData->GetScalarType())
266 {
267 vtkTemplateMacro(outIncA *=
268 static_cast<vtkIdType>(vtkImageGaussianSmoothGetTypeSize(static_cast<VTK_TT*>(nullptr))));
269 default:
270 vtkErrorMacro("Unknown scalar type");
271 return;
272 }
273
274 // Determine default starting position of input
275 coords[0] = inExt[0];
276 coords[1] = inExt[2];
277 coords[2] = inExt[4];
278
279 // get whole extent for boundary checking ...
280 inInfo->Get(vtkStreamingDemandDrivenPipeline::WHOLE_EXTENT(), wholeExtent);
281 wholeMin = wholeExtent[axis * 2];
282 wholeMax = wholeExtent[axis * 2 + 1];
283
284 // allocate memory for the kernel
285 radius = static_cast<int>(this->StandardDeviations[axis] * this->RadiusFactors[axis]);
286 size = 2 * radius + 1;
287 kernel = new double[size];
288
289 // loop over the convolution axis
290 previousClipped = currentClipped = 1;
291 max = outExt[axis * 2 + 1];
292 for (idxA = outExt[axis * 2]; idxA <= max; ++idxA)
293 {
294 // left boundary condition
295 coords[axis] = idxA - radius;
296 kernelLeftClip = wholeMin - coords[axis];
297 if (kernelLeftClip > 0)
298 { // front of kernel is cut off ("kernelStart" samples)
299 coords[axis] += kernelLeftClip;
300 }
301 else
302 {
303 kernelLeftClip = 0;
304 }
305 // Right boundary condition
306 kernelRightClip = (idxA + radius) - wholeMax;
307 if (kernelRightClip < 0)
308 {
309 kernelRightClip = 0;
310 }
311
312 // We can only use previous kernel if it is not clipped and new
313 // kernel is also not clipped.
314 currentClipped = kernelLeftClip + kernelRightClip;
315 if (currentClipped || previousClipped)
316 {
317 this->ComputeKernel(kernel, -radius + kernelLeftClip, radius - kernelRightClip,
318 static_cast<double>(this->StandardDeviations[axis]));
319 kernelSize = size - kernelLeftClip - kernelRightClip;
320 }
321 previousClipped = currentClipped;
322
323 /* now do the convolution on the rest of the axes */
324 inPtr = inData->GetScalarPointer(coords);
325 switch (inData->GetScalarType())
326 {
327 vtkTemplateMacro(vtkImageGaussianSmoothExecute(this, axis, kernel, kernelSize, inData,
328 static_cast<VTK_TT*>(inPtr), outData, outExt, static_cast<VTK_TT*>(outPtr), pcycle, target,
329 pcount, total));
330 default:
331 vtkErrorMacro("Unknown scalar type");
332 return;
333 }
334 outPtr = static_cast<void*>(static_cast<unsigned char*>(outPtr) + outIncA);
335 }
336
337 // get rid of temporary kernel
338 delete[] kernel;
339 }
340
341 //------------------------------------------------------------------------------
342 // This method decomposes the gaussian and smooths along each axis.
ThreadedRequestData(vtkInformation * vtkNotUsed (request),vtkInformationVector ** inputVector,vtkInformationVector * outputVector,vtkImageData *** inData,vtkImageData ** outData,int outExt[6],int id)343 void vtkImageGaussianSmooth::ThreadedRequestData(vtkInformation* vtkNotUsed(request),
344 vtkInformationVector** inputVector, vtkInformationVector* outputVector, vtkImageData*** inData,
345 vtkImageData** outData, int outExt[6], int id)
346 {
347 int inExt[6];
348 int target, count, total, cycle;
349
350 // for feed back, determine line target to get 50 progress update
351 // update is called every target lines. Progress is computed from
352 // the number of pixels processed so far.
353 count = 0;
354 target = 0;
355 total = 0;
356 cycle = 0;
357 if (id == 0)
358 {
359 // determine the number of pixels.
360 total = this->Dimensionality * (outExt[1] - outExt[0] + 1) * (outExt[3] - outExt[2] + 1) *
361 (outExt[5] - outExt[4] + 1) * inData[0][0]->GetNumberOfScalarComponents();
362 // pixels per update (50 updates)
363 target = total / 50;
364 }
365
366 // this filter expects that input is the same type as output.
367 if (inData[0][0]->GetScalarType() != outData[0]->GetScalarType())
368 {
369 vtkErrorMacro("Execute: input ScalarType, " << inData[0][0]->GetScalarType()
370 << ", must match out ScalarType "
371 << outData[0]->GetScalarType());
372 return;
373 }
374
375 // Decompose
376 vtkInformation* inInfo = inputVector[0]->GetInformationObject(0);
377 vtkInformation* outInfo = outputVector->GetInformationObject(0);
378 int wholeExt[6];
379 inInfo->Get(vtkStreamingDemandDrivenPipeline::WHOLE_EXTENT(), wholeExt);
380 outInfo->Get(vtkStreamingDemandDrivenPipeline::UPDATE_EXTENT(), inExt);
381 this->InternalRequestUpdateExtent(inExt, wholeExt);
382
383 switch (this->Dimensionality)
384 {
385 case 1:
386 this->ExecuteAxis(
387 0, inData[0][0], inExt, outData[0], outExt, &cycle, target, &count, total, inInfo);
388 break;
389 case 2:
390 int tempExt[6];
391 vtkImageData* tempData;
392 // compute intermediate extent
393 tempExt[0] = inExt[0];
394 tempExt[1] = inExt[1];
395 tempExt[2] = outExt[2];
396 tempExt[3] = outExt[3];
397 tempExt[4] = inExt[4];
398 tempExt[5] = inExt[5];
399 // create a temp data for intermediate results
400 tempData = vtkImageData::New();
401 tempData->SetExtent(tempExt);
402 tempData->AllocateScalars(
403 inData[0][0]->GetScalarType(), inData[0][0]->GetNumberOfScalarComponents());
404 this->ExecuteAxis(
405 1, inData[0][0], inExt, tempData, tempExt, &cycle, target, &count, total, inInfo);
406 this->ExecuteAxis(
407 0, tempData, tempExt, outData[0], outExt, &cycle, target, &count, total, inInfo);
408 // release temporary data
409 tempData->Delete();
410 break;
411 case 3:
412 // we do z first because it is most likely smallest
413 int temp0Ext[6], temp1Ext[6];
414 vtkImageData *temp0Data, *temp1Data;
415 // compute intermediate extents
416 temp0Ext[0] = inExt[0];
417 temp0Ext[1] = inExt[1];
418 temp0Ext[2] = inExt[2];
419 temp0Ext[3] = inExt[3];
420 temp0Ext[4] = outExt[4];
421 temp0Ext[5] = outExt[5];
422
423 temp1Ext[0] = inExt[0];
424 temp1Ext[1] = inExt[1];
425 temp1Ext[2] = outExt[2];
426 temp1Ext[3] = outExt[3];
427 temp1Ext[4] = outExt[4];
428 temp1Ext[5] = outExt[5];
429
430 // create a temp data for intermediate results
431 temp0Data = vtkImageData::New();
432 temp0Data->SetExtent(temp0Ext);
433 temp0Data->AllocateScalars(
434 inData[0][0]->GetScalarType(), inData[0][0]->GetNumberOfScalarComponents());
435
436 temp1Data = vtkImageData::New();
437 temp1Data->SetExtent(temp1Ext);
438 temp1Data->AllocateScalars(
439 inData[0][0]->GetScalarType(), inData[0][0]->GetNumberOfScalarComponents());
440 this->ExecuteAxis(
441 2, inData[0][0], inExt, temp0Data, temp0Ext, &cycle, target, &count, total, inInfo);
442 this->ExecuteAxis(
443 1, temp0Data, temp0Ext, temp1Data, temp1Ext, &cycle, target, &count, total, inInfo);
444 temp0Data->Delete();
445 this->ExecuteAxis(
446 0, temp1Data, temp1Ext, outData[0], outExt, &cycle, target, &count, total, inInfo);
447 temp1Data->Delete();
448 break;
449 }
450 }
451