#14878: Add well-pick zone filtering to RFT cross plot and tornado plot

Let the RFT parameter cross plot reference a RimWellFormationsFile and use its
well-pick formations to filter/group data by zone, pushed down to the tornado
plot and RFT correlation sub plots.
This commit is contained in:
Magne Sjaastad committed 2026-10-08 12:44:14 +02:00
1 parent 45814555b5
commit 6c42ba5ae0
9 files changed
+535 -101

No files matched your search

@@ -26,8 +26,12 @@
#include "RigEnsembleParameter.h"
#include "RigStatisticsTools.h"
#include "Formations/RimWellFormationsCollection.h"
#include "Formations/RimWellFormationsFile.h"
#include "RimEclipseCase.h"
#include "RimEclipseResultCase.h"
#include "RimOilField.h"
#include "RimProject.h"
#include "RimRftCrossPlotTools.h"
#include "RimSummaryCase.h"
@@ -44,6 +48,7 @@
#include "cafPdmPointer.h"
#include "cafPdmUiComboBoxEditor.h"
#include "cafPdmUiTreeSelectionEditor.h"
#include "qwt_picker_machine.h"
#include "qwt_plot.h"
@@ -86,9 +91,15 @@ RimParameterRftCrossPlot::RimParameterRftCrossPlot()
CAF_PDM_InitFieldNoDefault( &m_selectedTimeStep, "TimeStep", "Time Step" );
m_selectedTimeStep.uiCapability()->setUiEditorTypeName( caf::PdmUiComboBoxEditor::uiEditorTypeName() );
CAF_PDM_InitFieldNoDefault( &m_eclipseCase, "EclipseCase", "Eclipse Case (MD fallback)" );
CAF_PDM_InitField( &m_useDepthRange, "UseDepthRange", false, "Filter by Depth Range" );
CAF_PDM_InitField( &m_filterMode,
"FilterMode",
RimRftCrossPlotTools::DepthFilterModeEnum( RimRftCrossPlotTools::DepthFilterMode::NONE ),
"Depth Filter" );
CAF_PDM_InitField( &m_depthRangeMin, "DepthRangeMin", 0.0, "Min Depth" );
CAF_PDM_InitField( &m_depthRangeMax, "DepthRangeMax", 5000.0, "Max Depth" );
CAF_PDM_InitFieldNoDefault( &m_wellFormations, "WellFormations", "Well Formations File" );
CAF_PDM_InitFieldNoDefault( &m_selectedZones, "SelectedZones", "Zones" );
m_selectedZones.uiCapability()->setUiEditorTypeName( caf::PdmUiTreeSelectionEditor::uiEditorTypeName() );
CAF_PDM_InitField( &m_depthType, "DepthType", caf::AppEnum<RiaDefines::DepthType>( RiaDefines::DepthType::MEASURED_DEPTH ), "Depth Type" );
m_depthType.uiCapability()->setUiHidden( true ); // driven by the parent RimRftCorrelationReportPlot
CAF_PDM_InitField( &m_samplingMode, "SamplingMode", SamplingModeEnum( SamplingMode::MEAN_PER_REALIZATION ), "Sampling" );
@@ -164,6 +175,14 @@ void RimParameterRftCrossPlot::setEnsembleParameter( const QString& paramName )
m_ensembleParameter = paramName;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimParameterRftCrossPlot::setWellFormations( RimWellFormationsFile* wellFormationsFile )
{
m_wellFormations = wellFormationsFile;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
@@ -209,7 +228,7 @@ RimEclipseResultCase* RimParameterRftCrossPlot::eclipseCase() const
//--------------------------------------------------------------------------------------------------
bool RimParameterRftCrossPlot::useDepthRange() const
{
return m_useDepthRange();
return m_filterMode() != RimRftCrossPlotTools::DepthFilterMode::NONE;
}
//--------------------------------------------------------------------------------------------------
@@ -236,6 +255,30 @@ RiaDefines::DepthType RimParameterRftCrossPlot::depthType() const
return m_depthType();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimRftCrossPlotTools::DepthFilterMode RimParameterRftCrossPlot::filterMode() const
{
return m_filterMode();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<QString> RimParameterRftCrossPlot::selectedZones() const
{
return m_selectedZones();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimWellFormationsFile* RimParameterRftCrossPlot::wellFormationsFile() const
{
return m_wellFormations();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
@@ -244,6 +287,21 @@ RiuQwtPlotWidget* RimParameterRftCrossPlot::viewer()
return m_plotWidget;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<RimRftCrossPlotTools::DepthInterval> RimParameterRftCrossPlot::depthIntervals() const
{
return RimRftCrossPlotTools::buildDepthIntervals( useDepthRange(),
m_filterMode(),
m_depthRangeMin(),
m_depthRangeMax(),
m_wellFormations(),
m_wellName(),
m_selectedZones(),
m_depthType() );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
@@ -268,9 +326,7 @@ std::vector<RimParameterRftCrossPlot::CaseData> RimParameterRftCrossPlot::create
m_wellName(),
m_selectedTimeStep(),
m_eclipseCase(),
m_useDepthRange(),
m_depthRangeMin(),
m_depthRangeMax(),
depthIntervals(),
m_depthType() );
if ( samplesPerCase.size() != allCases.size() ) return {};
@@ -292,9 +348,7 @@ std::vector<RimParameterRftCrossPlot::CaseData> RimParameterRftCrossPlot::create
m_wellName(),
m_selectedTimeStep(),
m_eclipseCase(),
m_useDepthRange(),
m_depthRangeMin(),
m_depthRangeMax(),
depthIntervals(),
m_depthType() );
if ( pressurePerCase.size() != allCases.size() ) return {};
@@ -333,13 +387,15 @@ void RimParameterRftCrossPlot::updateAxes()
const int axisTitleSize = caf::FontTools::absolutePointSize( RiaPreferences::current()->defaultPlotFontSize(), m_axisTitleFontSize() );
const int axisValueSize = caf::FontTools::absolutePointSize( RiaPreferences::current()->defaultPlotFontSize(), m_axisValueFontSize() );
const QString pressureLabel = m_samplingMode() == SamplingMode::ALL_SAMPLES ? "Pressure" : "Mean Pressure";
const QString depthLabel = m_useDepthRange() ? QString( "%1 [%2 %3 - %4]" )
.arg( pressureLabel )
.arg( RimRftCrossPlotTools::depthTypeAbbreviation( m_depthType() ) )
.arg( m_depthRangeMin() )
.arg( m_depthRangeMax() )
: pressureLabel;
const QString pressureLabel = m_samplingMode() == SamplingMode::ALL_SAMPLES ? "Pressure" : "Mean Pressure";
const QString filterDescription = RimRftCrossPlotTools::depthFilterDescription( useDepthRange(),
m_filterMode(),
m_depthType(),
m_depthRangeMin(),
m_depthRangeMax(),
m_selectedZones() );
const QString depthLabel = filterDescription.isEmpty() ? pressureLabel
: QString( "%1 [%2]" ).arg( pressureLabel ).arg( filterDescription );
m_plotWidget->setAxisTitleText( RiuPlotAxis::defaultLeft(), depthLabel );
m_plotWidget->setAxisTitleEnabled( RiuPlotAxis::defaultLeft(), true );
@@ -518,11 +574,18 @@ void RimParameterRftCrossPlot::defineUiOrdering( QString uiConfigName, caf::PdmU
auto* depthGroup =
uiOrdering.addNewGroup( QString( "Depth Range (%1)" ).arg( RimRftCrossPlotTools::depthTypeAbbreviation( m_depthType() ) ) );
depthGroup->add( &m_useDepthRange );
depthGroup->add( &m_filterMode );
depthGroup->add( &m_depthRangeMin );
depthGroup->add( &m_depthRangeMax );
m_depthRangeMin.uiCapability()->setUiReadOnly( !m_useDepthRange() );
m_depthRangeMax.uiCapability()->setUiReadOnly( !m_useDepthRange() );
depthGroup->add( &m_wellFormations );
depthGroup->add( &m_selectedZones );
const bool useRange = m_filterMode() == RimRftCrossPlotTools::DepthFilterMode::DEPTH_RANGE;
const bool useZones = m_filterMode() == RimRftCrossPlotTools::DepthFilterMode::ZONES;
m_depthRangeMin.uiCapability()->setUiHidden( !useRange );
m_depthRangeMax.uiCapability()->setUiHidden( !useRange );
m_wellFormations.uiCapability()->setUiHidden( !useZones );
m_selectedZones.uiCapability()->setUiHidden( !useZones || !m_wellFormations() );
auto* crossPlotGroup = uiOrdering.addNewGroup( "Cross Plot Parameter" );
crossPlotGroup->add( &m_ensembleParameter );
@@ -564,6 +627,13 @@ void RimParameterRftCrossPlot::fieldChangedByUi( const caf::PdmFieldHandle* chan
m_selectedTimeStep = timeSteps.empty() ? QDateTime() : *timeSteps.begin();
}
if ( changedField == &m_wellName || changedField == &m_wellFormations )
{
// The selected zones belong to the previous well/formations file; clear them so stale zone
// names are not silently applied as a filter.
m_selectedZones = std::vector<QString>();
}
RimPlot::fieldChangedByUi( changedField, oldValue, newValue );
loadDataAndUpdate();
}
@@ -626,6 +696,32 @@ QList<caf::PdmOptionItemInfo> RimParameterRftCrossPlot::calculateValueOptions( c
options.push_back( caf::PdmOptionItemInfo( rc->caseUserDescription(), rc ) );
}
}
else if ( fieldNeedingOptions == &m_wellFormations )
{
options.push_back( caf::PdmOptionItemInfo( "None", static_cast<RimWellFormationsFile*>( nullptr ) ) );
auto* project = RimProject::current();
if ( project && project->activeOilField() && project->activeOilField()->wellFormationsCollection() )
{
for ( RimWellFormationsFile* file : project->activeOilField()->wellFormationsCollection()->wellFormationsFiles() )
options.push_back( caf::PdmOptionItemInfo( file->shortName(), file ) );
}
}
else if ( fieldNeedingOptions == &m_filterMode )
{
using DepthFilterMode = RimRftCrossPlotTools::DepthFilterMode;
for ( auto mode : { DepthFilterMode::NONE, DepthFilterMode::DEPTH_RANGE, DepthFilterMode::ZONES } )
{
options.push_back( caf::PdmOptionItemInfo( RimRftCrossPlotTools::DepthFilterModeEnum::uiText( mode ), mode ) );
}
}
else if ( fieldNeedingOptions == &m_selectedZones )
{
if ( m_wellFormations() )
{
for ( const QString& zoneName : m_wellFormations->zoneNames( m_wellName() ) )
options.push_back( caf::PdmOptionItemInfo( zoneName, zoneName ) );
}
}
else if ( fieldNeedingOptions == &m_ensembleParameter )
{
if ( m_ensemble() )
@@ -641,9 +737,7 @@ QList<caf::PdmOptionItemInfo> RimParameterRftCrossPlot::calculateValueOptions( c
m_wellName(),
m_selectedTimeStep(),
m_eclipseCase(),
m_useDepthRange(),
m_depthRangeMin(),
m_depthRangeMax(),
depthIntervals(),
m_depthType() );
}
@@ -750,14 +844,16 @@ void RimParameterRftCrossPlot::updatePlotTitle()
if ( m_useAutoPlotTitle && m_ensemble() )
{
if ( m_useDepthRange() )
const QString filterDescription = RimRftCrossPlotTools::depthFilterDescription( useDepthRange(),
m_filterMode(),
m_depthType(),
m_depthRangeMin(),
m_depthRangeMax(),
m_selectedZones() );
if ( !filterDescription.isEmpty() )
{
m_description = QString( "%1 vs RFT Pressure [%2 %3 - %4 m], %5" )
.arg( m_ensembleParameter() )
.arg( RimRftCrossPlotTools::depthTypeAbbreviation( m_depthType() ) )
.arg( m_depthRangeMin() )
.arg( m_depthRangeMax() )
.arg( m_ensemble->name() );
m_description =
QString( "%1 vs RFT Pressure [%2], %3" ).arg( m_ensembleParameter() ).arg( filterDescription ).arg( m_ensemble->name() );
}
else
{
@@ -20,6 +20,7 @@
#include "Appearance/RimFontSizeField.h"
#include "RiaDefines.h"
#include "RimPlot.h"
#include "RimRftCrossPlotTools.h"
#include "cafAppEnum.h"
#include "cafPdmField.h"
@@ -35,6 +36,7 @@
class RimEclipseResultCase;
class RimSummaryEnsemble;
class RimSummaryCase;
class RimWellFormationsFile;
class RiuQwtPlotWidget;
//==================================================================================================
@@ -74,16 +76,20 @@ public:
void setDepthRange( double minMd, double maxMd );
void setDepthType( RiaDefines::DepthType depthType );
void setEnsembleParameter( const QString& paramName );
void setWellFormations( RimWellFormationsFile* wellFormationsFile );
QString ensembleParameter() const;
QString wellName() const;
QDateTime selectedTimeStep() const;
RimSummaryEnsemble* ensemble() const;
RimEclipseResultCase* eclipseCase() const;
bool useDepthRange() const;
double depthRangeMin() const;
double depthRangeMax() const;
RiaDefines::DepthType depthType() const;
QString ensembleParameter() const;
QString wellName() const;
QDateTime selectedTimeStep() const;
RimSummaryEnsemble* ensemble() const;
RimEclipseResultCase* eclipseCase() const;
bool useDepthRange() const;
double depthRangeMin() const;
double depthRangeMax() const;
RiaDefines::DepthType depthType() const;
RimRftCrossPlotTools::DepthFilterMode filterMode() const;
std::vector<QString> selectedZones() const;
RimWellFormationsFile* wellFormationsFile() const;
RiuQwtPlotWidget* viewer();
@@ -120,17 +126,21 @@ private:
void cleanupBeforeClose();
RimSummaryCase* findClosestCase( const QPoint& canvasPos );
std::vector<RimRftCrossPlotTools::DepthInterval> depthIntervals() const;
private:
caf::PdmPtrField<RimSummaryEnsemble*> m_ensemble;
caf::PdmField<QString> m_wellName;
caf::PdmField<QDateTime> m_selectedTimeStep;
caf::PdmPtrField<RimEclipseResultCase*> m_eclipseCase;
caf::PdmField<bool> m_useDepthRange;
caf::PdmField<double> m_depthRangeMin;
caf::PdmField<double> m_depthRangeMax;
caf::PdmField<caf::AppEnum<RiaDefines::DepthType>> m_depthType;
caf::PdmField<SamplingModeEnum> m_samplingMode;
caf::PdmField<QString> m_ensembleParameter;
caf::PdmPtrField<RimSummaryEnsemble*> m_ensemble;
caf::PdmField<QString> m_wellName;
caf::PdmField<QDateTime> m_selectedTimeStep;
caf::PdmPtrField<RimEclipseResultCase*> m_eclipseCase;
caf::PdmField<RimRftCrossPlotTools::DepthFilterModeEnum> m_filterMode;
caf::PdmField<double> m_depthRangeMin;
caf::PdmField<double> m_depthRangeMax;
caf::PdmPtrField<RimWellFormationsFile*> m_wellFormations;
caf::PdmField<std::vector<QString>> m_selectedZones;
caf::PdmField<caf::AppEnum<RiaDefines::DepthType>> m_depthType;
caf::PdmField<SamplingModeEnum> m_samplingMode;
caf::PdmField<QString> m_ensembleParameter;
caf::PdmField<bool> m_useAutoPlotTitle;
caf::PdmField<QString> m_description;
@@ -23,6 +23,8 @@
#include "RimWellLogTrack.h"
#include "RimWellRftPlot.h"
#include "Formations/RimWellFormationsFile.h"
#include "RiuInterfaceToViewWindow.h"
#include "RiuPlotWidget.h"
#include "RiuQwtPlotWidget.h"
@@ -528,6 +530,8 @@ void RimRftCorrelationReportPlot::syncTornadoInputsFromCrossPlot()
{
if ( !m_tornadoPlot() || !m_parameterRftCrossPlot() ) return;
applyWellFormationsToSubPlots();
m_tornadoPlot->setEnsemble( m_parameterRftCrossPlot->ensemble() );
m_tornadoPlot->setWellName( m_parameterRftCrossPlot->wellName() );
m_tornadoPlot->setTimeStep( m_parameterRftCrossPlot->selectedTimeStep() );
@@ -536,6 +540,22 @@ void RimRftCorrelationReportPlot::syncTornadoInputsFromCrossPlot()
m_tornadoPlot->setUseDepthRange( m_parameterRftCrossPlot->useDepthRange() );
m_tornadoPlot->setDepthRange( m_parameterRftCrossPlot->depthRangeMin(), m_parameterRftCrossPlot->depthRangeMax() );
m_tornadoPlot->setDepthType( m_parameterRftCrossPlot->depthType() );
m_tornadoPlot->setFilterMode( m_parameterRftCrossPlot->filterMode() );
m_tornadoPlot->setSelectedZones( m_parameterRftCrossPlot->selectedZones() );
}
//--------------------------------------------------------------------------------------------------
/// Pushes the user's manual "Well Formations File" selection in the cross plot down to the sub
/// plots so their "Filter By: Zones" option becomes available.
//--------------------------------------------------------------------------------------------------
void RimRftCorrelationReportPlot::applyWellFormationsToSubPlots()
{
if ( !m_parameterRftCrossPlot() ) return;
RimWellFormationsFile* wellFormationsFile = m_parameterRftCrossPlot->wellFormationsFile();
m_parameterRftCrossPlot->setWellFormations( wellFormationsFile );
if ( m_tornadoPlot() ) m_tornadoPlot->setWellFormations( wellFormationsFile );
}
//--------------------------------------------------------------------------------------------------
@@ -90,6 +90,7 @@ private:
void onTornadoParameterSelected( const QString& paramName );
void syncTornadoInputsFromCrossPlot();
void applyDepthTypeToSubPlots();
void applyWellFormationsToSubPlots();
private:
caf::PdmProxyValueField<QString> m_name;
@@ -23,6 +23,9 @@
#include "RiaExtractionTools.h"
#include "Well/RigEclipseWellLogExtractor.h"
#include "Well/RigWellPathFormations.h"
#include "Formations/RimWellFormationsFile.h"
#include "RimEclipseResultCase.h"
#include "RimProject.h"
@@ -30,9 +33,22 @@
#include "RimSummaryEnsemble.h"
#include "RimWellPath.h"
#include <algorithm>
#include <limits>
#include <numeric>
namespace caf
{
template <>
void caf::AppEnum<RimRftCrossPlotTools::DepthFilterMode>::setUp()
{
addItem( RimRftCrossPlotTools::DepthFilterMode::NONE, "NONE", "None" );
addItem( RimRftCrossPlotTools::DepthFilterMode::DEPTH_RANGE, "DEPTH_RANGE", "Depth Range" );
addItem( RimRftCrossPlotTools::DepthFilterMode::ZONES, "ZONES", "Formation" );
setDefault( RimRftCrossPlotTools::DepthFilterMode::NONE );
}
} // namespace caf
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
@@ -76,7 +92,59 @@ std::vector<double> RimRftCrossPlotTools::filterPressuresByDepthRange( const std
double depthRangeMin,
double depthRangeMax )
{
if ( !useDepthRange ) return pressures;
std::vector<DepthInterval> intervals;
if ( useDepthRange ) intervals.push_back( { depthRangeMin, depthRangeMax } );
return filterPressuresByDepthIntervals( depths, pressures, intervals );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<RimRftCrossPlotTools::DepthInterval> RimRftCrossPlotTools::buildDepthIntervals( bool useFilter,
DepthFilterMode mode,
double depthRangeMin,
double depthRangeMax,
RimWellFormationsFile* wellFormationsFile,
const QString& wellName,
const std::vector<QString>& selectedZones,
RiaDefines::DepthType depthType )
{
if ( !useFilter || mode == DepthFilterMode::NONE ) return {};
if ( mode == DepthFilterMode::DEPTH_RANGE ) return { DepthInterval{ depthRangeMin, depthRangeMax } };
// ZONES mode
if ( !wellFormationsFile || selectedZones.empty() ) return {};
std::optional<RigWellPathFormations> formations = wellFormationsFile->formationsForWell( wellName );
if ( !formations.has_value() ) return {};
std::vector<DepthInterval> intervals;
for ( size_t i = 0; i < formations->formationCount(); ++i )
{
const RigWellPathFormation& formation = formations->formationAt( i );
bool isSelected = std::find( selectedZones.begin(), selectedZones.end(), formation.formationName ) != selectedZones.end();
if ( !isSelected ) continue;
if ( depthType == RiaDefines::DepthType::TRUE_VERTICAL_DEPTH )
intervals.push_back( { formation.tvdTop, formation.tvdBase } );
else
intervals.push_back( { formation.mdTop, formation.mdBase } );
}
return intervals;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<double> RimRftCrossPlotTools::filterPressuresByDepthIntervals( const std::vector<double>& depths,
const std::vector<double>& pressures,
const std::vector<DepthInterval>& depthIntervals )
{
if ( depthIntervals.empty() ) return pressures;
// Depth filter requested but no aligned depth data is available; exclude rather than
// silently return an unfiltered set of samples.
@@ -84,11 +152,45 @@ std::vector<double> RimRftCrossPlotTools::filterPressuresByDepthRange( const std
std::vector<double> samplesInRange;
for ( size_t i = 0; i < depths.size(); ++i )
if ( depths[i] >= depthRangeMin && depths[i] <= depthRangeMax ) samplesInRange.push_back( pressures[i] );
{
for ( const DepthInterval& interval : depthIntervals )
{
if ( depths[i] >= interval.top && depths[i] <= interval.base )
{
samplesInRange.push_back( pressures[i] );
break;
}
}
}
return samplesInRange;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RimRftCrossPlotTools::depthFilterDescription( bool useFilter,
DepthFilterMode mode,
RiaDefines::DepthType depthType,
double depthRangeMin,
double depthRangeMax,
const std::vector<QString>& selectedZones )
{
if ( !useFilter || mode == DepthFilterMode::NONE ) return {};
if ( mode == DepthFilterMode::ZONES )
{
if ( selectedZones.empty() ) return {};
QStringList zoneList;
for ( const QString& zone : selectedZones )
zoneList << zone;
return QString( "Zones: %1" ).arg( zoneList.join( ", " ) );
}
return QString( "%1 %2 - %3 m" ).arg( depthTypeAbbreviation( depthType ) ).arg( depthRangeMin ).arg( depthRangeMax );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
@@ -102,14 +204,12 @@ double RimRftCrossPlotTools::computeMean( const std::vector<double>& samples )
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<std::vector<double>> RimRftCrossPlotTools::computePressureSamplesPerCase( RimSummaryEnsemble* ensemble,
const QString& wellName,
const QDateTime& timeStep,
RimEclipseResultCase* eclipseCase,
bool useDepthRange,
double depthRangeMin,
double depthRangeMax,
RiaDefines::DepthType depthType )
std::vector<std::vector<double>> RimRftCrossPlotTools::computePressureSamplesPerCase( RimSummaryEnsemble* ensemble,
const QString& wellName,
const QDateTime& timeStep,
RimEclipseResultCase* eclipseCase,
const std::vector<DepthInterval>& depthIntervals,
RiaDefines::DepthType depthType )
{
if ( !ensemble || wellName.isEmpty() || !timeStep.isValid() ) return {};
@@ -154,7 +254,7 @@ std::vector<std::vector<double>> RimRftCrossPlotTools::computePressureSamplesPer
// operates on values consistent with what the user sees in the RFT plot.
std::vector<double> depths = rftCurveDepthValues( reader, wellName, timeStep, extractor, depthType );
samplesPerCase.push_back( filterPressuresByDepthRange( depths, pressures, useDepthRange, depthRangeMin, depthRangeMax ) );
samplesPerCase.push_back( filterPressuresByDepthIntervals( depths, pressures, depthIntervals ) );
}
return samplesPerCase;
@@ -163,17 +263,15 @@ std::vector<std::vector<double>> RimRftCrossPlotTools::computePressureSamplesPer
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<double> RimRftCrossPlotTools::computeMeanPressurePerCase( RimSummaryEnsemble* ensemble,
const QString& wellName,
const QDateTime& timeStep,
RimEclipseResultCase* eclipseCase,
bool useDepthRange,
double depthRangeMin,
double depthRangeMax,
RiaDefines::DepthType depthType )
std::vector<double> RimRftCrossPlotTools::computeMeanPressurePerCase( RimSummaryEnsemble* ensemble,
const QString& wellName,
const QDateTime& timeStep,
RimEclipseResultCase* eclipseCase,
const std::vector<DepthInterval>& depthIntervals,
RiaDefines::DepthType depthType )
{
const std::vector<std::vector<double>> samplesPerCase =
computePressureSamplesPerCase( ensemble, wellName, timeStep, eclipseCase, useDepthRange, depthRangeMin, depthRangeMax, depthType );
computePressureSamplesPerCase( ensemble, wellName, timeStep, eclipseCase, depthIntervals, depthType );
std::vector<double> pressurePerCase;
pressurePerCase.reserve( samplesPerCase.size() );
@@ -19,8 +19,11 @@
#include "RiaDefines.h"
#include "cafAppEnum.h"
#include <QDateTime>
#include <QString>
#include <QStringList>
#include <vector>
@@ -28,6 +31,7 @@ class RifReaderRftInterface;
class RigEclipseWellLogExtractor;
class RimEclipseResultCase;
class RimSummaryEnsemble;
class RimWellFormationsFile;
//==================================================================================================
///
@@ -37,6 +41,23 @@ class RimSummaryEnsemble;
//==================================================================================================
namespace RimRftCrossPlotTools
{
// How the active depth filter selects samples: a single depth range, or a set of named zones
// looked up in a well formations file.
enum class DepthFilterMode
{
NONE,
DEPTH_RANGE,
ZONES
};
using DepthFilterModeEnum = caf::AppEnum<DepthFilterMode>;
// A single inclusive depth interval, in the unit/datum of whichever depth type it was built for.
struct DepthInterval
{
double top = 0.0;
double base = 0.0;
};
// Short abbreviation used in axis titles and plot titles for the active depth type.
QString depthTypeAbbreviation( RiaDefines::DepthType depthType );
@@ -61,28 +82,53 @@ std::vector<double> filterPressuresByDepthRange( const std::vector<double>& dept
double depthRangeMin,
double depthRangeMax );
// Builds the active depth filter intervals from either a depth range or a set of selected zones.
// Returns an empty vector (meaning "no filter") when useFilter is false, or when ZONES mode is
// selected but no well formations file, well name, or matching zones are available.
std::vector<DepthInterval> buildDepthIntervals( bool useFilter,
DepthFilterMode mode,
double depthRangeMin,
double depthRangeMax,
RimWellFormationsFile* wellFormationsFile,
const QString& wellName,
const std::vector<QString>& selectedZones,
RiaDefines::DepthType depthType );
// Returns the subset of pressures whose corresponding depth lies within any of depthIntervals
// (inclusive). An empty depthIntervals list means "no filter", returning pressures unchanged; a
// size mismatch between depths/pressures returns an empty vector (no aligned depth data).
std::vector<double> filterPressuresByDepthIntervals( const std::vector<double>& depths,
const std::vector<double>& pressures,
const std::vector<DepthInterval>& depthIntervals );
// Short description of the active depth filter for titles/axis labels/group text, e.g.
// "MD 1000 - 2000 m" or "Zones: Valysar, Therys". Empty when filtering is disabled.
QString depthFilterDescription( bool useFilter,
DepthFilterMode mode,
RiaDefines::DepthType depthType,
double depthRangeMin,
double depthRangeMax,
const std::vector<QString>& selectedZones );
// Arithmetic mean of the given samples, or infinity if samples is empty.
double computeMean( const std::vector<double>& samples );
// Computes the individual RFT pressure samples within the depth range per ensemble case.
// Indices match ensemble->allSummaryCases(). A case with no data gets an empty vector.
std::vector<std::vector<double>> computePressureSamplesPerCase( RimSummaryEnsemble* ensemble,
const QString& wellName,
const QDateTime& timeStep,
RimEclipseResultCase* eclipseCase,
bool useDepthRange,
double depthRangeMin,
double depthRangeMax,
// Computes the individual RFT pressure samples within depthIntervals per ensemble case.
// Indices match ensemble->allSummaryCases(). A case with no data gets an empty vector. An empty
// depthIntervals list means no filtering.
std::vector<std::vector<double>> computePressureSamplesPerCase( RimSummaryEnsemble* ensemble,
const QString& wellName,
const QDateTime& timeStep,
RimEclipseResultCase* eclipseCase,
const std::vector<DepthInterval>& depthIntervals,
RiaDefines::DepthType depthType = RiaDefines::DepthType::MEASURED_DEPTH );
// Computes mean RFT pressure per ensemble case (one entry per case, infinity = no data).
// Indices match ensemble->allSummaryCases().
std::vector<double> computeMeanPressurePerCase( RimSummaryEnsemble* ensemble,
const QString& wellName,
const QDateTime& timeStep,
RimEclipseResultCase* eclipseCase,
bool useDepthRange,
double depthRangeMin,
double depthRangeMax,
RiaDefines::DepthType depthType = RiaDefines::DepthType::MEASURED_DEPTH );
std::vector<double> computeMeanPressurePerCase( RimSummaryEnsemble* ensemble,
const QString& wellName,
const QDateTime& timeStep,
RimEclipseResultCase* eclipseCase,
const std::vector<DepthInterval>& depthIntervals,
RiaDefines::DepthType depthType = RiaDefines::DepthType::MEASURED_DEPTH );
} // namespace RimRftCrossPlotTools
@@ -30,6 +30,8 @@
#include "RimSummaryEnsemble.h"
#include "RimSummaryEnsembleTools.h"
#include "Formations/RimWellFormationsFile.h"
#include "RiuContextMenuLauncher.h"
#include "RiuGroupedBarChartBuilder.h"
#include "RiuPlotItem.h"
@@ -60,8 +62,14 @@ RimRftTornadoPlot::RimRftTornadoPlot()
CAF_PDM_InitFieldNoDefault( &m_selectedTimeStep, "TimeStep", "Time Step" );
CAF_PDM_InitFieldNoDefault( &m_eclipseCase, "EclipseCase", "Eclipse Case (MD fallback)" );
CAF_PDM_InitField( &m_useDepthRange, "UseDepthRange", false, "Filter by Depth Range" );
CAF_PDM_InitField( &m_filterMode,
"FilterMode",
RimRftCrossPlotTools::DepthFilterModeEnum( RimRftCrossPlotTools::DepthFilterMode::DEPTH_RANGE ),
"Filter By" );
CAF_PDM_InitField( &m_depthRangeMin, "DepthRangeMin", 0.0, "Min Depth" );
CAF_PDM_InitField( &m_depthRangeMax, "DepthRangeMax", 5000.0, "Max Depth" );
CAF_PDM_InitFieldNoDefault( &m_wellFormations, "WellFormations", "Well Formations File" );
CAF_PDM_InitFieldNoDefault( &m_selectedZones, "SelectedZones", "Zones" );
CAF_PDM_InitField( &m_depthType, "DepthType", caf::AppEnum<RiaDefines::DepthType>( RiaDefines::DepthType::MEASURED_DEPTH ), "Depth Type" );
m_depthType.uiCapability()->setUiHidden( true ); // driven by the parent RimRftCorrelationReportPlot
@@ -155,6 +163,30 @@ void RimRftTornadoPlot::setDepthType( RiaDefines::DepthType depthType )
m_depthType = depthType;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimRftTornadoPlot::setFilterMode( RimRftCrossPlotTools::DepthFilterMode filterMode )
{
m_filterMode = filterMode;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimRftTornadoPlot::setWellFormations( RimWellFormationsFile* wellFormationsFile )
{
m_wellFormations = wellFormationsFile;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimRftTornadoPlot::setSelectedZones( const std::vector<QString>& zones )
{
m_selectedZones = zones;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
@@ -360,6 +392,21 @@ void RimRftTornadoPlot::onPlotItemSelected( std::shared_ptr<RiuPlotItem> plotIte
if ( !paramName.isEmpty() && m_parameterSelectedCallback ) m_parameterSelectedCallback( paramName );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<RimRftCrossPlotTools::DepthInterval> RimRftTornadoPlot::depthIntervals() const
{
return RimRftCrossPlotTools::buildDepthIntervals( m_useDepthRange(),
m_filterMode(),
m_depthRangeMin(),
m_depthRangeMax(),
m_wellFormations(),
m_wellName(),
m_selectedZones(),
m_depthType() );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
@@ -376,9 +423,7 @@ std::map<QString, double> RimRftTornadoPlot::addDataToChartBuilder( RiuGroupedBa
m_wellName(),
m_selectedTimeStep(),
m_eclipseCase(),
m_useDepthRange(),
m_depthRangeMin(),
m_depthRangeMax(),
depthIntervals(),
m_depthType() );
// For each numeric parameter, compute Pearson correlation against pressurePerCase
@@ -421,12 +466,14 @@ void RimRftTornadoPlot::updatePlotTitle()
if ( m_useAutoPlotTitle() && m_ensemble() )
{
const QString rangeStr = m_useDepthRange() ? QString( " [%1 %2 - %3 m]" )
.arg( RimRftCrossPlotTools::depthTypeAbbreviation( m_depthType() ) )
.arg( m_depthRangeMin() )
.arg( m_depthRangeMax() )
: QString();
m_description = QString( "Parameter Correlation vs RFT Pressure%1, %2" ).arg( rangeStr ).arg( m_ensemble->name() );
const QString filterDescription = RimRftCrossPlotTools::depthFilterDescription( m_useDepthRange(),
m_filterMode(),
m_depthType(),
m_depthRangeMin(),
m_depthRangeMax(),
m_selectedZones() );
const QString rangeStr = filterDescription.isEmpty() ? QString() : QString( " [%1]" ).arg( filterDescription );
m_description = QString( "Parameter Correlation vs RFT Pressure%1, %2" ).arg( rangeStr ).arg( m_ensemble->name() );
}
m_plotWidget->setPlotTitle( m_description() );
@@ -21,6 +21,7 @@
#include "Appearance/RimFontSizeField.h"
#include "RiaDefines.h"
#include "RimRftCrossPlotTools.h"
#include "cafAppEnum.h"
#include "cafPdmField.h"
@@ -35,6 +36,7 @@
class RimEclipseResultCase;
class RimSummaryEnsemble;
class RimWellFormationsFile;
class RiuQwtPlotWidget;
//==================================================================================================
@@ -63,6 +65,9 @@ public:
void setUseDepthRange( bool useDepthRange );
void setDepthRange( double minMd, double maxMd );
void setDepthType( RiaDefines::DepthType depthType );
void setFilterMode( RimRftCrossPlotTools::DepthFilterMode filterMode );
void setWellFormations( RimWellFormationsFile* wellFormationsFile );
void setSelectedZones( const std::vector<QString>& zones );
void setSelectedParameter( const QString& paramName );
RiuQwtPlotWidget* viewer();
@@ -94,16 +99,21 @@ private:
void updatePlotTitle();
void cleanupBeforeClose();
std::vector<RimRftCrossPlotTools::DepthInterval> depthIntervals() const;
private:
// Data source inputs
caf::PdmPtrField<RimSummaryEnsemble*> m_ensemble;
caf::PdmField<QString> m_wellName;
caf::PdmField<QDateTime> m_selectedTimeStep;
caf::PdmPtrField<RimEclipseResultCase*> m_eclipseCase;
caf::PdmField<bool> m_useDepthRange;
caf::PdmField<double> m_depthRangeMin;
caf::PdmField<double> m_depthRangeMax;
caf::PdmField<caf::AppEnum<RiaDefines::DepthType>> m_depthType;
caf::PdmPtrField<RimSummaryEnsemble*> m_ensemble;
caf::PdmField<QString> m_wellName;
caf::PdmField<QDateTime> m_selectedTimeStep;
caf::PdmPtrField<RimEclipseResultCase*> m_eclipseCase;
caf::PdmField<bool> m_useDepthRange;
caf::PdmField<RimRftCrossPlotTools::DepthFilterModeEnum> m_filterMode;
caf::PdmField<double> m_depthRangeMin;
caf::PdmField<double> m_depthRangeMax;
caf::PdmPtrField<RimWellFormationsFile*> m_wellFormations;
caf::PdmField<std::vector<QString>> m_selectedZones;
caf::PdmField<caf::AppEnum<RiaDefines::DepthType>> m_depthType;
// Tornado settings
caf::PdmField<bool> m_showAbsoluteValues;
@@ -103,3 +103,109 @@ TEST( RimRftCrossPlotToolsTest, DepthTypeAbbreviation )
EXPECT_EQ( QString( "MD" ), RimRftCrossPlotTools::depthTypeAbbreviation( RiaDefines::DepthType::MEASURED_DEPTH ) );
EXPECT_EQ( QString( "TVD" ), RimRftCrossPlotTools::depthTypeAbbreviation( RiaDefines::DepthType::TRUE_VERTICAL_DEPTH ) );
}
//--------------------------------------------------------------------------------------------------
/// Disabled filtering returns no intervals regardless of mode.
//--------------------------------------------------------------------------------------------------
TEST( RimRftCrossPlotToolsTest, BuildDepthIntervals_DisabledReturnsEmpty )
{
auto intervals = RimRftCrossPlotTools::buildDepthIntervals( false,
RimRftCrossPlotTools::DepthFilterMode::DEPTH_RANGE,
1000.0,
2000.0,
nullptr,
QString(),
{},
RiaDefines::DepthType::MEASURED_DEPTH );
EXPECT_TRUE( intervals.empty() );
}
//--------------------------------------------------------------------------------------------------
/// Depth range mode produces a single interval matching the requested range.
//--------------------------------------------------------------------------------------------------
TEST( RimRftCrossPlotToolsTest, BuildDepthIntervals_DepthRangeProducesSingleInterval )
{
auto intervals = RimRftCrossPlotTools::buildDepthIntervals( true,
RimRftCrossPlotTools::DepthFilterMode::DEPTH_RANGE,
1000.0,
2000.0,
nullptr,
QString(),
{},
RiaDefines::DepthType::MEASURED_DEPTH );
ASSERT_EQ( size_t( 1 ), intervals.size() );
EXPECT_DOUBLE_EQ( 1000.0, intervals[0].top );
EXPECT_DOUBLE_EQ( 2000.0, intervals[0].base );
}
//--------------------------------------------------------------------------------------------------
/// Zones mode with no well formations file available produces no intervals.
//--------------------------------------------------------------------------------------------------
TEST( RimRftCrossPlotToolsTest, BuildDepthIntervals_ZonesWithoutFileReturnsEmpty )
{
auto intervals = RimRftCrossPlotTools::buildDepthIntervals( true,
RimRftCrossPlotTools::DepthFilterMode::ZONES,
1000.0,
2000.0,
nullptr,
"WELL-A",
{ "ZoneA" },
RiaDefines::DepthType::MEASURED_DEPTH );
EXPECT_TRUE( intervals.empty() );
}
//--------------------------------------------------------------------------------------------------
/// An empty interval list means no filtering, so all pressures pass through.
//--------------------------------------------------------------------------------------------------
TEST( RimRftCrossPlotToolsTest, FilterPressuresByDepthIntervals_EmptyIntervalsReturnsAllSamples )
{
std::vector<double> depths{ 1000.0, 2000.0, 3000.0 };
std::vector<double> pressures{ 10.0, 20.0, 30.0 };
auto filtered = RimRftCrossPlotTools::filterPressuresByDepthIntervals( depths, pressures, {} );
EXPECT_EQ( pressures, filtered );
}
//--------------------------------------------------------------------------------------------------
/// Only samples within any of the given (possibly non-contiguous) intervals are returned.
//--------------------------------------------------------------------------------------------------
TEST( RimRftCrossPlotToolsTest, FilterPressuresByDepthIntervals_FiltersByMultipleIntervals )
{
std::vector<double> depths{ 1000.0, 1500.0, 2000.0, 2500.0, 3000.0 };
std::vector<double> pressures{ 10.0, 15.0, 20.0, 25.0, 30.0 };
std::vector<RimRftCrossPlotTools::DepthInterval> intervals{ { 1000.0, 1500.0 }, { 2800.0, 3200.0 } };
auto filtered = RimRftCrossPlotTools::filterPressuresByDepthIntervals( depths, pressures, intervals );
std::vector<double> expected{ 10.0, 15.0, 30.0 };
EXPECT_EQ( expected, filtered );
}
//--------------------------------------------------------------------------------------------------
/// A filter description is empty when filtering is disabled.
//--------------------------------------------------------------------------------------------------
TEST( RimRftCrossPlotToolsTest, DepthFilterDescription_DisabledReturnsEmpty )
{
QString description = RimRftCrossPlotTools::depthFilterDescription( false,
RimRftCrossPlotTools::DepthFilterMode::DEPTH_RANGE,
RiaDefines::DepthType::MEASURED_DEPTH,
1000.0,
2000.0,
{} );
EXPECT_TRUE( description.isEmpty() );
}
//--------------------------------------------------------------------------------------------------
/// Zone mode description lists the selected zone names.
//--------------------------------------------------------------------------------------------------
TEST( RimRftCrossPlotToolsTest, DepthFilterDescription_ZonesListsSelectedZoneNames )
{
QString description = RimRftCrossPlotTools::depthFilterDescription( true,
RimRftCrossPlotTools::DepthFilterMode::ZONES,
RiaDefines::DepthType::MEASURED_DEPTH,
1000.0,
2000.0,
{ "Valysar", "Therys" } );
EXPECT_EQ( QString( "Zones: Valysar, Therys" ), description );
}