3D Visualization of Well Lithology with Python, Pyvista and VTK - Tutorial

view2.png

There are standards for the lithology descriptions, but there are no standards about how to store lithological information and relate it to the drilling position. This disorder leads to the use of many formats and data files related to open and proprietary software.

In the search of “one tool that manages all tools”, as a similar concept of the “one ring that rule them all” from the Lord of the Rings (J.R.R Tolkien), we found that Python and its libraries: Pandas, Pyvista and VTK can do a decent job on the compilation, geotransformation, spatial location, and 3d geometry generation.

This tutorial deals with the 3D visualization as Vtk files on Paraview of the lithological information from hundreds of wells located on the Snake River - Idaho. The tutorial covers all steps from the download of the raw information processing to the list and arrays generation for the vtk file. The scripting work was done on a Jupyter Nobebook and the output 3D files were plotted on Paraview.

Video



Scripts

This is the whole Python script used for the tutorial:

import pandas as pd
import pyvista as pv
import numpy as np
import vtk
#import well location
wellLoc = pd.read_csv('../inputData/TV-HFM_Wells_1Location.csv',header=1,index_col=0,
                      usecols=[0,2,3,6],skipfooter=1,engine='python')
wellLoc.head()
Easting Northing Altitude_ft
Bore
A. Isaac 2333140.95 1372225.65 3204.0
A. Woodbridge 2321747.00 1360096.95 2967.2
A.D. Watkins 2315440.16 1342141.86 3168.3
A.L. Clark; 1 2276526.30 1364860.74 2279.1
A.L. Clark; 2 2342620.87 1362980.46 3848.6
#change coordinate system and elevation
from pyproj import Transformer
transformer = Transformer.from_crs('esri:102605','epsg:32611',always_xy=True)
points = list(zip(wellLoc.Easting,wellLoc.Northing))
coordsWgsUTM = np.array(list(transformer.itransform(points)))
wellLoc['EastingUTM']=coordsWgsUTM[:,0]
wellLoc['NorthingUTM']=coordsWgsUTM[:,1]
wellLoc['Elevation_m']=wellLoc['Altitude_ft']*0.3048
wellLoc.head()
Easting Northing Altitude_ft EastingUTM NorthingUTM Elevation_m
Bore
A. Isaac 2333140.95 1372225.65 3204.0 575546.628834 4.820355e+06 976.57920
A. Woodbridge 2321747.00 1360096.95 2967.2 564600.366582 4.807827e+06 904.40256
A.D. Watkins 2315440.16 1342141.86 3168.3 558944.843404 4.789664e+06 965.69784
A.L. Clark; 1 2276526.30 1364860.74 2279.1 519259.006159 4.810959e+06 694.66968
A.L. Clark; 2 2342620.87 1362980.46 3848.6 585351.150270 4.811460e+06 1173.05328
#generation of surface as delanuay tringulation
elevArray = wellLoc.loc[:,['EastingUTM','NorthingUTM','Elevation_m']].to_numpy()
elevCloud = pv.PolyData(elevArray)
surf = elevCloud.delaunay_2d()
surf.save('../outputData/elevSurf.vtk',binary=False)
#import well lithology
wellLito = pd.read_csv('../inputData/TV-HFM_Wells_2Lithology.csv',skipfooter=1,
                       header=1,index_col=0, usecols=[0,1,2,3],engine='python')
wellLito.head()
Depth_top_L Depth_bot_L PrimaryLith
Bore
A. Isaac 0.0 1.0 Soil
A. Isaac 1.0 53.0 Sand
A. Isaac 53.0 248.0 Basalt
A. Isaac 248.0 265.0 Sand
A. Isaac 265.0 323.0 Basalt
#create a dictionary for the lito code
litoDict = {}
i=0
for lito in wellLito.PrimaryLith.unique():
    litoDict[lito]=i
    i+=1
litoDict
{'Soil': 0,
 'Sand': 1,
 'Basalt': 2,
 'Granite': 3,
 'Hardpan/Caliche': 4,
 'Cinders/Scoria': 5,
 'Gravel': 6,
 'Clay': 7,
 'Talc/Soapstone': 8,
 'Shale': 9,
 'Lignite/Coal/Peat': 10,
 'Sandstone': 11,
 'Lime': 12,
 'Claystone': 13,
 'Mud': 14,
 'Ash/Tuff': 15,
 'Mudstone': 16,
 'Rhyolite': 17,
 'Siltstone': 18,
 'Silt': 19,
 'Shell': 20,
 'Conglomerate': 21,
 'Volcanics': 22,
 'Chert': 23,
 'Pyrite': 24,
 'Limestone/marl': 25,
 'Wood': 26,
 'Andesite': 27}
#identify lito by the code on the dataframe
wellLito['litoCode']=wellLito.PrimaryLith
wellLito = wellLito.replace({"litoCode": litoDict})
wellLito.head()
Depth_top_L Depth_bot_L PrimaryLith litoCode
Bore
A. Isaac 0.0 1.0 Soil 0
A. Isaac 1.0 53.0 Sand 1
A. Isaac 53.0 248.0 Basalt 2
A. Isaac 248.0 265.0 Sand 1
A. Isaac 265.0 323.0 Basalt 2
#generation of list arrays for the vtk
offsetList = []
linSec = []
linVerts = []

i=0
for index, values in wellLito.iterrows():
    x, y, z =wellLoc.loc[index][['EastingUTM','NorthingUTM','Elevation_m']]
    topLito = z - (values.Depth_top_L)*0.3048 
    botLito = z- (values.Depth_bot_L)*0.3048 
    cellVerts = [[x,y,topLito],[x,y,botLito]]
    offsetList.append(i*3)         
    linSec = linSec + [2,2*i,2*i+1]
    linVerts = linVerts + cellVerts
    i +=1

offsetArray = np.array(offsetList)
linArray = np.array(linSec)
cellType = np.ones([i])*3
vertArray = np.array(linVerts)
# create the unstructured grid and assign lito code
grid = pv.UnstructuredGrid(offsetArray, linArray, cellType, vertArray)
grid.cell_arrays["values"] = wellLito.litoCode.values
grid.save('../outputData/wellLito.vtu',binary=False)
1

Input data

You can download the input files for this tutorial on this link.

Data source

Bartolino, J.R., 2019, Hydrogeologic framework of the Treasure Valley and surrounding area, Idaho and Oregon: U.S. Geological Survey Scientific Investigations Report 2019–5138, 31 p., https://doi.org/10.3133/sir20195138.

Bartolino, J.R., 2020, Hydrogeologic Framework of the Treasure Valley and Surrounding Area, Idaho and Oregon: U.S. Geological Survey data release, https://doi.org/10.5066/P9CAC0F6.

4 Comments

Saul Montoya

Saul Montoya es Ingeniero Civil graduado de la Pontificia Universidad Católica del Perú en Lima con estudios de postgrado en Manejo e Ingeniería de Recursos Hídricos (Programa WAREM) de la Universidad de Stuttgart con mención en Ingeniería de Aguas Subterráneas y Hidroinformática.

 

Suscribe to our online newsletter

Subscribe for free newsletter, receive news, interesting facts and dates of our courses in water resources.