lasmnemonicsid 0.0.1rc0__tar.gz → 0.0.3rc0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {lasmnemonicsid-0.0.1rc0 → lasmnemonicsid-0.0.3rc0}/PKG-INFO +38 -11
- lasmnemonicsid-0.0.3rc0/README.md +72 -0
- {lasmnemonicsid-0.0.1rc0 → lasmnemonicsid-0.0.3rc0}/pyproject.toml +1 -1
- lasmnemonicsid-0.0.3rc0/src/LASMnemonicsID/LAS/LAS.py +194 -0
- lasmnemonicsid-0.0.3rc0/src/LASMnemonicsID/LAS/__init__.py +13 -0
- {lasmnemonicsid-0.0.1rc0 → lasmnemonicsid-0.0.3rc0}/src/LASMnemonicsID/utils/mnemonics.py +2 -0
- {lasmnemonicsid-0.0.1rc0 → lasmnemonicsid-0.0.3rc0}/src/lasmnemonicsid.egg-info/PKG-INFO +38 -11
- lasmnemonicsid-0.0.3rc0/tests/test_las.py +42 -0
- lasmnemonicsid-0.0.1rc0/README.md +0 -45
- lasmnemonicsid-0.0.1rc0/src/LASMnemonicsID/LAS/LAS.py +0 -148
- lasmnemonicsid-0.0.1rc0/src/LASMnemonicsID/LAS/__init__.py +0 -12
- lasmnemonicsid-0.0.1rc0/tests/test_las.py +0 -63
- {lasmnemonicsid-0.0.1rc0 → lasmnemonicsid-0.0.3rc0}/LICENSE +0 -0
- {lasmnemonicsid-0.0.1rc0 → lasmnemonicsid-0.0.3rc0}/setup.cfg +0 -0
- {lasmnemonicsid-0.0.1rc0 → lasmnemonicsid-0.0.3rc0}/src/LASMnemonicsID/DLIS/__init__.py +0 -0
- {lasmnemonicsid-0.0.1rc0 → lasmnemonicsid-0.0.3rc0}/src/LASMnemonicsID/__init__.py +0 -0
- {lasmnemonicsid-0.0.1rc0 → lasmnemonicsid-0.0.3rc0}/src/LASMnemonicsID/utils/__init__.py +0 -0
- {lasmnemonicsid-0.0.1rc0 → lasmnemonicsid-0.0.3rc0}/src/lasmnemonicsid.egg-info/SOURCES.txt +0 -0
- {lasmnemonicsid-0.0.1rc0 → lasmnemonicsid-0.0.3rc0}/src/lasmnemonicsid.egg-info/dependency_links.txt +0 -0
- {lasmnemonicsid-0.0.1rc0 → lasmnemonicsid-0.0.3rc0}/src/lasmnemonicsid.egg-info/requires.txt +0 -0
- {lasmnemonicsid-0.0.1rc0 → lasmnemonicsid-0.0.3rc0}/src/lasmnemonicsid.egg-info/top_level.txt +0 -0
- {lasmnemonicsid-0.0.1rc0 → lasmnemonicsid-0.0.3rc0}/tests/test_dlis.py +0 -0
- {lasmnemonicsid-0.0.1rc0 → lasmnemonicsid-0.0.3rc0}/tests/test_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lasmnemonicsid
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.3rc0
|
|
4
4
|
Summary: Well log mnemonic identification using lasio and dlisio to load LAS/DLIS files into DataFrames
|
|
5
5
|
Author-email: Nobleza Energy <info@nobleza-energy.com>
|
|
6
6
|
License: MIT
|
|
@@ -36,7 +36,6 @@ Requires-Dist: flake8>=6.0.0; extra == "dev"
|
|
|
36
36
|
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
37
37
|
Dynamic: license-file
|
|
38
38
|
|
|
39
|
-
|
|
40
39
|
<p align="center">
|
|
41
40
|
<img src="https://raw.githubusercontent.com/Nobleza-Energy/LASMnemonicsID/main/logo.png" alt="LASMnemonicsID Logo" width="200"/>
|
|
42
41
|
</p>
|
|
@@ -61,10 +60,9 @@ Dynamic: license-file
|
|
|
61
60
|
pip install lasmnemonicsid
|
|
62
61
|
```
|
|
63
62
|
|
|
64
|
-
|
|
65
|
-
|
|
66
63
|
## 🚀 QuickStart
|
|
67
|
-
|
|
64
|
+
|
|
65
|
+
```python
|
|
68
66
|
from LASMnemonicsID.LAS import parseLAS
|
|
69
67
|
|
|
70
68
|
# Load LAS file
|
|
@@ -72,12 +70,41 @@ df = parseLAS("your_well.las")
|
|
|
72
70
|
print(df.head())
|
|
73
71
|
```
|
|
74
72
|
|
|
75
|
-
## 🧪 Test with your Data
|
|
76
|
-
|
|
73
|
+
## 🧪 Test with your Data: Multiple files will load into a dictionary
|
|
74
|
+
|
|
75
|
+
```python
|
|
77
76
|
from LASMnemonicsID.LAS import parseLAS
|
|
78
77
|
|
|
79
|
-
# Load
|
|
80
|
-
|
|
81
|
-
print(
|
|
82
|
-
|
|
78
|
+
# Load all .las within the Directory → {filename: df}
|
|
79
|
+
data = parseLAS("/path/to/your/data/")
|
|
80
|
+
print("Files:", list(data.keys()))
|
|
81
|
+
|
|
82
|
+
# Dataframes
|
|
83
|
+
df = parseLAS('/path/to/yourfile.las')
|
|
84
|
+
print(df.head())
|
|
83
85
|
```
|
|
86
|
+
|
|
87
|
+
## 📈 Star History
|
|
88
|
+
|
|
89
|
+
[](https://star-history.com/#Nobleza-Energy/LASMnemonicsID&Date)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
## 📄 How to Cite
|
|
93
|
+
|
|
94
|
+
If you use `LASMnemonicsID` in your research or project, please cite it as follows:
|
|
95
|
+
|
|
96
|
+
**APA**
|
|
97
|
+
|
|
98
|
+
> Nobleza Energy. (2025). LASMnemonicsID: Well log mnemonic identification using lasio and dlisio [Software]. GitHub. https://github.com/Nobleza-Energy/LASMnemonicsID
|
|
99
|
+
|
|
100
|
+
**BibTeX**
|
|
101
|
+
|
|
102
|
+
```bibtex
|
|
103
|
+
@software{LASMnemonicsID,
|
|
104
|
+
author = {Nobleza Energy},
|
|
105
|
+
title = {LASMnemonicsID: Well log mnemonic identification using lasio and dlisio},
|
|
106
|
+
year = {2025},
|
|
107
|
+
publisher = {GitHub},
|
|
108
|
+
journal = {GitHub repository},
|
|
109
|
+
url = {https://github.com/Nobleza-Energy/LASMnemonicsID}
|
|
110
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/Nobleza-Energy/LASMnemonicsID/main/logo.png" alt="LASMnemonicsID Logo" width="200"/>
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<h1 align="center">LASMnemonicsID</h1>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<b>Well log mnemonic identification using lasio and dlisio</b>
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
<p align="center">
|
|
12
|
+
<a href="https://pypi.org/project/lasmnemonicsid/"><img src="https://img.shields.io/pypi/v/lasmnemonicsid.svg" alt="PyPI"></a>
|
|
13
|
+
<a href="https://pypi.org/project/lasmnemonicsid/"><img src="https://img.shields.io/pypi/pyversions/lasmnemonicsid.svg" alt="Python Versions"></a>
|
|
14
|
+
<a href="https://github.com/Nobleza-Energy/LASMnemonicsID/blob/main/LICENSE"><img src="https://img.shields.io/github/license/Nobleza-Energy/LASMnemonicsID.svg" alt="License"></a>
|
|
15
|
+
</p>
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 📦 Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pip install lasmnemonicsid
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## 🚀 QuickStart
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
from LASMnemonicsID.LAS import parseLAS
|
|
29
|
+
|
|
30
|
+
# Load LAS file
|
|
31
|
+
df = parseLAS("your_well.las")
|
|
32
|
+
print(df.head())
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## 🧪 Test with your Data: Multiple files will load into a dictionary
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
from LASMnemonicsID.LAS import parseLAS
|
|
39
|
+
|
|
40
|
+
# Load all .las within the Directory → {filename: df}
|
|
41
|
+
data = parseLAS("/path/to/your/data/")
|
|
42
|
+
print("Files:", list(data.keys()))
|
|
43
|
+
|
|
44
|
+
# Dataframes
|
|
45
|
+
df = parseLAS('/path/to/yourfile.las')
|
|
46
|
+
print(df.head())
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## 📈 Star History
|
|
50
|
+
|
|
51
|
+
[](https://star-history.com/#Nobleza-Energy/LASMnemonicsID&Date)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
## 📄 How to Cite
|
|
55
|
+
|
|
56
|
+
If you use `LASMnemonicsID` in your research or project, please cite it as follows:
|
|
57
|
+
|
|
58
|
+
**APA**
|
|
59
|
+
|
|
60
|
+
> Nobleza Energy. (2025). LASMnemonicsID: Well log mnemonic identification using lasio and dlisio [Software]. GitHub. https://github.com/Nobleza-Energy/LASMnemonicsID
|
|
61
|
+
|
|
62
|
+
**BibTeX**
|
|
63
|
+
|
|
64
|
+
```bibtex
|
|
65
|
+
@software{LASMnemonicsID,
|
|
66
|
+
author = {Nobleza Energy},
|
|
67
|
+
title = {LASMnemonicsID: Well log mnemonic identification using lasio and dlisio},
|
|
68
|
+
year = {2025},
|
|
69
|
+
publisher = {GitHub},
|
|
70
|
+
journal = {GitHub repository},
|
|
71
|
+
url = {https://github.com/Nobleza-Energy/LASMnemonicsID}
|
|
72
|
+
}
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "lasmnemonicsid"
|
|
7
|
-
version = "0.0.
|
|
7
|
+
version = "0.0.3c"
|
|
8
8
|
description = "Well log mnemonic identification using lasio and dlisio to load LAS/DLIS files into DataFrames"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import LASMnemonicsID.utils.mnemonics as mnm
|
|
2
|
+
from LASMnemonicsID.utils.mnemonics import (
|
|
3
|
+
gamma_names,
|
|
4
|
+
sp_names,
|
|
5
|
+
caliper_names,
|
|
6
|
+
deepres_names,
|
|
7
|
+
rxo_names,
|
|
8
|
+
density_names,
|
|
9
|
+
density_correction_names,
|
|
10
|
+
neutron_names,
|
|
11
|
+
dtc_names,
|
|
12
|
+
dts_names,
|
|
13
|
+
pe_names,
|
|
14
|
+
)
|
|
15
|
+
import os
|
|
16
|
+
import pathlib
|
|
17
|
+
import pandas as pd
|
|
18
|
+
import lasio
|
|
19
|
+
from os.path import join
|
|
20
|
+
from sys import stdout
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
|
|
23
|
+
# Function that creates the mnemonic dictionary
|
|
24
|
+
def create_mnemonic_dict(
|
|
25
|
+
gamma_names,
|
|
26
|
+
sp_names,
|
|
27
|
+
caliper_names,
|
|
28
|
+
deepres_names,
|
|
29
|
+
rxo_names,
|
|
30
|
+
density_names,
|
|
31
|
+
density_correction_names,
|
|
32
|
+
neutron_names,
|
|
33
|
+
dtc_names,
|
|
34
|
+
dts_names,
|
|
35
|
+
pe_names,
|
|
36
|
+
):
|
|
37
|
+
"""
|
|
38
|
+
Function that creates the mnemonic dictionary with the mnemonics per log type.
|
|
39
|
+
"""
|
|
40
|
+
mnemonic_dict = {
|
|
41
|
+
"gamma": gamma_names,
|
|
42
|
+
"sp": sp_names,
|
|
43
|
+
"caliper": caliper_names,
|
|
44
|
+
"deepres": deepres_names,
|
|
45
|
+
"rxo": rxo_names,
|
|
46
|
+
"density": density_names,
|
|
47
|
+
"density_correction": density_correction_names,
|
|
48
|
+
"neutron": neutron_names,
|
|
49
|
+
"dtc": dtc_names,
|
|
50
|
+
"dts": dts_names,
|
|
51
|
+
"pe": pe_names,
|
|
52
|
+
}
|
|
53
|
+
return mnemonic_dict
|
|
54
|
+
|
|
55
|
+
def parseLAS(input_path, verbose=True, preferred_names=None):
|
|
56
|
+
"""
|
|
57
|
+
Parse LAS file or all in directory → DataFrame or {filename: df}.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
input_path (str/Path): LAS file or directory
|
|
61
|
+
verbose (bool): Print info
|
|
62
|
+
preferred_names (dict, optional): Mapping of curve types to preferred column names and preferred original columns.
|
|
63
|
+
Example: {"deepres": "RT", "deepres_preferred_original": "AT90", "gamma": "GR"}
|
|
64
|
+
If not provided, defaults to standard petrophysical names.
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
DataFrame (single) or dict {filename: df} (multiple/dir)
|
|
68
|
+
"""
|
|
69
|
+
input_path = Path(input_path)
|
|
70
|
+
|
|
71
|
+
# Define default standard names
|
|
72
|
+
std_names = {
|
|
73
|
+
"gamma": "GR",
|
|
74
|
+
"sp": "SP",
|
|
75
|
+
"caliper": "CALI",
|
|
76
|
+
"deepres": "RT",
|
|
77
|
+
"rxo": "RXO",
|
|
78
|
+
"density": "RHOB",
|
|
79
|
+
"density_correction": "DRHO",
|
|
80
|
+
"neutron": "NPHI",
|
|
81
|
+
"dtc": "DT",
|
|
82
|
+
"dts": "DTS",
|
|
83
|
+
"pe": "PEF"
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
# Update with user preferences if provided
|
|
87
|
+
if preferred_names:
|
|
88
|
+
std_names.update(preferred_names)
|
|
89
|
+
|
|
90
|
+
# Case 1: Single File
|
|
91
|
+
if input_path.is_file() and input_path.suffix.lower() == '.las':
|
|
92
|
+
df = _read_single_las(input_path, verbose, std_names)
|
|
93
|
+
return df if df is not None else None
|
|
94
|
+
|
|
95
|
+
# Case 2: Directory (Recursive)
|
|
96
|
+
las_files = list(input_path.rglob("*.las"))
|
|
97
|
+
if not las_files:
|
|
98
|
+
if verbose:
|
|
99
|
+
print(f"No LAS files found in {input_path}")
|
|
100
|
+
return {}
|
|
101
|
+
|
|
102
|
+
las_dict = {}
|
|
103
|
+
for las_file in las_files:
|
|
104
|
+
df = _read_single_las(las_file, verbose, std_names)
|
|
105
|
+
if df is not None:
|
|
106
|
+
filename = las_file.name
|
|
107
|
+
las_dict[filename] = df
|
|
108
|
+
|
|
109
|
+
# Return single DF if only 1 file found, else dict
|
|
110
|
+
if len(las_dict) == 1:
|
|
111
|
+
return next(iter(las_dict.values()))
|
|
112
|
+
|
|
113
|
+
return las_dict
|
|
114
|
+
|
|
115
|
+
def _read_single_las(las_file_path, verbose, std_names):
|
|
116
|
+
"""Read single LAS file to DataFrame and standardize ALL curves."""
|
|
117
|
+
try:
|
|
118
|
+
las_data = lasio.read(las_file_path)
|
|
119
|
+
df = las_data.df()
|
|
120
|
+
|
|
121
|
+
if df is None or df.empty:
|
|
122
|
+
if verbose:
|
|
123
|
+
print(f"✗ Empty DataFrame: {las_file_path.name}")
|
|
124
|
+
return None
|
|
125
|
+
|
|
126
|
+
# Ensure index is depth (float)
|
|
127
|
+
df.index = df.index.astype(float)
|
|
128
|
+
|
|
129
|
+
# Standardize ALL curves (GR, RHOB, NPHI, etc.)
|
|
130
|
+
_standardize_all_curves(las_data, df, std_names)
|
|
131
|
+
|
|
132
|
+
if verbose:
|
|
133
|
+
print(f"✓ {las_file_path.name}")
|
|
134
|
+
return df
|
|
135
|
+
|
|
136
|
+
except lasio.exceptions.LASHeaderError as e:
|
|
137
|
+
if verbose:
|
|
138
|
+
print(f"✗ LASHeaderError in {las_file_path.name}: {e}")
|
|
139
|
+
except Exception as e:
|
|
140
|
+
if verbose:
|
|
141
|
+
print(f"✗ Error in {las_file_path.name}: {type(e).__name__}: {e}")
|
|
142
|
+
return None
|
|
143
|
+
|
|
144
|
+
def _get_well_name(las_file_path):
|
|
145
|
+
"""Extract well name from LAS file"""
|
|
146
|
+
try:
|
|
147
|
+
las_data = lasio.read(las_file_path)
|
|
148
|
+
return str(las_data.well.WELL.value).strip()
|
|
149
|
+
except:
|
|
150
|
+
return las_file_path.stem
|
|
151
|
+
|
|
152
|
+
def _standardize_all_curves(las_data, df, std_names):
|
|
153
|
+
"""
|
|
154
|
+
Rename ALL curves in the DataFrame to standard abbreviations
|
|
155
|
+
based on the mnemonic dictionary.
|
|
156
|
+
"""
|
|
157
|
+
# 1. Get the dictionary of aliases
|
|
158
|
+
mnem_dict = create_mnemonic_dict(
|
|
159
|
+
gamma_names, sp_names, caliper_names, deepres_names, rxo_names,
|
|
160
|
+
density_names, density_correction_names, neutron_names,
|
|
161
|
+
dtc_names, dts_names, pe_names
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
# 2. Track which columns we've already renamed to avoid duplicates
|
|
165
|
+
renamed = set()
|
|
166
|
+
|
|
167
|
+
# 3. For each curve type, find all aliases in the file
|
|
168
|
+
for curve_type, aliases in mnem_dict.items():
|
|
169
|
+
# Find all matching columns in df
|
|
170
|
+
matching = [col for col in df.columns if col.lower() in [a.lower() for a in aliases]]
|
|
171
|
+
|
|
172
|
+
if not matching:
|
|
173
|
+
continue
|
|
174
|
+
|
|
175
|
+
# Use standard name if provided, otherwise use curve_type.upper()
|
|
176
|
+
target_name = std_names.get(curve_type, curve_type.upper())
|
|
177
|
+
|
|
178
|
+
# If a preferred original column is specified, use it
|
|
179
|
+
preferred_original = std_names.get(f"{curve_type}_preferred_original")
|
|
180
|
+
|
|
181
|
+
if preferred_original and preferred_original in matching:
|
|
182
|
+
# Rename preferred original to target_name
|
|
183
|
+
df.rename(columns={preferred_original: target_name}, inplace=True)
|
|
184
|
+
renamed.add(target_name)
|
|
185
|
+
else:
|
|
186
|
+
# Otherwise, pick the first matching alias
|
|
187
|
+
df.rename(columns={matching[0]: target_name}, inplace=True)
|
|
188
|
+
renamed.add(target_name)
|
|
189
|
+
|
|
190
|
+
# Remove all other matching columns
|
|
191
|
+
for col in matching:
|
|
192
|
+
if col != target_name and col in df.columns:
|
|
193
|
+
df.drop(columns=[col], inplace=True)
|
|
194
|
+
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lasmnemonicsid
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.3rc0
|
|
4
4
|
Summary: Well log mnemonic identification using lasio and dlisio to load LAS/DLIS files into DataFrames
|
|
5
5
|
Author-email: Nobleza Energy <info@nobleza-energy.com>
|
|
6
6
|
License: MIT
|
|
@@ -36,7 +36,6 @@ Requires-Dist: flake8>=6.0.0; extra == "dev"
|
|
|
36
36
|
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
37
37
|
Dynamic: license-file
|
|
38
38
|
|
|
39
|
-
|
|
40
39
|
<p align="center">
|
|
41
40
|
<img src="https://raw.githubusercontent.com/Nobleza-Energy/LASMnemonicsID/main/logo.png" alt="LASMnemonicsID Logo" width="200"/>
|
|
42
41
|
</p>
|
|
@@ -61,10 +60,9 @@ Dynamic: license-file
|
|
|
61
60
|
pip install lasmnemonicsid
|
|
62
61
|
```
|
|
63
62
|
|
|
64
|
-
|
|
65
|
-
|
|
66
63
|
## 🚀 QuickStart
|
|
67
|
-
|
|
64
|
+
|
|
65
|
+
```python
|
|
68
66
|
from LASMnemonicsID.LAS import parseLAS
|
|
69
67
|
|
|
70
68
|
# Load LAS file
|
|
@@ -72,12 +70,41 @@ df = parseLAS("your_well.las")
|
|
|
72
70
|
print(df.head())
|
|
73
71
|
```
|
|
74
72
|
|
|
75
|
-
## 🧪 Test with your Data
|
|
76
|
-
|
|
73
|
+
## 🧪 Test with your Data: Multiple files will load into a dictionary
|
|
74
|
+
|
|
75
|
+
```python
|
|
77
76
|
from LASMnemonicsID.LAS import parseLAS
|
|
78
77
|
|
|
79
|
-
# Load
|
|
80
|
-
|
|
81
|
-
print(
|
|
82
|
-
|
|
78
|
+
# Load all .las within the Directory → {filename: df}
|
|
79
|
+
data = parseLAS("/path/to/your/data/")
|
|
80
|
+
print("Files:", list(data.keys()))
|
|
81
|
+
|
|
82
|
+
# Dataframes
|
|
83
|
+
df = parseLAS('/path/to/yourfile.las')
|
|
84
|
+
print(df.head())
|
|
83
85
|
```
|
|
86
|
+
|
|
87
|
+
## 📈 Star History
|
|
88
|
+
|
|
89
|
+
[](https://star-history.com/#Nobleza-Energy/LASMnemonicsID&Date)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
## 📄 How to Cite
|
|
93
|
+
|
|
94
|
+
If you use `LASMnemonicsID` in your research or project, please cite it as follows:
|
|
95
|
+
|
|
96
|
+
**APA**
|
|
97
|
+
|
|
98
|
+
> Nobleza Energy. (2025). LASMnemonicsID: Well log mnemonic identification using lasio and dlisio [Software]. GitHub. https://github.com/Nobleza-Energy/LASMnemonicsID
|
|
99
|
+
|
|
100
|
+
**BibTeX**
|
|
101
|
+
|
|
102
|
+
```bibtex
|
|
103
|
+
@software{LASMnemonicsID,
|
|
104
|
+
author = {Nobleza Energy},
|
|
105
|
+
title = {LASMnemonicsID: Well log mnemonic identification using lasio and dlisio},
|
|
106
|
+
year = {2025},
|
|
107
|
+
publisher = {GitHub},
|
|
108
|
+
journal = {GitHub repository},
|
|
109
|
+
url = {https://github.com/Nobleza-Energy/LASMnemonicsID}
|
|
110
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
|
|
2
|
+
import lasio
|
|
3
|
+
import pytest
|
|
4
|
+
import pandas as pd
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from LASMnemonicsID.LAS import parseLAS
|
|
7
|
+
from LASMnemonicsID.utils.mnemonics import (
|
|
8
|
+
gamma_names, density_names, neutron_names,
|
|
9
|
+
dtc_names, sp_names, caliper_names # All types
|
|
10
|
+
)
|
|
11
|
+
from LASMnemonicsID.utils.mnemonics import find_column
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_parseLAS_single_folder():
|
|
15
|
+
"""Test directory → {filename: df} dict."""
|
|
16
|
+
data_dir = Path(__file__).parent / 'data'
|
|
17
|
+
result = parseLAS(data_dir, verbose=False)
|
|
18
|
+
assert isinstance(result, dict)
|
|
19
|
+
assert len(result) >= 1 # Adaptive: any # files
|
|
20
|
+
first_df = next(iter(result.values()))
|
|
21
|
+
assert isinstance(first_df, pd.DataFrame)
|
|
22
|
+
assert len(first_df) > 0
|
|
23
|
+
assert 'GR' in first_df.columns # Standardization ✓
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_parseLAS_empty_dir():
|
|
27
|
+
empty_path = Path(__file__).parent / 'empty_dir'
|
|
28
|
+
empty_path.mkdir(exist_ok=True)
|
|
29
|
+
result = parseLAS(empty_path, verbose=False)
|
|
30
|
+
assert result == {}
|
|
31
|
+
empty_path.rmdir() # Cleanup
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_parseLAS_single_file():
|
|
35
|
+
"""Test single file → DataFrame."""
|
|
36
|
+
data_dir = Path(__file__).parent / 'data'
|
|
37
|
+
sample_las_paths = list(data_dir.glob('*.las'))
|
|
38
|
+
first_file = sample_las_paths[0]
|
|
39
|
+
df = parseLAS(first_file, verbose=False)
|
|
40
|
+
assert isinstance(df, pd.DataFrame)
|
|
41
|
+
assert len(df) > 0
|
|
42
|
+
assert 'GR' in df.columns # Standardization ✓
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
<p align="center">
|
|
3
|
-
<img src="https://raw.githubusercontent.com/Nobleza-Energy/LASMnemonicsID/main/logo.png" alt="LASMnemonicsID Logo" width="200"/>
|
|
4
|
-
</p>
|
|
5
|
-
|
|
6
|
-
<h1 align="center">LASMnemonicsID</h1>
|
|
7
|
-
|
|
8
|
-
<p align="center">
|
|
9
|
-
<b>Well log mnemonic identification using lasio and dlisio</b>
|
|
10
|
-
</p>
|
|
11
|
-
|
|
12
|
-
<p align="center">
|
|
13
|
-
<a href="https://pypi.org/project/lasmnemonicsid/"><img src="https://img.shields.io/pypi/v/lasmnemonicsid.svg" alt="PyPI"></a>
|
|
14
|
-
<a href="https://pypi.org/project/lasmnemonicsid/"><img src="https://img.shields.io/pypi/pyversions/lasmnemonicsid.svg" alt="Python Versions"></a>
|
|
15
|
-
<a href="https://github.com/Nobleza-Energy/LASMnemonicsID/blob/main/LICENSE"><img src="https://img.shields.io/github/license/Nobleza-Energy/LASMnemonicsID.svg" alt="License"></a>
|
|
16
|
-
</p>
|
|
17
|
-
|
|
18
|
-
---
|
|
19
|
-
|
|
20
|
-
## 📦 Installation
|
|
21
|
-
|
|
22
|
-
```bash
|
|
23
|
-
pip install lasmnemonicsid
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
## 🚀 QuickStart
|
|
29
|
-
```
|
|
30
|
-
from LASMnemonicsID.LAS import parseLAS
|
|
31
|
-
|
|
32
|
-
# Load LAS file
|
|
33
|
-
df = parseLAS("your_well.las")
|
|
34
|
-
print(df.head())
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
## 🧪 Test with your Data
|
|
38
|
-
```
|
|
39
|
-
from LASMnemonicsID.LAS import parseLAS
|
|
40
|
-
|
|
41
|
-
# Load and inspect
|
|
42
|
-
df = parseLAS("path/to/well.las")
|
|
43
|
-
print(f"✅ {len(df)} rows, {len(df.columns)} curves")
|
|
44
|
-
print(df.columns.tolist())
|
|
45
|
-
```
|
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
import LASMnemonicsID.utils.mnemonics as mnm
|
|
2
|
-
from LASMnemonicsID.utils.mnemonics import (
|
|
3
|
-
gamma_names,
|
|
4
|
-
sp_names,
|
|
5
|
-
caliper_names,
|
|
6
|
-
deepres_names,
|
|
7
|
-
rxo_names,
|
|
8
|
-
density_names,
|
|
9
|
-
density_correction_names,
|
|
10
|
-
neutron_names,
|
|
11
|
-
dtc_names,
|
|
12
|
-
dts_names,
|
|
13
|
-
pe_names,
|
|
14
|
-
)
|
|
15
|
-
import os
|
|
16
|
-
import pathlib
|
|
17
|
-
import pandas as pd
|
|
18
|
-
import lasio
|
|
19
|
-
from os.path import join
|
|
20
|
-
from sys import stdout
|
|
21
|
-
from pathlib import Path
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
# Function that create the mnemonic dictionary
|
|
28
|
-
def create_mnemonic_dict(
|
|
29
|
-
gamma_names,
|
|
30
|
-
sp_names,
|
|
31
|
-
caliper_names,
|
|
32
|
-
deepres_names,
|
|
33
|
-
rxo_names,
|
|
34
|
-
density_names,
|
|
35
|
-
density_correction_names,
|
|
36
|
-
neutron_names,
|
|
37
|
-
dtc_names,
|
|
38
|
-
dts_names,
|
|
39
|
-
pe_names,
|
|
40
|
-
):
|
|
41
|
-
"""
|
|
42
|
-
Function that create the mnemonic dictionary with the mnemonics per log type in the utils module
|
|
43
|
-
"""
|
|
44
|
-
|
|
45
|
-
mnemonic_dict = {
|
|
46
|
-
"gamma": gamma_names,
|
|
47
|
-
"sp": sp_names,
|
|
48
|
-
"caliper": caliper_names,
|
|
49
|
-
"deepres": deepres_names,
|
|
50
|
-
"rxo": rxo_names,
|
|
51
|
-
"density": density_names,
|
|
52
|
-
"density_correction": density_correction_names,
|
|
53
|
-
"neutron": neutron_names,
|
|
54
|
-
"dtc": dtc_names,
|
|
55
|
-
"dts": dts_names,
|
|
56
|
-
"pe": pe_names,
|
|
57
|
-
}
|
|
58
|
-
return mnemonic_dict
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
def parseLAS(directory_path, verbose=True):
|
|
63
|
-
"""
|
|
64
|
-
Parse all LAS files in directory (recursive) into dict of DataFrames or single DataFrame.
|
|
65
|
-
|
|
66
|
-
Args:
|
|
67
|
-
directory_path (str/Path): Directory containing LAS files
|
|
68
|
-
verbose (bool): Print processing info
|
|
69
|
-
|
|
70
|
-
Returns:
|
|
71
|
-
dict or DataFrame: {folder: {well: df}} or single df if one file found
|
|
72
|
-
"""
|
|
73
|
-
directory_path = Path(directory_path)
|
|
74
|
-
well_logs = {}
|
|
75
|
-
|
|
76
|
-
# Find all LAS files recursively
|
|
77
|
-
las_files = list(directory_path.rglob("*.las"))
|
|
78
|
-
|
|
79
|
-
if not las_files:
|
|
80
|
-
if verbose:
|
|
81
|
-
print("No LAS files found.")
|
|
82
|
-
return {}
|
|
83
|
-
|
|
84
|
-
if len(las_files) == 1:
|
|
85
|
-
# Return single DataFrame if only one file
|
|
86
|
-
return _read_single_las(las_files[0], verbose)
|
|
87
|
-
|
|
88
|
-
# Multiple files: group by parent folder
|
|
89
|
-
for las_file in las_files:
|
|
90
|
-
folder_name = las_file.parent.name
|
|
91
|
-
if folder_name not in well_logs:
|
|
92
|
-
well_logs[folder_name] = {}
|
|
93
|
-
|
|
94
|
-
df = _read_single_las(las_file, verbose)
|
|
95
|
-
if df is not None:
|
|
96
|
-
well_name = _get_well_name(las_file)
|
|
97
|
-
well_logs[folder_name][well_name] = df
|
|
98
|
-
|
|
99
|
-
return well_logs
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
def _read_single_las(las_file_path, verbose):
|
|
103
|
-
"""Read single LAS file to DataFrame"""
|
|
104
|
-
try:
|
|
105
|
-
las_data = lasio.read(las_file_path)
|
|
106
|
-
df = las_data.df()
|
|
107
|
-
if df is None or df.empty:
|
|
108
|
-
if verbose:
|
|
109
|
-
print(f"✗ Empty DataFrame: {las_file_path.name}")
|
|
110
|
-
return None
|
|
111
|
-
|
|
112
|
-
df.index = df.index.astype(float)
|
|
113
|
-
# df.dropna(inplace=True)
|
|
114
|
-
|
|
115
|
-
# Standardize GR curve
|
|
116
|
-
_standardize_gr_curve(las_data, df)
|
|
117
|
-
|
|
118
|
-
if verbose:
|
|
119
|
-
print(f"✓ {las_file_path.name}")
|
|
120
|
-
return df
|
|
121
|
-
|
|
122
|
-
except lasio.exceptions.LASHeaderError as e:
|
|
123
|
-
if verbose:
|
|
124
|
-
print(f"✗ LASHeaderError in {las_file_path.name}: {e}")
|
|
125
|
-
except Exception as e:
|
|
126
|
-
if verbose:
|
|
127
|
-
print(f"✗ Error in {las_file_path.name}: {type(e).__name__}: {e}")
|
|
128
|
-
return None
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
def _get_well_name(las_file_path):
|
|
132
|
-
"""Extract well name from LAS file"""
|
|
133
|
-
try:
|
|
134
|
-
las_data = lasio.read(las_file_path)
|
|
135
|
-
return str(las_data.well.WELL.value).strip()
|
|
136
|
-
except:
|
|
137
|
-
return las_file_path.stem
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
def _standardize_gr_curve(las_data, df):
|
|
141
|
-
"""Rename gamma ray curve to GR"""
|
|
142
|
-
global gamma_names # Assuming gamma_names defined elsewhere
|
|
143
|
-
for curve in las_data.curves:
|
|
144
|
-
if curve.mnemonic.lower() in gamma_names:
|
|
145
|
-
df.rename(columns={curve.mnemonic: "GR"}, inplace=True)
|
|
146
|
-
break
|
|
147
|
-
|
|
148
|
-
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import lasio
|
|
2
|
-
import pytest
|
|
3
|
-
import pandas as pd
|
|
4
|
-
from pathlib import Path
|
|
5
|
-
from LASMnemonicsID.LAS import parseLAS
|
|
6
|
-
from LASMnemonicsID.utils.mnemonics import (
|
|
7
|
-
gamma_names, density_names, neutron_names,
|
|
8
|
-
dtc_names, sp_names, caliper_names # All types
|
|
9
|
-
)
|
|
10
|
-
from LASMnemonicsID.utils.mnemonics import find_column
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def test_parseLAS_single_folder(sample_las_paths):
|
|
14
|
-
result = parseLAS(sample_las_paths[0].parent, verbose=False)
|
|
15
|
-
assert isinstance(result, dict)
|
|
16
|
-
assert len(result) == 1 # 'data' folder
|
|
17
|
-
wells = result['data']
|
|
18
|
-
assert len(wells) >= 10 # Multiple unique wells now
|
|
19
|
-
first_df = next(iter(wells.values()))
|
|
20
|
-
assert isinstance(first_df, pd.DataFrame)
|
|
21
|
-
assert len(first_df) > 0 # New files have data!
|
|
22
|
-
assert 'GR' in first_df.columns # GR standardization
|
|
23
|
-
|
|
24
|
-
def test_parseLAS_empty_dir():
|
|
25
|
-
result = parseLAS(Path(__file__).parent / 'empty_dir', verbose=False)
|
|
26
|
-
assert result == {}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
# Test for parsing all curves and identifying and renaming all of them into a new dataframe
|
|
31
|
-
def test_parse_all_curves_first_file():
|
|
32
|
-
data_dir = Path(__file__).parent / 'data'
|
|
33
|
-
first_file = next(data_dir.glob('*.las'))
|
|
34
|
-
|
|
35
|
-
print(f"🔍 Parsing {first_file.name} for ALL curves...")
|
|
36
|
-
las_data = lasio.read(first_file)
|
|
37
|
-
df = las_data.df()
|
|
38
|
-
|
|
39
|
-
# Test ALL curve types with find_column (your utils logic)
|
|
40
|
-
curve_types = {
|
|
41
|
-
'gamma': gamma_names,
|
|
42
|
-
'density': density_names,
|
|
43
|
-
'neutron': neutron_names,
|
|
44
|
-
'dtc': dtc_names,
|
|
45
|
-
'sp': sp_names,
|
|
46
|
-
'caliper': caliper_names
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
found_curves = {}
|
|
50
|
-
for curve_type, names in curve_types.items():
|
|
51
|
-
col = find_column(df, curve_type)
|
|
52
|
-
found_curves[curve_type] = col
|
|
53
|
-
status = "✅" if col else "❌"
|
|
54
|
-
print(f"{status} {curve_type.upper()}: {col}")
|
|
55
|
-
|
|
56
|
-
print(f"\n📊 DataFrame: {df.shape}")
|
|
57
|
-
print("Columns:", list(df.columns[:20]), "...")
|
|
58
|
-
print("\nHead:")
|
|
59
|
-
print(df.head(10))
|
|
60
|
-
|
|
61
|
-
# Assert key curves found
|
|
62
|
-
assert found_curves['gamma'] # GR required
|
|
63
|
-
assert len(df.columns) > 20
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lasmnemonicsid-0.0.1rc0 → lasmnemonicsid-0.0.3rc0}/src/lasmnemonicsid.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{lasmnemonicsid-0.0.1rc0 → lasmnemonicsid-0.0.3rc0}/src/lasmnemonicsid.egg-info/requires.txt
RENAMED
|
File without changes
|
{lasmnemonicsid-0.0.1rc0 → lasmnemonicsid-0.0.3rc0}/src/lasmnemonicsid.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|