Ejemplo n.º 1
0
void mitk::CorrectorAlgorithm::TobiasHeimannCorrectionAlgorithm(mitkIpPicDescriptor* pic)
{
/*!
Some documentation (not by the original author)

TobiasHeimannCorrectionAlgorithm will be called, when the user has finished drawing a freehand line.

There should be different results, depending on the line's properties:

1. Without any prior segmentation, the start point and the end point of the drawn line will be 
connected to a contour and the area enclosed by the contour will be marked as segmentation.

2. When the whole line is inside a segmentation, start and end point will be connected to
a contour and the area of this contour will be subtracted from the segmentation.

3. When the line starts inside a segmentation and ends outside with only a single
transition from segmentation to no-segmentation, nothing will happen.

4. When there are multiple transitions between inside-segmentation and
outside-segmentation, the line will be divided in so called segments. Each segment is
either fully inside or fully outside a segmentation. When it is inside a segmentation, its
enclosed area will be subtracted from the segmentation. When the segment is outside a
segmentation, its enclosed area it will be added to the segmentation.

The algorithm is described in full length in Tobias Heimann's diploma thesis 
(MBI Technical Report 145, p. 37 - 40).
*/

  int oaSize = 1000000; // if we need a fixed number, then let it be big
  int* _ofsArray = new int[ oaSize ];
  for (int i=0; i<oaSize; i++) _ofsArray[i] = 0;

  std::vector<TSegData> segData;
  segData.reserve( 16 );
    
  Contour* contour3D = const_cast<Contour*>(m_Contour.GetPointer());
  ContourUtils::Pointer contourUtils = ContourUtils::New();
  Contour::Pointer projectedContour = contourUtils->ProjectContourTo2DSlice( m_WorkingImage, contour3D, true, false ); 
      
  if (projectedContour.IsNull()) 
  {
    delete[] _ofsArray;
    return;
  }

  if (projectedContour->GetNumberOfPoints() < 2) 
  {
    delete[] _ofsArray;
    return;
  }
  
  // convert the projected contour into a ipSegmentation format
  mitkIpInt4_t* _points = new mitkIpInt4_t[2 * projectedContour->GetNumberOfPoints()];
  const Contour::PathType::VertexListType* pointsIn2D = projectedContour->GetContourPath()->GetVertexList();
  unsigned int index(0);
  for ( Contour::PathType::VertexListType::const_iterator iter = pointsIn2D->begin(); 
        iter != pointsIn2D->end();
        ++iter, ++index )
  {
    _points[ 2 * index + 0 ] = static_cast<mitkIpInt4_t>( (*iter)[0] + 0.5 );
    _points[ 2 * index + 1 ] = static_cast<mitkIpInt4_t>( (*iter)[1] + 0.5 );
  }
 
  // store ofsets of the drawn line in array
  int _ofsNum = 0;
  unsigned int num = projectedContour->GetNumberOfPoints();
  int lastOfs = -1;
  for (unsigned int i=0; i<num-1; i++) 
  {
    float x = _points [2*i] + 0.5;
    float y = _points [2*i+1] + 0.5;
    float difX = _points [2*i+2] - x + 0.5;
    float difY = _points [2*i+3] - y + 0.5;
    float length = sqrt( difX*difX + difY*difY );
    float dx = difX / length;
    float dy = difY / length;
    for (int p=0; ((float)p)<length; p++) 
    {
      // if ofs is out of bounds, very nasty things will happen, so better check coordinates:
      if (x<0) x=0.5;
      else if (x>=pic->n[0]) x = pic->n[0]-0.5;
      if (y<0) y=0.5;
      else if (y>=pic->n[1]) y = pic->n[1]-0.5;
      // ok, now store safe ofs
      int ofs = (int)(x) + pic->n[0]*((int)(y));
      x += dx;
      y += dy;
      if (ofs != lastOfs) 
      {
        _ofsArray[_ofsNum++] = ofs;
        lastOfs = ofs;
      }
    }
  }

  if (_ofsNum == 0)
  {
    // contour was completely outside the binary image
    delete[] _ofsArray;
    delete[] _points;
    return;
  }

  ipMITKSegmentationTYPE* picdata = static_cast<ipMITKSegmentationTYPE*>(pic->data);

  // divide line in logical segments:
  int numSegments = 0;
  ipMITKSegmentationTYPE state = *(picdata + _ofsArray[0]);
  int ofsP = 1;
  int modifyStart, modifyEnd;  // start of first and end of last segment
  bool nextSegment;
  segData.clear();
  do 
  {
    nextSegment = false;
    while (ofsP<_ofsNum && *(picdata + _ofsArray[ofsP])==state) ofsP++;
    if (ofsP<_ofsNum) 
    {
      int lineStart = ofsP-1;
      if (numSegments==0) modifyStart = ofsP;
      state = *(picdata + _ofsArray[ofsP]);
      while (ofsP<_ofsNum && *(picdata + _ofsArray[ofsP])==state) ofsP++;
      if (ofsP<_ofsNum) 
      {
        int lineEnd = ofsP;
        modifyEnd = lineEnd;
        nextSegment = true;
        // now we've got a valid segment from lineStart to lineEnd
        TSegData thisSegData;
        thisSegData.lineStart = lineStart;
        thisSegData.lineEnd = lineEnd;
        thisSegData.modified = modifySegment( lineStart, lineEnd, state, pic, _ofsArray );
        segData.push_back( thisSegData );
        numSegments++;
      }
    }
  } while (nextSegment);

  for (int segNr=0; segNr < numSegments; segNr++) 
  {
    // draw line if modified:
    if ( segData[segNr].modified ) 
    {
      for (int i=segData[segNr].lineStart+1; i<segData[segNr].lineEnd; i++) 
      {
        *(picdata + _ofsArray[i]) = 1;
      }
    }
  }

  if (numSegments == 0) 
  {
    if (num <= 1) 
    { // only a single pixel. _ofsArray[_ofsNum-1] in else statement would crash, so don't do anything
      // no movement: delete operation

      // This behaviour would probably confuse users when they use the correction 
      // tool to change a segmentation and it deletes much more than selected
     
      // if (state == 1)  ipMITKSegmentationReplaceRegion4N( pic, _ofsArray[0], 0 );
    }
    else if ( *(picdata + _ofsArray[_ofsNum-1]) == *(picdata + _ofsArray[0]))
    {
      // start point and end point both inside or both outside any segmentation
      // normal paint operation
      mitkIpInt4_t* p = new mitkIpInt4_t[2 * num];
      for (unsigned int i = 0; i < num; i++) 
      {
        p[2 * i] = (mitkIpInt4_t) _points [2 * i];
        p[2 * i + 1] = (mitkIpInt4_t) _points [2 * i + 1];
      }

      if (state == 0) ipMITKSegmentationCombineRegion (pic, p, num, 0, IPSEGMENTATION_OR,  1);
      else            ipMITKSegmentationCombineRegion (pic, p, num, 0, IPSEGMENTATION_AND, 0);

      delete[] p;
    }
  }

  int numberOfContourPoints( 0 );
  int oneContourOffset( 0 );
  int newBufferSize( 0 );

  int imageSize = pic->n[0]*pic->n[1];
  for (oneContourOffset = 0; oneContourOffset < imageSize; oneContourOffset++)
    if ( ((ipMITKSegmentationTYPE*) pic->data)[oneContourOffset]> 0) break;

  float* contourPoints = ipMITKSegmentationGetContour8N( pic, oneContourOffset, numberOfContourPoints, newBufferSize ); // memory allocated with malloc
  
  if (contourPoints) 
  {
    
    // copy point from float* to mitk::Contour 
    Contour::Pointer contourInImageIndexCoordinates = Contour::New();
    contourInImageIndexCoordinates->Initialize();
    Point3D newPoint;
    for (int index = 0; index < numberOfContourPoints; ++index)
    {
      newPoint[0] = contourPoints[ 2 * index + 0 ];
      newPoint[1] = contourPoints[ 2 * index + 1];
      newPoint[2] = 0;

      contourInImageIndexCoordinates->AddVertex( newPoint );
    }

    free(contourPoints);
    
    ContourUtils::Pointer contourUtils = ContourUtils::New();
    contourUtils->FillContourInSlice( contourInImageIndexCoordinates, m_WorkingImage );
  }
  
  delete[] _ofsArray;
  delete[] _points;
}
Ejemplo n.º 2
0
/**
Uses ipSegmentation algorithms to do the actual region growing. The result (binary image) is first smoothed by a 5x5 circle mask, then
its contour is extracted and converted to MITK coordinates.
*/
mitkIpPicDescriptor* mitk::RegionGrowingTool::PerformRegionGrowingAndUpdateContour(int timestep)
{
  // 1. m_OriginalPicSlice and m_SeedPointMemoryOffset are set to sensitive values, as well as m_LowerThreshold and m_UpperThreshold
  assert (m_OriginalPicSlice);
  if (m_OriginalPicSlice->n[0] != 256 || m_OriginalPicSlice->n[1] != 256) // ???
  assert( (m_SeedPointMemoryOffset < static_cast<int>( m_OriginalPicSlice->n[0] * m_OriginalPicSlice->n[1] )) && (m_SeedPointMemoryOffset >= 0) ); // inside the image

  // 2. ipSegmentation is used to perform region growing
  float ignored;
  int oneContourOffset( 0 );
  mitkIpPicDescriptor* regionGrowerResult = ipMITKSegmentationGrowRegion4N( m_OriginalPicSlice,
                                                                        m_SeedPointMemoryOffset,       // seed point
                                                                        true,              // grayvalue interval relative to seed point gray value?
                                                                        m_LowerThreshold,
                                                                        m_UpperThreshold,
                                                                        0,                  // continue until done (maxIterations == 0)
                                                                        NULL,               // allocate new memory (only this time, on mouse move we'll reuse the old buffer)
                                                                        oneContourOffset,   // a pixel that is near the resulting contour
                                                                        ignored             // ignored by us
                                                                      );

  if (!regionGrowerResult || oneContourOffset == -1)
  {
    ContourModel::Pointer dummyContour = ContourModel::New();
    dummyContour->Initialize();
    FeedbackContourTool::SetFeedbackContour( *dummyContour );

    if (regionGrowerResult) ipMITKSegmentationFree(regionGrowerResult);
    return NULL;
  }

  // 3. We smooth the result a little to reduce contour complexity
  bool smoothResult( true ); // currently fixed, perhaps remove else block
  mitkIpPicDescriptor* smoothedRegionGrowerResult;
  if (smoothResult)
  {
    // Smooth the result (otherwise very detailed contour)
    smoothedRegionGrowerResult = SmoothIPPicBinaryImage( regionGrowerResult, oneContourOffset );

    ipMITKSegmentationFree( regionGrowerResult );
  }
  else
  {
    smoothedRegionGrowerResult = regionGrowerResult;
  }

  // 4. convert the result of region growing into a mitk::Contour
  // At this point oneContourOffset could be useless, if smoothing destroyed a thin bridge. In these
  // cases, we have two or more unconnected segmentation regions, and we don't know, which one is touched by oneContourOffset.
  // In the bad case, the contour is not the one around our seedpoint, so the result looks very strange to the user.
  // -> we remove the point where the contour started so far. Then we look from the bottom of the image for the first segmentation pixel
  //    and start another contour extraction from there. This is done, until the seedpoint is inside the contour
  int numberOfContourPoints( 0 );
  int newBufferSize( 0 );
  float* contourPoints = ipMITKSegmentationGetContour8N( smoothedRegionGrowerResult, oneContourOffset, numberOfContourPoints, newBufferSize ); // memory allocated with malloc
  if (contourPoints)
  {
    while ( !ipMITKSegmentationIsInsideContour( contourPoints,                                               // contour
                                                numberOfContourPoints,                                       // points in contour
                                                m_SeedPointMemoryOffset % smoothedRegionGrowerResult->n[0],  // test point x
                                                m_SeedPointMemoryOffset / smoothedRegionGrowerResult->n[0]   // test point y
                                              ) )
    {
      // we decide that this cannot be part of the segmentation because the seedpoint is not contained in the contour (fill the 4-neighborhood with 0)
      ipMITKSegmentationReplaceRegion4N( smoothedRegionGrowerResult, oneContourOffset, 0 );

      // move the contour offset to the last row (x position of the seed point)
      int rowLength = smoothedRegionGrowerResult->n[0]; // number of pixels in a row
      oneContourOffset =    m_SeedPointMemoryOffset % smoothedRegionGrowerResult->n[0]  // x of seed point
                          + rowLength*(smoothedRegionGrowerResult->n[1]-1);              // y of last row

      while (    oneContourOffset >=0
              && (*(static_cast<ipMITKSegmentationTYPE*>(smoothedRegionGrowerResult->data) + oneContourOffset) == 0) )
      {
        oneContourOffset -= rowLength; // if pixel at data+oneContourOffset is 0, then move up one row
      }

      if ( oneContourOffset < 0 )
      {
        break; // just use the last contour we found
      }

      free(contourPoints); // release contour memory
      contourPoints = ipMITKSegmentationGetContour8N( smoothedRegionGrowerResult, oneContourOffset, numberOfContourPoints, newBufferSize ); // memory allocated with malloc
    }

    // copy point from float* to mitk::Contour
    ContourModel::Pointer contourInImageIndexCoordinates = ContourModel::New();
    contourInImageIndexCoordinates->Expand(timestep + 1);
    contourInImageIndexCoordinates->SetClosed(true, timestep);
    Point3D newPoint;
    for (int index = 0; index < numberOfContourPoints; ++index)
    {
      newPoint[0] = contourPoints[ 2 * index + 0 ] - 0.5;//correction is needed because the output of the algorithm is center based
      newPoint[1] = contourPoints[ 2 * index + 1 ] - 0.5;//and we want our contour displayed corner based.
      newPoint[2] = 0;

      contourInImageIndexCoordinates->AddVertex( newPoint, timestep );
    }

    free(contourPoints);

    ContourModel::Pointer contourInWorldCoordinates = FeedbackContourTool::BackProjectContourFrom2DSlice( m_ReferenceSlice->GetGeometry(), contourInImageIndexCoordinates, true );   // true: sub 0.5 for ipSegmentation correctio

    FeedbackContourTool::SetFeedbackContour( *contourInWorldCoordinates );
  }

  // 5. Result HAS TO BE freed by caller, contains the binary region growing result
  return smoothedRegionGrowerResult;
}
bool mitk::SetRegionTool::OnMousePressed ( StateMachineAction*, InteractionEvent* interactionEvent )
{
  mitk::InteractionPositionEvent* positionEvent = dynamic_cast<mitk::InteractionPositionEvent*>( interactionEvent );
  //const PositionEvent* positionEvent = dynamic_cast<const PositionEvent*>(stateEvent->GetEvent());
  if (!positionEvent) return false;

  m_LastEventSender = positionEvent->GetSender();
  m_LastEventSlice = m_LastEventSender->GetSlice();
  int timeStep = positionEvent->GetSender()->GetTimeStep();

  // 1. Get the working image
  Image::Pointer workingSlice   = FeedbackContourTool::GetAffectedWorkingSlice( positionEvent );
  if ( workingSlice.IsNull() ) return false; // can't do anything without the segmentation

  // if click was outside the image, don't continue
  const BaseGeometry* sliceGeometry = workingSlice->GetGeometry();
  itk::Index<2> projectedPointIn2D;
  sliceGeometry->WorldToIndex( positionEvent->GetPositionInWorld(), projectedPointIn2D );
  if ( !sliceGeometry->IsIndexInside( projectedPointIn2D ) )
  {
    MITK_ERROR << "point apparently not inside segmentation slice" << std::endl;
    return false; // can't use that as a seed point
  }

    // Convert to ipMITKSegmentationTYPE (because ipMITKSegmentationGetContour8N relys on that data type)
    itk::Image< ipMITKSegmentationTYPE, 2 >::Pointer correctPixelTypeImage;
    CastToItkImage( workingSlice, correctPixelTypeImage );
    assert (correctPixelTypeImage.IsNotNull() );

  // possible bug in CastToItkImage ?
  // direction maxtrix is wrong/broken/not working after CastToItkImage, leading to a failed assertion in
  // mitk/Core/DataStructures/mitkSlicedGeometry3D.cpp, 479:
  // virtual void mitk::SlicedGeometry3D::SetSpacing(const mitk::Vector3D&): Assertion `aSpacing[0]>0 && aSpacing[1]>0 && aSpacing[2]>0' failed
  // solution here: we overwrite it with an unity matrix
  itk::Image< ipMITKSegmentationTYPE, 2 >::DirectionType imageDirection;
  imageDirection.SetIdentity();
  correctPixelTypeImage->SetDirection(imageDirection);

    Image::Pointer temporarySlice = Image::New();
  //  temporarySlice = ImportItkImage( correctPixelTypeImage );
    CastToMitkImage( correctPixelTypeImage, temporarySlice );


  // check index positions
  mitkIpPicDescriptor* originalPicSlice = mitkIpPicNew();
  CastToIpPicDescriptor( temporarySlice, originalPicSlice );

  int m_SeedPointMemoryOffset = projectedPointIn2D[1] * originalPicSlice->n[0] + projectedPointIn2D[0];

  if ( m_SeedPointMemoryOffset >= static_cast<int>( originalPicSlice->n[0] * originalPicSlice->n[1] ) ||
       m_SeedPointMemoryOffset < 0 )
  {
    MITK_ERROR << "Memory offset calculation if mitk::SetRegionTool has some serious flaw! Aborting.." << std::endl;
    return false;
  }

  // 2. Determine the contour that surronds the selected "piece of the image"

  // find a contour seed point
  unsigned int oneContourOffset = static_cast<unsigned int>( m_SeedPointMemoryOffset ); // safe because of earlier check if m_SeedPointMemoryOffset < 0

  /**
    * The logic of finding a starting point for the contour is the following:
    *
    *  - If the initial seed point is 0, we are either inside a hole or outside of every segmentation.
    *    We move to the right until we hit a 1, which must be part of a contour.
    *
    *  - If the initial seed point is 1, then ...
    *    we now do the same (running to the right) until we hit a 1
    *
    *  In both cases the found contour point is used to extract a contour and
    *  then a test is applied to find out if the initial seed point is contained
    *  in the contour. If this is the case, filling should be applied, otherwise
    *  nothing is done.
    */
  unsigned int size = originalPicSlice->n[0] * originalPicSlice->n[1];
/*
  unsigned int rowSize = originalPicSlice->n[0];
*/
  ipMITKSegmentationTYPE* data = static_cast<ipMITKSegmentationTYPE*>(originalPicSlice->data);

  if ( data[oneContourOffset] == 0 ) // initial seed 0
  {
    for ( ; oneContourOffset < size; ++oneContourOffset )
    {
      if ( data[oneContourOffset] > 0 ) break;
    }
  }
  else if ( data[oneContourOffset] == 1 ) // initial seed 1
  {
    unsigned int lastValidPixel = size-1; // initialization, will be changed lateron
    bool inSeg = true;    // inside segmentation?
    for ( ; oneContourOffset < size; ++oneContourOffset )
    {
      if ( ( data[oneContourOffset] == 0 ) && inSeg ) // pixel 0 and inside-flag set: this happens at the first pixel outside a filled region
      {
        inSeg = false;
        lastValidPixel = oneContourOffset - 1; // store the last pixel position inside a filled region
        break;
      }
      else // pixel 1, inside-flag doesn't matter: this happens while we are inside a filled region
      {
        inSeg = true; // first iteration lands here
      }

    }
    oneContourOffset = lastValidPixel;
  }
  else
  {
    MITK_ERROR << "Fill/Erase was never intended to work with other than binary images." << std::endl;
    m_FillContour = false;
    return false;
  }

  if (oneContourOffset == size) // nothing found until end of slice
  {
    m_FillContour = false;
    return false;
  }

  int numberOfContourPoints( 0 );
  int newBufferSize( 0 );
  //MITK_INFO << "getting contour from offset " << oneContourOffset << " ("<<oneContourOffset%originalPicSlice->n[0]<<","<<oneContourOffset/originalPicSlice->n[0]<<")"<<std::endl;
  float* contourPoints = ipMITKSegmentationGetContour8N( originalPicSlice, oneContourOffset, numberOfContourPoints, newBufferSize ); // memory allocated with malloc

  //MITK_INFO << "contourPoints " << contourPoints << " (N="<<numberOfContourPoints<<")"<<std::endl;
  assert(contourPoints == NULL || numberOfContourPoints > 0);

  bool cursorInsideContour = ipMITKSegmentationIsInsideContour( contourPoints, numberOfContourPoints, projectedPointIn2D[0], projectedPointIn2D[1]);

  // decide if contour should be filled or not
  m_FillContour = cursorInsideContour;

  if (m_FillContour)
  {
    // copy point from float* to mitk::Contour
    ContourModel::Pointer contourInImageIndexCoordinates = ContourModel::New();
    contourInImageIndexCoordinates->Expand(timeStep + 1);
    contourInImageIndexCoordinates->SetClosed(true, timeStep);
    Point3D newPoint;
    for (int index = 0; index < numberOfContourPoints; ++index)
    {
      newPoint[0] = contourPoints[ 2 * index + 0 ] - 0.5;
      newPoint[1] = contourPoints[ 2 * index + 1] - 0.5;
      newPoint[2] = 0;

      contourInImageIndexCoordinates->AddVertex(newPoint, timeStep);
    }

    m_SegmentationContourInWorldCoordinates = FeedbackContourTool::BackProjectContourFrom2DSlice( workingSlice->GetGeometry(), contourInImageIndexCoordinates, true ); // true, correct the result from ipMITKSegmentationGetContour8N

    // 3. Show the contour
    FeedbackContourTool::SetFeedbackContour( *m_SegmentationContourInWorldCoordinates );

    FeedbackContourTool::SetFeedbackContourVisible(true);
    mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow());
  }

  // always generate a second contour, containing the whole image (used when CTRL is pressed)
  {
    // copy point from float* to mitk::Contour
    ContourModel::Pointer contourInImageIndexCoordinates = ContourModel::New();
    contourInImageIndexCoordinates->Expand(timeStep + 1);
    contourInImageIndexCoordinates->SetClosed(true, timeStep);
    Point3D newPoint;
    newPoint[0] = 0; newPoint[1] = 0; newPoint[2] = 0.0;
    contourInImageIndexCoordinates->AddVertex( newPoint, timeStep );
    newPoint[0] = originalPicSlice->n[0]; newPoint[1] = 0; newPoint[2] = 0.0;
    contourInImageIndexCoordinates->AddVertex( newPoint, timeStep );
    newPoint[0] = originalPicSlice->n[0]; newPoint[1] = originalPicSlice->n[1]; newPoint[2] = 0.0;
    contourInImageIndexCoordinates->AddVertex( newPoint, timeStep );
    newPoint[0] = 0; newPoint[1] = originalPicSlice->n[1]; newPoint[2] = 0.0;
    contourInImageIndexCoordinates->AddVertex( newPoint, timeStep );

    m_WholeImageContourInWorldCoordinates = FeedbackContourTool::BackProjectContourFrom2DSlice( workingSlice->GetGeometry(), contourInImageIndexCoordinates, true ); // true, correct the result from ipMITKSegmentationGetContour8N

    // 3. Show the contour
    FeedbackContourTool::SetFeedbackContour( *m_SegmentationContourInWorldCoordinates );

    FeedbackContourTool::SetFeedbackContourVisible(true);
    mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow());
  }


  free(contourPoints);

  return true;
}