Reading Well Data into Subsurface

Authors: Miguel de la Varga and Alexander Juestel

# This example will get into detail about how we are using `welly` and `striplog` to
# import borehole data.

# We start by using pooch to download the dataset into a temp file;
import pooch

from dataclasses import asdict
import matplotlib.pyplot as plt
from striplog import Component

import subsurface as sb
from subsurface.reader import ReaderFilesHelper
from subsurface.reader.wells import read_collar, read_survey, read_lith, WellyToSubsurfaceHelper, welly_to_subsurface
from subsurface.structs.base_structures.common_data_utils import to_netcdf

base_url = "https://raw.githubusercontent.com/softwareunderground/subsurface/main/tests/data/borehole/"

data_hash = "55d58e1e1ed22509579d46f736fa5f07f4428c6744bd16dbd919242d14348da7"
raw_borehole_data_csv = pooch.retrieve(url=base_url + 'kim_ready.csv',
                                       known_hash=data_hash)

This dataset consist on a csv file containing the following columns: x, y, name, num, z, year, 7,8,9, altitude, base, formation, top, _top_abs and md. This is a good example of how varied borehole data can be provided. We will need to be able to extract specific information to construct the subsurface object.

# To read csv we are using `pandas` but since `pandas.read_csv` has a lot of arguments, we have created some
# helper classes to facilitate the reading of the data for this specific context. These *Helpers* are just a python
# data class with a bit of funcitonality for setter and getter.

# Also since well data sometimes is given in multiple files - for collars, asseys and surveys - we will read those
# subset of the data into its own `pandas.Dataframe`. Let's start:
reading_collars = ReaderFilesHelper(
    file_or_buffer=raw_borehole_data_csv,  # Path to file
    index_col="name",  # Column used as index
    usecols=['x', 'y', 'altitude', "name"]  # Specific columns for collar
)

# We can see the fields from the class easily converting it to a dict
asdict(reading_collars)

Out:

{'file_or_buffer': '/home/runner/.cache/pooch/7c2d6aa7551f31216f7236d3f9fb70a6-kim_ready.csv', 'usecols': ['x', 'y', 'altitude', 'name'], 'col_names': None, 'drop_cols': None, 'format': '.csv', 'index_map': None, 'columns_map': None, 'additional_reader_kwargs': {}, 'file_or_buffer_type': <class 'str'>, 'index_col': 'name', 'header': 'infer'}

The rest of fields of ReaderFilesHelper would be used for different .csv configurations. With a ReaderFilesHelper we can use it for specific functions to read the file into pandas:

collar = read_collar(reading_collars)
collar
x y altitude
name
KCL12 303412 3913997 108.713287
KCL12 303412 3913997 108.713287
KCL12 303412 3913997 108.713287
KCL12 303412 3913997 108.713287
KCL12 303412 3913997 108.713287
... ... ... ...
USLXX 318982 3935253 227.418152
USLXX 318982 3935253 227.418152
USLXX 318982 3935253 227.418152
USLXX 318982 3935253 227.418152
USLXX 318982 3935253 227.418152

721 rows × 3 columns



We do the same for survey and lithologies:

survey = read_survey(
    ReaderFilesHelper(
        file_or_buffer=raw_borehole_data_csv,
        index_col="name",
        usecols=["name", "md"]
    )
)
survey

Out:

/home/runner/work/subsurface/subsurface/subsurface/reader/wells/well_files_reader.py:140: UserWarning: inc and/or azi columns are not present in the file. The boreholes will be straight.
  warnings.warn('inc and/or azi columns are not present in the file.'
md inc azi
name
KCL12 0.000000 0 0
KCL12 1035.597077 0 0
KCL12 1652.245636 0 0
KCL12 2109.993683 0 0
KCL12 2109.984893 0 0
... ... ... ...
USLXX 308.885376 0 0
USLXX 601.202972 0 0
USLXX 753.114075 0 0
USLXX 1197.413513 0 0
USLXX 1623.969910 0 0

721 rows × 3 columns



lith = read_lith(
    ReaderFilesHelper(
        file_or_buffer=raw_borehole_data_csv,
        usecols=['name', 'top', 'base', 'formation'],
        columns_map={'top': 'top',
                     'base': 'base',
                     'formation': 'component lith',
                     }
    )
)

lith
top base component lith
name
KCL12 0.000000 1035.597077 topo
KCL12 1035.597077 1652.245636 etchegoin
KCL12 1652.245636 2109.993683 macoma
KCL12 2109.993683 2110.003683 mclure
KCL12 2109.984893 2799.027984 fruitvale
... ... ... ...
USLXX 308.885376 601.202972 freeman_jewett
USLXX 601.202972 753.114075 vedder
USLXX 753.114075 1197.413513 cretaceous
USLXX 1197.413513 1623.969910 basement
USLXX 1623.969910 1623.979910 NaN

721 rows × 3 columns



At this point we have all the necessary data in pandas.Dataframe. However, to construct a subsurface.UnstructuredData object we are going to need to convert the data to the usual scheme of: vertex, cells, vertex_attr and cells_attr. To do this we will use welly and striplog

Welly is a family of classes to facilitate the loading, processing, and analysis of subsurface wells and well data, such as striplogs, formation tops, well log curves, and synthetic seismograms.

The class WellyToSubsurfaceHelper contains the methods to create a welly project and export it to a subsurface data class.

wts = WellyToSubsurfaceHelper(collar_df=collar, survey_df=survey, lith_df=lith)

Out:

The following striplog failed being processed:  []

In the field p is stored a welly project (https://github.com/agile-geoscience/welly/blob/master/tutorial/04_Project.ipynb) and we can use it to explore and visualize properties of each well.

wts.p
IndexUWIDataCurves
0KerrXX2 curveslith, lith_log
1Steele_Petroleum_Co4_12 curveslith, lith_log
2PACIFIC_STATESXX2 curveslith, lith_log
3Russell12 curveslith, lith_log
4XX32_152 curveslith, lith_log
5St._Anthony12 curveslith, lith_log
6Kuhn81_152 curveslith, lith_log
7Bellevue_Deep12 curveslith, lith_log
8RoeXX2 curveslith, lith_log
9SUPERIOR_TENNECO_GAUTXX2 curveslith, lith_log
10Union_Tribe_BXX2 curveslith, lith_log
11MARQUIS12 curveslith, lith_log
12S_&_D_Killingwoth_EPM12 curveslith, lith_log
13West_Rio_Bravo12 curveslith, lith_log
14Ingram13_732 curveslith, lith_log
15KCL_G12 curveslith, lith_log
16Tidewater_Capital_Co.XX2 curveslith, lith_log
17Mobil_Pan_Pet_KCL86_352 curveslith, lith_log
18HayesXX2 curveslith, lith_log
19TennecoXX2 curveslith, lith_log
20Golden_State_VintnersXX2 curveslith, lith_log
21Gow12 curveslith, lith_log
22Westates442 curveslith, lith_log
23Bergman_Trust12 curveslith, lith_log
24Del_Fortuna12 curveslith, lith_log
25Amalgamated_Happold22 curveslith, lith_log
26TWIXX2 curveslith, lith_log
27BALD_EAGLE742 curveslith, lith_log
28Kramer12 curveslith, lith_log
29Kern_AXX2 curveslith, lith_log
30Tenneco_Ladd_Rio_Bravo_North872 curveslith, lith_log
31112 curveslith, lith_log
32Roe32 curveslith, lith_log
33KCL152 curveslith, lith_log
34Tenneco_Rio_Bravo322 curveslith, lith_log
35KCL122 curveslith, lith_log
36KCL_A58_82 curveslith, lith_log
37Mobil_Pan_Petroleum_KCL31_152 curveslith, lith_log
38KCL_B452 curveslith, lith_log
39Roberts_Cox23_12 curveslith, lith_log
40RUSSELLXX2 curveslith, lith_log
41KerncoXX2 curveslith, lith_log
42Davies12 curveslith, lith_log
43KCL_11442 curveslith, lith_log
44Arkelian23_262 curveslith, lith_log
45Cities_Service_TennecoXX2 curveslith, lith_log
46KCL87_252 curveslith, lith_log
4733XX2 curveslith, lith_log
48CURRY12 curveslith, lith_log
49JewettaXX2 curveslith, lith_log
50DearingerXX2 curveslith, lith_log
51McCulloch_Camp_et_al1_362 curveslith, lith_log
52Shell_Magee_GlideXX2 curveslith, lith_log
53Wright_Bloemer742 curveslith, lith_log
54Sharples_Marathon_BillingtonXX2 curveslith, lith_log
55Kimberlina12 curveslith, lith_log
56CallowayXX2 curveslith, lith_log
57Helbling12 curveslith, lith_log
58API_BartellXX2 curveslith, lith_log
59Famosa12_12 curveslith, lith_log
60Bishop_FeeXX2 curveslith, lith_log
61Wiedman55_262 curveslith, lith_log
62USLXX2 curveslith, lith_log
63Tenneco_Sun11x_312 curveslith, lith_log
64Law_et_alXX2 curveslith, lith_log
65XXXX2 curveslith, lith_log
66R.A._Shafter_A12 curveslith, lith_log


stripLog = wts.p[0].data['lith']
stripLog

Out:

Striplog(11 Intervals, start=0.0, stop=4604.7157137)
stripLog.plot()
plt.gcf()
reading well data

Out:

<Figure size 150x1500 with 1 Axes>

Once we have the WellyToSubsurfaceHelper the function welly_to_subsurface will directly convert the objet to subsurface.UnstructuredData – using the trajectory module of welly.

formations = ["topo", "etchegoin", "macoma", "chanac", "mclure",
              "santa_margarita", "fruitvale",
              "round_mountain", "olcese", "freeman_jewett", "vedder", "eocene",
              "cretaceous",
              "basement", "null"]

unstruct = welly_to_subsurface(
    wts,
    table=[Component({'lith': l}) for l in formations] # This is to keep the lithology ids constant across all the wells
)
unstruct

Out:

The following boreholes failed being processed:  []

<xarray.Dataset>
Dimensions:       (points: 3350, XYZ: 3, cell: 3283, nodes: 2, cell_attr: 1, vertex_attr: 0)
Coordinates:
  * points        (points) int64 0 1 2 3 4 5 6 ... 3344 3345 3346 3347 3348 3349
  * XYZ           (XYZ) <U1 'X' 'Y' 'Z'
  * cell_attr     (cell_attr) object 'lith_log'
  * vertex_attr   (vertex_attr) int64
    UWI           (cell) object 'KerrXX' 'KerrXX' ... 'R.A._Shafter_A1'
    Depth         (cell) float64 40.56 121.7 202.8 ... 3.952e+03 4.035e+03
Dimensions without coordinates: cell, nodes
Data variables:
    vertex        (points, XYZ) float64 2.932e+05 3.924e+06 ... -3.963e+03
    cells         (cell, nodes) int64 0 1 1 2 2 3 ... 3347 3347 3348 3348 3349
    cell_attrs    (cell, cell_attr) int64 1 1 1 1 1 1 1 ... 14 14 14 14 14 14 14
    vertex_attrs  (points, vertex_attr) float64

We can save the data into a netcdf by simply calling

to_netcdf(unstruct, "wells_unstructured_temp.nc")

We are done. Now the well data is a subsurface.UnstructuredData and can be used as usual.

element = sb.LineSet(unstruct)
pyvista_mesh = sb.visualization.to_pyvista_line(element, radius=50)

# Plot default LITH
interactive_plot =sb.visualization.pv_plot([pyvista_mesh])
reading well data

Out:

/opt/hostedtoolcache/Python/3.8.12/x64/lib/python3.8/site-packages/pyvista/core/dataset.py:1332: PyvistaDeprecationWarning: Use of `cell_arrays` is deprecated. Use `cell_data` instead.
  warnings.warn(

Total running time of the script: ( 0 minutes 2.263 seconds)

Gallery generated by Sphinx-Gallery