PlusLib  2.9.0
Software library for tracked ultrasound image acquisition, calibration, and processing.
vtkPlusBoneEnhancer.cxx
Go to the documentation of this file.
1 /*=Plus=header=begin======================================================
2 Program: Plus
3 Copyright (c) Laboratory for Percutaneous Surgery. All rights reserved.
4 See License.txt for details.
5 =========================================================Plus=header=end*/
6 
7 // Local includes
8 #include "PlusConfigure.h"
9 #include "PlusMath.h"
10 #include "vtkPlusBoneEnhancer.h"
13 #include "vtkPlusSequenceIO.h"
14 
15 // VTK includes
16 #include <vtkImageDilateErode3D.h>
17 #include <vtkImageGaussianSmooth.h>
18 #include <vtkImageIslandRemoval2D.h>
19 #include <vtkImageSobel2D.h>
20 #include <vtkImageThreshold.h>
21 #include <vtkObjectFactory.h>
22 #include "vtkImageAlgorithm.h"
23 
24 #include <igsioTrackedFrame.h>
25 #include <igsioVideoFrame.h>
26 #include <vtkIGSIOTrackedFrameList.h>
27 
28 
29 #include <cmath>
30 
31 //----------------------------------------------------------------------------
33 
34 //----------------------------------------------------------------------------
36 : ScanConverter(NULL),
37  NumberOfScanLines(0),
38  NumberOfSamplesPerScanLine(0),
39 
40  RadiusStartMm(0),
41  RadiusStopMm(0),
42  ThetaStartDeg(0),
43  ThetaStopDeg(0),
44 
45  GaussianSmooth(NULL),
46  EdgeDetector(NULL),
47  ImageBinarizer(NULL),
48  BinaryImageForMorphology(NULL),
49  IslandRemover(NULL),
50  ImageEroder(NULL),
51  ImageDialator(NULL),
52 
53  ConversionImage(NULL),
54  IslandAreaThreshold(-1),
55  BoneOutlineDepthPx(3), // Note: this only changes the appearance/thickness of the 3D model. Different numbers do not change what is or is not marked as bone.
56  BonePushBackPx(9), // Horizontal distance between where a shadow is located, and where the bone begins
57 
58  LinesImage(NULL),
59  ProcessedLinesImage(NULL),
60  FirstFrame(true),
61 
62  SaveIntermediateResults(false)
63 {
64 
65  this->GaussianSmooth = vtkSmartPointer<vtkImageGaussianSmooth>::New(); // Used to smooth the image
66  this->EdgeDetector = vtkSmartPointer<vtkImageSobel2D>::New(); // Used to outline edges of the image
67  this->ImageBinarizer = vtkSmartPointer<vtkImageThreshold>::New(); // Used to convert into a binary image
68  this->BinaryImageForMorphology = vtkSmartPointer<vtkImageData>::New(); // The Binary image
69  this->IslandRemover = vtkSmartPointer<vtkImageIslandRemoval2D>::New(); // Used to remove islands (small isolated groups of pixels)
70  this->ImageEroder = vtkSmartPointer<vtkImageDilateErode3D>::New(); // Used to Erode the image
71  this->ImageDialator = vtkSmartPointer<vtkImageDilateErode3D>::New(); // Used to Dilate the image
72 
73 
74  // Set the default parameters for the filters mentioned above
75  this->SetDilationKernelSize(1, 1);
76  this->SetErosionKernelSize(5, 5);
77  this->SetGaussianStdDev(7.0);
78  this->SetGaussianKernelSize(7.0);
79  this->GaussianSmooth->SetDimensionality(2);
80 
81  this->ConversionImage = vtkSmartPointer<vtkImageData>::New();
82  this->ConversionImage->SetExtent(0, 0, 0, 0, 0, 0);
83 
84  this->BinaryImageForMorphology->SetExtent(0, 0, 0, 0, 0, 0);
85  this->ImageBinarizer->SetInValue(255);
86  this->ImageBinarizer->SetOutValue(0);
87  this->ImageBinarizer->ThresholdBetween(55, 255);
88 
89  this->IslandRemover->SetIslandValue(255);
90  this->IslandRemover->SetReplaceValue(0);
91  this->IslandRemover->SetAreaThreshold(0);
92 
93  this->ImageEroder->SetKernelSize(this->ErosionKernelSize[0], this->ErosionKernelSize[1], 1);
94  this->ImageEroder->SetErodeValue(255);
95  this->ImageEroder->SetDilateValue(0);
96 
97  this->ImageDialator->SetKernelSize(this->DilationKernelSize[0], this->DilationKernelSize[1], 1);
98  this->ImageDialator->SetErodeValue(0);
99  this->ImageDialator->SetDilateValue(255);
100 
101  this->LinesImage = vtkSmartPointer<vtkImageData>::New();
102  this->ProcessedLinesImage = vtkSmartPointer<vtkImageData>::New();
103 
104  this->LinesImage->SetExtent(0, 0, 0, 0, 0, 0);
105  this->ProcessedLinesImage->SetExtent(0, 0, 0, 0, 0, 0);
106 
107  this->IntermediateImageMap.clear();
108 }
109 
110 //----------------------------------------------------------------------------
112 {
113  // Make sure contained smart pointers are deleted
114  this->IntermediateImageMap.clear();
115  this->IntermediatePostfixes.clear();
116 }
117 
118 //----------------------------------------------------------------------------
119 void vtkPlusBoneEnhancer::PrintSelf(ostream& os, vtkIndent indent)
120 {
121  this->Superclass::PrintSelf(os, indent);
122 }
123 
124 //----------------------------------------------------------------------------
125 PlusStatus vtkPlusBoneEnhancer::ReadConfiguration(vtkSmartPointer<vtkXMLDataElement> processingElement)
126 {
127  XML_VERIFY_ELEMENT(processingElement, this->GetTagName());
128 
129  //Read things in the ScanConversion tag
130  vtkSmartPointer<vtkXMLDataElement> scanConversionElement = processingElement->FindNestedElementWithName("ScanConversion");
131  if (scanConversionElement != NULL)
132  {
133  // Call scanline generator with appropriate scanconvert
134  const char* transducerGeometry = scanConversionElement->GetAttribute("TransducerGeometry");
135  if (transducerGeometry == NULL)
136  {
137  LOG_ERROR("Scan converter TransducerGeometry is undefined");
138  return PLUS_FAIL;
139  }
140  else
141  {
142  LOG_INFO("Scan converter is defined.");
143  }
144 
145  vtkSmartPointer<vtkPlusUsScanConvert> scanConverter;
146  if (STRCASECMP(transducerGeometry, "CURVILINEAR") == 0)
147  {
148  this->ScanConverter = vtkSmartPointer<vtkPlusUsScanConvert>::Take(vtkPlusUsScanConvertCurvilinear::New());
149  }
150  else if (STRCASECMP(transducerGeometry, "LINEAR") == 0)
151  {
152  this->ScanConverter = vtkSmartPointer<vtkPlusUsScanConvert>::Take(vtkPlusUsScanConvertLinear::New());
153  }
154  else
155  {
156  LOG_ERROR("Invalid scan converter TransducerGeometry: " << transducerGeometry);
157  return PLUS_FAIL;
158  }
159  this->ScanConverter->ReadConfiguration(scanConversionElement);
160 
161  XML_READ_SCALAR_ATTRIBUTE_OPTIONAL(int, RadiusStartMm, scanConversionElement);
162  XML_READ_SCALAR_ATTRIBUTE_OPTIONAL(int, RadiusStopMm, scanConversionElement);
163  XML_READ_SCALAR_ATTRIBUTE_OPTIONAL(int, ThetaStartDeg, scanConversionElement);
164  XML_READ_SCALAR_ATTRIBUTE_OPTIONAL(int, ThetaStopDeg, scanConversionElement);
165  }
166  else
167  {
168  LOG_INFO("ScanConversion section not found in config file!");
169  }
170 
171  // Read image processing options from configuration
172  vtkXMLDataElement* imageProcessingOperations = processingElement->FindNestedElementWithName("ImageProcessingOperations");
173  if (imageProcessingOperations != NULL)
174  {
175 
176  // Read SaveIntermediateResults tag
177  vtkSmartPointer<vtkXMLDataElement> saveIntermediateResultsBool = imageProcessingOperations->FindNestedElementWithName("SaveIntermediateResults");
178  if (saveIntermediateResultsBool == NULL)
179  {
180  LOG_WARNING("Unable to locate SaveIntermediateResults element. Using default value: " << std::boolalpha << (this->SaveIntermediateResults));
181  }
182  else
183  {
184  XML_READ_BOOL_ATTRIBUTE_OPTIONAL(SaveIntermediateResults, saveIntermediateResultsBool);
185  }
186 
187  // Read tags related to the Gaussian filter
188  vtkSmartPointer<vtkXMLDataElement> gaussianParameters = imageProcessingOperations->FindNestedElementWithName("GaussianSmoothing");
189  if (gaussianParameters == NULL)
190  {
191  LOG_WARNING("Unable to locate GaussianSmoothing parameters element. Using default values.");
192  }
193  else
194  {
195  XML_READ_SCALAR_ATTRIBUTE_REQUIRED(double, GaussianStdDev, gaussianParameters);
196  XML_READ_SCALAR_ATTRIBUTE_REQUIRED(double, GaussianKernelSize, gaussianParameters);
197  }
198 
199  // Read tags related to Island Removal
200  vtkSmartPointer<vtkXMLDataElement> islandRemovalParameters = imageProcessingOperations->FindNestedElementWithName("IslandRemoval");
201  if (islandRemovalParameters == NULL)
202  {
203  LOG_WARNING("Unable to locate IslandRemoval parameters element. Using default values.");
204  }
205  else
206  {
207  XML_READ_SCALAR_ATTRIBUTE_REQUIRED(int, IslandAreaThreshold, islandRemovalParameters);
208  }
209 
210  // Read tags related to Erosion
211  vtkSmartPointer<vtkXMLDataElement> erosionParameters = imageProcessingOperations->FindNestedElementWithName("Erosion");
212  if (erosionParameters == NULL)
213  {
214  LOG_WARNING("Unable to locate Erosion paramters element. Using default values.");
215  }
216  else
217  {
218  XML_READ_VECTOR_ATTRIBUTE_REQUIRED(int, 2, ErosionKernelSize, erosionParameters);
219  }
220 
221  // Read tags related to Dialation
222  vtkSmartPointer<vtkXMLDataElement> dilationParameters = imageProcessingOperations->FindNestedElementWithName("Dilation");
223  if (dilationParameters == NULL)
224  {
225  LOG_WARNING("Unable to locate Dilation parameters element. Using default values.");
226  }
227  else
228  {
229  XML_READ_VECTOR_ATTRIBUTE_REQUIRED(int, 2, DilationKernelSize, dilationParameters);
230  }
231  }
232  else
233  {
234  // If this section in not in the xml file, use all filters with default values
235  LOG_INFO("ImageProcessingOperations section not found in config file");
236  LOG_INFO("Enabling all filters and using default values.");
237  }
238 
239  // Read tags related to scan lines
240  XML_READ_SCALAR_ATTRIBUTE_REQUIRED(int, NumberOfScanLines, processingElement);
241  XML_READ_SCALAR_ATTRIBUTE_REQUIRED(int, NumberOfSamplesPerScanLine, processingElement);
242 
243  int rfImageExtent[6] = { 0, this->NumberOfSamplesPerScanLine - 1, 0, this->NumberOfScanLines - 1, 0, 0 };
244  this->ScanConverter->SetInputImageExtent(rfImageExtent);
245 
246  return PLUS_SUCCESS;
247 }
248 
249 //----------------------------------------------------------------------------
250 // Writes the parameters that were used to a config file
251 PlusStatus vtkPlusBoneEnhancer::WriteConfiguration(vtkSmartPointer<vtkXMLDataElement> processingElement)
252 {
253  XML_VERIFY_ELEMENT(processingElement, this->GetTagName());
254 
255  //Write the parameters for filters to the scanner's properties to the output config file
256  processingElement->SetAttribute("Type", this->GetProcessorTypeName());
257  processingElement->SetIntAttribute("NumberOfScanLines", NumberOfScanLines);
258  processingElement->SetIntAttribute("NumberOfSamplesPerScanLine", NumberOfSamplesPerScanLine);
259 
260  XML_FIND_NESTED_ELEMENT_CREATE_IF_MISSING(scanConversionElement, processingElement, "ScanConversion");
261  this->ScanConverter->WriteConfiguration(scanConversionElement);
262  scanConversionElement->SetDoubleAttribute("RadiusStartMm", this->RadiusStartMm);
263  scanConversionElement->SetDoubleAttribute("RadiusStopMm", this->RadiusStopMm);
264  scanConversionElement->SetIntAttribute("ThetaStartDeg", this->ThetaStartDeg);
265  scanConversionElement->SetIntAttribute("ThetaStopDeg", this->ThetaStopDeg);
266 
267  //Write the parameters for filters to the output config file
268  XML_FIND_NESTED_ELEMENT_CREATE_IF_MISSING(imageProcessingOperations, processingElement, "ImageProcessingOperations");
269 
270  XML_FIND_NESTED_ELEMENT_CREATE_IF_MISSING(saveIntermediateResultsBool, imageProcessingOperations, "SaveIntermediateResults");
271  XML_WRITE_BOOL_ATTRIBUTE(SaveIntermediateResults, saveIntermediateResultsBool)
272 
273  XML_FIND_NESTED_ELEMENT_CREATE_IF_MISSING(gaussianParameters, imageProcessingOperations, "GaussianSmoothing");
274  gaussianParameters->SetDoubleAttribute("GaussianStdDev", this->GaussianStdDev);
275  gaussianParameters->SetDoubleAttribute("GaussianKernelSize", this->GaussianKernelSize);
276 
277  XML_FIND_NESTED_ELEMENT_CREATE_IF_MISSING(islandRemovalParameters, imageProcessingOperations, "IslandRemoval");
278  islandRemovalParameters->SetIntAttribute("IslandAreaThreshold", IslandAreaThreshold);
279 
280  XML_FIND_NESTED_ELEMENT_CREATE_IF_MISSING(erosionParameters, imageProcessingOperations, "Erosion");
281  erosionParameters->SetVectorAttribute("ErosionKernelSize", 2, this->ErosionKernelSize);
282 
283  XML_FIND_NESTED_ELEMENT_CREATE_IF_MISSING(dilationParameters, imageProcessingOperations, "Dilation");
284  dilationParameters->SetVectorAttribute("DilationKernelSize", 2, this->DilationKernelSize);
285 
286  return PLUS_SUCCESS;
287 }
288 
289 //----------------------------------------------------------------------------
291 {
292  // Allocate lines image.
293  int* linesImageExtent = this->ScanConverter->GetInputImageExtent();
294 
295  LOG_DEBUG("Lines image extent: "
296  << linesImageExtent[0] << ", " << linesImageExtent[1]
297  << ", " << linesImageExtent[2] << ", " << linesImageExtent[3]
298  << ", " << linesImageExtent[4] << ", " << linesImageExtent[5]);
299 
300  this->BinaryImageForMorphology->SetExtent(linesImageExtent);
301  this->BinaryImageForMorphology->AllocateScalars(VTK_UNSIGNED_CHAR, 1);
302 
303  this->LinesImage->SetExtent(linesImageExtent);
304  this->LinesImage->AllocateScalars(VTK_UNSIGNED_CHAR, 1);
305 
306  //Set up variables related to image extents
307  int dims[3] = { 0, 0, 0 };
308  this->LinesImage->GetDimensions(dims);
309 
310  return PLUS_SUCCESS;
311 }
312 
313 //----------------------------------------------------------------------------
314 // Fills the lines image by subsampling the input image along scanlines.
315 // Also computes pixel statistics.
316 void vtkPlusBoneEnhancer::FillLinesImage(vtkSmartPointer<vtkImageData> inputImageData)
317 {
318  int* linesImageExtent = this->ScanConverter->GetInputImageExtent();
319  int lineLengthPx = linesImageExtent[1] - linesImageExtent[0] + 1;
320  int numScanLines = linesImageExtent[3] - linesImageExtent[2] + 1;
321 
322  // For calculating pixel intensity mean and variance. Algorithm taken from:
323  // https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Online_algorithm
324 
325  double mean = 0.0;
326  double sumSquareDiff = 0.0; //named M2 in online notes
327  long pixelCount = 0;
328  double currentValue = 0.0; //temporary value for each loop. //Named value in online notes
329  double valueMeanDiff = 0.0; //Named delta in online notes
330 
331  double directionVectorX;
332  double directionVectorY;
333  int pixelCoordX;
334  int pixelCoordY;
335 
336  int* inputExtent = inputImageData->GetExtent();
337  for (int scanLine = 0; scanLine < numScanLines; ++scanLine)
338  {
339  double start[4] = { 0, 0, 0, 0 };
340  double end[4] = { 0, 0, 0, 0 };
341  ScanConverter->GetScanLineEndPoints(scanLine, start, end);
342 
343  directionVectorX = static_cast<double>(end[0] - start[0]) / (lineLengthPx - 1);
344  directionVectorY = static_cast<double>(end[1] - start[1]) / (lineLengthPx - 1);
345  for (int pointIndex = 0; pointIndex < lineLengthPx; ++pointIndex)
346  {
347  pixelCoordX = start[0] + directionVectorX * pointIndex;
348  pixelCoordY = start[1] + directionVectorY * pointIndex;
349  if (pixelCoordX < inputExtent[0] || pixelCoordX > inputExtent[1]
350  || pixelCoordY < inputExtent[2] || pixelCoordY > inputExtent[3])
351  {
352  this->LinesImage->SetScalarComponentFromFloat(pointIndex, scanLine, 0, 0, 0);
353  continue; // outside of the specified extent
354  }
355  currentValue = inputImageData->GetScalarComponentAsDouble(pixelCoordX, pixelCoordY, 0, 0);
356  this->LinesImage->SetScalarComponentFromFloat(pointIndex, scanLine, 0, 0, currentValue);
357 
358  ++pixelCount;
359  valueMeanDiff = currentValue - mean;
360  mean = mean + valueMeanDiff / pixelCount;
361  sumSquareDiff = sumSquareDiff + valueMeanDiff * (currentValue - mean);
362  }
363  }
364 }
365 
366 //----------------------------------------------------------------------------
367 void vtkPlusBoneEnhancer::VectorImageToUchar(vtkSmartPointer<vtkImageData> inputImage)
368 {
369  unsigned char* vOutput = 0;
370  unsigned char edgeDetectorOutput0;
371  unsigned char edgeDetectorOutput1;
372  float output = 0.0; // Keep this in [0..255] instead [0..1] for possible future optimization.
373  float output2 = 0.0;
374 
375  int dims[3] = { 0, 0, 0 };
376  this->LinesImage->GetDimensions(dims);
377  this->ConversionImage->SetExtent(this->LinesImage->GetExtent());
378  this->ConversionImage->AllocateScalars(VTK_UNSIGNED_CHAR, 1);
379  for (int y = dims[1] - 1; y >= 0; --y)
380  {
381  // Initialize variables for a new scan line.
382 
383  for (int x = dims[0] - 1; x >= 0; --x) // Go towards transducer
384  {
385  edgeDetectorOutput0 = static_cast<unsigned char>(inputImage->GetScalarComponentAsFloat(x, y, 0, 0));
386  edgeDetectorOutput1 = static_cast<unsigned char>(inputImage->GetScalarComponentAsFloat(x, y, 0, 1));
387  vOutput = static_cast<unsigned char*>(this->ConversionImage->GetScalarPointer(x, y, 0));
388  output = (float)(edgeDetectorOutput0 + edgeDetectorOutput1) / (float)2; // Not mathematically correct, but a quick approximation of sqrt(x^2 + y^2)
389 
390  *vOutput = (unsigned char)std::max(0, std::min(255, (int)output));
391  }
392  }
393 }
394 
395 //----------------------------------------------------------------------------
396 /*
397 Takes a vtkSmartPointer<vtkImageData> as an argument and modifies it such that all images in a row
398 that have a bone shadow behind it are removed
399 */
400 void vtkPlusBoneEnhancer::MarkShadowOutline(vtkSmartPointer<vtkImageData> inputImage)
401 {
402  int dims[3] = { 0, 0, 0 };
403  inputImage->GetDimensions(dims);
404 
405  int keepInfoCounter;
406  bool foundBone;
407  unsigned char* vOutput;
408 
409  int lastVistedValue = 0;
410 
411  //Setup variables for recording bone areas
412  std::map<std::string, int> currentBoneArea;
413  int boneAreaStart = dims[1] - 1; //The y coordinate of where the bone outline starts
414  int boneDepthSum = 0; //The sum of the x coordinates of each pixel in the bone outline
415  int boneMaxDepth = dims[0] - 1; //The x coordinate of the right-most pixel in the bone outline
416  int boneMinDepth = 0; //The x coordinate of the left-most pixel in the bone outline
417  int boneAreaDifferenceSlope = 3; //If two pixels are seperated by this value or greater in the x coordinate, they are marked as seperate bones
418 
419  for (int y = dims[1] - 1; y >= 0; --y)
420  {
421 
422  //When an image is detected, keep up to this many pixles after it
423  keepInfoCounter = this->BoneOutlineDepthPx + this->BonePushBackPx;
424  foundBone = false;
425 
426  for (int x = dims[0] - 1; x >= 0; --x)
427  {
428  vOutput = static_cast<unsigned char*>(inputImage->GetScalarPointer(x, y, 0));
429 
430  //If an image is detected
431  if (*vOutput != 0)
432  {
433  if (keepInfoCounter == 0 || keepInfoCounter > this->BoneOutlineDepthPx)
434  {
435  *vOutput = 0;
436  }
437 
438  if (keepInfoCounter == this->BoneOutlineDepthPx + this->BonePushBackPx)
439  {
440  if (foundBone == false)
441  {
442  //found the first bone
443  foundBone = true;
444 
445  //the two bone pixels are far enough appart, save them as being parts of different bone areas
446  if (std::abs(x - lastVistedValue) >= boneAreaDifferenceSlope && y != dims[1] - 1)
447  {
448  //check if the preveous area had any bone
449  if (boneDepthSum != 0)
450  {
451  //Save info related to where the bone area
452  currentBoneArea["depth"] = boneDepthSum / (boneAreaStart - y); // Store the outline's average x-coordinate
453  currentBoneArea["xMax"] = boneMaxDepth; // Store the outline's maximum x-coordinate (Used for efficiency)
454  currentBoneArea["xMin"] = std::max(boneMinDepth - this->BoneOutlineDepthPx, 0); // Store the outline's minimum x-coordinate (Used for efficiency)
455  currentBoneArea["yMax"] = boneAreaStart; // Store the outline's maximum y-coordinate
456  currentBoneArea["yMin"] = y + 1; // Store the outline's minimum y-coordinate
457  this->BoneAreasInfo.push_back(currentBoneArea);
458  currentBoneArea.clear();
459  }
460  boneAreaStart = y;
461  boneDepthSum = 0;
462  boneMaxDepth = x;
463  boneMinDepth = x;
464  }
465  else
466  {
467  if (x > boneMaxDepth)
468  {
469  boneMaxDepth = x;
470  }
471  if (x < boneMinDepth)
472  {
473  boneMinDepth = x;
474  }
475  }
476  boneDepthSum += x;
477  lastVistedValue = x;
478 
479  }
480  }
481  }
482  if (foundBone == true && keepInfoCounter != 0)
483  {
484  if (keepInfoCounter <= this->BoneOutlineDepthPx && *vOutput == 0)
485  {
486  *vOutput = 255;
487  }
488  keepInfoCounter--;
489  }
490  }
491 
492  //if no bones were found on this row, but there was a bone before this, save it
493  if (foundBone == false)
494  {
495  lastVistedValue = 0;
496  if (boneDepthSum != 0)
497  {
498  //Save info related to where the bone area
499  currentBoneArea["depth"] = boneDepthSum / (boneAreaStart - y); // Store the outline's average x-coordinate
500  currentBoneArea["xMax"] = boneMaxDepth; // Store the outline's maximum x-coordinate (Used for efficiency)
501  currentBoneArea["xMin"] = std::max(boneMinDepth - this->BoneOutlineDepthPx, 0); // Store the outline's minimum x-coordinate (Used for efficiency)
502  currentBoneArea["yMax"] = boneAreaStart; // Store the outline's maximum y-coordinate
503  currentBoneArea["yMin"] = y + 1; // Store the outline's minimum y-coordinate
504  this->BoneAreasInfo.push_back(currentBoneArea);
505  boneDepthSum = 0;
506  currentBoneArea.clear();
507  }
508  boneMaxDepth = dims[0] - 1;
509  boneMinDepth = 0;
510 
511  boneAreaStart = y - 1;
512  }
513  }
514 
515  //save the last bone that goes off-screen
516  if (boneDepthSum != 0)
517  {
518  //Save info related to where the bone area
519  currentBoneArea["depth"] = boneDepthSum / (boneAreaStart + 1); // Store the outline's average x-coordinate
520  currentBoneArea["xMax"] = boneMaxDepth; // Store the outline's maximum x-coordinate (Used for efficiency)
521  currentBoneArea["xMin"] = std::max(boneMinDepth - this->BoneOutlineDepthPx, 0); // Store the outline's minimum x-coordinate (Used for efficiency)
522  currentBoneArea["yMax"] = boneAreaStart; // Store the outline's maximum y-coordinate
523  currentBoneArea["yMin"] = 0; // Store the outline's minimum y-coordinate
524  this->BoneAreasInfo.push_back(currentBoneArea);
525  currentBoneArea.clear();
526  }
527 }
528 
529 //----------------------------------------------------------------------------
530 //a way of threasholding based on the standard deviation of a row
531 void vtkPlusBoneEnhancer::ThresholdViaStdDeviation(vtkSmartPointer<vtkImageData> inputImage)
532 {
533  int fatLayerToCut = 20; //The area of fat too close to the transducer should not be considered
534 
535  float vInput = 0;
536  unsigned char* vOutput = 0;
537 
538  int dims[3] = { 0, 0, 0 };
539  inputImage->GetDimensions(dims);
540 
541  int max;
542 
543  //values used to calculate the standard deviation
544  int pixelSum;
545  int squearSum;
546  float pixelAverage;
547  float meanDiffSum;
548  float meanDiffAverage;
549  float thresholdValue;
550 
551  for (int y = dims[1] - 1; y >= 0; --y)
552  {
553  max = 0;
554 
555  pixelSum = 0;
556  squearSum = 0;
557  pixelAverage = 0;
558 
559  //determine the average, sum, and max of the row
560  for (int x = dims[0] - 1; x >= fatLayerToCut; --x)
561  {
562  vInput = inputImage->GetScalarComponentAsFloat(x, y, 0, 0);
563  pixelSum += vInput;
564  squearSum += vInput * vInput;
565 
566  if (vInput > max)
567  {
568  max = vInput;
569  }
570  }
571  pixelAverage = pixelSum / (dims[0] - fatLayerToCut);
572 
573  //determine the standard deviation of the row
574  meanDiffSum = squearSum + (dims[0] - fatLayerToCut) * pixelAverage * pixelAverage + (-2 * pixelAverage * pixelSum);
575  meanDiffAverage = meanDiffSum / (dims[0] - fatLayerToCut);
576  thresholdValue = max - 3 * pow(meanDiffAverage, 0.5f);
577 
578 
579  //if a pixel's value is too low, remove it
580  if (pixelSum != 0)
581  {
582  for (int x = dims[0] - 1; x >= 0; --x)
583  {
584  vOutput = static_cast<unsigned char*>(inputImage->GetScalarPointer(x, y, 0));
585  if (*vOutput < thresholdValue && *vOutput != 0)
586  {
587  *vOutput = 0;
588  }
589  }
590  }
591  }
592 }
593 
594 //----------------------------------------------------------------------------
595 // If a pixel in MaskImage is > 0, the corresponding pixel in InputImage will remain unchanged, otherwise it will be set to 0
596 void vtkPlusBoneEnhancer::ImageConjunction(vtkSmartPointer<vtkImageData> InputImage, vtkSmartPointer<vtkImageData> MaskImage)
597 {
598  // Images must be of the same dimension, an should already be, I should check this though
599  unsigned char* inputPixelPointer = 0;
600 
601  int dims[3] = { 0, 0, 0 };
602  this->LinesImage->GetDimensions(dims); // This will be the same as InputImage, as long as InputImage is converted to linesImage previously
603 
604  for (int y = dims[1] - 1; y >= 0; --y)
605  {
606  // Initialize variables for a new scan line.
607 
608  for (int x = dims[0] - 1; x >= 0; --x) // Go towards transducer
609  {
610  if (static_cast<unsigned char>(MaskImage->GetScalarComponentAsFloat(x, y, 0, 0)) > 0)
611  {
612  //do nothing
613  }
614  else
615  {
616  inputPixelPointer = static_cast<unsigned char*>(InputImage->GetScalarPointer(x, y, 0));
617  *inputPixelPointer = 0;
618  }
619  }
620  }
621 }
622 
623 
624 //----------------------------------------------------------------------------
625 // Processes a given frame and marks potential bone areas.
626 PlusStatus vtkPlusBoneEnhancer::ProcessFrame(igsioTrackedFrame* inputFrame, igsioTrackedFrame* outputFrame)
627 {
628  //Process the input into a linear image
629  vtkSmartPointer<vtkImageData> intermediateImage = this->UnprocessedFrameToLinearImage(inputFrame);
630  //Remove noise and mark all possible bones
631  this->RemoveNoise(intermediateImage);
632  //Reconvert the image back into a fan-image and return it
633  this->LinearToFanImage(intermediateImage, outputFrame);
634  return PLUS_SUCCESS;
635 }
636 
637 void vtkPlusBoneEnhancer::LinearToFanImage(vtkSmartPointer<vtkImageData> inputImage, igsioTrackedFrame* outputFrame)
638 {
639 
640  //Setup so that the image can be converted into a fan-image
641  this->ProcessedLinesImage->DeepCopy(inputImage);
642  igsioVideoFrame* outputImage = outputFrame->GetImageData();
643  this->ScanConverter->SetInputData(this->ProcessedLinesImage);
644  this->ScanConverter->SetOutput(inputImage);
645  this->ScanConverter->Update();
646 
647  outputImage->DeepCopyFrom(inputImage);
648 }
649 
650 //----------------------------------------------------------------------------
651 // takes an unprocessed frame image and returns it as a linear image
652 vtkSmartPointer<vtkImageData> vtkPlusBoneEnhancer::UnprocessedFrameToLinearImage(igsioTrackedFrame* inputFrame)
653 {
654  if (this->FirstFrame == true)
655  {
656  //set up variables for future loops
657  this->ProcessImageExtents();
658  this->FirstFrame = false;
659  }
660  this->BoneAreasInfo.clear();
661 
662  igsioVideoFrame* inputImage = inputFrame->GetImageData();
663  //an image used to transport output between filters
664  vtkSmartPointer<vtkImageData> intermediateImage = vtkSmartPointer<vtkImageData>::New();
665 
666  //Convert the image to a readable non-fan image
667  this->ScanConverter->SetInputData(inputImage->GetImage());
668  // Generate lines image.
669  if (this->SaveIntermediateResults)
670  {
671  this->AddIntermediateFromFilter("_01Lines_1PreFillLines", this->ScanConverter);
672  }
673  this->FillLinesImage(inputImage->GetImage());
674  if (this->SaveIntermediateResults)
675  {
676  this->AddIntermediateImage("_01Lines_2FilterEnd", this->LinesImage);
677  }
678  intermediateImage->DeepCopy(this->LinesImage);
679 
680  return intermediateImage;
681 }
682 
683 //----------------------------------------------------------------------------
684 // Takes an Ultrasound image and removes all noise, then marks all potential
685 // bone areas using a white outline.
686 void vtkPlusBoneEnhancer::RemoveNoise(vtkSmartPointer<vtkImageData> inputImage)
687 {
688 
689  //Threashold the image based on the standard deviation of a pixel's columns
690  this->ThresholdViaStdDeviation(inputImage);
691  if (this->SaveIntermediateResults)
692  {
693  this->AddIntermediateImage("_02Threshold_1FilterEnd", inputImage);
694  }
695 
696  //Use gaussian smoothing
697  this->GaussianSmooth->SetInputData(inputImage);
698  if (this->SaveIntermediateResults)
699  {
700  this->AddIntermediateFromFilter("_03Gaussian_1FilterEnd", this->GaussianSmooth);
701  }
702 
703  //Edge detection
704  this->EdgeDetector->SetInputConnection(this->GaussianSmooth->GetOutputPort());
705  this->EdgeDetector->Update();
706  this->VectorImageToUchar(this->EdgeDetector->GetOutput());
707  if (this->SaveIntermediateResults)
708  {
709  this->AddIntermediateImage("_04EdgeDetector_1FilterEnd", this->ConversionImage);
710  }
711 
712  // Since we perform morphological operations, we must binarize the image
713  this->ImageBinarizer->SetInputData(this->ConversionImage);
714  if (this->SaveIntermediateResults)
715  {
716  this->AddIntermediateFromFilter("_05BinaryImageForMorphology_1FilterEnd", this->ImageBinarizer);
717  }
718 
719  //Remove small clusters of pixels
720  this->IslandRemover->SetInputConnection(this->ImageBinarizer->GetOutputPort());
721  this->IslandRemover->Update();
722  if (this->SaveIntermediateResults)
723  {
724  this->AddIntermediateImage("_06Island_1FilterEnd", this->IslandRemover->GetOutput());
725  }
726 
727  //Erode the image
728  this->ImageEroder->SetKernelSize(this->ErosionKernelSize[0], this->ErosionKernelSize[1], 1);
729  this->ImageEroder->SetInputConnection(this->IslandRemover->GetOutputPort());
730  if (this->SaveIntermediateResults)
731  {
732  this->AddIntermediateFromFilter("_07Erosion_1FilterEnd", this->ImageEroder);
733  }
734 
735  //Dilate the image
736  this->ImageDialator->SetKernelSize(this->DilationKernelSize[0], this->DilationKernelSize[1], 1);
737  this->ImageDialator->SetInputConnection(this->ImageEroder->GetOutputPort());
738  this->ImageDialator->Update();
739  this->BinaryImageForMorphology->DeepCopy(this->ImageDialator->GetOutput());
740  if (this->SaveIntermediateResults)
741  {
742  this->AddIntermediateImage("_08Dilation_1FilterEnd", this->BinaryImageForMorphology);
743  }
744 
745  //Detect each possible bone area, then subject it to various tests to confirm if it is valid
747  if (this->SaveIntermediateResults)
748  {
749  this->AddIntermediateImage("_09PostFilters_1ShadowOutline", this->BinaryImageForMorphology);
750  }
751 
752  // Save all stored intermediate images to mha files in output
753  if (this->SaveIntermediateResults)
754  {
756  }
757 
758  inputImage->DeepCopy(this->BinaryImageForMorphology);
759 }
760 
761 
762 //----------------------------------------------------------------------------
763 /*
764 Finds and saves all intermediate images that have been recorded.
765 Saves the images by calling this->SaveIntermediateResultToFile()
766 Returns PLUS_FAIL if this->SaveIntermediateResultToFile() encounters an error occured during this
767 process, returns PLUS_SUCCESS otherwise.
768 */
770 {
771  for (int postfixIndex = this->IntermediatePostfixes.size() - 1; postfixIndex >= 0; postfixIndex -= 1)
772  {
773  if (this->SaveIntermediateResultToFile(this->IntermediatePostfixes.at(postfixIndex)) == PLUS_FAIL)
774  {
775  return PLUS_FAIL;
776  }
777  }
778  return PLUS_SUCCESS;
779 }
780 
781 //----------------------------------------------------------------------------
782 /*
783 Takes a postfix as an argument and saves the intermediate image associated with that postfix
784 Returns PLUS_FAIL if an error occured during this process, returns PLUS_SUCCESS otherwise
785 */
787 {
788  std::map<char*, vtkSmartPointer<vtkIGSIOTrackedFrameList> >::iterator indexIterator = this->IntermediateImageMap.find(fileNamePostfix);
789  if (indexIterator != this->IntermediateImageMap.end())
790  {
791  //Try to save the intermediate image
792  if (vtkPlusSequenceIO::Write(this->IntermediateImageFileName + "_Plus" + std::string(fileNamePostfix) + ".mha", this->IntermediateImageMap[fileNamePostfix], US_IMG_ORIENT_MF, false) == PLUS_FAIL)
793  {
794  LOG_ERROR("An issue occured when trying to save the intermediate image with the postfix: " << fileNamePostfix);
795  return PLUS_FAIL;
796  }
797  else
798  {
799  LOG_INFO("Sucessfully wrote the intermediate image with the postfix: " << fileNamePostfix);
800  }
801  }
802 
803  return PLUS_SUCCESS;
804 }
805 
806 //----------------------------------------------------------------------------
807 void vtkPlusBoneEnhancer::AddIntermediateImage(char* fileNamePostfix, vtkSmartPointer<vtkImageData> image)
808 {
809  if (fileNamePostfix == "")
810  {
811  LOG_WARNING("The empty string was given as an intermediate image file postfix.");
812  }
813 
814  // See if the intermediate image should be created
815  std::map<char*, vtkSmartPointer<vtkIGSIOTrackedFrameList> >::iterator indexIterator = this->IntermediateImageMap.find(fileNamePostfix);
816  if (indexIterator != this->IntermediateImageMap.end()){}
817  else
818  {
819  // Create if not found
820  this->IntermediateImageMap[fileNamePostfix] = vtkIGSIOTrackedFrameList::New();
821 
822  this->IntermediatePostfixes.push_back(fileNamePostfix);
823  }
824 
825  //Add the current frame to its vtkIGSIOTrackedFrameList
826  igsioVideoFrame linesVideoFrame;
827  linesVideoFrame.DeepCopyFrom(image);
828  igsioTrackedFrame linesTrackedFrame;
829  linesTrackedFrame.SetImageData(linesVideoFrame);
830  this->IntermediateImageMap[fileNamePostfix]->AddTrackedFrame(&linesTrackedFrame);
831 }
832 
833 //----------------------------------------------------------------------------
834 // Given a vtk filter, get the image that would display at that point and save it
835 void vtkPlusBoneEnhancer::AddIntermediateFromFilter(char* fileNamePostfix, vtkImageAlgorithm* imageFilter)
836 {
837  if (fileNamePostfix == "")
838  {
839  LOG_WARNING("The empty string was given as an intermediate image file postfix.");
840  }
841 
842  vtkSmartPointer<vtkImageData> tempOutputImage = vtkSmartPointer<vtkImageData>::New();
843  imageFilter->SetOutput(tempOutputImage);
844  imageFilter->Update();
845  this->AddIntermediateImage(fileNamePostfix, tempOutputImage);
846 }
847 
848 //----------------------------------------------------------------------------
849 void vtkPlusBoneEnhancer::SetGaussianStdDev(double gaussianStdDev)
850 {
851  this->GaussianStdDev = gaussianStdDev;
852  this->GaussianSmooth->SetStandardDeviation(gaussianStdDev);
853 }
854 
855 //----------------------------------------------------------------------------
856 void vtkPlusBoneEnhancer::SetGaussianKernelSize(double gaussianKernelSize)
857 {
858  this->GaussianKernelSize = gaussianKernelSize;
859  this->GaussianSmooth->SetRadiusFactor(gaussianKernelSize);
860 }
861 
862 //----------------------------------------------------------------------------
863 void vtkPlusBoneEnhancer::SetIslandAreaThreshold(int islandAreaThreshold)
864 {
865  this->IslandAreaThreshold = islandAreaThreshold;
866  if (islandAreaThreshold < 0)
867  {
868  this->IslandRemover->SetAreaThreshold(0);
869  }
870  else
871  {
872  this->IslandRemover->SetAreaThreshold(islandAreaThreshold);
873  }
874 }
vtkSmartPointer< vtkImageGaussianSmooth > GaussianSmooth
vtkSmartPointer< vtkImageSobel2D > EdgeDetector
void FillLinesImage(vtkSmartPointer< vtkImageData > inputImageData)
void VectorImageToUchar(vtkSmartPointer< vtkImageData > inputImage)
igsioStatus PlusStatus
Definition: PlusCommon.h:40
vtkSmartPointer< vtkImageDilateErode3D > ImageDialator
virtual PlusStatus ProcessImageExtents()
void ImageConjunction(vtkSmartPointer< vtkImageData > inputImage, vtkSmartPointer< vtkImageData > maskImage)
virtual PlusStatus WriteConfiguration(vtkSmartPointer< vtkXMLDataElement > processingElement)
PlusStatus SaveAllIntermediateResultsToFile()
#define PLUS_FAIL
Definition: PlusCommon.h:43
vtkSmartPointer< vtkPlusUsScanConvert > ScanConverter
void MarkShadowOutline(vtkSmartPointer< vtkImageData > inputImage)
static vtkPlusUsScanConvertLinear * New()
virtual void PrintSelf(ostream &os, vtkIndent indent) VTK_OVERRIDE
virtual const char * GetProcessorTypeName()
vtkStandardNewMacro(vtkPlusBoneEnhancer)
vtkSmartPointer< vtkImageThreshold > ImageBinarizer
static igsioStatus Write(const std::string &filename, igsioTrackedFrame *frame, US_IMAGE_ORIENTATION orientationInFile=US_IMG_ORIENT_MF, bool useCompression=true, bool EnableImageDataWrite=true)
vtkSmartPointer< vtkImageData > ProcessedLinesImage
vtkSmartPointer< vtkImageIslandRemoval2D > IslandRemover
static vtkPlusUsScanConvertCurvilinear * New()
void AddIntermediateFromFilter(char *fileNamePostfix, vtkImageAlgorithm *imageAlgorithm)
#define PLUS_SUCCESS
Definition: PlusCommon.h:44
std::vector< char * > IntermediatePostfixes
virtual void PrintSelf(ostream &os, vtkIndent indent)
void AddIntermediateImage(char *fileNamePostfix, vtkSmartPointer< vtkImageData > image)
const char * start
Definition: phidget22.h:5116
int x
Definition: phidget22.h:4265
virtual void SetDilationKernelSize(int, int)
void SetGaussianStdDev(double GaussianStdDev)
virtual void SetErosionKernelSize(int, int)
virtual PlusStatus ProcessFrame(igsioTrackedFrame *inputFrame, igsioTrackedFrame *outputFrame)
PlusStatus SaveIntermediateResultToFile(char *fileNamePostfix)
std::string IntermediateImageFileName
Direction vectors of rods y
Definition: algo3.m:15
void LinearToFanImage(vtkSmartPointer< vtkImageData > inputImage, igsioTrackedFrame *outputFrame)
void ThresholdViaStdDeviation(vtkSmartPointer< vtkImageData > inputImage)
void SetGaussianKernelSize(double GaussianKernelSize)
vtkSmartPointer< vtkImageData > ConversionImage
vtkSmartPointer< vtkImageDilateErode3D > ImageEroder
std::map< char *, vtkSmartPointer< vtkIGSIOTrackedFrameList > > IntermediateImageMap
vtkSmartPointer< vtkImageData > UnprocessedFrameToLinearImage(igsioTrackedFrame *inputFrame)
std::vector< std::map< std::string, int > > BoneAreasInfo
vtkSmartPointer< vtkImageData > BinaryImageForMorphology
void SetIslandAreaThreshold(int islandAreaThreshold)
vtkSmartPointer< vtkImageData > LinesImage
virtual PlusStatus ReadConfiguration(vtkSmartPointer< vtkXMLDataElement > processingElement)
void RemoveNoise(vtkSmartPointer< vtkImageData > inputImage)
Localize bone surfaces in ultrasound images.