ITK 6.0.0
Insight Toolkit
 
Loading...
Searching...
No Matches
itkBoxUtilities.h
Go to the documentation of this file.
1/*=========================================================================
2 *
3 * Copyright NumFOCUS
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0.txt
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 *
17 *=========================================================================*/
18#ifndef itkBoxUtilities_h
19#define itkBoxUtilities_h
20
21#include "itkProgressReporter.h"
27#include "itkOffset.h"
30#include <algorithm> // For min.
31
32/*
33 *
34 * This code was contributed in the Insight Journal paper:
35 * "Efficient implementation of kernel filtering"
36 * by Beare R., Lehmann G
37 * https://doi.org/10.54294/igq8fn
38 *
39 */
40
42{
43
44template <typename TIterator>
45inline TIterator *
46setConnectivityEarlyBox(TIterator * it, bool fullyConnected = false)
47{
48 // activate the "previous" neighbours
49 typename TIterator::OffsetType offset;
50 it->ClearActiveList();
51 if (!fullyConnected)
52 {
53 // only activate the neighbors that are face connected
54 // to the current pixel. do not include the center pixel
55 offset.Fill(0);
56 for (unsigned int d = 0; d < TIterator::Dimension; ++d)
57 {
58 offset[d] = -1;
59 it->ActivateOffset(offset);
60 offset[d] = 0;
61 }
62 }
63 else
64 {
65 // activate all neighbors that are face+edge+vertex
66 // connected to the current pixel. do not include the center pixel
67 const unsigned int centerIndex = it->GetCenterNeighborhoodIndex();
68 for (unsigned int d = 0; d < centerIndex; ++d)
69 {
70 offset = it->GetOffset(d);
71 // check for positives in any dimension
72 bool keep = true;
73 for (unsigned int i = 0; i < TIterator::Dimension; ++i)
74 {
75 if (offset[i] > 0)
76 {
77 keep = false;
78 break;
79 }
80 }
81 if (keep)
82 {
83 it->ActivateOffset(offset);
84 }
85 }
86 offset.Fill(0);
87 it->DeactivateOffset(offset);
88 }
89 return it;
90}
91
92} // namespace itk_impl_details
93
94namespace itk
95{
96
97template <typename TInputImage, typename TOutputImage>
98void
99BoxAccumulateFunction(const TInputImage * inputImage,
100 const TOutputImage * outputImage,
101 typename TInputImage::RegionType inputRegion,
102 typename TOutputImage::RegionType outputRegion)
103{
104 // type alias
105 using InputImageType = TInputImage;
106 using OffsetType = typename TInputImage::OffsetType;
107 using OutputImageType = TOutputImage;
108 using OutputPixelType = typename TOutputImage::PixelType;
109
110 using InputIterator = ImageRegionConstIterator<TInputImage>;
111
112 using NOutputIterator = ShapedNeighborhoodIterator<TOutputImage>;
113 InputIterator inIt(inputImage, inputRegion);
114 auto kernelRadius = TInputImage::SizeType::Filled(1);
115
116 NOutputIterator noutIt(kernelRadius, outputImage, outputRegion);
117 // this iterator is fully connected
119
121 oBC.SetConstant(OutputPixelType{});
122 noutIt.OverrideBoundaryCondition(&oBC);
123 // This uses several iterators. An alternative and probably better
124 // approach would be to copy the input to the output and convolve
125 // with the following weights (in 2D)
126 // -(dim - 1) 1
127 // 1 1
128 // The result of each convolution needs to get written back to the
129 // image being convolved so that the accumulation propagates
130 // This should be implementable with neighborhood operators.
131
132 std::vector<int> weights;
133 for (auto idxIt = noutIt.GetActiveIndexList().begin(); idxIt != noutIt.GetActiveIndexList().end(); ++idxIt)
134 {
135 OffsetType offset = noutIt.GetOffset(*idxIt);
136 int w = -1;
137 for (unsigned int k = 0; k < InputImageType::ImageDimension; ++k)
138 {
139 if (offset[k] != 0)
140 {
141 w *= offset[k];
142 }
143 }
144 // std::cout << offset << " " << w << std::endl;
145 weights.push_back(w);
146 }
147
148 for (inIt.GoToBegin(), noutIt.GoToBegin(); !noutIt.IsAtEnd(); ++inIt, ++noutIt)
149 {
150 OutputPixelType sum = 0;
151 {
152 typename NOutputIterator::ConstIterator sIt = noutIt.Begin();
153 for (int k = 0; !sIt.IsAtEnd(); ++sIt, ++k)
154 {
155 sum += sIt.Get() * weights[k];
156 }
157 }
158 noutIt.SetCenterPixel(sum + inIt.Get());
159 }
160}
161
162// a function to generate corners of arbitrary dimension box
163template <typename TImage>
164std::vector<typename TImage::OffsetType>
165CornerOffsets(const TImage * im)
166{
167 using NIterator = ShapedNeighborhoodIterator<TImage>;
168 auto unitradius = TImage::SizeType::Filled(1);
169 const NIterator n1(unitradius, im, im->GetRequestedRegion());
170 const unsigned int centerIndex = n1.GetCenterNeighborhoodIndex();
171 typename NIterator::OffsetType offset;
172 std::vector<typename TImage::OffsetType> result;
173 for (unsigned int d = 0; d < centerIndex * 2 + 1; ++d)
174 {
175 offset = n1.GetOffset(d);
176 // check whether this is a corner - corners have no zeros
177 bool corner = true;
178 for (unsigned int k = 0; k < TImage::ImageDimension; ++k)
179 {
180 if (offset[k] == 0)
181 {
182 corner = false;
183 break;
184 }
185 }
186 if (corner)
187 {
188 result.push_back(offset);
189 }
190 }
191 return (result);
192}
193
194template <typename TInputImage, typename TOutputImage>
195void
196BoxMeanCalculatorFunction(const TInputImage * accImage,
197 TOutputImage * outputImage,
198 typename TInputImage::RegionType inputRegion,
199 typename TOutputImage::RegionType outputRegion,
200 typename TInputImage::SizeType radius)
201{
202 // type alias
203 using InputImageType = TInputImage;
204 using RegionType = typename TInputImage::RegionType;
205 using SizeType = typename TInputImage::SizeType;
206 using IndexType = typename TInputImage::IndexType;
207 using OffsetType = typename TInputImage::OffsetType;
208 using OutputImageType = TOutputImage;
209 using OutputPixelType = typename TOutputImage::PixelType;
210 // use the face generator for speed
212 using FaceListType = typename FaceCalculatorType::FaceListType;
213 FaceCalculatorType faceCalculator;
214
216
217 // this process is actually slightly asymmetric because we need to
218 // subtract rectangles that are next to our kernel, not overlapping it
219 SizeType kernelSize;
220 SizeType internalRadius;
221 SizeType regionLimit;
222
223 IndexType regionStart = inputRegion.GetIndex();
224 for (unsigned int i = 0; i < TInputImage::ImageDimension; ++i)
225 {
226 kernelSize[i] = radius[i] * 2 + 1;
227 internalRadius[i] = radius[i] + 1;
228 regionLimit[i] = inputRegion.GetSize()[i] + regionStart[i] - 1;
229 }
230
231 using AccPixType = typename NumericTraits<OutputPixelType>::RealType;
232 // get a set of offsets to corners for a unit hypercube in this image
233 std::vector<OffsetType> unitCorners = CornerOffsets<TInputImage>(accImage);
234 std::vector<OffsetType> realCorners;
235 std::vector<AccPixType> weights;
236 // now compute the weights
237 for (unsigned int k = 0; k < unitCorners.size(); ++k)
238 {
239 int prod = 1;
240 OffsetType thisCorner;
241 for (unsigned int i = 0; i < TInputImage::ImageDimension; ++i)
242 {
243 prod *= unitCorners[k][i];
244 if (unitCorners[k][i] > 0)
245 {
246 thisCorner[i] = radius[i];
247 }
248 else
249 {
250 thisCorner[i] = -(static_cast<OffsetValueType>(radius[i]) + 1);
251 }
252 }
253 weights.push_back((AccPixType)prod);
254 realCorners.push_back(thisCorner);
255 }
256
257 FaceListType faceList = faceCalculator(accImage, outputRegion, internalRadius);
258 // start with the body region
259 for (const auto & face : faceList)
260 {
261 if (&face == &faceList.front())
262 {
263 // this is the body region. This is meant to be an optimized
264 // version that doesn't use neighborhood regions
265 // compute the various offsets
266 AccPixType pixelscount = 1;
267 for (unsigned int i = 0; i < TInputImage::ImageDimension; ++i)
268 {
269 pixelscount *= (AccPixType)(2 * radius[i] + 1);
270 }
271
272 using OutputIteratorType = ImageRegionIterator<OutputImageType>;
273 using InputIteratorType = ImageRegionConstIterator<InputImageType>;
274
275 using CornerItVecType = std::vector<InputIteratorType>;
276 CornerItVecType cornerItVec;
277 // set up the iterators for each corner
278 for (unsigned int k = 0; k < realCorners.size(); ++k)
279 {
280 typename InputImageType::RegionType tReg = face;
281 tReg.SetIndex(tReg.GetIndex() + realCorners[k]);
282 InputIteratorType tempIt(accImage, tReg);
283 tempIt.GoToBegin();
284 cornerItVec.push_back(tempIt);
285 }
286 // set up the output iterator
287 OutputIteratorType oIt(outputImage, face);
288 // now do the work
289 for (oIt.GoToBegin(); !oIt.IsAtEnd(); ++oIt)
290 {
291 AccPixType sum = 0;
292 // check each corner
293 for (unsigned int k = 0; k < cornerItVec.size(); ++k)
294 {
295 sum += weights[k] * cornerItVec[k].Get();
296 // increment each corner iterator
297 ++(cornerItVec[k]);
298 }
299 oIt.Set(static_cast<OutputPixelType>(sum / pixelscount));
300 }
301 }
302 else
303 {
304 // now we need to deal with the border regions
305 using OutputIteratorType = ImageRegionIteratorWithIndex<OutputImageType>;
306 OutputIteratorType oIt(outputImage, face);
307 // now do the work
308 for (oIt.GoToBegin(); !oIt.IsAtEnd(); ++oIt)
309 {
310 // figure out the number of pixels in the box by creating an
311 // equivalent region and cropping - this could probably be
312 // included in the loop below.
313 RegionType currentKernelRegion;
314 currentKernelRegion.SetSize(kernelSize);
315 // compute the region's index
316 IndexType kernelRegionIdx = oIt.GetIndex();
317 const IndexType centIndex = kernelRegionIdx;
318 for (unsigned int i = 0; i < TInputImage::ImageDimension; ++i)
319 {
320 kernelRegionIdx[i] -= radius[i];
321 }
322 currentKernelRegion.SetIndex(kernelRegionIdx);
323 currentKernelRegion.Crop(inputRegion);
324 const OffsetValueType edgepixelscount = currentKernelRegion.GetNumberOfPixels();
325 AccPixType sum = 0;
326 // rules are : for each corner,
327 // for each dimension
328 // if dimension offset is positive -> this is
329 // a leading edge. Crop if outside the input
330 // region
331 // if dimension offset is negative -> this is
332 // a trailing edge. Ignore if it is outside
333 // image region
334 for (unsigned int k = 0; k < realCorners.size(); ++k)
335 {
336 IndexType thisCorner = centIndex + realCorners[k];
337 bool includeCorner = true;
338 for (unsigned int j = 0; j < TInputImage::ImageDimension; ++j)
339 {
340 if (unitCorners[k][j] > 0)
341 {
342 // leading edge - crop it
343 thisCorner[j] = std::min(thisCorner[j], static_cast<OffsetValueType>(regionLimit[j]));
344 }
345 else
346 {
347 // trailing edge - check bounds
348 if (thisCorner[j] < regionStart[j])
349 {
350 includeCorner = false;
351 break;
352 }
353 }
354 }
355 if (includeCorner)
356 {
357 sum += accImage->GetPixel(thisCorner) * weights[k];
358 }
359 }
360
361 oIt.Set(static_cast<OutputPixelType>(sum / (AccPixType)edgepixelscount));
362 }
363 }
364 }
365}
366
367template <typename TInputImage, typename TOutputImage>
368void
369BoxSigmaCalculatorFunction(const TInputImage * accImage,
370 TOutputImage * outputImage,
371 typename TInputImage::RegionType inputRegion,
372 typename TOutputImage::RegionType outputRegion,
373 typename TInputImage::SizeType radius)
374{
375 // type alias
376 using InputImageType = TInputImage;
377 using RegionType = typename TInputImage::RegionType;
378 using SizeType = typename TInputImage::SizeType;
379 using IndexType = typename TInputImage::IndexType;
380 using OffsetType = typename TInputImage::OffsetType;
381 using OutputImageType = TOutputImage;
382 using OutputPixelType = typename TOutputImage::PixelType;
383 using InputPixelType = typename TInputImage::PixelType;
384 // use the face generator for speed
386 using FaceListType = typename FaceCalculatorType::FaceListType;
387 FaceCalculatorType faceCalculator;
388
390
391 // this process is actually slightly asymmetric because we need to
392 // subtract rectangles that are next to our kernel, not overlapping it
393 SizeType kernelSize;
394 SizeType internalRadius;
395 SizeType regionLimit;
396 IndexType regionStart = inputRegion.GetIndex();
397 for (unsigned int i = 0; i < TInputImage::ImageDimension; ++i)
398 {
399 kernelSize[i] = radius[i] * 2 + 1;
400 internalRadius[i] = radius[i] + 1;
401 regionLimit[i] = inputRegion.GetSize()[i] + regionStart[i] - 1;
402 }
403
404 using AccPixType = typename NumericTraits<OutputPixelType>::RealType;
405 // get a set of offsets to corners for a unit hypercube in this image
406 std::vector<OffsetType> unitCorners = CornerOffsets<TInputImage>(accImage);
407 std::vector<OffsetType> realCorners;
408 std::vector<AccPixType> weights;
409 // now compute the weights
410 for (unsigned int k = 0; k < unitCorners.size(); ++k)
411 {
412 int prod = 1;
413 OffsetType thisCorner;
414 for (unsigned int i = 0; i < TInputImage::ImageDimension; ++i)
415 {
416 prod *= unitCorners[k][i];
417 if (unitCorners[k][i] > 0)
418 {
419 thisCorner[i] = radius[i];
420 }
421 else
422 {
423 thisCorner[i] = -(static_cast<OffsetValueType>(radius[i]) + 1);
424 }
425 }
426 weights.push_back((AccPixType)prod);
427 realCorners.push_back(thisCorner);
428 }
429
430 FaceListType faceList = faceCalculator(accImage, outputRegion, internalRadius);
431 // start with the body region
432 for (const auto & face : faceList)
433 {
434 if (&face == &faceList.front())
435 {
436 // this is the body region. This is meant to be an optimized
437 // version that doesn't use neighborhood regions
438 // compute the various offsets
439 AccPixType pixelscount = 1;
440 for (unsigned int i = 0; i < TInputImage::ImageDimension; ++i)
441 {
442 pixelscount *= (AccPixType)(2 * radius[i] + 1);
443 }
444
445 using OutputIteratorType = ImageRegionIterator<OutputImageType>;
446 using InputIteratorType = ImageRegionConstIterator<InputImageType>;
447
448 using CornerItVecType = std::vector<InputIteratorType>;
449 CornerItVecType cornerItVec;
450 // set up the iterators for each corner
451 for (unsigned int k = 0; k < realCorners.size(); ++k)
452 {
453 typename InputImageType::RegionType tReg = face;
454 tReg.SetIndex(tReg.GetIndex() + realCorners[k]);
455 InputIteratorType tempIt(accImage, tReg);
456 tempIt.GoToBegin();
457 cornerItVec.push_back(tempIt);
458 }
459 // set up the output iterator
460 OutputIteratorType oIt(outputImage, face);
461 // now do the work
462 for (oIt.GoToBegin(); !oIt.IsAtEnd(); ++oIt)
463 {
464 AccPixType sum = 0;
465 AccPixType squareSum = 0;
466 // check each corner
467 for (unsigned int k = 0; k < cornerItVec.size(); ++k)
468 {
469 const InputPixelType & i = cornerItVec[k].Get();
470 sum += weights[k] * i[0];
471 squareSum += weights[k] * i[1];
472 // increment each corner iterator
473 ++(cornerItVec[k]);
474 }
475
476 oIt.Set(static_cast<OutputPixelType>(std::sqrt((squareSum - sum * sum / pixelscount) / (pixelscount - 1))));
477 }
478 }
479 else
480 {
481 // now we need to deal with the border regions
482 using OutputIteratorType = ImageRegionIteratorWithIndex<OutputImageType>;
483 OutputIteratorType oIt(outputImage, face);
484 // now do the work
485 for (oIt.GoToBegin(); !oIt.IsAtEnd(); ++oIt)
486 {
487 // figure out the number of pixels in the box by creating an
488 // equivalent region and cropping - this could probably be
489 // included in the loop below.
490 RegionType currentKernelRegion;
491 currentKernelRegion.SetSize(kernelSize);
492 // compute the region's index
493 IndexType kernelRegionIdx = oIt.GetIndex();
494 const IndexType centIndex = kernelRegionIdx;
495 for (unsigned int i = 0; i < TInputImage::ImageDimension; ++i)
496 {
497 kernelRegionIdx[i] -= radius[i];
498 }
499 currentKernelRegion.SetIndex(kernelRegionIdx);
500 currentKernelRegion.Crop(inputRegion);
501 const SizeValueType edgepixelscount = currentKernelRegion.GetNumberOfPixels();
502 AccPixType sum = 0;
503 AccPixType squareSum = 0;
504 // rules are : for each corner,
505 // for each dimension
506 // if dimension offset is positive -> this is
507 // a leading edge. Crop if outside the input
508 // region
509 // if dimension offset is negative -> this is
510 // a trailing edge. Ignore if it is outside
511 // image region
512 for (unsigned int k = 0; k < realCorners.size(); ++k)
513 {
514 IndexType thisCorner = centIndex + realCorners[k];
515 bool includeCorner = true;
516 for (unsigned int j = 0; j < TInputImage::ImageDimension; ++j)
517 {
518 if (unitCorners[k][j] > 0)
519 {
520 // leading edge - crop it
521 thisCorner[j] = std::min(thisCorner[j], static_cast<OffsetValueType>(regionLimit[j]));
522 }
523 else
524 {
525 // trailing edge - check bounds
526 if (thisCorner[j] < regionStart[j])
527 {
528 includeCorner = false;
529 break;
530 }
531 }
532 }
533 if (includeCorner)
534 {
535 const InputPixelType & i = accImage->GetPixel(thisCorner);
536 sum += weights[k] * i[0];
537 squareSum += weights[k] * i[1];
538 }
539 }
540
541 oIt.Set(
542 static_cast<OutputPixelType>(std::sqrt((squareSum - sum * sum / edgepixelscount) / (edgepixelscount - 1))));
543 }
544 }
545 }
546}
547
548template <typename TInputImage, typename TOutputImage>
549void
550BoxSquareAccumulateFunction(const TInputImage * inputImage,
551 TOutputImage * outputImage,
552 typename TInputImage::RegionType inputRegion,
553 typename TOutputImage::RegionType outputRegion)
554{
555 // type alias
556 using InputImageType = TInputImage;
557 using OffsetType = typename TInputImage::OffsetType;
558 using OutputImageType = TOutputImage;
559 using OutputPixelType = typename TOutputImage::PixelType;
560 using ValueType = typename OutputPixelType::ValueType;
561 using InputPixelType = typename TInputImage::PixelType;
562
563 using InputIterator = ImageRegionConstIterator<TInputImage>;
564
565 using NOutputIterator = ShapedNeighborhoodIterator<TOutputImage>;
566 InputIterator inIt(inputImage, inputRegion);
567 auto kernelRadius = TInputImage::SizeType::Filled(1);
568
569 NOutputIterator noutIt(kernelRadius, outputImage, outputRegion);
570 // this iterator is fully connected
572
574 oBC.SetConstant(OutputPixelType{});
575 noutIt.OverrideBoundaryCondition(&oBC);
576 // This uses several iterators. An alternative and probably better
577 // approach would be to copy the input to the output and convolve
578 // with the following weights (in 2D)
579 // -(dim - 1) 1
580 // 1 1
581 // The result of each convolution needs to get written back to the
582 // image being convolved so that the accumulation propagates
583 // This should be implementable with neighborhood operators.
584
585 std::vector<int> weights;
586 typename NOutputIterator::ConstIterator sIt;
587 for (auto idxIt = noutIt.GetActiveIndexList().begin(); idxIt != noutIt.GetActiveIndexList().end(); ++idxIt)
588 {
589 OffsetType offset = noutIt.GetOffset(*idxIt);
590 int w = -1;
591 for (unsigned int k = 0; k < InputImageType::ImageDimension; ++k)
592 {
593 if (offset[k] != 0)
594 {
595 w *= offset[k];
596 }
597 }
598 weights.push_back(w);
599 }
600
601 for (inIt.GoToBegin(), noutIt.GoToBegin(); !noutIt.IsAtEnd(); ++inIt, ++noutIt)
602 {
603 ValueType sum = 0;
604 ValueType squareSum = 0;
605 int k = 0;
606 for (k = 0, sIt = noutIt.Begin(); !sIt.IsAtEnd(); ++sIt, ++k)
607 {
608 const OutputPixelType & v = sIt.Get();
609 sum += v[0] * weights[k];
610 squareSum += v[1] * weights[k];
611 }
612 OutputPixelType o;
613 const InputPixelType & i = inIt.Get();
614 o[0] = sum + i;
615 o[1] = squareSum + i * i;
616 noutIt.SetCenterPixel(o);
617 }
618}
619} // namespace itk
620
621#endif
This boundary condition returns a constant value for out-of-bounds image pixels.
void SetConstant(const OutputPixelType &c)
A multi-dimensional iterator templated over image type that walks a region of pixels.
A multi-dimensional iterator templated over image type that walks pixels within a region and is speci...
A multi-dimensional iterator templated over image type that walks a region of pixels.
void SetSize(const SizeType &size)
A neighborhood iterator which can take on an arbitrary shape.
A function object that determines a neighborhood of values at an image boundary according to a Neuman...
TIterator * setConnectivityEarlyBox(TIterator *it, bool fullyConnected=false)
The "itk" namespace contains all Insight Segmentation and Registration Toolkit (ITK) classes....
std::vector< typename TImage::OffsetType > CornerOffsets(const TImage *im)
unsigned long SizeValueType
Definition itkIntTypes.h:86
long OffsetValueType
Definition itkIntTypes.h:97
void BoxSigmaCalculatorFunction(const TInputImage *accImage, TOutputImage *outputImage, typename TInputImage::RegionType inputRegion, typename TOutputImage::RegionType outputRegion, typename TInputImage::SizeType radius)
void BoxMeanCalculatorFunction(const TInputImage *accImage, TOutputImage *outputImage, typename TInputImage::RegionType inputRegion, typename TOutputImage::RegionType outputRegion, typename TInputImage::SizeType radius)
void BoxAccumulateFunction(const TInputImage *inputImage, const TOutputImage *outputImage, typename TInputImage::RegionType inputRegion, typename TOutputImage::RegionType outputRegion)
void BoxSquareAccumulateFunction(const TInputImage *inputImage, TOutputImage *outputImage, typename TInputImage::RegionType inputRegion, typename TOutputImage::RegionType outputRegion)
const IndexValueType * GetIndex() const
Definition itkIndex.h:227
void SetIndex(const IndexValueType val[VDimension])
Definition itkIndex.h:237
Splits an image into a main region and several "face" regions which are used to handle computations o...