Filtering data in 1D#

[1]:
# %load_ext autoreload
# %autoreload 2

import cmocean
import numpy as np
import pandas as pd
import verde as vd

import airbornegeo
[2]:
data_df = pd.read_csv("data/AGAP_gravity_survey_processed.csv")
data_df = data_df[
    [
        "easting",
        "northing",
        "line",
        "unixtime",
        "grav_disturbance",
        "distance_along_line",
        "height",
    ]
]
data_df = data_df.sort_values(["line", "unixtime"]).reset_index(drop=True)
data_df.head()
[2]:
easting northing line unixtime grav_disturbance distance_along_line height
0 1.000024e+06 226237.330771 1 1.229507e+09 1186.4 0.000000 4156.1
1 1.000083e+06 226246.631269 1 1.229507e+09 342.1 59.842447 4156.0
2 1.000142e+06 226255.809132 1 1.229507e+09 -1965.9 119.693401 4156.1
3 1.000201e+06 226264.969079 1 1.229507e+09 820.0 179.545645 4156.4
4 1.000260e+06 226274.156809 1 1.229507e+09 3198.0 239.285174 4156.6
[3]:
# extract a single line from the survey
line_df = data_df[data_df.line == 4]

# Create a survey for the single-line demo
line_survey = airbornegeo.Survey(
    line_df,
    line_column="line",
    distance_column="distance_along_line",
)

Filter a line#

Spatial filtering#

We can perform 1D filtering based on the distance along the flight line.

[4]:
line_survey.filter_line(
    filter_width=19_000,  # 19 km low pass gaussian filter
    filter_type="lowpass",
    filter_shape="gaussian",
    data_column="grav_disturbance",
    filter_by_column="distance_along_line",
    result_column="grav_disturbance_filtered_spatial",
    pad_width_percentage=10,
)
[4]:
airbornegeo.Survey 3,762 rows × 8 columns
Column attributes
lineline
line_typenot set
distancedistance_along_line
timenot set
heightnot set
latitudenot set
longitudenot set
CRS & metadata0 metadata entries
crsNone
Statistics0/8 computed — access an attribute or call .describe()
regionnot computed
line_countsnot computed
line_lengthsnot computed
total_lengthnot computed
median_line_lengthsnot computed
line_azimuthsnot computed
mean_line_azimuthsnot computed
median_line_spacingsnot computed
IntersectionsNone

None — call create_intersection_table()

Datafirst 5 rows
easting northing line unixtime grav_disturbance distance_along_line height grav_disturbance_filtered_spatial
8591 1.316050e+06 392090.546753 4 1.230570e+09 1971.6 0.000000 3574.5 -17.269885
8592 1.315990e+06 392078.637888 4 1.230570e+09 1878.6 61.614338 3574.3 -17.272291
8593 1.315929e+06 392066.728534 4 1.230570e+09 -602.0 123.228587 3574.0 -17.279394
8594 1.315869e+06 392054.833235 4 1.230570e+09 -2875.9 184.956734 3573.7 -17.291215
8595 1.315808e+06 392042.937442 4 1.230570e+09 39.0 246.684793 3573.5 -17.307745
[5]:
maxabs = vd.maxabs(line_df.grav_disturbance, percentile=60)
ax = line_df.plot.line(
    "distance_along_line",
    "grav_disturbance",
    style="b.",
    ms=0.6,
    ylim=(-maxabs, maxabs),
)
ax = line_df.plot.line(
    "distance_along_line",
    "grav_disturbance_filtered_spatial",
    style="r.",
    ms=0.6,
    ax=ax,
    ylim=(-maxabs, maxabs),
)
ax.set_ylabel("mGal")
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
File ~/airbornegeo/.pixi/envs/default/lib/python3.14/site-packages/pandas/core/indexes/base.py:3641, in Index.get_loc(self, key)
   3640 try:
-> 3641     return self._engine.get_loc(casted_key)
   3642 except KeyError as err:

File pandas/_libs/index.pyx:168, in pandas._libs.index.IndexEngine.get_loc()
--> 168 'Could not get source, probably due dynamically evaluated source code.'

File pandas/_libs/index.pyx:197, in pandas._libs.index.IndexEngine.get_loc()
--> 197 'Could not get source, probably due dynamically evaluated source code.'

File pandas/_libs/hashtable_class_helper.pxi:7668, in pandas._libs.hashtable.PyObjectHashTable.get_item()
-> 7668 'Could not get source, probably due dynamically evaluated source code.'

File pandas/_libs/hashtable_class_helper.pxi:7676, in pandas._libs.hashtable.PyObjectHashTable.get_item()
-> 7676 'Could not get source, probably due dynamically evaluated source code.'

KeyError: 'grav_disturbance_filtered_spatial'

The above exception was the direct cause of the following exception:

KeyError                                  Traceback (most recent call last)
Cell In[5], line 9
      5     style="b.",
      6     ms=0.6,
      7     ylim=(-maxabs, maxabs),
      8 )
----> 9 ax = line_df.plot.line(
     10     "distance_along_line",
     11     "grav_disturbance_filtered_spatial",
     12     style="r.",

File ~/airbornegeo/.pixi/envs/default/lib/python3.14/site-packages/pandas/plotting/_core.py:1295, in PlotAccessor.line(self, x, y, color, **kwargs)
   1293 if color is not None:
   1294     kwargs["color"] = color
-> 1295 return self(kind="line", x=x, y=y, **kwargs)

File ~/airbornegeo/.pixi/envs/default/lib/python3.14/site-packages/pandas/plotting/_core.py:1169, in PlotAccessor.__call__(self, *args, **kwargs)
   1166         except (IndexError, KeyError, TypeError):
   1167             pass
-> 1169 data = data[y]
   1171 if isinstance(data, ABCSeries):
   1172     label_name = label_kw or y

File ~/airbornegeo/.pixi/envs/default/lib/python3.14/site-packages/pandas/core/frame.py:4378, in DataFrame.__getitem__(self, key)
   4374
   4375         if is_single_key:
   4376             if self.columns.nlevels > 1:
   4377                 return self._getitem_multilevel(key)
-> 4378             indexer = self.columns.get_loc(key)
   4379             if is_integer(indexer):
   4380                 indexer = [indexer]
   4381         else:

File ~/airbornegeo/.pixi/envs/default/lib/python3.14/site-packages/pandas/core/indexes/base.py:3648, in Index.get_loc(self, key)
   3643     if isinstance(casted_key, slice) or (
   3644         isinstance(casted_key, abc.Iterable)
   3645         and any(isinstance(x, slice) for x in casted_key)
   3646     ):
   3647         raise InvalidIndexError(key) from err
-> 3648     raise KeyError(key) from err
   3649 except TypeError:
   3650     # If we have a listlike key, _check_indexing_error will raise
   3651     #  InvalidIndexError. Otherwise we fall through and re-raise
   3652     #  the TypeError.
   3653     self._check_indexing_error(key)

KeyError: 'grav_disturbance_filtered_spatial'
_images/filtering_data_in_1D_7_1.png

Temporal filtering#

We can also perform 1D filtering based on the time.

[ ]:
line_df = line_df.sort_values("unixtime")

line_survey = airbornegeo.Survey(
    line_df,
    line_column="line",
    distance_column="distance_along_line",
)

line_survey.filter_line(
    filter_width=400,  # 400 sec low pass gaussian filter
    filter_type="lowpass",
    filter_shape="gaussian",
    data_column="grav_disturbance",
    filter_by_column="unixtime",
    result_column="grav_disturbance_filtered_temporal",
    pad_width_percentage=10,
)
[ ]:
ax = line_df.plot.line(
    "distance_along_line",
    "grav_disturbance",
    style="b.",
    ms=0.6,
    ylim=(-maxabs, maxabs),
)
ax = line_df.plot.line(
    "distance_along_line",
    "grav_disturbance_filtered_temporal",
    style="r.",
    ms=0.6,
    ax=ax,
    ylim=(-maxabs, maxabs),
)
ax.set_ylabel("mGal")
Text(0, 0.5, 'mGal')
_images/filtering_data_in_1D_10_1.png

Filtering with equivalent sources#

If our data is gravity or magnetic anomalies, an alternative method is to fit an equivalent source model to the unfiltered data, and either either block-reduction, damping, source-depth, or predicting at a high elevation (upward continuation) to smooth the data.

[ ]:
eqs = line_survey.eq_sources_1d(
    data_column="grav_disturbance",
    depth="default",
    damping=1e4,
    block_size=500,
)

line_df["grav_disturbance_filtered_eqs"] = eqs.predict(
    (
        line_df.distance_along_line,
        np.zeros_like(line_df.distance_along_line),
        line_df.height,
        # np.full_like(line_df.height, 10e3) # use this if you want to upward continue the data
    )
)

Compare filtering methods#

[ ]:
ylim = vd.minmax(
    line_df[
        [
            "grav_disturbance_filtered_spatial",
            "grav_disturbance_filtered_temporal",
            "grav_disturbance_filtered_eqs",
        ]
    ],
)
ax = line_df.plot.line(
    "distance_along_line",
    "grav_disturbance_filtered_spatial",
    style="b.",
    ms=0.6,
    ylim=ylim,
)
ax = line_df.plot.line(
    "distance_along_line",
    "grav_disturbance_filtered_temporal",
    style="r.",
    ms=0.4,
    ax=ax,
    ylim=ylim,
)
ax = line_df.plot.line(
    "distance_along_line",
    "grav_disturbance_filtered_eqs",
    style="g.",
    ms=0.2,
    ax=ax,
    ylim=ylim,
)
ax.legend(markerscale=10)
ax.set_ylabel("mGal")
Text(0, 0.5, 'mGal')
_images/filtering_data_in_1D_14_1.png

Filter all lines in a survey#

Spatial filtering#

[ ]:
# plot the unfiltered gravity disturbance data
maxabs = vd.maxabs(line_df.grav_disturbance, percentile=95)
ax = data_df.plot.scatter(
    "easting",
    "northing",
    c="grav_disturbance",
    s=0.1,
    cmap=cmocean.cm.balance,
    vmin=-maxabs,
    vmax=maxabs,
)
ax.set_aspect("equal")
_images/filtering_data_in_1D_17_0.png
[ ]:
# Create survey for full dataset
survey = airbornegeo.Survey(
    data_df,
    line_column="line",
    distance_column="distance_along_line",
)
[ ]:
survey.filter_line(
    filter_width=19_000,  # 19 km low pass gaussian filter
    filter_type="lowpass",
    filter_shape="gaussian",
    data_column="grav_disturbance",
    filter_by_column="distance_along_line",
    result_column="grav_disturbance_filtered_spatial",
    pad_width_percentage=10,
)
[ ]:
# plot the filtered gravity disturbance data
maxabs = vd.maxabs(line_df.grav_disturbance_filtered_spatial, percentile=95)
ax = data_df.plot.scatter(
    "easting",
    "northing",
    c="grav_disturbance_filtered_spatial",
    s=0.1,
    cmap=cmocean.cm.balance,
    vmin=-maxabs,
    vmax=maxabs,
)
ax.set_aspect("equal")
_images/filtering_data_in_1D_20_0.png

Temporal filtering#

[ ]:
survey.filter_line(
    filter_width=500,  # 500 sec low pass gaussian filter
    filter_type="lowpass",
    filter_shape="gaussian",
    data_column="grav_disturbance",
    filter_by_column="unixtime",
    result_column="grav_disturbance_filtered_temporal",
    pad_width_percentage=10,
)
[ ]:
# plot the filtered gravity disturbance data
maxabs = vd.maxabs(line_df.grav_disturbance_filtered_temporal, percentile=95)
ax = data_df.plot.scatter(
    "easting",
    "northing",
    c="grav_disturbance_filtered_temporal",
    s=0.1,
    cmap=cmocean.cm.balance,
    vmin=-maxabs,
    vmax=maxabs,
)
ax.set_aspect("equal")
_images/filtering_data_in_1D_23_0.png

Equivalent source filtering#

[ ]:
eqs = survey.eq_sources_1d(
    data_column="grav_disturbance",
    depth="default",
    damping=1e4,
    # damping=1e5,
    # damping=1e6,
    block_size=500,
)
[ ]:
pbar = data_df.groupby("line")
for segment_name, segment_data in pbar:
    line_eqs = eqs[segment_name]
    data_df.loc[data_df.line == segment_name, "grav_disturbance_filtered_eqs"] = (
        line_eqs.predict(
            (
                segment_data.distance_along_line,
                np.zeros_like(segment_data.distance_along_line),
                segment_data.height,
            )
        )
    )
[ ]:
# plot the filtered gravity disturbance data
maxabs = vd.maxabs(line_df.grav_disturbance_filtered_eqs, percentile=95)
ax = data_df.plot.scatter(
    "easting",
    "northing",
    c="grav_disturbance_filtered_eqs",
    s=0.1,
    cmap=cmocean.cm.balance,
    vmin=-maxabs,
    vmax=maxabs,
)
ax.set_aspect("equal")
_images/filtering_data_in_1D_27_0.png