Marker Collections#

This section offers an overview of the different functions and properties available for the Petrel Marker collection objects.

The cells below show how a marker collection can be accessed, converted to a DataFrame and then how to do different operations including:

  • adding a new marker to a well

  • changing marker attribute values such as depths

  • creating a new marker attribute


All data used in this notebook comes from the FORCE wells dataset:

NPD, 2021, FORCE 2020 Lithology Machine Learning Competition Results: https://www.npd.no/en/force/Previous-events/results-of-the-FORCE-2020-lithology-competition/

[43]:
import numpy as np

from cegalprizm.pythontool import PetrelConnection
[44]:
petrel = PetrelConnection(allow_experimental=True)
print('Connected to {}'.format(petrel.get_current_project_name()))
Connected to ForceDataset.pet

Check out the API documentation to view a detailed description of all the functions and properties available for marker collections.

To retrieve all the marker collections from our Petrel project we can use the .markercollections property. This property returns all the marker collections within the project in a dictionary where the keys represent the path of the marker collection within the Petrel input tree and the value represents the name of the marker collection.

[45]:
petrel.markercollections
[45]:
MarkerCollections({'Input/Formations': MarkerCollection(petrel_name="Formations"), 'Input/Groups': MarkerCollection(petrel_name="Groups"), 'Input/Casing': MarkerCollection(petrel_name="Casing")})

Using list comprehension, the marker collections can be viewed as a list:

[46]:
marker_list = [mc for mc in petrel.markercollections]
marker_list
[46]:
[MarkerCollection(petrel_name="Formations"),
 MarkerCollection(petrel_name="Groups"),
 MarkerCollection(petrel_name="Casing")]

Select the first marker in the marker list and get its path using the .path property:

[47]:
marker_path = marker_list[0].path
marker_path
[47]:
'Input/Formations'

Let’s access the marker collection that is in position 0 of a list of marker collections from the path we grabbed above:

[48]:
mc = petrel.markercollections[marker_path]
mc
[48]:
MarkerCollection(petrel_name="Formations")

Like other Petrel objects, the API lets us view the statistics for our marker collection with the .retrieve_stats() function:

[38]:
mc.retrieve_stats()
[38]:
{'Lat Delta': '0.0131189999999961',
 'Y Min': '6781405.1',
 'Number of points': '6',
 'Type of data': 'Continuous',
 'Number of horizons': '0',
 'MD Max': '2043.37',
 'TWT auto Max': '1860.14',
 'TWT auto Min': '594.64',
 'Sum': '-10126.60',
 'X Min': '456403.92',
 'Number of attributes': '30',
 'Min': '-1962.04',
 'Long Max': '2.19488597222222',
 'Z Max': '-630.67',
 'Lat Max': '61.1763947222222',
 'Long Delta': '0.00541113888888889',
 'Z Delta': '1331.37',
 'Number of faults': '0',
 'X Delta': '273.130000000005',
 'Y Max': '6782863.23',
 'TWT auto Delta': '1265.5',
 'Delta': '1331.36',
 '-----------------': '-----------------',
 'Lat Min': '61.1632757222222',
 'MD Min': '50',
 'Number of wells': '3',
 'Max': '-630.67',
 'Long Min': '2.18947483333333',
 'MD Delta': '1993.37',
 'Number of defined values': '6',
 'Variance': '269445.28',
 'Z Min': '-1962.04',
 'X Max': '456677.05',
 'Y Delta': '1458.13000000082',
 'Mean': '-1687.77',
 'Number of others': '5',
 'Std. dev.': '519.08'}

Marker collection comments#

We can access comments on the marker collection using the .comments property.

At first we have no comments. The marker collection attribute comments therefore returns an empty string ’ ’

[9]:
mc.comments
[9]:
''

We can add a comment using the add_comment() function. It is important to set the .readonly property on the marker collection as False:

[10]:
mc.readonly = False
mc.add_comment(new_comment = 'Formation data from NPD')

Checking the comments attribute again we can see that the comment has been added:

[11]:
mc.comments
[11]:
'Formation data from NPD'

Convert to a DataFrame#

Marker collections can also be converted to a DataFrame using the .as_dataframe() function.

[12]:
mc_df = mc.as_dataframe()
mc_df.head(10)
[12]:
Petrel index Well identifier (Well name) Well identifier (UWI) Surface X Y Z TWT picked TWT auto Geological age ... Dip azimuth coherence PVD auto Interpreter Observation number Used by dep.conv. Used by geo mod Zone log Edited by user Symbol Locked to fault
0 1 A15 Zone 2 in zone log 456655.105580 6.781525e+06 -1857.486639 NaN 1750.520592 NaN ... 0.995633 -1857.486639 Lisa Casteleyn <NA> True True <NA> False 0 0
1 2 A15 Zone 3 in zone log 456656.927491 6.781516e+06 -1866.088922 NaN 1761.698040 NaN ... 0.976220 -1866.088922 Lisa Casteleyn <NA> True True <NA> False 0 0
2 3 A15 Zone 4 in zone log 456659.564876 6.781502e+06 -1878.146458 NaN 1777.589272 NaN ... 0.978196 -1878.146458 Lisa Casteleyn <NA> True True <NA> False 0 0
3 4 A15 Zone 6 in zone log 456665.802997 6.781469e+06 -1906.155845 NaN 1806.465154 NaN ... 0.598988 -1906.155845 Lisa Casteleyn <NA> True True <NA> False 0 0
4 5 A15 Zone 7 in zone log 456675.778375 6.781413e+06 -1955.577733 NaN 1853.927886 NaN ... 0.909592 -1955.577733 Lisa Casteleyn <NA> True True <NA> False 0 0

5 rows × 30 columns

[13]:
[col for col in mc_df.columns]
[13]:
['Petrel index',
 'Well identifier (Well name)',
 'Well identifier (UWI)',
 'Surface',
 'X',
 'Y',
 'Z',
 'TWT picked',
 'TWT auto',
 'Geological age',
 'MD',
 'Confidence factor',
 'Dip angle',
 'Dip azimuth',
 'Missing',
 'TVT',
 'TST',
 'TVT zone',
 'TST zone',
 'Dip azimuth average',
 'Dip azimuth coherence',
 'PVD auto',
 'Interpreter',
 'Observation number',
 'Used by dep.conv.',
 'Used by geo mod',
 'Zone log',
 'Edited by user',
 'Symbol',
 'Locked to fault']

Marker stratigraphies#

We can get a marker stratigraphy by using the .stratigraphies property. We can use list comprehension and slicing to view the first ten stratigraphies:

[14]:
stratigraphies = [strat for strat in mc.stratigraphies]
stratigraphies[0:10]
[14]:
[MarkerStratigraphy("Zone 2 in zone log"),
 MarkerStratigraphy("Zone 3 in zone log"),
 MarkerStratigraphy("Zone 4 in zone log"),
 MarkerStratigraphy("Zone 6 in zone log"),
 MarkerStratigraphy("Zone 7 in zone log")]
[15]:
mc.stratigraphies[3]
[15]:
MarkerStratigraphy("Zone 6 in zone log")
[16]:
type(mc.stratigraphies[3])
[16]:
cegalprizm.pythontool.markerstratigraphy.MarkerStratigraphy

Marker attributes#

We can access the attributes for the marker collection using the .attributes property. We can use list comprehension and slicing to view the first ten attributes:

[17]:
attributes = [attr for attr in mc.attributes]
attributes[0:10]
[17]:
[MarkerAttribute("Z"),
 MarkerAttribute("TWT picked"),
 MarkerAttribute("TWT auto"),
 MarkerAttribute("Geological age"),
 MarkerAttribute("MD"),
 MarkerAttribute("Confidence factor"),
 MarkerAttribute("Dip angle"),
 MarkerAttribute("Dip azimuth"),
 MarkerAttribute("Missing"),
 MarkerAttribute("TVT")]
[18]:
depth = mc.attributes["MD"]

We can then pick up the MD attribute and then convert it to a DataFrame using the .as_dataframe() function:

[19]:
depth_df = depth.as_dataframe()
depth_df.head()
[19]:
Petrel index Well identifier (Well name) Well identifier (UWI) Surface Value
0 1 A15 Zone 2 in zone log 1882.415
1 2 A15 Zone 3 in zone log 1895.605
2 3 A15 Zone 4 in zone log 1914.160
3 4 A15 Zone 6 in zone log 1957.405
4 5 A15 Zone 7 in zone log 2033.370

Changing the value of an attribute#

We can change the value of an attribute by using the set_value() function. When changing the value of one marker the set_value() function takes in a data array of the new value to be set, the marker stratigraphy and a wellbore object.

Lets take the first marker from the depth_df DataFrame above by selecting the Surface value from the first row. By adding an if clause in the list comprehension we can find the right stratigraphy from all of the available marker stratigraphies:

[20]:
strat = [i for i in mc.stratigraphies if depth_df.loc[0]['Surface'] in str(i)][0]
strat
[20]:
MarkerStratigraphy("Zone 2 in zone log")

Now we can pick up the well value for the first row of the depth_df DataFrame and select its wellbore object from Petrel:

[21]:
well = [i for i in petrel.wells if depth_df.loc[0]['Well identifier (Well name)'] in i.petrel_name][0]
well
[21]:
Well(petrel_name="A15")

We can grab the original depth value from the DataFrame we created from the MD attribute above by selecting the Value column from the first row of the depth_df DataFrame, which returns a float. The set_values() function takes in an array, so we convert this single depth value to a numpy array using the np.array() function.

In the set_values() method we can pass in the original depth + 1, the marker stratigraphy and the wellbore:

[40]:
original_depth = np.array([depth_df.loc[0].Value])
original_depth
[40]:
array([1882.415])
[22]:
depth.set_values(data = original_depth+1,
                 include_unconnected_markers = True,
                 marker_stratigraphy = strat,
                 well = well
                )

To check that the value has changed, we can create a new marker collection object, pick up the MD attribute and then get a DataFrame. The depth value has now increased by 1.

[41]:
mc2 = petrel.markercollections[marker_path]
depth2 = mc2.attributes["MD"]
depth2.as_dataframe().head()
[41]:
Petrel index Well identifier (Well name) Well identifier (UWI) Surface Value
0 1 A15 Zone 2 in zone log 1892.415
1 2 A15 Zone 3 in zone log 1905.605
2 3 A15 Zone 4 in zone log 1924.160
3 4 A15 Zone 6 in zone log 1967.405
4 5 A15 Zone 7 in zone log 2043.370

When changing the values for every marker in the marker collection, an array of values can be passed to the data parameter in the set_values() function. The array must be the same length as the original marker collection, otherwise the marker stratigraphy and well values must be specified.

In this example we take the original depth values and simply add 10.

Note that when we access all the values in the depth_df DataFrame, it already returns an array.

[24]:
depth.set_values(data = depth_df.Value.values+10,
                 include_unconnected_markers = True
                )

To check that this worked as expected, we pick up the marker collection again and convert to a DataFrame:

[25]:
mc_plus10 = petrel.markercollections[marker_path]
depth_plus10 = mc_plus10.attributes["MD"].as_dataframe()
depth_plus10.head()
[25]:
Petrel index Well identifier (Well name) Well identifier (UWI) Surface Value
0 1 A15 Zone 2 in zone log 1892.415
1 2 A15 Zone 3 in zone log 1905.605
2 3 A15 Zone 4 in zone log 1924.160
3 4 A15 Zone 6 in zone log 1967.405
4 5 A15 Zone 7 in zone log 2043.370

Adding a new attribute to a marker collection#

Using the .add_attribute() function we can add a new attribute to the marker collection. This function takes in the data that we want to add, the name of the attribute nad the data type (continuous or false). Furthermore, we can provide a MarkerStratigraphy to include only markers for one specified stratigraphy. Lastly, we can also provide a well to include only markers for a specified well:

[26]:
mc_plus10.readonly = False
mc_plus10.add_attribute(depth_plus10.Value.values/1000, 'kms', 'continuous' )
mc_add_attr = petrel.markercollections[marker_path]
[i for i in mc_add_attr.attributes if 'kms' in i.petrel_name]
[26]:
[MarkerAttribute("kms")]
[27]:
mc_plus10.attributes['kms'].as_dataframe().head()
[27]:
Petrel index Well identifier (Well name) Well identifier (UWI) Surface Value
0 1 A15 Zone 2 in zone log 1.892415
1 2 A15 Zone 3 in zone log 1.905605
2 3 A15 Zone 4 in zone log 1.924160
3 4 A15 Zone 6 in zone log 1.967405
4 5 A15 Zone 7 in zone log 2.043370

Adding a marker to a marker collection#

We can add a new marker to a marker collection by using the add_marker() function which takes in three parameters

  1. well: a wellbore petrel object of type cegalprizm.pythontool.borehole.Well

  2. marker_stratigraphy: a marker stratigraphy of type cegalprizm.pythontool.markerstratigraphy.MarkerStratigraphy

  3. measured_depth

First we can grab a Well object using petrel.wells for the well that occurs first in our marker collection (first row):

[28]:
well_name = depth_df.loc[0]['Well identifier (Well name)']
well = petrel.wells[[i.path for i in petrel.wells if well_name in i.petrel_name][0]]
well
[28]:
Well(petrel_name="A15")
[29]:
type(well)
[29]:
cegalprizm.pythontool.borehole.Well

Lets take a look at the existing markers for this well.

[30]:
mc_df.loc[mc_df['Well identifier (Well name)']==well.petrel_name][['Well identifier (Well name)',
                                                                   'Surface',
                                                                   'MD',]]
[30]:
Well identifier (Well name) Surface MD
0 A15 Zone 2 in zone log 1882.415
1 A15 Zone 3 in zone log 1895.605
2 A15 Zone 4 in zone log 1914.160
3 A15 Zone 6 in zone log 1957.405
4 A15 Zone 7 in zone log 2033.370

We can find which stratigraphies that do not exist in this well and select the first one

[31]:
all_stratigraphies = [i for i in mc.stratigraphies]
well_stratigraphies = [i for i in mc_df.Surface]
stratigraphy = list(set(all_stratigraphies)-set(well_stratigraphies))[0]
stratigraphy
[31]:
MarkerStratigraphy("Zone 2 in zone log")

A new marker can be added for the well, using the add_marker() function and passing in the wellbore object for the well, the marker_stratigraphy we want to add and a measured depth:

[32]:
mc.readonly=False
mc.add_marker(well=well, marker_stratigraphy=stratigraphy, measured_depth=50)

To check if the new marker has been added, the marker collection can be picked up again as a DataFrame and then filtered down to the well of interest

[33]:
mc_added_marker = petrel.markercollections[marker_path]
mc_df_added_marker = mc_added_marker.as_dataframe()
mc_df_added_marker.loc[mc_df_added_marker['Well identifier (Well name)']==well.petrel_name][['Well identifier (Well name)',
                                                                   'Surface',
                                                                   'MD',]]
[33]:
Well identifier (Well name) Surface MD
0 A15 Zone 2 in zone log 1892.415
1 A15 Zone 3 in zone log 1905.605
2 A15 Zone 4 in zone log 1924.160
3 A15 Zone 6 in zone log 1967.405
4 A15 Zone 7 in zone log 2043.370
5 A15 Zone 2 in zone log 50.000