Compare Survey Lines for QC#

This tutorial shows how to compare two EDI survey lines before applying the same processing workflow to both. It is useful when a project has several parallel lines, adjacent profiles, or repeat surveys and you need to decide whether one first-pass QC configuration is defensible.

The example uses the bundled L18PLT and L22PLT lines:

  • data/AMT/WILLY_DATA/L18PLT

  • data/AMT/WILLY_DATA/L22PLT

The goal is not to prove that the two lines are geologically identical. The goal is more practical: check whether the input structure, frequency coverage, and first-pass quality metrics are similar enough to start with the same QC pipeline, while still estimating line-specific corrections later.

What You Will Learn#

After this tutorial you should be able to:

  • load two EDI line folders;

  • build comparable station inventories;

  • compare station count, frequency rows, and optional EDI sections;

  • compare frequency-band overlap;

  • compute QC and confidence summaries by line;

  • decide whether one first-pass config can be reused;

  • adapt the same pattern to your own EDI data.

Input Assumptions#

The examples below assume that the two line folders are available in the repository sample data:

data/
  AMT/
    WILLY_DATA/
      L18PLT/
        18-001A.edi
        ...
      L22PLT/
        22-10U.edi
        ...

For your own project, replace these two paths with your line directories. Keep the line labels short, because they will be used in tables and plot legends.

Load Both Lines#

Start with pycsamt.api.read_edis() for each line:

 1from pathlib import Path
 2
 3from pycsamt.api import read_edis
 4
 5lines = {
 6    "L18PLT": Path("data/AMT/WILLY_DATA/L18PLT"),
 7    "L22PLT": Path("data/AMT/WILLY_DATA/L22PLT"),
 8}
 9
10surveys = {
11    name: read_edis(
12        path,
13        recursive=False,
14        strict=False,
15        on_dup="replace",
16        progress=False,
17    )
18    for name, path in lines.items()
19}
20
21for name, survey in surveys.items():
22    print(name, survey.summary())

The survey object keeps the public loading summary, while survey.collection is the lower-level object used by the QC functions.

Build Comparable Inventory Tables#

The first check is structural: number of stations, number of frequency rows, and whether optional EDI sections such as tipper or spectra are present.

 1import pandas as pd
 2
 3inventories = []
 4for name, survey in surveys.items():
 5    table = survey.summary().to_pandas(copy=True)
 6    table["line"] = name
 7    inventories.append(table)
 8
 9inventory = pd.concat(inventories, ignore_index=True)
10print(inventory[["line", "station", "n_freq", "tipper", "spectra"]].head())

Example output:

  line    station  n_freq  tipper  spectra
L18PLT 23-18-001A      53   False    False
L18PLT 23-18-002U      53   False    False
L18PLT 23-18-003A      53   False    False
L18PLT 23-18-004A      53   False    False
L18PLT 23-18-005U      53   False    False

For the bundled data, both lines have a regular EDI structure and the same median number of frequency rows.

Station and optional-section counts for L18PLT and L22PLT

Compare Frequency Coverage#

Before reusing a frequency-band parameter, compare the effective frequency range. The QC table contains period limits, so frequency limits can be derived from pmax and pmin:

 1from pycsamt.emtools.qc import build_qc_table
 2
 3qc_tables = []
 4for name, survey in surveys.items():
 5    qc = build_qc_table(
 6        survey.collection,
 7        include_skew=True,
 8        recursive=False,
 9        api=True,
10    ).to_pandas(copy=True)
11    qc["line"] = name
12    qc_tables.append(qc)
13
14qc = pd.concat(qc_tables, ignore_index=True)
15frequency_summary = qc.groupby("line").agg(
16    min_freq_hz=("pmax", lambda value: 1.0 / value.max()),
17    max_freq_hz=("pmin", lambda value: 1.0 / value.min()),
18    median_frac_ok=("frac_ok", "median"),
19)
20print(frequency_summary)

The two bundled lines share essentially the same first-pass band:

        min_freq_hz  max_freq_hz  median_frac_ok
line
L18PLT        1.01     1.04e+04           1.000
L22PLT        1.01     1.04e+04           1.000

This supports using the same initial select_band setting for both lines, for example band_hz: [1.0, 10000.0].

Frequency-band overlap between L18PLT and L22PLT

Compare QC and Confidence Metrics#

Frequency coverage is necessary, but not sufficient. Compare station-level quality metrics before deciding that a shared workflow is reasonable:

 1from pycsamt.emtools.qc import station_confidence_table
 2
 3confidence_tables = []
 4for name, survey in surveys.items():
 5    confidence = station_confidence_table(
 6        survey.collection,
 7        method="composite",
 8        relerr_threshold=0.20,
 9        offdiag_tolerance_log10=0.35,
10        diagonal_leakage_max=0.35,
11        phase_jump_tolerance_deg=90.0,
12        spatial_tolerance_log10=0.60,
13        spacing_m=200.0,
14        recursive=False,
15        api=True,
16    ).to_pandas(copy=True)
17    confidence["line"] = name
18    confidence_tables.append(confidence)
19
20confidence = pd.concat(confidence_tables, ignore_index=True)
21print(
22    confidence.groupby("line").agg(
23        stations=("station", "count"),
24        confidence_min=("confidence", "min"),
25        confidence_median=("confidence", "median"),
26        confidence_max=("confidence", "max"),
27    )
28)

Example output:

        stations  confidence_min  confidence_median  confidence_max
line
L18PLT        28           0.544              0.672           0.812
L22PLT        25           0.542              0.691           0.809

The medians are close, but they are not identical. That is the useful answer: the same first-pass QC config is reasonable, but any station rejection, static-shift correction, or inversion weighting should still be reviewed per line.

Station-confidence distributions for L18PLT and L22PLT

Make the Processing Decision#

Summarise the comparison as a decision table. This is the part worth keeping in a project note or reviewer response, because it explains why you reused a workflow or why you did not.

 1line_summary = pd.DataFrame(
 2    [
 3        {
 4            "line": name,
 5            "stations": len(surveys[name].summary().to_pandas()),
 6            "median_n_freq": inventory.loc[
 7                inventory["line"] == name, "n_freq"
 8            ].median(),
 9            "median_frac_ok": qc.loc[
10                qc["line"] == name, "frac_ok"
11            ].median(),
12            "median_confidence": confidence.loc[
13                confidence["line"] == name, "confidence"
14            ].median(),
15        }
16        for name in surveys
17    ]
18)
19print(line_summary)

Example output:

  line  stations  median_n_freq  median_frac_ok  median_confidence
L18PLT        28             53           1.000              0.672
L22PLT        25             53           1.000              0.691

For these two lines, a practical decision is:

  • reuse the same first-pass QC and frequency-band config;

  • keep the line labels separate in all outputs;

  • estimate static-shift factors separately for each line;

  • review stations with low confidence before inversion preparation;

  • do not merge the lines unless the modelling objective requires it.

Processing decisions after comparing L18PLT and L22PLT

Adapting This Tutorial#

For your own data, replace only the lines dictionary:

1lines = {
2    "Line_A": Path("path/to/line_a_edis"),
3    "Line_B": Path("path/to/line_b_edis"),
4}

Then rerun the same inventory, QC, confidence, and decision-table steps. If the frequency bands differ, use separate select_band parameters. If confidence distributions differ strongly, inspect the weaker line before applying corrections or preparing inversion input.

See Also#

Read an EDI Survey

Load EDI surveys and inspect the public survey object.

Inspect and QC a Survey

Build detailed QC tables and diagnostic plots for one line.

Run a Pipeline From Config

Store the resulting first-pass workflow in a reusable config file.

Correct Static Shift

Estimate static-shift factors after the QC decision is understood.