Quick start#
Welcome to the Quick start guide! This should help you get started using welly
.
First some preliminaries…
import numpy as np
import matplotlib.pyplot as plt
import welly
welly.__version__
'0.5.2'
Create a project from LAS#
Use the read_las()
function to load a well by passing a single filename, a POSIX-style path with wildcards (e.g. 'file_*.las'
) or URL as a str
.
This creates a project containing one (or more, depending on the wildcard) wells.
project = welly.read_las('https://geocomp.s3.amazonaws.com/data/P-129.LAS')
project
0it [00:00, ?it/s]
1it [00:01, 1.90s/it]
1it [00:01, 1.90s/it]
Index | UWI | Data | Curves |
---|---|---|---|
0 | Long = 63* 45'24.460 W | 24 curves | CALI, HCAL, PEF, DT, DTS, DPHI_SAN, DPHI_LIM, DPHI_DOL, NPHI_SAN, NPHI_LIM, NPHI_DOL, RLA5, RLA3, RLA4, RLA1, RLA2, RXOZ, RXO_HRLT, RT_HRLT, RM_HRLT, DRHO, RHOB, GR, SP |
Clearly there are some issues with this well. Have a look at the Well object docs to see how to fix them.
You can index into a project to get at a single well.
well = project[0]
well
Kennetcook #2 Long = 63* 45'24.460 W | |
---|---|
crs | CRS({}) |
location | Lat = 45* 12' 34.237" N |
country | CA |
province | Nova Scotia |
latitude | |
longitude | |
datum | |
section | 45.20 Deg N |
range | PD 176 |
township | 63.75 Deg W |
ekb | 94.8 |
egl | 90.3 |
gl | 90.3 |
tdd | 1935.0 |
tdl | 1935.0 |
td | None |
data | CALI, DPHI_DOL, DPHI_LIM, DPHI_SAN, DRHO, DT, DTS, GR, HCAL, NPHI_DOL, NPHI_LIM, NPHI_SAN, PEF, RHOB, RLA1, RLA2, RLA3, RLA4, RLA5, RM_HRLT, RT_HRLT, RXOZ, RXO_HRLT, SP |
The well’s header
contains the well information from the WELL part of the file.
PLEASE NOTE The header
attribute is under active development and will change in the next release of welly
. We will make it more natural to find basic information about the well, without having to know how to read an LAS file.
well.header
original_mnemonic | mnemonic | unit | value | descr | section | |
---|---|---|---|---|---|---|
0 | VERS | VERS | 2.0 | Version | ||
1 | WRAP | WRAP | YES | Version | ||
2 | STRT | STRT | M | 1.0668 | START DEPTH | Well |
3 | STOP | STOP | M | 1939.1376 | STOP DEPTH | Well |
4 | STEP | STEP | M | 0.1524 | STEP | Well |
... | ... | ... | ... | ... | ... | ... |
137 | TLI | TLI | M | 280.0 | Top Log Interval | Parameter |
138 | UWID | UWID | Unique Well Identification Number | Parameter | ||
139 | WN | WN | Kennetcook #2 | Well Name | Parameter | |
140 | EPD | EPD | M | 90.300003 | Elevation of Permanent Datum above Mean Sea Level | Parameter |
141 | UNKNOWN | Other |
142 rows × 6 columns
The well’s location
contains the location info from PARAMS:
well.location
Location({'position': None, 'crs': CRS({}), 'location': 'Lat = 45* 12\' 34.237" N', 'country': 'CA', 'province': 'Nova Scotia', 'latitude': '', 'longitude': '', 'datum': '', 'section': '45.20 Deg N', 'range': 'PD 176', 'township': '63.75 Deg W', 'ekb': 94.8, 'egl': 90.3, 'gl': 90.3, 'tdd': 1935.0, 'tdl': 1935.0, 'td': None, 'deviation': None})
Basic plots#
Welly produces simple matplotlib
plots. You can add your own Matplotlib code to build on them.
Let’s plot some important logs. We can put two logs in the same track, as shown, and add depth tracks:
tracks = ['MD', 'CALI', 'GR', 'SP', 'RHOB', ['DT', 'DTS'], 'MD']
well.plot(tracks=tracks)
Curve data#
The well curves are stored in well.data
, which is a dictionary mapping mnemonic to welly.Curve
object:
well.data
{'CALI': Curve(mnemonic=CALI, units=in, start=1.0668, stop=1939.1376, step=0.1524, count=[12718]),
'HCAL': Curve(mnemonic=HCAL, units=in, start=1.0668, stop=1939.1376, step=0.1524, count=[2139]),
'PEF': Curve(mnemonic=PEF, units=, start=1.0668, stop=1939.1376, step=0.1524, count=[12718]),
'DT': Curve(mnemonic=DT, units=us/ft, start=1.0668, stop=1939.1376, step=0.1524, count=[10850]),
'DTS': Curve(mnemonic=DTS, units=us/ft, start=1.0668, stop=1939.1376, step=0.1524, count=[10850]),
'DPHI_SAN': Curve(mnemonic=DPHI_SAN, units=m3/m3, start=1.0668, stop=1939.1376, step=0.1524, count=[12718]),
'DPHI_LIM': Curve(mnemonic=DPHI_LIM, units=m3/m3, start=1.0668, stop=1939.1376, step=0.1524, count=[12718]),
'DPHI_DOL': Curve(mnemonic=DPHI_DOL, units=m3/m3, start=1.0668, stop=1939.1376, step=0.1524, count=[12718]),
'NPHI_SAN': Curve(mnemonic=NPHI_SAN, units=m3/m3, start=1.0668, stop=1939.1376, step=0.1524, count=[12718]),
'NPHI_LIM': Curve(mnemonic=NPHI_LIM, units=m3/m3, start=1.0668, stop=1939.1376, step=0.1524, count=[12718]),
'NPHI_DOL': Curve(mnemonic=NPHI_DOL, units=m3/m3, start=1.0668, stop=1939.1376, step=0.1524, count=[12718]),
'RLA5': Curve(mnemonic=RLA5, units=ohm.m, start=1.0668, stop=1939.1376, step=0.1524, count=[12718]),
'RLA3': Curve(mnemonic=RLA3, units=ohm.m, start=1.0668, stop=1939.1376, step=0.1524, count=[12718]),
'RLA4': Curve(mnemonic=RLA4, units=ohm.m, start=1.0668, stop=1939.1376, step=0.1524, count=[12718]),
'RLA1': Curve(mnemonic=RLA1, units=ohm.m, start=1.0668, stop=1939.1376, step=0.1524, count=[12718]),
'RLA2': Curve(mnemonic=RLA2, units=ohm.m, start=1.0668, stop=1939.1376, step=0.1524, count=[12718]),
'RXOZ': Curve(mnemonic=RXOZ, units=ohm.m, start=1.0668, stop=1939.1376, step=0.1524, count=[12718]),
'RXO_HRLT': Curve(mnemonic=RXO_HRLT, units=ohm.m, start=1.0668, stop=1939.1376, step=0.1524, count=[2139]),
'RT_HRLT': Curve(mnemonic=RT_HRLT, units=ohm.m, start=1.0668, stop=1939.1376, step=0.1524, count=[12718]),
'RM_HRLT': Curve(mnemonic=RM_HRLT, units=ohm.m, start=1.0668, stop=1939.1376, step=0.1524, count=[12718]),
'DRHO': Curve(mnemonic=DRHO, units=g/cm3, start=1.0668, stop=1939.1376, step=0.1524, count=[12718]),
'RHOB': Curve(mnemonic=RHOB, units=g/cm3, start=1.0668, stop=1939.1376, step=0.1524, count=[12707]),
'GR': Curve(mnemonic=GR, units=gAPI, start=1.0668, stop=1939.1376, step=0.1524, count=[12718]),
'SP': Curve(mnemonic=SP, units=mV, start=1.0668, stop=1939.1376, step=0.1524, count=[12718])}
Let’s assign gr
to the gamma-ray log so we can inspect it more easily:
gr = well.data['GR']
gr
GR [gAPI] | |
---|---|
1.0668 : 1939.1376 : 0.1524 | |
index_units | m |
code | None |
description | Gamma-Ray |
log_type | None |
api | None |
date | 10-Oct-2007 |
null | -999.25 |
run | 1 |
service_company | Schlumberger |
_alias | GR |
Stats | |
samples (NaNs) | 12718 (0) |
min mean max | 3.89 78.986 267.94 |
Depth | Value |
1.0668 | 46.6987 |
1.2192 | 46.6987 |
1.3716 | 46.6987 |
⋮ | ⋮ |
1938.8328 | 92.2462 |
1938.9852 | 92.2462 |
1939.1376 | 92.2462 |
It’s often helpful to make a quick plot:
gr.plot()
<AxesSubplot:title={'center':'GR'}, xlabel='gAPI'>
Curve data as a pandas.DataFrame
#
The df
attribute (attributes are like ordinary variables; they do not have parentheses after them) of a single curve is a dataframe:
gr.df
GR | |
---|---|
DEPT | |
1.0668 | 46.69865036 |
1.2192 | 46.69865036 |
1.3716 | 46.69865036 |
1.5240 | 46.69865036 |
1.6764 | 46.69865036 |
... | ... |
1938.5280 | 92.24622345 |
1938.6804 | 92.24622345 |
1938.8328 | 92.24622345 |
1938.9852 | 92.24622345 |
1939.1376 | 92.24622345 |
12718 rows × 1 columns
And you can get at all of the well data in a similar way, except that this is a method not an attribute.
well.df()
CALI | HCAL | PEF | DT | DTS | DPHI_SAN | DPHI_LIM | DPHI_DOL | NPHI_SAN | NPHI_LIM | ... | RLA1 | RLA2 | RXOZ | RXO_HRLT | RT_HRLT | RM_HRLT | DRHO | RHOB | GR | SP | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
DEPT | |||||||||||||||||||||
1.0668 | 2.4438154697 | 4.3912849426 | 3.5864000320 | NaN | NaN | 0.1574800014 | 0.1984400004 | 0.2590999901 | 0.4650999904 | 0.3364700079 | ... | 0.0320999995 | 0.0279399995 | 0.0576100014 | 0.0255800001 | 0.0255800001 | 0.0550099984 | 0.1942329407 | 2.3901498318 | 46.69865036 | 120.1250 |
1.2192 | 2.4438154697 | 4.3912849426 | 3.5864000320 | NaN | NaN | 0.1574800014 | 0.1984400004 | 0.2590999901 | 0.4650999904 | 0.3364700079 | ... | 0.0320999995 | 0.0279399995 | 0.0576100014 | 0.0255800001 | 0.0255800001 | 0.0550099984 | 0.1942329407 | 2.3901498318 | 46.69865036 | 120.1250 |
1.3716 | 2.4438154697 | 4.3912849426 | 3.5864000320 | NaN | NaN | 0.1574800014 | 0.1984400004 | 0.2590999901 | 0.4650999904 | 0.3364700079 | ... | 0.0320999995 | 0.0279399995 | 0.0576100014 | 0.0255800001 | 0.0255800001 | 0.0550099984 | 0.1942329407 | 2.3901498318 | 46.69865036 | 120.1250 |
1.5240 | 2.4438154697 | 4.3912849426 | 3.5864000320 | NaN | NaN | 0.1574800014 | 0.1984400004 | 0.2590999901 | 0.4650999904 | 0.3364700079 | ... | 0.0320999995 | 0.0279399995 | 0.0576100014 | 0.0255800001 | 0.0255800001 | 0.0550099984 | 0.1942329407 | 2.3901498318 | 46.69865036 | 120.1250 |
1.6764 | 2.4438154697 | 4.3912849426 | 3.5864000320 | NaN | NaN | 0.1574800014 | 0.1984400004 | 0.2590999901 | 0.4650999904 | 0.3364700079 | ... | 0.0320999995 | 0.0279399995 | 0.0576100014 | 0.0255800001 | 0.0255800001 | 0.0550099984 | 0.1942329407 | 2.3901498318 | 46.69865036 | 120.1250 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
1938.5280 | 2.4202680588 | NaN | 2.2369799614 | NaN | NaN | 0.5464199781 | 585.9415283200 | 541.6757202100 | 0.1283400059 | 0.0841699988 | ... | 274.0264892600 | 663.1040649400 | 7.1023502350 | NaN | 7.3863301277 | 0.0272899996 | 0.0613951497 | NaN | 92.24622345 | 73.4375 |
1938.6804 | 2.4202680588 | NaN | 2.2369799614 | NaN | NaN | 0.5464199781 | 585.9415283200 | 541.6757202100 | 0.1283400059 | 0.0841699988 | ... | 274.0264892600 | 663.1040649400 | 7.1022100449 | NaN | 7.3860998154 | 0.0272899996 | 0.0613951497 | NaN | 92.24622345 | 73.6875 |
1938.8328 | 2.4202680588 | NaN | 2.2369799614 | NaN | NaN | 0.5464199781 | 585.9415283200 | 541.6757202100 | 0.1283400059 | 0.0841699988 | ... | 274.0264892600 | 663.1040649400 | 7.0968699455 | NaN | 7.3806500435 | 0.0272899996 | 0.0613951497 | NaN | 92.24622345 | 73.0000 |
1938.9852 | 2.4202680588 | NaN | 2.2369799614 | NaN | NaN | 0.5464199781 | 585.9415283200 | 541.6757202100 | 0.1283400059 | 0.0841699988 | ... | 274.0264892600 | 663.1040649400 | 7.0391001701 | NaN | 7.3211698532 | 0.0272899996 | 0.0613951497 | NaN | 92.24622345 | 73.9375 |
1939.1376 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
12718 rows × 24 columns
It has to be a method because we can pass in extra options like aliases.
Curve aliases#
We can make a dictionary mapping alias names (what we want to call curves) to a list of mnemonics (what they are actually called). The list of mnemonics is ordered, and this order expresses your preference. So, for example, in the alias dictionary below, the GR log will be used in preference to the GAM log.
alias = {
"Gamma": ["GR", "GAM", "GRC", "SGR", "NGT"],
"Density": ["RHOZ", "RHOB", "DEN", "RHOZ"],
"Sonic": ["DT", "AC", "DTP", "DT4P"],
"Caliper": ["CAL", "CALI", "CALS", "C1"],
'Porosity SS': ['NPSS', 'DPSS'],
}
Lots of methods in welly
take an alias. Welly will use the alias dictionary to decide what to show.
Here, the keys
argument narrows down the columns to include in the dataframe.
well.df(keys=['Gamma', 'Density', 'Sonic', 'DTS'], alias=alias)
Gamma | Density | Sonic | DTS | |
---|---|---|---|---|
DEPT | ||||
1.0668 | 46.69865036 | 2.3901498318 | NaN | NaN |
1.2192 | 46.69865036 | 2.3901498318 | NaN | NaN |
1.3716 | 46.69865036 | 2.3901498318 | NaN | NaN |
1.5240 | 46.69865036 | 2.3901498318 | NaN | NaN |
1.6764 | 46.69865036 | 2.3901498318 | NaN | NaN |
... | ... | ... | ... | ... |
1938.5280 | 92.24622345 | NaN | NaN | NaN |
1938.6804 | 92.24622345 | NaN | NaN | NaN |
1938.8328 | 92.24622345 | NaN | NaN | NaN |
1938.9852 | 92.24622345 | NaN | NaN | NaN |
1939.1376 | NaN | NaN | NaN | NaN |
12718 rows × 4 columns
There are a lot of NaNs there, but if we just wanted to see the middle of the well, we can pass in a basis
to narrow it down (and even resample the data if we want).
Let’s look at 1000 to 1500 m, resampled to 1 m sample interval:
basis = range(1000, 1501)
well.df(keys=['Gamma', 'Density', 'Sonic', 'DTS'], alias=alias, basis=basis)
Gamma | Density | Sonic | DTS | |
---|---|---|---|---|
DEPT | ||||
1000.0 | 109.4141766008 | 2.6799457361 | 64.2276356014 | 106.9954482717 |
1001.0 | 97.9911234089 | 2.6556390146 | 66.5059106783 | 114.9005662249 |
1002.0 | 121.8829895962 | 2.7326798533 | 72.6423786195 | 128.5069715885 |
1003.0 | 116.8162654985 | 2.7270846667 | 67.0266704008 | 114.4374434938 |
1004.0 | 104.1488654040 | 2.7105148702 | 66.5885520597 | 112.6149668241 |
... | ... | ... | ... | ... |
1496.0 | 48.4150835474 | 2.5053586284 | 57.5187979772 | 91.2555331735 |
1497.0 | 35.7518339679 | 2.2265663635 | 59.8886118010 | 98.4930620567 |
1498.0 | 43.7772705495 | 2.3508678784 | 59.2961889052 | 99.9149563203 |
1499.0 | 36.5637318164 | 2.3360053329 | 63.2269721143 | 98.0971322843 |
1500.0 | 64.5593145777 | 2.6044978626 | 53.9417406514 | 89.4845678370 |
501 rows × 4 columns
Curve quality#
We can check the quality of curves with a dictionary of tests. There are some predefined tests in the welly.quality
module, but you can write your own functions and pass them in instead (the function should take a curve as its argument, and return True
if the test passed).
import welly.quality as q
tests = {
'Each': [
q.no_flat,
q.no_monotonic,
q.no_gaps,
],
'Gamma': [
q.all_positive,
q.all_below(450),
q.check_units(['API', 'GAPI']),
],
'DT': [
q.all_positive,
],
'Sonic': [
q.all_between(1, 10000), # 1333 to 5000 m/s
q.no_spikes(10), # 10 spikes allowed
],
}
passed = well.qc_data(tests, alias=alias)
This returns a dictionary of curves in which the values are dictionaries of test name: test result pairs.
passed
{'CALI': {'no_flat': False, 'no_monotonic': False, 'no_gaps': True},
'HCAL': {'no_flat': False, 'no_monotonic': False, 'no_gaps': True},
'PEF': {'no_flat': False, 'no_monotonic': False, 'no_gaps': True},
'DT': {'no_flat': True,
'no_monotonic': True,
'no_gaps': True,
'all_positive': True,
'all_between': True,
'no_spikes': False},
'DTS': {'no_flat': True, 'no_monotonic': True, 'no_gaps': True},
'DPHI_SAN': {'no_flat': False, 'no_monotonic': False, 'no_gaps': True},
'DPHI_LIM': {'no_flat': False, 'no_monotonic': False, 'no_gaps': True},
'DPHI_DOL': {'no_flat': False, 'no_monotonic': False, 'no_gaps': True},
'NPHI_SAN': {'no_flat': False, 'no_monotonic': False, 'no_gaps': True},
'NPHI_LIM': {'no_flat': False, 'no_monotonic': False, 'no_gaps': True},
'NPHI_DOL': {'no_flat': False, 'no_monotonic': False, 'no_gaps': True},
'RLA5': {'no_flat': False, 'no_monotonic': False, 'no_gaps': True},
'RLA3': {'no_flat': False, 'no_monotonic': False, 'no_gaps': True},
'RLA4': {'no_flat': False, 'no_monotonic': False, 'no_gaps': True},
'RLA1': {'no_flat': False, 'no_monotonic': False, 'no_gaps': True},
'RLA2': {'no_flat': False, 'no_monotonic': False, 'no_gaps': True},
'RXOZ': {'no_flat': False, 'no_monotonic': False, 'no_gaps': True},
'RXO_HRLT': {'no_flat': False, 'no_monotonic': False, 'no_gaps': True},
'RT_HRLT': {'no_flat': False, 'no_monotonic': False, 'no_gaps': True},
'RM_HRLT': {'no_flat': False, 'no_monotonic': False, 'no_gaps': True},
'DRHO': {'no_flat': False, 'no_monotonic': False, 'no_gaps': True},
'RHOB': {'no_flat': False, 'no_monotonic': False, 'no_gaps': True},
'GR': {'no_flat': False,
'no_monotonic': False,
'no_gaps': True,
'all_positive': True,
'all_below': True,
'check_units': False},
'SP': {'no_flat': False, 'no_monotonic': False, 'no_gaps': True}}
There’s also an HTML table for rendering in Notebooks:
from IPython.display import HTML
html = well.qc_table_html(tests, alias=alias)
HTML(html)
Curve | Passed | Score | all_between | no_spikes | all_positive | no_flat | check_units | no_gaps | all_below | no_monotonic |
---|---|---|---|---|---|---|---|---|---|---|
CALI | 1 / 3 | 0.333 | False | True | False | |||||
HCAL | 1 / 3 | 0.333 | False | True | False | |||||
PEF | 1 / 3 | 0.333 | False | True | False | |||||
DT | 5 / 6 | 0.833 | True | False | True | True | True | True | ||
DTS | 3 / 3 | 1.000 | True | True | True | |||||
DPHI_SAN | 1 / 3 | 0.333 | False | True | False | |||||
DPHI_LIM | 1 / 3 | 0.333 | False | True | False | |||||
DPHI_DOL | 1 / 3 | 0.333 | False | True | False | |||||
NPHI_SAN | 1 / 3 | 0.333 | False | True | False | |||||
NPHI_LIM | 1 / 3 | 0.333 | False | True | False | |||||
NPHI_DOL | 1 / 3 | 0.333 | False | True | False | |||||
RLA5 | 1 / 3 | 0.333 | False | True | False | |||||
RLA3 | 1 / 3 | 0.333 | False | True | False | |||||
RLA4 | 1 / 3 | 0.333 | False | True | False | |||||
RLA1 | 1 / 3 | 0.333 | False | True | False | |||||
RLA2 | 1 / 3 | 0.333 | False | True | False | |||||
RXOZ | 1 / 3 | 0.333 | False | True | False | |||||
RXO_HRLT | 1 / 3 | 0.333 | False | True | False | |||||
RT_HRLT | 1 / 3 | 0.333 | False | True | False | |||||
RM_HRLT | 1 / 3 | 0.333 | False | True | False | |||||
DRHO | 1 / 3 | 0.333 | False | True | False | |||||
RHOB | 1 / 3 | 0.333 | False | True | False | |||||
GR | 3 / 6 | 0.500 | True | False | False | True | True | False | ||
SP | 1 / 3 | 0.333 | False | True | False |
Add a striplog#
In principle, Welly can hold data from anywhere. Let’s add data from another source. Striplog can create a geological log from an image or a CSV. Let’s try it:
from striplog import Legend, Striplog
legend = Legend.builtin('NSDOE')
strip = Striplog.from_image('https://geocomp.s3.amazonaws.com/data/P-129_280_1935.png', 280, 1935, legend=legend)
strip.plot()
Add this to the well data:
well.data['strip'] = strip
tracks = ['MD', 'strip', 'GR', 'RHOB', ['DT', 'DTS'], 'MD']
well.plot(tracks=tracks)
Write a LAS file#
At any point, you can write out a new LAS file.
Let’s write a new file with only the DT and DTS logs, and a different NULL value to the original:
well.to_las('out.las', keys=['DT', 'DTS'], null_value=-111.111)
You can perform a shell command in Jupyter Notebooks by putting a !
before the command. So on Linux, here’s how we can check the new file exists:
!ls -l out.las
-rw-r--r-- 1 runner docker 434247 Feb 28 13:55 out.las
!head -12 out.las
~Version ---------------------------------------------------
VERS. 2.0 : CWLS log ASCII Standard -VERSION 2.0
WRAP. NO : One line per depth step
DLM . SPACE : Column Data Section Delimiter
~Well ------------------------------------------------------
STRT .m 1.0668 : START DEPTH
STOP .m 1939.1376 : STOP DEPTH
STEP .m 0.15240 : STEP
NULL . -111.111 : NULL VALUE
COMP . Elmworth Energy Corporation : COMPANY
WELL . Kennetcook #2 : WELL
FLD . Windsor Block : FIELD
© 2022 Agile Scientific, CC BY