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 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{
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(pixelscount > 1
477 ? static_cast<OutputPixelType>(std::sqrt((squareSum - sum * sum / pixelscount) / (pixelscount - 1)))
478 : OutputPixelType{});
479 }
480 }
481 else
482 {
483 // now we need to deal with the border regions
484 using OutputIteratorType = ImageRegionIteratorWithIndex<OutputImageType>;
485 OutputIteratorType oIt(outputImage, face);
486 // now do the work
487 for (oIt.GoToBegin(); !oIt.IsAtEnd(); ++oIt)
488 {
489 // figure out the number of pixels in the box by creating an
490 // equivalent region and cropping - this could probably be
491 // included in the loop below.
492 RegionType currentKernelRegion;
493 currentKernelRegion.SetSize(kernelSize);
494 // compute the region's index
495 IndexType kernelRegionIdx = oIt.GetIndex();
496 const IndexType centIndex = kernelRegionIdx;
497 for (unsigned int i = 0; i < TInputImage::ImageDimension; ++i)
498 {
499 kernelRegionIdx[i] -= radius[i];
500 }
501 currentKernelRegion.SetIndex(kernelRegionIdx);
502 currentKernelRegion.Crop(inputRegion);
503 const SizeValueType edgepixelscount = currentKernelRegion.GetNumberOfPixels();
504 AccPixType sum = 0;
505 AccPixType squareSum = 0;
506 // rules are : for each corner,
507 // for each dimension
508 // if dimension offset is positive -> this is
509 // a leading edge. Crop if outside the input
510 // region
511 // if dimension offset is negative -> this is
512 // a trailing edge. Ignore if it is outside
513 // image region
514 for (unsigned int k = 0; k < realCorners.size(); ++k)
515 {
516 IndexType thisCorner = centIndex + realCorners[k];
517 bool includeCorner = true;
518 for (unsigned int j = 0; j < TInputImage::ImageDimension; ++j)
519 {
520 if (unitCorners[k][j] > 0)
521 {
522 // leading edge - crop it
523 thisCorner[j] = std::min(thisCorner[j], static_cast<OffsetValueType>(regionLimit[j]));
524 }
525 else
526 {
527 // trailing edge - check bounds
528 if (thisCorner[j] < regionStart[j])
529 {
530 includeCorner = false;
531 break;
532 }
533 }
534 }
535 if (includeCorner)
536 {
537 const InputPixelType & i = accImage->GetPixel(thisCorner);
538 sum += weights[k] * i[0];
539 squareSum += weights[k] * i[1];
540 }
541 }
542
543 oIt.Set(edgepixelscount > 1 ? static_cast<OutputPixelType>(
544 std::sqrt((squareSum - sum * sum / edgepixelscount) / (edgepixelscount - 1)))
545 : OutputPixelType{});
546 }
547 }
548 }
549}
550
551template <typename TInputImage, typename TOutputImage>
552void
553BoxSquareAccumulateFunction(const TInputImage * inputImage,
554 TOutputImage * outputImage,
555 typename TInputImage::RegionType inputRegion,
556 typename TOutputImage::RegionType outputRegion)
557{
558 // type alias
559 using InputImageType = TInputImage;
560 using OffsetType = typename TInputImage::OffsetType;
561 using OutputImageType = TOutputImage;
562 using OutputPixelType = typename TOutputImage::PixelType;
563 using ValueType = typename OutputPixelType::ValueType;
564 using InputPixelType = typename TInputImage::PixelType;
565
566 using InputIterator = ImageRegionConstIterator<TInputImage>;
567
568 using NOutputIterator = ShapedNeighborhoodIterator<TOutputImage>;
569 InputIterator inIt(inputImage, inputRegion);
570 auto kernelRadius = TInputImage::SizeType::Filled(1);
571
572 NOutputIterator noutIt(kernelRadius, outputImage, outputRegion);
573 // this iterator is fully connected
575
577 oBC.SetConstant(OutputPixelType{});
578 noutIt.OverrideBoundaryCondition(&oBC);
579 // This uses several iterators. An alternative and probably better
580 // approach would be to copy the input to the output and convolve
581 // with the following weights (in 2D)
582 // -(dim - 1) 1
583 // 1 1
584 // The result of each convolution needs to get written back to the
585 // image being convolved so that the accumulation propagates
586 // This should be implementable with neighborhood operators.
587
588 std::vector<int> weights;
589 typename NOutputIterator::ConstIterator sIt;
590 for (auto idxIt = noutIt.GetActiveIndexList().begin(); idxIt != noutIt.GetActiveIndexList().end(); ++idxIt)
591 {
592 OffsetType offset = noutIt.GetOffset(*idxIt);
593 int w = -1;
594 for (unsigned int k = 0; k < InputImageType::ImageDimension; ++k)
595 {
596 if (offset[k] != 0)
597 {
598 w *= offset[k];
599 }
600 }
601 weights.push_back(w);
602 }
603
604 for (inIt.GoToBegin(), noutIt.GoToBegin(); !noutIt.IsAtEnd(); ++inIt, ++noutIt)
605 {
606 ValueType sum = 0;
607 ValueType squareSum = 0;
608 int k = 0;
609 for (k = 0, sIt = noutIt.Begin(); !sIt.IsAtEnd(); ++sIt, ++k)
610 {
611 const OutputPixelType & v = sIt.Get();
612 sum += v[0] * weights[k];
613 squareSum += v[1] * weights[k];
614 }
615 OutputPixelType o;
616 const InputPixelType & i = inIt.Get();
617 o[0] = sum + i;
618 o[1] = squareSum + i * i;
619 noutIt.SetCenterPixel(o);
620 }
621}
622} // namespace itk
623
624#endif
Const version of ShapedNeighborhoodIterator, defining iteration of a local N-dimensional neighborhood...
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 BoxAccumulateFunction(const TInputImage *inputImage, TOutputImage *outputImage, typename TInputImage::RegionType inputRegion, typename TOutputImage::RegionType outputRegion)
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 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...