Files
ResInsight/ApplicationLibCode/ProjectDataModel/ContourMap/RimContourMapPeaksCollection.cpp
T
Magne Sjaastad 61db518efc Address review feedback: add range validation and input-size assertion
- RimContourMapPeaksCollection: clamp m_peakCount to [1,20] and m_minDistance to >= 0.0

- RigContourMapPeakFinder: assert z.size() matches nx*ny before indexing in computeProminenceAndRegionSize
2026-09-30 16:26:04 +02:00

266 lines
11 KiB
C++

/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026- 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 "RimContourMapPeaksCollection.h"
#include "RimContourMapPeak.h"
#include "ContourMap/RigContourMapPeakFinder.h"
#include "ContourMap/RigContourMapProjection.h"
#include "ContourMap/RimContourMapProjection.h"
#include "Polygons/RimPolygon.h"
#include "Polygons/RimPolygonCollection.h"
#include "RimTools.h"
#include "Riu3DMainWindowTools.h"
#include "cafPdmUiOrdering.h"
#include <algorithm>
CAF_PDM_SOURCE_INIT( RimContourMapPeaksCollection, "RimContourMapPeaksCollection" );
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimContourMapPeaksCollection::RimContourMapPeaksCollection()
{
CAF_PDM_InitObject( "Contour Map Peaks", ":/WellTargetPoint16x16.png" );
CAF_PDM_InitField( &m_peakCount, "PeakCount", 10, "Maximum Number of Peaks" );
m_peakCount.setRange( 1, 20 );
CAF_PDM_InitField( &m_minDistance, "MinDistance", 0.0, "Minimum Distance" );
m_minDistance.setMinValue( 0.0 );
CAF_PDM_InitField( &m_minProminence, "MinProminence", 0.0, "Minimum Prominence" );
m_minProminence.uiCapability()->setUiToolTip( "Ignore peaks rising less than this value above the highest saddle to a higher peak" );
CAF_PDM_InitFieldNoDefault( &m_resultSignature, "ResultSignature", "Result Signature" );
m_resultSignature.uiCapability()->setUiHidden( true );
CAF_PDM_InitFieldNoDefault( &m_peaks, "Peaks", "Peaks" );
nameField()->uiCapability()->setUiReadOnly( true );
setName( "Contour Map Peaks" );
}
//--------------------------------------------------------------------------------------------------
/// Creates both the RimContourMapPeak metadata object and its single-point marker polygon (used to
/// visualize the peak's position as a sphere, reusing the existing polygon rendering/mirroring machinery).
//--------------------------------------------------------------------------------------------------
RimContourMapPeak*
RimContourMapPeaksCollection::addPeak( int rank, double value, double prominence, const cvf::Vec3d& domainPosition, double sphereRadiusFactor )
{
auto polygonCollection = RimTools::polygonCollection();
if ( !polygonCollection ) return nullptr;
auto* peak = new RimContourMapPeak;
peak->setValues( rank, value, prominence, domainPosition );
auto* markerPolygon = polygonCollection->appendUserDefinedPolygon();
markerPolygon->setPointsInDomainCoords( { domainPosition } );
markerPolygon->setIsClosed( false );
markerPolygon->setName( peak->name() );
markerPolygon->setReadOnly( true );
markerPolygon->setShowLines( false );
markerPolygon->setShowSpheres( true );
markerPolygon->setSphereRadiusFactor( sphereRadiusFactor );
markerPolygon->setSphereColor( cvf::Color3f::DEEP_PINK );
markerPolygon->coordinatesChanged.send();
peak->setMarkerPolygon( markerPolygon );
m_peaks.push_back( peak );
return peak;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimContourMapPeaksCollection::clearPeaks()
{
m_peaks.deleteChildren();
m_resultSignature = "";
updateOutdatedState();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimContourMapPeaksCollection::updateVisualization()
{
if ( auto* polygonCollection = RimTools::polygonCollection() )
{
polygonCollection->updateViewsAfterPolygonsChanged();
}
updateConnectedEditors();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<RimContourMapPeak*> RimContourMapPeaksCollection::peaks() const
{
return m_peaks.childrenByType();
}
//--------------------------------------------------------------------------------------------------
/// Deletes the existing peaks and recomputes new ones for the owning contour map projection, using
/// the current peakCount/minDistance field values.
//--------------------------------------------------------------------------------------------------
void RimContourMapPeaksCollection::computePeaks()
{
auto* contourMapProjection = firstAncestorOrThisOfType<RimContourMapProjection>();
if ( !contourMapProjection ) return;
auto rigContourMapProjection = contourMapProjection->mapProjection();
if ( !rigContourMapProjection ) return;
RigContourMapPeakFinder::Settings settings;
settings.maxPeaks = std::max( 1, m_peakCount() );
settings.minDistance = std::max( 0.0, m_minDistance() );
settings.minProminence = std::max( 0.0, m_minProminence() );
settings.excludeEdgePeaks = true;
auto peaks = rigContourMapProjection->findPeaks( settings );
// Rank the computed peaks by value (highest first), independent of the prominence-based criterion
// used to select which peaks to keep.
std::sort( peaks.begin(), peaks.end(), []( const auto& a, const auto& b ) { return a.z > b.z; } );
clearPeaks();
auto origin3d = rigContourMapProjection->origin3d();
auto depth = rigContourMapProjection->topDepthBoundingBox();
// Sphere radius factor is multiplied by the view's characteristic cell size when rendered.
const double sphereRadiusFactor = 0.3;
for ( size_t i = 0; i < peaks.size(); ++i )
{
const auto& peak = peaks[i];
cvf::Vec3d domainPoint( origin3d.x() + peak.x, origin3d.y() + peak.y, depth );
addPeak( static_cast<int>( i + 1 ), peak.z, peak.prominence, domainPoint, sphereRadiusFactor );
}
m_resultSignature = currentResultSignature();
updateOutdatedState();
updateVisualization();
Riu3DMainWindowTools::selectAsCurrentItem( this );
Riu3DMainWindowTools::setExpanded( this );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimContourMapPeaksCollection::updateOutdatedState()
{
QString newName = "Contour Map Peaks";
if ( !m_peaks.empty() && m_resultSignature() != currentResultSignature() ) newName += " (Outdated)";
if ( newName != name() )
{
setName( newName );
updateConnectedEditors();
}
}
//--------------------------------------------------------------------------------------------------
/// Identifies the result the peaks are computed from: result, time step and value filter.
//--------------------------------------------------------------------------------------------------
QString RimContourMapPeaksCollection::currentResultSignature() const
{
auto* contourMapProjection = firstAncestorOrThisOfType<RimContourMapProjection>();
if ( !contourMapProjection ) return {};
QString signature = contourMapProjection->resultDescriptionText() + "|" + contourMapProjection->currentTimeStepName();
if ( auto* rigContourMapProjection = contourMapProjection->mapProjection() )
{
if ( auto valueFilter = rigContourMapProjection->valueFilter() )
{
signature += QString( "|%1|%2" ).arg( valueFilter->first ).arg( valueFilter->second );
}
}
return signature;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimContourMapPeaksCollection::initAfterRead()
{
removePeaksWithMarkersSharedByOtherPeaks();
}
//--------------------------------------------------------------------------------------------------
/// When a contour map view is duplicated, the copied peaks reference the marker polygons of the
/// original peaks. Such peaks are removed from the copy without deleting the markers, so that one view
/// never deletes the markers of another view. The peaks can be recomputed in the copied view.
//--------------------------------------------------------------------------------------------------
void RimContourMapPeaksCollection::removePeaksWithMarkersSharedByOtherPeaks()
{
for ( auto* peak : peaks() )
{
auto* marker = peak->markerPolygon();
if ( !marker ) continue;
const auto referringObjects = marker->objectsWithReferringPtrFields();
const bool isShared = std::any_of( referringObjects.begin(),
referringObjects.end(),
[peak]( caf::PdmObjectHandle* obj )
{ return obj != peak && dynamic_cast<RimContourMapPeak*>( obj ) != nullptr; } );
if ( !isShared ) continue;
peak->releaseMarkerPolygon();
m_peaks.removeChild( peak );
delete peak;
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimContourMapPeaksCollection::fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue )
{
if ( changedField == &m_peakCount && m_peakCount() < 1 ) m_peakCount = 1;
if ( changedField == &m_minDistance && m_minDistance() < 0.0 ) m_minDistance = 0.0;
if ( changedField == &m_minProminence && m_minProminence() < 0.0 ) m_minProminence = 0.0;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimContourMapPeaksCollection::defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering )
{
uiOrdering.add( &m_peakCount );
uiOrdering.add( &m_minDistance );
uiOrdering.add( &m_minProminence );
uiOrdering.addNewButton( "Compute", [this]() { computePeaks(); } );
uiOrdering.skipRemainingFields();
}