Create multiple polygons from a contour map

* Fix overwrite of value thresholds
* Fix performance issue with filtered values
* Add "Delete All Polygons" from the polygon collection
* Add area threshold where user can specify area cutoff
This commit is contained in:
Magne Sjaastad authored and GitHub committed 2025-02-24 10:06:06 +01:00
1 parent bbd79cca6a
commit f4c31e0b28
23 files changed
+390 -112

No files matched your search

@@ -7,6 +7,7 @@ set(SOURCE_GROUP_HEADER_FILES
${CMAKE_CURRENT_LIST_DIR}/RicExportPolygonPolFeature.h
${CMAKE_CURRENT_LIST_DIR}/RicSimplifyPolygonFeature.h
${CMAKE_CURRENT_LIST_DIR}/RicBasicPolygonFeature.h
${CMAKE_CURRENT_LIST_DIR}/RicDeleteAllPolygonsFeature.h
)
set(SOURCE_GROUP_SOURCE_FILES
@@ -18,6 +19,7 @@ set(SOURCE_GROUP_SOURCE_FILES
${CMAKE_CURRENT_LIST_DIR}/RicExportPolygonPolFeature.cpp
${CMAKE_CURRENT_LIST_DIR}/RicSimplifyPolygonFeature.cpp
${CMAKE_CURRENT_LIST_DIR}/RicBasicPolygonFeature.cpp
${CMAKE_CURRENT_LIST_DIR}/RicDeleteAllPolygonsFeature.cpp
)
list(APPEND COMMAND_CODE_HEADER_FILES ${SOURCE_GROUP_HEADER_FILES})
@@ -24,8 +24,7 @@
#include "Polygons/RimPolygonCollection.h"
#include "Polygons/RimPolygonTools.h"
#include "Rim3dView.h"
#include "RimOilField.h"
#include "RimProject.h"
#include "RimTools.h"
#include "Riu3DMainWindowTools.h"
@@ -38,8 +37,7 @@ CAF_CMD_SOURCE_INIT( RicCreatePolygonFeature, "RicCreatePolygonFeature" );
//--------------------------------------------------------------------------------------------------
void RicCreatePolygonFeature::onActionTriggered( bool isChecked )
{
auto proj = RimProject::current();
auto polygonCollection = proj->activeOilField()->polygonCollection();
auto polygonCollection = RimTools::polygonCollection();
auto newPolygon = polygonCollection->appendUserDefinedPolygon();
polygonCollection->uiCapability()->updateAllRequiredEditors();
@@ -0,0 +1,46 @@
////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2025 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#include "RicDeleteAllPolygonsFeature.h"
#include "Polygons/RimPolygonCollection.h"
#include "RimTools.h"
#include <QAction>
CAF_CMD_SOURCE_INIT( RicDeleteAllPolygonsFeature, "RicDeleteAllPolygonsFeature" );
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RicDeleteAllPolygonsFeature::onActionTriggered( bool isChecked )
{
auto polygonCollection = RimTools::polygonCollection();
polygonCollection->deleteAllPolygons();
polygonCollection->updateConnectedEditors();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RicDeleteAllPolygonsFeature::setupActionLook( QAction* actionToSetup )
{
actionToSetup->setText( "Delete All Polygons" );
actionToSetup->setIcon( QIcon( ":/Erase.png" ) );
}
@@ -0,0 +1,33 @@
////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2025 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#pragma once
#include "cafCmdFeature.h"
//==================================================================================================
///
//==================================================================================================
class RicDeleteAllPolygonsFeature : public caf::CmdFeature
{
CAF_CMD_HEADER_INIT;
protected:
void onActionTriggered( bool isChecked ) override;
void setupActionLook( QAction* actionToSetup ) override;
};
@@ -25,8 +25,7 @@
#include "Polygons/RimPolygonInView.h"
#include "Polygons/RimPolygonTools.h"
#include "Rim3dView.h"
#include "RimOilField.h"
#include "RimProject.h"
#include "RimTools.h"
#include "Riu3DMainWindowTools.h"
@@ -55,8 +54,7 @@ void RicDuplicatePolygonFeature::onActionTriggered( bool isChecked )
auto sourcePolygon = selPolygons[0];
auto proj = RimProject::current();
auto polygonCollection = proj->activeOilField()->polygonCollection();
auto polygonCollection = RimTools::polygonCollection();
auto newPolygon = polygonCollection->createUserDefinedPolygon();
newPolygon->setPointsInDomainCoords( sourcePolygon->pointsInDomainCoords() );
@@ -24,8 +24,7 @@
#include "Polygons/RimPolygonCollection.h"
#include "Polygons/RimPolygonFile.h"
#include "Polygons/RimPolygonTools.h"
#include "RimOilField.h"
#include "RimProject.h"
#include "RimTools.h"
#include "Riu3DMainWindowTools.h"
#include "RiuFileDialogTools.h"
@@ -56,8 +55,7 @@ void RicImportPolygonFileFeature::onActionTriggered( bool isChecked )
// Remember the path to next time
app->setLastUsedDialogDirectory( RimPolygonTools::polygonCacheName(), QFileInfo( fileNames.last() ).absolutePath() );
auto proj = RimProject::current();
auto polygonCollection = proj->activeOilField()->polygonCollection();
auto polygonCollection = RimTools::polygonCollection();
RimPolygon* objectToSelect = nullptr;
@@ -35,7 +35,7 @@ void RicCreateContourMapPolygonAdvancedFeature::onActionTriggered( bool isChecke
auto sourceImage = RicCreateContourMapPolygonTools::convertToBinaryImage( rigContourMapProjection );
ImageProcessingDialog dlg;
RicPolygonFromImageDialog dlg;
dlg.setSourceImageData( sourceImage );
dlg.show();
dlg.updateAndShowImages();
@@ -45,7 +45,7 @@ void RicCreateContourMapPolygonAdvancedFeature::onActionTriggered( bool isChecke
auto processedImage = dlg.processedImageData();
if ( processedImage.empty() ) return;
RicCreateContourMapPolygonTools::createAndAddBoundaryPolygonFromImage( processedImage, rigContourMapProjection );
RicCreateContourMapPolygonTools::createPolygonObjects( processedImage, rigContourMapProjection );
}
//--------------------------------------------------------------------------------------------------
@@ -41,9 +41,7 @@ void RicCreateContourMapPolygonFeature::onActionTriggered( bool isChecked )
auto eroded = RigPolygonTools::erode( floodFilled, kernelSize );
auto dilated = RigPolygonTools::dilate( eroded, kernelSize );
if ( dilated.empty() ) return;
RicCreateContourMapPolygonTools::createAndAddBoundaryPolygonFromImage( dilated, rigContourMapProjection );
RicCreateContourMapPolygonTools::createPolygonObjects( dilated, rigContourMapProjection );
}
//--------------------------------------------------------------------------------------------------
@@ -30,8 +30,105 @@
#include "RimGeoMechContourMapView.h"
#include "RimTools.h"
#include "RiuMainWindow.h"
#include "cvfBase.h"
#include <QInputDialog>
namespace internal
{
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<std::pair<int, std::vector<cvf::Vec3d>>> findPolygons( std::vector<std::vector<int>> image,
const RigContourMapProjection* contourMapProjection )
{
std::vector<std::pair<int, std::vector<cvf::Vec3d>>> polygons;
if ( !contourMapProjection ) return {};
if ( image.empty() ) return {};
auto xVertexPositions = contourMapProjection->xVertexPositions();
auto yVertexPositions = contourMapProjection->yVertexPositions();
auto origin3d = contourMapProjection->origin3d();
auto depth = contourMapProjection->topDepthBoundingBox();
auto hasAnyValue = []( const std::vector<std::vector<int>>& image ) -> bool
{
for ( const auto& row : image )
{
for ( int val : row )
{
if ( val != 0 ) return true;
}
}
return false;
};
while ( hasAnyValue( image ) )
{
std::vector<cvf::Vec3d> polygonDomainCoords;
auto boundaryPoints = RigPolygonTools::boundary( image );
for ( const auto& [i, j] : boundaryPoints )
{
auto xDomain = xVertexPositions.at( i ) + origin3d.x();
auto yDomain = yVertexPositions.at( j ) + origin3d.y();
polygonDomainCoords.emplace_back( cvf::Vec3d( xDomain, yDomain, depth ) );
}
// Epsilon used to simplify polygon. Useful range typical value in [5..50]
const double defaultEpsilon = 40.0;
RigPolygonTools::simplifyPolygon( polygonDomainCoords, defaultEpsilon );
if ( polygonDomainCoords.size() >= 3 )
{
const auto area = RigPolygonTools::area( boundaryPoints );
polygons.push_back( { area, polygonDomainCoords } );
}
// Subtract all pixels inside the polygon by setting their value to 0
const int value = 0;
image = RigPolygonTools::assignValueInsidePolygon( image, boundaryPoints, value );
/*
// NB: Do not remove, debug code to export images to file
{
QString filename = "boundary_removed";
filename = QString( "f:/scratch/images/%1%2.png" ).arg( filename ).arg( QString::number( imageCounter ) );
RicCreateContourMapPolygonTools::exportVectorAsGrayscaleImage( currentImage, filename );
}
*/
}
return polygons;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void createPolygonObjects( const std::vector<std::vector<cvf::Vec3d>>& polygons )
{
auto polygonCollection = RimTools::polygonCollection();
if ( !polygonCollection ) return;
for ( const auto& polygonDomainCoords : polygons )
{
auto newPolygon = polygonCollection->appendUserDefinedPolygon();
newPolygon->setPointsInDomainCoords( polygonDomainCoords );
newPolygon->coordinatesChanged.send();
}
polygonCollection->updateAllRequiredEditors();
}
} // namespace internal
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
@@ -168,12 +265,14 @@ std::vector<std::vector<int>> RicCreateContourMapPolygonTools::convertToBinaryIm
std::vector<std::vector<int>> image( vertexSizeIJ.x(), std::vector<int>( vertexSizeIJ.y(), 0 ) );
auto filteredValues = rigContourMapProjection->aggregatedVertexResultsFiltered();
for ( cvf::uint i = 0; i < vertexSizeIJ.x(); i++ )
{
for ( cvf::uint j = 0; j < vertexSizeIJ.y(); j++ )
{
double valueAtVertex = rigContourMapProjection->filteredValueAtVertex( i, j );
auto vertexIndex = rigContourMapProjection->vertexIndex( i, j );
double valueAtVertex = vertexIndex < filteredValues.size() ? filteredValues[vertexIndex] : std::numeric_limits<double>::infinity();
if ( !std::isinf( valueAtVertex ) )
{
image[i][j] = 1;
@@ -191,46 +290,45 @@ std::vector<std::vector<int>> RicCreateContourMapPolygonTools::convertToBinaryIm
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimPolygon* RicCreateContourMapPolygonTools::createAndAddBoundaryPolygonFromImage( std::vector<std::vector<int>> image,
const RigContourMapProjection* contourMapProjection )
void RicCreateContourMapPolygonTools::createPolygonObjects( std::vector<std::vector<int>> image,
const RigContourMapProjection* contourMapProjection )
{
if ( !contourMapProjection ) return nullptr;
if ( image.empty() ) return nullptr;
auto polygons = internal::findPolygons( image, contourMapProjection );
if ( polygons.empty() ) return;
std::vector<cvf::Vec3d> polygonDomainCoords;
auto xVertexPositions = contourMapProjection->xVertexPositions();
auto yVertexPositions = contourMapProjection->yVertexPositions();
auto origin3d = contourMapProjection->origin3d();
auto depth = contourMapProjection->topDepthBoundingBox();
std::sort( polygons.begin(), polygons.end(), []( const auto& a, const auto& b ) { return a.first > b.first; } );
auto boundaryPoints = RigPolygonTools::boundary( image );
for ( auto [i, j] : boundaryPoints )
int polygonCount = 0;
QStringList polygonInfo;
for ( const auto& polygon : polygons )
{
double xDomain = xVertexPositions.at( i ) + origin3d.x();
double yDomain = yVertexPositions.at( j ) + origin3d.y();
polygonDomainCoords.emplace_back( cvf::Vec3d( xDomain, yDomain, depth ) );
polygonInfo
<< QString( "Polygon %1: Normalized area %2, %3 vertices " ).arg( polygonCount++ ).arg( polygon.first ).arg( polygon.second.size() );
if ( polygonCount > 10 )
{
polygonInfo << "More polygons found, but only the first 10 are shown.";
break;
}
}
// Epsilon used to simplify polygon. Useful range typical value in [5..50]
const double defaultEpsilon = 40.0;
RigPolygonTools::simplifyPolygon( polygonDomainCoords, defaultEpsilon );
QString txt = polygonInfo.join( "\n" );
txt += "\n\n Define normalized area threshold:";
if ( polygonDomainCoords.size() >= 3 )
bool ok;
int areaThreshold = QInputDialog::getInt( RiuMainWindow::instance(), "Create Polygons", txt, 10, 1, 10000, 1, &ok );
if ( ok )
{
auto polygonCollection = RimTools::polygonCollection();
std::vector<std::vector<cvf::Vec3d>> polygonsToCreate;
for ( const auto& polygon : polygons )
{
if ( polygon.first >= areaThreshold )
{
polygonsToCreate.push_back( polygon.second );
}
}
auto newPolygon = polygonCollection->appendUserDefinedPolygon();
newPolygon->setPointsInDomainCoords( polygonDomainCoords );
newPolygon->coordinatesChanged.send();
polygonCollection->uiCapability()->updateAllRequiredEditors();
return newPolygon;
internal::createPolygonObjects( polygonsToCreate );
}
return nullptr;
}
//--------------------------------------------------------------------------------------------------
@@ -40,7 +40,7 @@ void exportVectorAsGrayscaleImage( const std::vector<std::vector<int>>& data, co
std::vector<std::vector<int>> convertImageToBinary( QImage image );
std::vector<std::vector<int>> convertToBinaryImage( const RigContourMapProjection* contourMapProjection );
RimPolygon* createAndAddBoundaryPolygonFromImage( std::vector<std::vector<int>> image, const RigContourMapProjection* contourMapProjection );
void createPolygonObjects( std::vector<std::vector<int>> image, const RigContourMapProjection* contourMapProjection );
const RigContourMapProjection* findCurrentContourMapProjection();
@@ -24,12 +24,13 @@
#include <QDialogButtonBox>
#include <QGuiApplication>
#include <QLineEdit>
#include <QScreen>
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
ImageProcessingDialog::ImageProcessingDialog( QWidget* parent /*= nullptr */ )
RicPolygonFromImageDialog::RicPolygonFromImageDialog( QWidget* parent /*= nullptr */ )
: QDialog( parent )
{
setWindowTitle( "Image Morphology Tool" );
@@ -75,6 +76,17 @@ ImageProcessingDialog::ImageProcessingDialog( QWidget* parent /*= nullptr */ )
showFinal->setChecked( true );
settingsLayout->addWidget( showFinal );
{
auto* label = new QLabel( "Minimum polygon area", this );
areaThresholdLineEdit = new QLineEdit( this );
areaThresholdLineEdit->setText( "10" );
auto* layout = new QHBoxLayout();
layout->addWidget( label );
layout->addWidget( areaThresholdLineEdit );
settingsLayout->addLayout( layout );
}
settingsLayout->addStretch();
graphicsView = new QGraphicsView( this );
@@ -95,13 +107,13 @@ ImageProcessingDialog::ImageProcessingDialog( QWidget* parent /*= nullptr */ )
connect( buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject );
// Connect signals and slots
connect( showInput, &QCheckBox::clicked, this, &ImageProcessingDialog::showImages );
connect( showEroded, &QCheckBox::clicked, this, &ImageProcessingDialog::showImages );
connect( showDilated, &QCheckBox::clicked, this, &ImageProcessingDialog::showImages );
connect( showFinal, &QCheckBox::clicked, this, &ImageProcessingDialog::showImages );
connect( showInput, &QCheckBox::clicked, this, &RicPolygonFromImageDialog::showImages );
connect( showEroded, &QCheckBox::clicked, this, &RicPolygonFromImageDialog::showImages );
connect( showDilated, &QCheckBox::clicked, this, &RicPolygonFromImageDialog::showImages );
connect( showFinal, &QCheckBox::clicked, this, &RicPolygonFromImageDialog::showImages );
connect( kernelSpinBox, &QSpinBox::valueChanged, this, &ImageProcessingDialog::updateAndShowImages );
connect( transparencySlider, &QSlider::valueChanged, this, &ImageProcessingDialog::updateAndShowImages );
connect( kernelSpinBox, &QSpinBox::valueChanged, this, &RicPolygonFromImageDialog::updateAndShowImages );
connect( transparencySlider, &QSlider::valueChanged, this, &RicPolygonFromImageDialog::updateAndShowImages );
resizeAndCenterDialog( 0.6 ); // 1.0 means to the full screen size
}
@@ -109,7 +121,7 @@ ImageProcessingDialog::ImageProcessingDialog( QWidget* parent /*= nullptr */ )
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void ImageProcessingDialog::performDilation()
void RicPolygonFromImageDialog::performDilation()
{
if ( sourceData.empty() )
{
@@ -124,7 +136,7 @@ void ImageProcessingDialog::performDilation()
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void ImageProcessingDialog::performErosion()
void RicPolygonFromImageDialog::performErosion()
{
if ( sourceData.empty() )
{
@@ -139,7 +151,7 @@ void ImageProcessingDialog::performErosion()
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void ImageProcessingDialog::updateAndShowImages()
void RicPolygonFromImageDialog::updateAndShowImages()
{
performErosion();
performDilation();
@@ -150,7 +162,7 @@ void ImageProcessingDialog::updateAndShowImages()
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void ImageProcessingDialog::showImages()
void RicPolygonFromImageDialog::showImages()
{
if ( sourceData.empty() )
{
@@ -216,7 +228,7 @@ void ImageProcessingDialog::showImages()
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<std::vector<int>> ImageProcessingDialog::processedImageData() const
std::vector<std::vector<int>> RicPolygonFromImageDialog::processedImageData() const
{
return processedData;
}
@@ -224,7 +236,7 @@ std::vector<std::vector<int>> ImageProcessingDialog::processedImageData() const
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void ImageProcessingDialog::setSourceImageData( std::vector<std::vector<int>> imageData )
void RicPolygonFromImageDialog::setSourceImageData( std::vector<std::vector<int>> imageData )
{
sourceData = imageData;
}
@@ -232,7 +244,15 @@ void ImageProcessingDialog::setSourceImageData( std::vector<std::vector<int>> im
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
int ImageProcessingDialog::kernelAdjustedSize() const
int RicPolygonFromImageDialog::areaThreshold() const
{
return areaThresholdLineEdit->text().toInt();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
int RicPolygonFromImageDialog::kernelAdjustedSize() const
{
// The kernel size should be odd and positive
return kernelSpinBox->value() * 2 - 1;
@@ -241,7 +261,7 @@ int ImageProcessingDialog::kernelAdjustedSize() const
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void ImageProcessingDialog::computeFinal()
void RicPolygonFromImageDialog::computeFinal()
{
if ( sourceData.empty() )
{
@@ -260,7 +280,7 @@ void ImageProcessingDialog::computeFinal()
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void ImageProcessingDialog::resizeAndCenterDialog( double scale )
void RicPolygonFromImageDialog::resizeAndCenterDialog( double scale )
{
QScreen* screen = QGuiApplication::primaryScreen();
if ( !screen ) return;
@@ -281,7 +301,7 @@ void ImageProcessingDialog::resizeAndCenterDialog( double scale )
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void ImageProcessingDialog::resizeEvent( QResizeEvent* event )
void RicPolygonFromImageDialog::resizeEvent( QResizeEvent* event )
{
QDialog::resizeEvent( event );
showImages();
@@ -33,16 +33,18 @@
#include <QSpinBox>
#include <QVBoxLayout>
class ImageProcessingDialog : public QDialog
class RicPolygonFromImageDialog : public QDialog
{
Q_OBJECT
public:
ImageProcessingDialog( QWidget* parent = nullptr );
RicPolygonFromImageDialog( QWidget* parent = nullptr );
std::vector<std::vector<int>> processedImageData() const;
void setSourceImageData( std::vector<std::vector<int>> imageData );
int areaThreshold() const;
public slots:
void updateAndShowImages();
@@ -69,5 +71,7 @@ private:
QCheckBox* showEroded;
QCheckBox* showFinal;
QLineEdit* areaThresholdLineEdit;
std::vector<std::vector<int>> sourceData, dilatedData, erodedData, processedData;
};