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; }
/** 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; }