mirror of
https://github.com/OPM/ResInsight.git
synced 2026-08-27 05:37:21 -05:00
382 lines
14 KiB
C++
382 lines
14 KiB
C++
/////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Copyright (C) 2024- 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 "RicCreateContourMapPolygonTools.h"
|
|
|
|
#include "RicExportContourMapToTextFeature.h"
|
|
|
|
#include "ContourMap/RigContourMapProjection.h"
|
|
#include "RigPolygonTools.h"
|
|
|
|
#include "ContourMap/RimContourMapProjection.h"
|
|
#include "ContourMap/RimEclipseContourMapView.h"
|
|
#include "Polygons/RimPolygon.h"
|
|
#include "Polygons/RimPolygonCollection.h"
|
|
#include "Polygons/RimPolygonInViewCollection.h"
|
|
#include "RimGeoMechContourMapView.h"
|
|
#include "RimGridView.h"
|
|
#include "RimProject.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();
|
|
|
|
// Update polygon collections in all views so new polygons become visible
|
|
if ( auto project = RimProject::current() )
|
|
{
|
|
for ( auto* view : project->allViews() )
|
|
{
|
|
if ( auto* gridView = dynamic_cast<RimGridView*>( view ) )
|
|
{
|
|
if ( auto* polyCollection = gridView->polygonInViewCollection() )
|
|
{
|
|
polyCollection->updateFromPolygonCollection();
|
|
polyCollection->updateConnectedEditors();
|
|
}
|
|
gridView->scheduleCreateDisplayModelAndRedraw();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace internal
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
QImage RicCreateContourMapPolygonTools::convertBinaryToImage( const std::vector<std::vector<int>>& data, QColor color, int transparency )
|
|
{
|
|
if ( data.empty() || data[0].empty() )
|
|
{
|
|
qWarning( "Data is empty. Cannot export an image." );
|
|
return {};
|
|
}
|
|
|
|
// Get dimensions
|
|
int height = static_cast<int>( data[0].size() );
|
|
int width = static_cast<int>( data.size() );
|
|
|
|
// Create a QImage
|
|
QImage image( width, height, QImage::Format_ARGB32 );
|
|
|
|
// Fill QImage with data
|
|
for ( int y = 0; y < height; ++y )
|
|
{
|
|
for ( int x = 0; x < width; ++x )
|
|
{
|
|
int value = std::clamp( data[x][y], 0, 255 );
|
|
if ( value > 0 )
|
|
image.setPixel( x, height - y - 1, qRgba( color.red(), color.green(), color.blue(), transparency ) ); // Grayscale
|
|
else
|
|
{
|
|
image.setPixel( x, height - y - 1, qRgba( 0, 0, 0, transparency ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
return image;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
QImage RicCreateContourMapPolygonTools::convertBinaryToGrayscaleImage( const std::vector<std::vector<int>>& data, int colorValue )
|
|
{
|
|
if ( data.empty() || data[0].empty() )
|
|
{
|
|
qWarning( "Data is empty. Cannot export an image." );
|
|
return {};
|
|
}
|
|
|
|
// Get dimensions
|
|
int height = static_cast<int>( data[0].size() );
|
|
int width = static_cast<int>( data.size() );
|
|
|
|
// Create a QImage
|
|
QImage image( width, height, QImage::Format_Grayscale8 );
|
|
|
|
// Fill QImage with data
|
|
for ( int y = 0; y < height; ++y )
|
|
{
|
|
for ( int x = 0; x < width; ++x )
|
|
{
|
|
int value = std::clamp( data[x][y] * colorValue, 0, 255 ); // Ensure value is in [0, 255]
|
|
image.setPixel( x, height - y - 1, qRgb( value, value, value ) ); // Grayscale
|
|
}
|
|
}
|
|
|
|
return image;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
void RicCreateContourMapPolygonTools::exportVectorAsImage( const std::vector<std::vector<int>>& data, int transparency, const QString& filename )
|
|
{
|
|
if ( data.empty() || data[0].empty() )
|
|
{
|
|
qWarning( "Data is empty. Cannot export an image." );
|
|
return;
|
|
}
|
|
|
|
auto image = convertBinaryToImage( data, QColorConstants::Green, transparency );
|
|
|
|
if ( !image.save( filename, "PNG" ) )
|
|
{
|
|
qWarning( "Failed to save image as PNG." );
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
void RicCreateContourMapPolygonTools::exportVectorAsGrayscaleImage( const std::vector<std::vector<int>>& data, const QString& filename )
|
|
{
|
|
if ( data.empty() || data[0].empty() )
|
|
{
|
|
qWarning( "Data is empty. Cannot export an image." );
|
|
return;
|
|
}
|
|
|
|
auto image = convertBinaryToGrayscaleImage( data, 255 );
|
|
|
|
// Save the QImage as a PNG file
|
|
if ( !image.save( filename, "PNG" ) )
|
|
{
|
|
qWarning( "Failed to save image as PNG." );
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
std::vector<std::vector<int>> RicCreateContourMapPolygonTools::convertImageToBinary( QImage image )
|
|
{
|
|
std::vector<std::vector<int>> binaryImage( image.width(), std::vector<int>( image.height(), 0 ) );
|
|
for ( int i = 0; i < image.width(); ++i )
|
|
{
|
|
for ( int j = 0; j < image.height(); ++j )
|
|
{
|
|
auto pixelColor = image.pixel( i, j );
|
|
auto gray = qGray( pixelColor );
|
|
|
|
binaryImage[i][image.height() - j - 1] = gray > 0 ? 1 : 0;
|
|
}
|
|
}
|
|
return binaryImage;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
std::vector<std::vector<int>> RicCreateContourMapPolygonTools::convertToBinaryImage( const RigContourMapProjection* rigContourMapProjection )
|
|
{
|
|
if ( !rigContourMapProjection ) return {};
|
|
|
|
auto vertexSizeIJ = rigContourMapProjection->numberOfVerticesIJ();
|
|
|
|
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++ )
|
|
{
|
|
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;
|
|
}
|
|
else
|
|
{
|
|
image[i][j] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
return image;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
void RicCreateContourMapPolygonTools::createPolygonObjects( std::vector<std::vector<int>> image,
|
|
const RigContourMapProjection* contourMapProjection )
|
|
{
|
|
auto polygons = internal::findPolygons( image, contourMapProjection );
|
|
if ( polygons.empty() ) return;
|
|
|
|
const size_t polygonCountThreshold = 10;
|
|
if ( polygons.size() > polygonCountThreshold )
|
|
{
|
|
std::sort( polygons.begin(), polygons.end(), []( const auto& a, const auto& b ) { return a.first > b.first; } );
|
|
|
|
int polygonCount = 0;
|
|
QStringList polygonInfo;
|
|
for ( const auto& polygon : polygons )
|
|
{
|
|
polygonInfo
|
|
<< QString( "Polygon %1: Pixel area %2, vertex count %3 " ).arg( polygonCount++ ).arg( polygon.first ).arg( polygon.second.size() );
|
|
if ( polygonCount > 10 )
|
|
{
|
|
polygonInfo << QString( "More polygons found, but only the first 10 are shown." );
|
|
break;
|
|
}
|
|
}
|
|
|
|
QString txt = polygonInfo.join( "\n" );
|
|
txt += "\n\n Create polygons with pixel area above threshold: ";
|
|
|
|
bool ok;
|
|
int areaThreshold = QInputDialog::getInt( RiuMainWindow::instance(), "Create Polygons", txt, 10, 1, 10000, 1, &ok );
|
|
if ( ok )
|
|
{
|
|
std::vector<std::vector<cvf::Vec3d>> polygonsToCreate;
|
|
for ( const auto& polygon : polygons )
|
|
{
|
|
if ( polygon.first >= areaThreshold )
|
|
{
|
|
polygonsToCreate.push_back( polygon.second );
|
|
}
|
|
}
|
|
|
|
internal::createPolygonObjects( polygonsToCreate );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
std::vector<std::vector<cvf::Vec3d>> polygonsToCreate;
|
|
for ( const auto& polygon : polygons )
|
|
{
|
|
polygonsToCreate.push_back( polygon.second );
|
|
}
|
|
internal::createPolygonObjects( polygonsToCreate );
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
const RigContourMapProjection* RicCreateContourMapPolygonTools::findCurrentContourMapProjection()
|
|
{
|
|
RimContourMapProjection* contourMapProjection = nullptr;
|
|
|
|
auto [existingEclipseContourMap, existingGeoMechContourMap] = RicExportContourMapToTextFeature::findContourMapView();
|
|
if ( existingEclipseContourMap ) contourMapProjection = existingEclipseContourMap->contourMapProjection();
|
|
if ( existingGeoMechContourMap ) contourMapProjection = existingGeoMechContourMap->contourMapProjection();
|
|
|
|
if ( !contourMapProjection ) return nullptr;
|
|
|
|
return contourMapProjection->mapProjection();
|
|
}
|