This notebook goes with the Agile blog post of 23 October.
Set up a conda
environment with:
conda create -n welly python=3.6 matplotlib=2.0 scipy pandas
You'll need welly
in your environment:
conda install tqdm # Should happen automatically but doesn't
pip install welly
This will also install the latest versions of striplog
and lasio
.
In [1]:
import welly
In [2]:
ls ../data/*.LAS
In [3]:
import lasio
l = lasio.read('../data/P-129.LAS') # Line 1.
That's it! But the object itself doesn't tell us much — it's really just a container:
In [4]:
l
Out[4]:
In [5]:
l.header['Well'] # Line 2.
Out[5]:
You can go in and find the KB if you know what to look for:
In [6]:
l.header['Parameter']['EKB']
Out[6]:
The curves are all present one big NumPy array:
In [7]:
l.data
Out[7]:
Or we can go after a single curve object:
In [8]:
l.curves.GR # Line 3.
Out[8]:
And there's a shortcut to its data:
In [9]:
l['GR'] # Line 4.
Out[9]:
...so it's easy to make a plot against depth:
In [10]:
import matplotlib.pyplot as plt
%matplotlib inline
plt.figure(figsize=(15,3))
plt.plot(l['DEPT'], l['GR'])
plt.show()
In [11]:
l.df().head() # Line 5.
Out[11]:
In [12]:
from welly import Well
w = Well.from_las('../data/P-129.LAS') # Line 6.
welly
Wells know how to display some basics:
In [13]:
w
Out[13]:
And the Well
object also has lasio
's access to a pandas DataFrame:
In [14]:
w.df().head()
Out[14]:
In [15]:
gr = w.data['GR'] # Line 7.
gr
Out[15]:
One important thing about Curves is that each one knows its own depths — they are stored as a property called basis
. (It's not actually stored, but computed on demand from the start depth, the sample interval (which must be constant for the whole curve) and the number of samples in the object.)
In [16]:
gr.basis
Out[16]:
In [17]:
gr.to_basis(start=300, stop=1000).plot() # Line 8.
In [18]:
sm = gr.smooth(window_length=15, samples=False) # Line 9.
sm.plot()
In [19]:
print("Data shape: {}".format(w.las.data.shape))
w.las.data
Out[19]:
But we might want to do some other things, such as specify which curves you want (optionally using aliases like GR1, GRC, NGC, etc for GR), resample the data, or specify a start and stop depth — welly
can do all this stuff. This method is also wrapped by Project.data_as_matrix()
which is nice because it ensures that all the wells are exported at the same sample interval.
Here are the curves in this well:
In [20]:
w.data.keys()
Out[20]:
In [21]:
keys=['CALI', 'DT', 'DTS', 'RHOB', 'SP']
In [22]:
w.plot(tracks=['TVD']+keys)
In [23]:
X, basis = w.data_as_matrix(keys=keys, start=275, stop=1850, step=0.5, return_basis=True)
In [24]:
w.data['CALI'].shape
Out[24]:
So CALI had 12,718 points in it... since we downsampled to 0.5 m and removed the top and tail, we should have substantially fewer points:
In [25]:
X.shape
Out[25]:
In [26]:
plt.figure(figsize=(15,3))
plt.plot(X.T[0])
plt.show()
In [27]:
w.location
Out[27]:
Let's look at some of the header:
# LAS format log file from PETREL
# Project units are specified as depth units
#==================================================================
~Version information
VERS. 2.0:
WRAP. YES:
#==================================================================
~WELL INFORMATION
#MNEM.UNIT DATA DESCRIPTION
#---- ------ -------------- -----------------------------
STRT .M 1.0668 :START DEPTH
STOP .M 1939.13760 :STOP DEPTH
STEP .M 0.15240 :STEP
NULL . -999.25 :NULL VALUE
COMP . Elmworth Energy Corporation :COMPANY
WELL . Kennetcook #2 :WELL
FLD . Windsor Block :FIELD
LOC . Lat = 45* 12' 34.237" N :LOCATION
PROV . Nova Scotia :PROVINCE
UWI. Long = 63* 45'24.460 W :UNIQUE WELL ID
LIC . P-129 :LICENSE NUMBER
CTRY . CA :COUNTRY (WWW code)
DATE. 10-Oct-2007 :LOG DATE {DD-MMM-YYYY}
SRVC . Schlumberger :SERVICE COMPANY
LATI .DEG :LATITUDE
LONG .DEG :LONGITUDE
GDAT . :GeoDetic Datum
SECT . 45.20 Deg N :Section
RANG . PD 176 :Range
TOWN . 63.75 Deg W :Township
Look at LOC and UWI. There are two problems:
We can fix this in two steps:
We'll define these in reverse because the remapping uses the transforming function.
In [28]:
import re
def transform_ll(text):
"""
Parses malformed lat and lon so they load properly.
"""
def callback(match):
d = match.group(1).strip()
m = match.group(2).strip()
s = match.group(3).strip()
c = match.group(4).strip()
if c.lower() in ('w', 's') and d[0] != '-':
d = '-' + d
return ' '.join([d, m, s])
pattern = re.compile(r""".+?([-0-9]+?).? ?([0-9]+?).? ?([\.0-9]+?).? +?([NESW])""", re.I)
text = pattern.sub(callback, text)
return welly.utils.dms2dd([float(i) for i in text.split()])
Make sure that works!
In [29]:
print(transform_ll("""Lat = 45* 12' 34.237" N"""))
In [30]:
remap = {
'LATI': 'LOC', # Use LOC for the parameter LATI.
'LONG': 'UWI', # Use UWI for the parameter LONG.
'LOC': None, # Use nothing for the parameter SECT.
'SECT': None, # Use nothing for the parameter SECT.
'RANG': None, # Use nothing for the parameter RANG.
'TOWN': None, # Use nothing for the parameter TOWN.
}
funcs = {
'LATI': transform_ll, # Pass LATI through this function before loading.
'LONG': transform_ll, # Pass LONG through it too.
'UWI': lambda x: "No UWI, fix this!"
}
In [31]:
w = Well.from_las('../data/P-129.LAS', remap=remap, funcs=funcs)
In [32]:
w.location.latitude, w.location.longitude
Out[32]:
In [33]:
w.uwi
Out[33]:
Let's just hope the mess is the same mess in every well. (LOL, no-one's that lucky.)
© 2017 agilescientific.com and licensed CC-BY 4.0