Skip to content

Commit fa6f934

Browse files
committed
lasio update
Subclass LASFile from lasio for more complete log object with better read / write compatability. Include edits for all petrophysical calculations and log viewers to accept the new data type.
1 parent 3d9b1f7 commit fa6f934

File tree

7 files changed

+543
-1085
lines changed

7 files changed

+543
-1085
lines changed

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,15 @@
44

55
# PetroPy
66

7-
A petrophysics with python package allowing scientific python computing of conventional and unconventional formation evaluation. Reads las files and creates a [pandas](http://pandas.pydata.org) dataframe of the log data. Includes a basic petrophysical workflow and a simple log viewer based on XML templates.
7+
A petrophysics with python package allowing scientific python computing of conventional and unconventional formation evaluation. Reads las files using [lasio](https://github.com/kinverarity1/lasio). Includes a petrophysical workflow and a log viewer based on XML templates.
88

99
<div align="center">
1010
<img src="https://github.com/toddheitmann/PetroPy/blob/master/university_6-17_no1.png"><br>
1111
</div>
1212

1313
## Requirements
1414

15+
- [lasio](https://github.com/kinverarity1/lasio)
1516
- [numpy](http://www.numpy.org)
1617
- [scipy](https://www.scipy.org)
1718
- [pandas](http://pandas.pydata.org)
@@ -32,7 +33,7 @@ The basic workflow in the module is functional, but requires downloading source
3233
- [x] Curve edit manual manipulation
3334
- [x] Curve edit module for shifting data
3435
- [x] Electrofacies module
35-
- [ ] Replace log object by subclassing [lasio](https://github.com/kinverarity1/lasio) object
36+
- [x] Replace log object by subclassing [lasio](https://github.com/kinverarity1/lasio) object
3637
- [ ] Add to pypi package registry
3738

3839
### Examples

petropy/datasets.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"""
66

77
import os
8-
from las import Las
8+
from log import Log
99

1010
def log_data(source):
1111
"""
@@ -43,7 +43,7 @@ def log_data(source):
4343
else:
4444
raise ValueError('%s is not valid source' % source)
4545

46-
log = Las(las_path)
46+
log = Log(las_path)
4747
log.tops_from_csv(tops[source])
4848

4949
return log

petropy/download.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,12 @@ def kgs_download(save_dir = None):
9292
saves them in the folder data/kgs.
9393
9494
95+
Parameters
96+
----------
97+
save_dir : str (default None)
98+
path to directory to save data. defaults to data folder within petropy
99+
100+
95101
Example
96102
-------
97103
>>> from petropy import kgs_download

petropy/electrofacies.py

Lines changed: 80 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,25 +10,71 @@
1010
from sklearn.decomposition import PCA
1111
from sklearn.cluster import MiniBatchKMeans
1212

13-
from log import Parameter
14-
15-
def electrofacies(logs, curves, formations, n_clusters, log_scale = [], n_components = 0.85):
13+
def electrofacies(logs, formations, curves, n_clusters, log_scale = [], n_components = 0.85, curve_name = 'FACIES'):
14+
"""
15+
Electrofacies function to group intervals by rock type. Also referred to as heterogenous rock analysis.
16+
17+
Parameters
18+
----------
19+
logs : list of Log objects
20+
List of Log objects
21+
formations: list of formation names
22+
List of strings containg formation names which should be previously loaded into Log objects
23+
curves : list of curve names
24+
List of strings containing curve names as inputs in the electrofacies calculations
25+
n_clusters : int
26+
Number of clusters to group intervals. Number of electrofacies.
27+
log_scale : list of curve names
28+
List of string containing curve names which are preprocessed on a log scale. For example, deep
29+
resistivity separates better on a log scale, and is graph logarithmically when viewing data in
30+
a log viewer.
31+
n_components : int, float, None or string (default 0.85)
32+
Number of principal components to keep. If value is less than one, the number of principal
33+
components be the number required to exceed the explained variance.
34+
curve_name : str (default 'FACIES')
35+
Name of the new electrofacies curve.
36+
37+
Example
38+
-------
39+
>>> # loads sample Wolfcamp calculates electrofacies for that well
40+
>>> import petropy as ptr
41+
>>> log = ptr.log_data('WFMP') # reads sample Wolfcamp Log from las file
42+
>>> logs = [log]
43+
>>> f = ['WFMPA', 'WFMPB', 'WFMPC']
44+
>>> c = ['GR_N', 'RESDEEP_N', 'RHOB_N', 'NPHI_N', 'PE_N']
45+
>>> scale = ['RESDEEP_N']
46+
>>> logs = electrofacies(logs, f, c, 8, log_scale = scale)
47+
48+
>>> # loads logs from a list of paths and calculates electrofacies across the wells
49+
>>> import petropy as ptr
50+
>>> file_paths = ['path/to/log1.las', 'path/to/log2.las', 'path/to/log3.las']
51+
>>> logs = [ptr.Log(x) for x in file_paths] # create list of Log objects
52+
>>> tops_csv = 'path/to/tops.csv'
53+
>>> for log in logs:
54+
log.tops_from_csv(tops_csv) # add formation tops to wells
55+
>>> f = ['FORM1', 'FORM2'] # list of formation tops. If single formation use f = ['FORM']
56+
>>> c = ['GR_N', 'RESDEEP_N', 'RHOB_N', 'NPHI_N', 'PE_N']
57+
>>> scale = ['RESDEEP_N']
58+
>>> logs = electrofacies(logs, f, c, 8, log_scale = scale) # run electrofacies across logs in list
59+
60+
"""
1661

1762
df = pd.DataFrame()
1863

1964
for log in logs:
2065

21-
if log.uwi == '':
66+
if log.well['UWI'] is None:
2267
raise ValueError('UWI required for log identification.')
2368

24-
log.curve_df['UWI'] = log.uwi
69+
log_df = log.df()
70+
log_df['UWI'] = log.well['UWI'].value
71+
log_df['DEPTH_INDEX'] = np.arange(0, len(log[0]))
2572

2673
for formation in formations:
2774
top = log.tops[formation]
2875
bottom = log.next_formation_depth(formation)
29-
df = df.append(log.curve_df[(log.curve_df.DEPTH >= top) & (log.curve_df.DEPTH < bottom)])
30-
31-
log.curve_df.drop(['UWI'], 1, inplace = True)
76+
depth_index = np.intersect1d(np.where(log[0] >= top)[0], np.where(log[0] < bottom)[0])
77+
df = df.append(log_df.iloc[depth_index])
3278

3379
for s in log_scale:
3480
df[s] = np.log(df[s])
@@ -42,35 +88,47 @@ def electrofacies(logs, curves, formations, n_clusters, log_scale = [], n_compon
4288
components = pd.DataFrame(data = pc.transform(X), index = df[not_null_rows].index)
4389
components.columns = ['PC%i' % x for x in range(1, pc.n_components_ + 1)]
4490
components['UWI'] = df.loc[not_null_rows, 'UWI']
91+
components['DEPTH_INDEX'] = df.loc[not_null_rows, 'DEPTH_INDEX']
4592

4693
size = len(components) // 20
4794
if size > 10000:
4895
size = 10000
4996
elif size < 100:
5097
size = 100
5198

52-
facies = MiniBatchKMeans(n_clusters = n_clusters, batch_size = size).fit_predict(components)
53-
54-
df.loc[not_null_rows, 'FACIES'] = facies
99+
df.loc[not_null_rows, curve_name] = MiniBatchKMeans(n_clusters = n_clusters, batch_size = size).fit_predict(components)
55100

56101
for log in logs:
57102

58-
uwi = log.uwi
103+
uwi = log.well['UWI'].value
59104

60105
for v, vector in enumerate(pc.components_):
61106
v += 1
62-
column = 'PC%i' % v
107+
pc_curve = 'PC%i' % v
63108

64109
### add eigenvector data to header ###
65110

66-
pc_parameter = Parameter(name = column, unit = '', des = 'Principal Component from electrofacies')
67-
log.curve_parameters.append(pc_parameter)
68-
log.curve_values.append(column)
69-
log.curve_df = log.curve_df.join(components[components.UWI == uwi][column])
70-
71-
facies_parameter = Parameter(name = 'FACIES', unit = '', des = 'Electrofacies')
72-
log.curve_parameters.append(facies_parameter)
73-
log.curve_values.append('FACIES')
74-
log.curve_df = log.curve_df.join(df[df.UWI == uwi]['FACIES'])
111+
if pc_curve in log.keys():
112+
data = log[pc_curve]
113+
depth_index = components.loc[components.UWI == uwi, 'DEPTH_INDEX']
114+
data[depth_index] = np.copy(components.loc[components.UWI == uwi, pc_curve])
115+
else:
116+
data = np.empty(len(log[0]))
117+
data[:] = np.nan
118+
depth_index = components.loc[components.UWI == uwi, 'DEPTH_INDEX']
119+
data[depth_index] = np.copy(components.loc[components.UWI == uwi, pc_curve])
120+
log.add_curve(pc_curve, np.copy(data),
121+
descr = 'Pricipal Component %i from electrofacies' % v)
122+
123+
if curve_name in log.keys():
124+
data = log[curve_name]
125+
depth_index = df.loc[df.UWI == uwi, 'DEPTH_INDEX']
126+
data[depth_index] = df.loc[df.UWI == uwi, curve_name]
127+
else:
128+
data = np.empty(len(log[0]))
129+
data[:] = np.nan
130+
depth_index = components.loc[components.UWI == uwi, 'DEPTH_INDEX']
131+
data[depth_index] = np.copy(components.loc[components.UWI == uwi, pc_curve])
132+
log.add_curve(curve_name, np.copy(data), descr = 'Electrofacies')
75133

76134
return logs

0 commit comments

Comments
 (0)