Skip to content

Seismic cross plot - #14266

Open
akustn wants to merge 4 commits into
equinor:mainfrom
akustn:seismic-cross-plot
Open

Seismic cross plot#14266
akustn wants to merge 4 commits into
equinor:mainfrom
akustn:seismic-cross-plot

Conversation

@akustn

@akustn akustn commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Issue
Resolves #14200

Approach
Short description of the approach

(Screenshot of new behavior in GUI if applicable)

  • PR title captures the intent of the changes, and is fitting for release notes.
  • Added appropriate release note label
  • Commit history is consistent and clean, in line with the contribution guidelines.
  • Make sure unit tests pass locally after every commit (git rebase -i main --exec 'just rapid-tests')

When applicable

  • When screenshots are changed: Review screenshot-PR in ert-testdata,
    merge screenshot-PR in ert-testdata before merging this PR.
  • When there are user facing changes: Updated documentation
  • New behavior or changes to existing untested code: Ensured that unit tests are added (See Ground Rules).
  • Large PR: Prepare changes in small commits for more convenient review
  • Bug fix: Add regression test for the bug
  • Bug fix: Add backport label to latest release (format: 'backport release-branch-name')

@akustn akustn self-assigned this Aug 24, 2026
@codecov-commenter

codecov-commenter commented Aug 24, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 91.95%. Comparing base (4ff378d) to head (f7d5f5f).
⚠️ Report is 40 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #14266      +/-   ##
==========================================
+ Coverage   91.92%   91.95%   +0.02%     
==========================================
  Files         484      486       +2     
  Lines       33662    33792     +130     
==========================================
+ Hits        30944    31072     +128     
- Misses       2718     2720       +2     
Flag Coverage Δ
cli-tests 36.40% <0.00%> (-0.15%) ⬇️
fuzz 44.29% <22.30%> (-0.09%) ⬇️
gui-tests 58.60% <43.84%> (-0.06%) ⬇️
performance-and-unit-tests 81.03% <100.00%> (+0.06%) ⬆️
test 45.89% <22.30%> (-0.10%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/ert/gui/plotting/ert_plots/__init__.py 100.00% <100.00%> (ø)
src/ert/gui/plotting/ert_plots/cross.py 100.00% <100.00%> (ø)
src/ert/gui/plotting/plot_api.py 80.26% <100.00%> (+0.06%) ⬆️
src/ert/gui/plotting/plot_window.py 79.52% <100.00%> (+0.82%) ⬆️
src/ert/gui/plotting/utils/plot_maps.py 100.00% <100.00%> (ø)
...tting/widgets/plot_realization_selection_widget.py 100.00% <100.00%> (ø)

... and 2 files with indirect coverage changes

@ertomatic

Copy link
Copy Markdown
Collaborator

Screenshots differ from baselines. A baseline update PR has been prepared: equinor/ert-testdata#92

ertomatic pushed a commit to equinor/ert-testdata that referenced this pull request Aug 25, 2026
@codspeed-hq

codspeed-hq Bot commented Aug 25, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 36 untouched benchmarks


Comparing akustn:seismic-cross-plot (f7d5f5f) with main (4ff378d)

Open in CodSpeed

@akustn
akustn force-pushed the seismic-cross-plot branch from 53aee8f to f7d5f5f Compare August 25, 2026 11:51
ertomatic pushed a commit to equinor/ert-testdata that referenced this pull request Aug 25, 2026
@akustn
akustn requested a review from achaikou August 25, 2026 12:41

@achaikou achaikou left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good job with plots!! 🎉 And users seemed happy! 🐱

Unfortunately for you, I am a Very Evil Reviewer ™️ , so comments apply 😈


Btw, regarding the checkboxes:

  • PR name might be "Add seismic cross plot" as PR name is supposed to show up in the release notes (and would be default commit name I think if you squash)
  • Release-note label should be release-notes:unreleased-feature-changes if you think this is more seismic-related. release-notes:new-feature would have fit better if cross plot was enabled for other types, not only seismic. But could be used if you want too (then I think it will go to the top of release notes)
  • Commit history doesn't matter if you plan to squash, otherwise we can take it later!
  • Need to talk to someone about this message "Screenshots differ from the baseline". I think that PR should be checked and merged after yours is merged, but I never done this before so don't really know 😬

"Statistics",
"Std dev",
"Misfits",
"Cross plot",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regarding commit name "fixup! Add seismic cross plot" in which this change is added:

I am not sure if you plan to squash and merge (then it doesn't matter), or rebase and merge (then this fixup change probably should refer to a different commit, as this is a test file).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"fixup! Add seismic cross plot" is a fixup for this specific commit, because locally I ran "just rapid-tests" which passed, but then discovered that by adding the cross plot there was a gui test failing, there for it is a fixup to the first commit.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And yes, I'll squash the fixups into the commits which they are a fixup for.

numeric = pd.to_numeric(pd.Series(values), errors="coerce")
if numeric.notna().all():
return numeric.astype(np.float32).to_numpy()
return pd.Index(values).astype(str).to_numpy()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this function may return numeric key or string key?
What if observations return numeric key and responses return string key?
Can those be matched?
And why do we need both, numbers and strings? What is the danger?

(And generally this function seems purely technical, with no domain logic behind it. It makes me wonder if we really need this function).

responses = ensemble_data.copy()
responses.columns = _to_matchable_key(responses.columns.to_numpy())

common_keys = obs_by_key_index.index.intersection(responses.columns)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Everywhere we match observations and responses (in update's get_responses_and_observations and in EnsembleWidget), we now match with tolerance use_observation_locations_in_respective_responses because there is no guarantee that float values would always be exactly the same.
So do match calculations here require some tolerance-adjustments as well? Wouldn't small mismatch along the line throw all calculations off?

raw_key_index = observation_data.loc["key_index"].to_numpy()
key_index = _to_matchable_key(raw_key_index)
series = pd.Series(obs_values, index=key_index)
return series[~series.index.duplicated(keep="first")].dropna()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When can index be duplicated?
Index by definition is supposed to be unique, so this line is suspicious.

I only see this happening because observation data is stacked together from several ensembles (that can in theory belong to different experiments).
But if in ensemble A for index 100 value is 10 and in ensemble B for index 100 value is 10000, it does not seem sensible that responses from B would be matched with observations from A because we kept first value.

One es_mda ensemble selected:

Image

Two ensembles selected:

Image

es_mda suddenly mutated into values from selected ensemble because only first values for index key were kept.

def _match_obs_to_responses(
ensemble_data: pd.DataFrame, obs_by_key_index: pd.Series
) -> tuple[np.ndarray, np.ndarray]:
responses = ensemble_data.copy()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we would still need to copy this if we manage to do something with the keys, so that there would be no need to exchange columns with _to_matchable_key

(yes, I struggle to understand why we need _to_matchable_key in the first place 😄 )

Comment on lines +1779 to +1792
mock_plot_api_cls = MagicMock(spec=PlotApi)
mock_plot_api = MagicMock(spec=PlotApi)
mock_plot_api_cls.return_value = mock_plot_api

storage_version = "0.0"
mock_plot_api.api_version = storage_version
monkeypatch.setattr(
"ert.gui.plotting.plot_window.get_storage_api_version",
lambda: storage_version,
)
monkeypatch.setattr(
"ert.gui.plotting.plot_window.PlotApi",
mock_plot_api_cls,
)

@achaikou achaikou Aug 28, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those lines appear in each test, right?
Also setting of attributes on mock_plot_api happens everytime too.

Is there an option to extract those to a helper like setup_mock_plot_api(responses_key_def=None, ensembles_return_value=None, ...) or is something preventing it from looking and working nicely?

It just seems like half of the test is just an uninteresting mocking setup 😄



@pytest.mark.slow
def test_that_realization_selector_ignores_hidden_ensembles_when_computing_max_size(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are these hidden ensembles and how can I create one in GUI?
I've never heard of those before! 🐈

Also wonder if this setup deserves it own test or if it can be added to a previous one with hidden ensemble size=20 to assure that no, still just 8 realizations are visible.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I understood things:
Hidden means the ensemble is built from storage, plot_api.py has a line defining an ensembleObject as hidden if the name starts with a . or if the ensemble is undefined (e.g. an experiment/ensemble is created but not yet run).

So I think it makes sense to test that these are not included in the realization selector, but I agree that it might make sense to move it into the previous test.

assert plot_window._realization_group.isVisible() is False


@pytest.mark.slow

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is it slow? Is it about switching between tabs or setting up the environment?

Just wonder if this test would become faster if instead of being 4 tests via parametrization we iterate over all tabs in one test itself with 3 tabs being "not expected to show up" and 1 tab being "expected to show up!!".

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

marked as slow because the tests need to wait until a specific tab is active and therefore working with timeouts (I have seen that with other tests in test_plot_window.py as well, It only enables you to either specifically run slow tests or to skip slow tests, as far as I read up on it.)

But yes this could potentially be moved into one test and done via parameterization

)


@pytest.mark.slow

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if it would help with speeding it up a bit, but maybe this test could be combined with previous one and test that cross plot is enabled only for seismic keys?
Basically create two keys - seismic and summary - in one test and just check that when seismic key is enabled there is one setup, when summary key is enabled, there is a different setup?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah sounds like a good idea!

assert labels == [str(i) for i in range(3)]


def test_that_realization_selection_group_is_hidden_on_plot_window_startup(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this test couldn't easily be a part of
test_that_realization_selection_group_is_visible_only_when_cross_plot_tab_is_active

It if it only visible when cross_plot tab is active, then name applies it should be hidden at startup 😄

But if you prefer to have these separate, no complains from me! 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Create cross-plot for seismic

4 participants