las-read-rs 0.1.6__cp38-abi3-macosx_11_0_arm64.whl
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.
- las_read_rs-0.1.6.dist-info/METADATA +167 -0
- las_read_rs-0.1.6.dist-info/RECORD +7 -0
- las_read_rs-0.1.6.dist-info/WHEEL +4 -0
- lasio_rs/__init__.py +1 -0
- lasio_rs/_lasio_rs.abi3.so +0 -0
- lasio_rs/_lasio_rs.cp313-win_amd64.pyd +0 -0
- lasio_rs/las.py +235 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: las-read-rs
|
|
3
|
+
Version: 0.1.6
|
|
4
|
+
Requires-Dist: pandas
|
|
5
|
+
Requires-Dist: numpy
|
|
6
|
+
Summary: A high-performance LAS (Log ASCII Standard) file reader written in Rust
|
|
7
|
+
Author-email: Emiliano Flores <jemilianofl@github.com>
|
|
8
|
+
Requires-Python: >=3.8
|
|
9
|
+
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
10
|
+
|
|
11
|
+
# las_read_rs 🦀
|
|
12
|
+
|
|
13
|
+
[](https://pypi.org/project/las-read-rs/)
|
|
14
|
+
[](https://github.com/jemilianofl/lasio-rs/actions)
|
|
15
|
+
|
|
16
|
+
**High-performance LAS (Log ASCII Standard) file reader written in Rust** with Python bindings. A faster alternative to pure Python implementations, capable of parsing large geophysical log files efficiently.
|
|
17
|
+
|
|
18
|
+
## ⚡ Performance
|
|
19
|
+
|
|
20
|
+
Benchmarked with a **92 MB** LAS file (400 curves, 21,842 points each):
|
|
21
|
+
|
|
22
|
+
| Library | Read Time | Speedup |
|
|
23
|
+
|---------|-----------|---------|
|
|
24
|
+
| **las_read_rs** (Rust) | ~1.0s | **9x faster** 🚀 |
|
|
25
|
+
| lasio (Python) | ~9.0s | baseline |
|
|
26
|
+
|
|
27
|
+
## 🚀 Installation
|
|
28
|
+
|
|
29
|
+
**Requirements:**
|
|
30
|
+
- Python 3.8 or higher
|
|
31
|
+
- Supported OS: Windows, macOS, Linux
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install las_read_rs
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**Optional dependencies for export features:**
|
|
38
|
+
```bash
|
|
39
|
+
pip install pandas # For DataFrame and CSV export
|
|
40
|
+
pip install openpyxl # For Excel export
|
|
41
|
+
pip install polars # For Polars DataFrame
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## 📖 Quick Start
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
import lasio_rs
|
|
48
|
+
|
|
49
|
+
# Read a LAS file
|
|
50
|
+
las = lasio_rs.read("well_log.las")
|
|
51
|
+
|
|
52
|
+
# Access metadata
|
|
53
|
+
print(las.version['VERS'].value) # "2.0"
|
|
54
|
+
print(las.well['WELL'].value) # Well name
|
|
55
|
+
|
|
56
|
+
# Access curve data directly
|
|
57
|
+
depth = las['DEPT'] # Returns list of floats
|
|
58
|
+
gr = las['GR'] # Gamma Ray values
|
|
59
|
+
|
|
60
|
+
# List all curves
|
|
61
|
+
for curve_name in las.keys():
|
|
62
|
+
curve = las.curves[curve_name]
|
|
63
|
+
print(f"{curve_name} ({curve.unit}): {len(curve.data)} points")
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## 📊 DataFrame Conversion
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
# Convert to pandas DataFrame
|
|
70
|
+
df = las.to_df()
|
|
71
|
+
print(df.head())
|
|
72
|
+
|
|
73
|
+
# Convert to polars DataFrame
|
|
74
|
+
df_polars = las.to_polars()
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## 💾 Export Formats
|
|
78
|
+
|
|
79
|
+
### CSV Export
|
|
80
|
+
```python
|
|
81
|
+
las.to_csv("output.csv")
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Excel Export
|
|
85
|
+
```python
|
|
86
|
+
las.to_excel("output.xlsx", sheet_name="Well Data")
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### LAS Export (2.0 and 3.0)
|
|
90
|
+
```python
|
|
91
|
+
# Export as LAS 2.0
|
|
92
|
+
las.to_las("output_v2.las", version="2.0")
|
|
93
|
+
|
|
94
|
+
# Export as LAS 3.0
|
|
95
|
+
las.to_las("output_v3.las", version="3.0")
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## 🔧 API Reference
|
|
99
|
+
|
|
100
|
+
### `lasio_rs.read(path)`
|
|
101
|
+
Reads a LAS file and returns a `LASFile` object.
|
|
102
|
+
|
|
103
|
+
### `LASFile` Properties
|
|
104
|
+
| Property | Description |
|
|
105
|
+
|----------|-------------|
|
|
106
|
+
| `version` | Version section (VERS, WRAP) |
|
|
107
|
+
| `well` | Well information (STRT, STOP, STEP, NULL, WELL, etc.) |
|
|
108
|
+
| `curves` | Curve metadata and data |
|
|
109
|
+
| `params` | Parameter section |
|
|
110
|
+
|
|
111
|
+
### `LASFile` Methods
|
|
112
|
+
| Method | Description |
|
|
113
|
+
|--------|-------------|
|
|
114
|
+
| `las[mnemonic]` | Get curve data as list |
|
|
115
|
+
| `las.keys()` | List curve mnemonics |
|
|
116
|
+
| `las.to_df()` | Convert to pandas DataFrame |
|
|
117
|
+
| `las.to_polars()` | Convert to polars DataFrame |
|
|
118
|
+
| `las.to_csv(path)` | Export to CSV |
|
|
119
|
+
| `las.to_excel(path)` | Export to Excel |
|
|
120
|
+
| `las.to_las(path, version)` | Export to LAS format |
|
|
121
|
+
|
|
122
|
+
## 🏗️ Building from Source
|
|
123
|
+
|
|
124
|
+
Requirements:
|
|
125
|
+
- Python 3.7+
|
|
126
|
+
- Rust (cargo, rustc)
|
|
127
|
+
- maturin
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
git clone https://github.com/jemilianofl/lasio-rs.git
|
|
131
|
+
cd lasio-rs
|
|
132
|
+
pip install maturin
|
|
133
|
+
maturin develop --release
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## 📋 Supported LAS Versions
|
|
137
|
+
|
|
138
|
+
- ✅ LAS 2.0
|
|
139
|
+
- ✅ LAS 3.0 (read support)
|
|
140
|
+
- ✅ Export to LAS 2.0/3.0
|
|
141
|
+
|
|
142
|
+
## 🤝 Compatibility with lasio
|
|
143
|
+
|
|
144
|
+
`las_read_rs` provides a similar API to the popular `lasio` library:
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
# lasio style
|
|
148
|
+
import lasio
|
|
149
|
+
las = lasio.read("file.las")
|
|
150
|
+
depth = las['DEPT']
|
|
151
|
+
|
|
152
|
+
# las_read_rs style (same!)
|
|
153
|
+
import lasio_rs
|
|
154
|
+
las = lasio_rs.read("file.las")
|
|
155
|
+
depth = las['DEPT']
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## 📄 License
|
|
159
|
+
|
|
160
|
+
MIT
|
|
161
|
+
|
|
162
|
+
## 🙏 Acknowledgments
|
|
163
|
+
|
|
164
|
+
- Built with [PyO3](https://pyo3.rs/) for Python bindings
|
|
165
|
+
- Uses [nom](https://github.com/Geal/nom) for parsing
|
|
166
|
+
- Inspired by [lasio](https://github.com/kinverarity1/lasio)
|
|
167
|
+
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
las_read_rs-0.1.6.dist-info/METADATA,sha256=1GVnr5NMXJizwiK06vMYjOUY9NzpGpavZKNL6m3cHbw,3979
|
|
2
|
+
las_read_rs-0.1.6.dist-info/WHEEL,sha256=82nIVBZDB2dcFlYZh5-bKZ63xGZXWMnioaoSK06flpU,103
|
|
3
|
+
lasio_rs/__init__.py,sha256=xYmJ9tdZoIRkXmot_OmqsH18AOP8kFUE_o3ne81VO90,19
|
|
4
|
+
lasio_rs/_lasio_rs.abi3.so,sha256=JjWa0mERYHAmk9YklJcl3Koh9eEq1EC1KPRf-MZVQmo,780224
|
|
5
|
+
lasio_rs/_lasio_rs.cp313-win_amd64.pyd,sha256=QTph_VU95twdlp_ToKpVo-F9O7HC3N4qY40I7gPQ1wI,1380352
|
|
6
|
+
lasio_rs/las.py,sha256=YSW0wO1BMu_osxe7nUHzmx2INo6vsd2RB3BHYxpiTT0,7577
|
|
7
|
+
las_read_rs-0.1.6.dist-info/RECORD,,
|
lasio_rs/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .las import *
|
|
Binary file
|
|
Binary file
|
lasio_rs/las.py
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
from ._lasio_rs import read as _rust_read
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
try:
|
|
5
|
+
from ._lasio_rs import read as _rust_read
|
|
6
|
+
except ImportError as e:
|
|
7
|
+
# Esto da un error mucho más claro al usuario
|
|
8
|
+
raise ImportError(
|
|
9
|
+
"No se pudo cargar el binario de Rust (_lasio_rs). "
|
|
10
|
+
"Asegúrate de haber instalado el paquete correctamente para tu plataforma."
|
|
11
|
+
) from e
|
|
12
|
+
|
|
13
|
+
class SectionItems:
|
|
14
|
+
def __init__(self, data_dict):
|
|
15
|
+
# Allow init from dict of HeaderItems or dict of dicts (from json)
|
|
16
|
+
self._data = {}
|
|
17
|
+
for k, v in data_dict.items():
|
|
18
|
+
if isinstance(v, dict):
|
|
19
|
+
# Check if it is a Curve dict or Header dict
|
|
20
|
+
self._data[k] = HeaderItem(v)
|
|
21
|
+
else:
|
|
22
|
+
self._data[k] = v
|
|
23
|
+
|
|
24
|
+
def __getitem__(self, key):
|
|
25
|
+
item = self._data.get(key)
|
|
26
|
+
if item:
|
|
27
|
+
return item
|
|
28
|
+
raise KeyError(f"{key} not found")
|
|
29
|
+
|
|
30
|
+
def keys(self):
|
|
31
|
+
return self._data.keys()
|
|
32
|
+
|
|
33
|
+
def values(self):
|
|
34
|
+
return self._data.values()
|
|
35
|
+
|
|
36
|
+
def items(self):
|
|
37
|
+
return self._data.items()
|
|
38
|
+
|
|
39
|
+
def get(self, key, default=None):
|
|
40
|
+
return self._data.get(key, default)
|
|
41
|
+
|
|
42
|
+
def __repr__(self):
|
|
43
|
+
return f"SectionItems({list(self.keys())})"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class HeaderItem:
|
|
47
|
+
def __init__(self, item_dict):
|
|
48
|
+
self.mnemonic = item_dict.get("mnemonic")
|
|
49
|
+
self.unit = item_dict.get("unit")
|
|
50
|
+
self.value = item_dict.get("value")
|
|
51
|
+
self.descr = item_dict.get("descr")
|
|
52
|
+
self.original_mnemonic = self.mnemonic # Compatibility
|
|
53
|
+
|
|
54
|
+
def __repr__(self):
|
|
55
|
+
return f'HeaderItem(mnemonic="{self.mnemonic}", unit="{self.unit}", value="{self.value}", descr="{self.descr}")'
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class CurveItem(HeaderItem):
|
|
59
|
+
def __init__(self, item_dict, las_file_ref):
|
|
60
|
+
super().__init__(item_dict)
|
|
61
|
+
self._las = las_file_ref
|
|
62
|
+
self._data = None
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def data(self):
|
|
66
|
+
if self._data is None:
|
|
67
|
+
# Lazy load from Rust
|
|
68
|
+
self._data = self._las._rust.get_curve_data(self.mnemonic)
|
|
69
|
+
if self._data is None:
|
|
70
|
+
self._data = []
|
|
71
|
+
return self._data
|
|
72
|
+
|
|
73
|
+
def __getitem__(self, index):
|
|
74
|
+
return self.data[index]
|
|
75
|
+
|
|
76
|
+
def __len__(self):
|
|
77
|
+
return len(self.data)
|
|
78
|
+
|
|
79
|
+
def __repr__(self):
|
|
80
|
+
return f'CurveItem(mnemonic="{self.mnemonic}", unit="{self.unit}", value="{self.value}", descr="{self.descr}", data.shape=({len(self)},))'
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class LASFile:
|
|
84
|
+
def __init__(self, rust_las):
|
|
85
|
+
self._rust = rust_las
|
|
86
|
+
|
|
87
|
+
# Hydrate metadata from JSON
|
|
88
|
+
header_json = rust_las.json_headers
|
|
89
|
+
header_data = json.loads(header_json)
|
|
90
|
+
|
|
91
|
+
self.version = SectionItems(header_data.get("version", {}))
|
|
92
|
+
self.well = SectionItems(header_data.get("well", {}))
|
|
93
|
+
self.params = SectionItems(header_data.get("params", {}))
|
|
94
|
+
|
|
95
|
+
# Curves needs special handling to link back to Rust for data
|
|
96
|
+
raw_curves = header_data.get("curves", {})
|
|
97
|
+
self.curves = SectionItems({})
|
|
98
|
+
# Overwrite with CurveItems
|
|
99
|
+
curve_dict = {}
|
|
100
|
+
for k, v in raw_curves.items():
|
|
101
|
+
curve_dict[k] = CurveItem(v, self)
|
|
102
|
+
self.curves = SectionItems(curve_dict)
|
|
103
|
+
|
|
104
|
+
def __getitem__(self, key):
|
|
105
|
+
# Access curve data directly by mnemonic
|
|
106
|
+
if isinstance(key, str):
|
|
107
|
+
if key in self.curves.keys():
|
|
108
|
+
return self.curves[key].data
|
|
109
|
+
raise KeyError(f"Curve {key} not found")
|
|
110
|
+
|
|
111
|
+
def keys(self):
|
|
112
|
+
return self.curves.keys()
|
|
113
|
+
|
|
114
|
+
def to_df(self):
|
|
115
|
+
"""Convert LAS data to a pandas DataFrame.
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
pandas.DataFrame: DataFrame with curve mnemonics as columns.
|
|
119
|
+
"""
|
|
120
|
+
try:
|
|
121
|
+
import pandas as pd
|
|
122
|
+
except ImportError:
|
|
123
|
+
raise ImportError(
|
|
124
|
+
"pandas is required for to_df(). Install with: pip install pandas"
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
data = {}
|
|
128
|
+
for mnemonic in self.curves.keys():
|
|
129
|
+
curve = self.curves[mnemonic]
|
|
130
|
+
data[mnemonic] = curve.data
|
|
131
|
+
return pd.DataFrame(data)
|
|
132
|
+
|
|
133
|
+
def to_polars(self):
|
|
134
|
+
"""Convert LAS data to a polars DataFrame.
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
polars.DataFrame: DataFrame with curve mnemonics as columns.
|
|
138
|
+
"""
|
|
139
|
+
try:
|
|
140
|
+
import polars as pl
|
|
141
|
+
except ImportError:
|
|
142
|
+
raise ImportError(
|
|
143
|
+
"polars is required for to_polars(). Install with: pip install polars"
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
data = {}
|
|
147
|
+
for mnemonic in self.curves.keys():
|
|
148
|
+
curve = self.curves[mnemonic]
|
|
149
|
+
data[mnemonic] = curve.data
|
|
150
|
+
return pl.DataFrame(data)
|
|
151
|
+
|
|
152
|
+
def to_csv(self, path, **kwargs):
|
|
153
|
+
"""Export LAS data to CSV file.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
path: Output file path.
|
|
157
|
+
**kwargs: Additional arguments passed to pandas.DataFrame.to_csv()
|
|
158
|
+
"""
|
|
159
|
+
df = self.to_df()
|
|
160
|
+
df.to_csv(path, index=False, **kwargs)
|
|
161
|
+
|
|
162
|
+
def to_excel(self, path, sheet_name="Data", **kwargs):
|
|
163
|
+
"""Export LAS data to Excel file.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
path: Output file path.
|
|
167
|
+
sheet_name: Name of the Excel sheet.
|
|
168
|
+
**kwargs: Additional arguments passed to pandas.DataFrame.to_excel()
|
|
169
|
+
|
|
170
|
+
Note: Requires openpyxl. Install with: pip install openpyxl
|
|
171
|
+
"""
|
|
172
|
+
df = self.to_df()
|
|
173
|
+
df.to_excel(path, sheet_name=sheet_name, index=False, **kwargs)
|
|
174
|
+
|
|
175
|
+
def to_las(self, path, version="2.0"):
|
|
176
|
+
"""Export LAS data to LAS file format.
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
path: Output file path.
|
|
180
|
+
version: LAS version ("2.0" or "3.0"). Default is "2.0".
|
|
181
|
+
"""
|
|
182
|
+
with open(path, "w") as f:
|
|
183
|
+
# Version Section
|
|
184
|
+
f.write("~Version Information\n")
|
|
185
|
+
f.write(
|
|
186
|
+
f" VERS. {version} : CWLS LOG ASCII STANDARD - VERSION {version}\n"
|
|
187
|
+
)
|
|
188
|
+
f.write(" WRAP. NO : One line per depth step\n")
|
|
189
|
+
|
|
190
|
+
# Well Section
|
|
191
|
+
f.write("~Well Information\n")
|
|
192
|
+
for mnem in self.well.keys():
|
|
193
|
+
item = self.well[mnem]
|
|
194
|
+
f.write(
|
|
195
|
+
f" {mnem:18s}.{item.unit or '':8s} {item.value or '':30s}: {item.descr or ''}\n"
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
# Curve Section
|
|
199
|
+
f.write("~Curve Information\n")
|
|
200
|
+
for mnem in self.curves.keys():
|
|
201
|
+
curve = self.curves[mnem]
|
|
202
|
+
f.write(
|
|
203
|
+
f" {mnem:18s}.{curve.unit or '':8s} : {curve.descr or ''}\n"
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
# Parameter Section (if exists)
|
|
207
|
+
if len(list(self.params.keys())) > 0:
|
|
208
|
+
f.write("~Parameter Information\n")
|
|
209
|
+
for mnem in self.params.keys():
|
|
210
|
+
item = self.params[mnem]
|
|
211
|
+
f.write(
|
|
212
|
+
f" {mnem:18s}.{item.unit or '':8s} {item.value or '':30s}: {item.descr or ''}\n"
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
# ASCII Data Section
|
|
216
|
+
f.write("~A")
|
|
217
|
+
for mnem in self.curves.keys():
|
|
218
|
+
f.write(f" {mnem}")
|
|
219
|
+
f.write("\n")
|
|
220
|
+
|
|
221
|
+
# Get data length from first curve
|
|
222
|
+
first_curve = self.curves[list(self.curves.keys())[0]]
|
|
223
|
+
nrows = len(first_curve.data)
|
|
224
|
+
|
|
225
|
+
for i in range(nrows):
|
|
226
|
+
row = []
|
|
227
|
+
for mnem in self.curves.keys():
|
|
228
|
+
val = self.curves[mnem].data[i]
|
|
229
|
+
row.append(f"{val:12.6f}")
|
|
230
|
+
f.write(" ".join(row) + "\n")
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def read(file_path):
|
|
234
|
+
rust_obj = _rust_read(str(file_path))
|
|
235
|
+
return LASFile(rust_obj)
|