diff --git a/pyborehole/logs_las.py b/pyborehole/logs_las.py index 9a3fce1..e769bec 100644 --- a/pyborehole/logs_las.py +++ b/pyborehole/logs_las.py @@ -7,6 +7,7 @@ import copy from pyborehole.plot import plot_well_logs as pwl +from pyborehole.utils import get_depth_ranges as gdr class LASLogs: @@ -340,6 +341,9 @@ def __init__(self, self.df.index.rename('DEPTH', inplace=True) self.df = self.df.reset_index() + # Getting depth ranges + self.log_ranges = gdr(self.df) + # Creating DataFrame from curve data self.curves = pd.DataFrame(list(zip([las.curves[i]['original_mnemonic'] for i in range(len(las.curves))], [las.curves[i]['mnemonic'] for i in range(len(las.curves))], @@ -354,6 +358,9 @@ def __init__(self, self.curves.at[0, 'original_mnemonic'] = 'DEPTH' self.curves.at[0, 'mnemonic'] = 'DEPTH' + # Merging Curves DataFrame and Depth Ranges + self.curves = self.curves.merge(self.log_ranges, on='mnemonic') + # Creating DataFrame from well header self.well_header = pd.DataFrame(list(zip([las.well[i]['mnemonic'] for i in range(len(las.well))], [las.well[i]['unit'] for i in range(len(las.well))], @@ -467,7 +474,6 @@ def plot_well_logs(self, return fig, ax - def plot_well_log_along_path(self, log: str, depth_column: str = 'DEPTH', @@ -1122,4 +1128,4 @@ def calculate_synthetic_seismic(self, rc_tdom): synth = np.apply_along_axis(lambda t0: np.convolve(t0, w, mode="same"), axis=0, arr=rc_tdom) - return synth \ No newline at end of file + return synth diff --git a/pyborehole/logs_lis.py b/pyborehole/logs_lis.py index 8c2d76c..e755058 100644 --- a/pyborehole/logs_lis.py +++ b/pyborehole/logs_lis.py @@ -3,6 +3,9 @@ import numpy as np import matplotlib +from pyborehole.plot import plot_well_logs as pwl +from pyborehole.utils import get_depth_ranges as gdr + class LISLogs: """Class to initiate a Well Log Object. @@ -192,9 +195,12 @@ def __init__(self, self.df = df # Changing the name of the depth column - self.df['DEPTH'] = self.df['DEPT'] + self.df.insert(0, 'DEPTH', self.df['DEPT']) self.df = self.df.drop('DEPT', axis=1) + # Getting depth ranges + self.log_ranges = gdr(self.df) + # Creating Curves DataFrame self.curves = pd.DataFrame(list(zip(columns, columns, columns, units)), columns=['original_mnemonic', @@ -202,6 +208,13 @@ def __init__(self, 'descr', 'unit']) + # Changing name of depth column in curves DataFrame + self.curves.at[0, 'original_mnemonic'] = 'DEPTH' + self.curves.at[0, 'mnemonic'] = 'DEPTH' + + # Merging Curves DataFrame and Depth Ranges + self.curves = self.curves.merge(self.log_ranges, on='mnemonic') + # Getting index of depth column depth_column_index = columns.index('DEPT') @@ -230,3 +243,88 @@ def __init__(self, 'value', 'descr']) + def plot_well_logs(self, + tracks: Union[str, list] = None, + depth_column: str = 'DEPTH', + depth_min: Union[int, float] = None, + depth_max: Union[int, float] = None, + colors: Union[str, list] = None, + add_well_tops: bool = False, + add_well_design: bool = False, + fill_between: int = None, + add_net_to_gross: int = None, + fontsize_well_tops: Union[int, float] = 12, + use_df_tdom: bool = False) -> Tuple[matplotlib.figure.Figure, matplotlib.axes.Axes]: + """Plot well logs. + + Parameters + __________ + + tracks : Union[str, list] + Name/s of the logs to be plotted, e.g. ``tracks='SGR'`` or ``tracks=['SGR', 'K']``. + depth_column : str, default: ``'DEPTH'`` + Name of the column holding the depths, e.g. ``depth_column='DEPTH'``. + depth_min : Union[int, float] + Minimum depth to be plotted, e.g. ``depth_min=0``. + depth_max : Union[int, float] + Maximum depth to be plotted, e.g. ``depth_max=2000``. + colors : Union[str, list], default: ``None`` + Colors of the logs, e.g. ``colors='black'`` or ``colors=['black', 'blue']``. + add_well_tops : bool, default: ``False`` + Boolean to add well tops to the plot, e.g. ``add_well_tops=True``. + add_well_design : bool, default: ``False`` + Boolean to add well design to the plot, e.g. ``add_well_design=True``. + fill_between : int, default: ``None`` + Number of the axis to fill, e.g. ``fill_between=0``. + add_net_to_gross : int, default: ``None`` + Number of axis to fill with the net to gross values, e.g. ``add_net_to_gross=0``. + + Returns + _______ + fig : matplotlib.figure.Figure + Matplotlib Figure. + ax : matplotlib.axes.Axes + Matplotlib Axes. + + Raises + ______ + TypeError + If the wrong input data types are provided. + ValueError + If no well tops are provided but ``add_well_tops`` is set to ``True``. + ValueError + If the wrong column names are provided. + + Examples + ________ + >>> import pyborehole + >>> from pyborehole.borehole import Borehole + >>> borehole = Borehole(name='Weisweiler R1') + >>> borehole.init_properties(location=(6.313031, 50.835676), crs='EPSG:4326', altitude_above_sea_level=136) + >>> borehole.add_well_logs(path='Well_logs.las') + >>> borehole.logs.plot_well_logs(tracks=['SGR', 'CAL'], depth_column='DEPTH', colors=['black', 'red']) + + See Also + ________ + plot_well_log_along_path : Plot well log along path. + calculate_vshale : Calculate Shale Volume. + calculate_vshale_linear : Calculate Shale Volume linear method. + calculate_net_to_gross : Calculate net to gross. + + .. versionadded:: 0.0.1 + """ + + fig, ax = pwl(self, + tracks=tracks, + depth_column=depth_column, + depth_min=depth_min, + depth_max=depth_max, + colors=colors, + add_well_tops=add_well_tops, + add_well_design=add_well_design, + fill_between=fill_between, + add_net_to_gross=add_net_to_gross, + fontsize_well_tops=fontsize_well_tops, + use_df_tdom=use_df_tdom) + + return fig, ax \ No newline at end of file diff --git a/pyborehole/plot.py b/pyborehole/plot.py index 476680a..dc70580 100644 --- a/pyborehole/plot.py +++ b/pyborehole/plot.py @@ -1,4 +1,4 @@ -from typing import Union, Tuple +from typing import Union, Tuple, List import matplotlib import matplotlib.pyplot as plt import numpy as np @@ -209,7 +209,7 @@ def plot_well_logs(logs, # Creating plot fig, ax = plt.subplots(1, 1 + j + k + l, - figsize=(1 + j + k + l * 1.8, 8), + figsize=((1 + j + k + l) * 1.8, 8), sharey=True) if not add_well_tops and not add_net_to_gross: @@ -354,6 +354,8 @@ def plot_well_logs(logs, 'unit'].iloc[ 0], color='black') + plt.tight_layout() + return fig, ax # Creating plot if tracks is of type list @@ -609,3 +611,90 @@ def plot_well_logs(logs, ax.set_xlabel('Diameter [in]') return fig, ax + + +def plot_logs_multiple_wells(wells: list, + params: List[dict]): + # Checking that the provided lists have the same length + if len(wells) != len(params): + raise ValueError('The same number of well objects and params dicts must be provided') + + min_depths = [] + max_depths = [] + buffers = [] + dfs = [] + + colors = ['black','black','black', 'black'] + + fig, ax = plt.subplots(1, + len(wells), + figsize=(len(wells) * 1.8, 8) + ) + + # Setting y label + ax[0].set_ylabel('Depth [m]') + + for i in range(len(params)): + + # Selecting depth range + if 'depth_min' not in params[i]: + depth_min = min(wells[i].logs.df[params[i]['depth_column']]) + min_depths.append(depth_min) + else: + depth_min = params[i]['depth_min'] + min_depths = depth_min + if 'depth_max' not in params[i]: + depth_max = max(wells[i].logs.df[params[i]['depth_column']]) + max_depths.append(depth_max) + else: + depth_max = params[i]['depth_max'] + max_depths = depth_max + + # Setting buffer for plotting + buffer = (depth_max - depth_min) / 20 + buffers.append(buffer) + + # Selecting depth range + df = wells[i].logs.df[(wells[i].logs.df[params[i]['depth_column']] >= depth_min) & ( + wells[i].logs.df[params[i]['depth_column']] <= depth_max)] + + dfs.append(df) + + ax[i].plot(dfs[i][params[i]['track']], + dfs[i][params[i]['depth_column']], color='black', linewidth=0.5) + + # Setting Grid + ax[i].grid() + + # Inverting y axis + ax[i].invert_yaxis() + + # Setting y limits + ax[i].set_ylim(depth_max + buffer, + depth_min - buffer) + + # Setting x limits + ax[i].set_xlim(params[i]['xmin'], + params[i]['xmax']) + + # Setting tick params + ax[i].tick_params(top=True, labeltop=True, bottom=False, labelbottom=False) + + # Setting x label position + ax[i].xaxis.set_label_position('top') + + # Setting x label + ax[i].set_xlabel(params[i]['track'] + ' [%s]' % + wells[i].logs.curves[wells[i].logs.curves['original_mnemonic'] == params[i]['track']].reset_index( + drop=True)[ + 'unit'].iloc[0], + color='black' if isinstance(colors[i], type(None)) else colors[i]) + + # Setting title + ax[i].set_title(wells[i].name) + + plt.tight_layout() + + return fig, ax + + diff --git a/pyborehole/utils.py b/pyborehole/utils.py new file mode 100644 index 0000000..61fd94e --- /dev/null +++ b/pyborehole/utils.py @@ -0,0 +1,35 @@ +import pandas as pd + +# Defining function to get depth ranges +def get_depth_ranges(df: pd.DataFrame) -> pd.DataFrame: + + # Creating empty lists + column_name_list = [] + min_depth_list = [] + max_depth_list = [] + + for i in range(len(df.columns)): + # Getting column name + column_name = df.columns[i] + + # Selecting Data + data = df[[df.columns[i]] + ['DEPTH']].dropna() + + # Getting minimum depth of log + min_depth = min(data.loc[:, ~data.columns.duplicated()].copy()['DEPTH']) + + # Getting maximum depth of log + max_depth = max(data.loc[:, ~data.columns.duplicated()].copy()['DEPTH']) + + # Appending values to list + column_name_list.append(column_name) + min_depth_list.append(min_depth) + max_depth_list.append(max_depth) + + data_dict = {'mnemonic': column_name_list, + 'Depth Min': min_depth_list, + 'Depth Max': max_depth_list} + + data_df = pd.DataFrame.from_dict(data=data_dict, orient='columns')#, columns=['original_mnemonic', 'Depth Min', 'Depth Max']) + + return data_df \ No newline at end of file