log2frame 0.2.1__tar.gz → 0.2.3__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.
- {log2frame-0.2.1/src/log2frame.egg-info → log2frame-0.2.3}/PKG-INFO +9 -3
- {log2frame-0.2.1 → log2frame-0.2.3}/README.md +8 -2
- {log2frame-0.2.1 → log2frame-0.2.3}/pyproject.toml +1 -1
- log2frame-0.2.3/src/log2frame/__init__.py +162 -0
- log2frame-0.2.3/src/log2frame/dictionaries/units.py +69 -0
- {log2frame-0.2.1 → log2frame-0.2.3}/src/log2frame/dlis.py +41 -12
- {log2frame-0.2.1 → log2frame-0.2.3}/src/log2frame/las.py +46 -10
- {log2frame-0.2.1 → log2frame-0.2.3}/src/log2frame/lis.py +94 -10
- {log2frame-0.2.1 → log2frame-0.2.3}/src/log2frame/log.py +233 -7
- {log2frame-0.2.1 → log2frame-0.2.3}/src/log2frame/pack.py +218 -7
- {log2frame-0.2.1 → log2frame-0.2.3}/src/log2frame/rft.py +146 -2
- log2frame-0.2.3/src/log2frame/writer.py +192 -0
- {log2frame-0.2.1 → log2frame-0.2.3/src/log2frame.egg-info}/PKG-INFO +9 -3
- {log2frame-0.2.1 → log2frame-0.2.3}/src/log2frame.egg-info/SOURCES.txt +3 -2
- {log2frame-0.2.1 → log2frame-0.2.3}/test/test_readers.py +1 -2
- log2frame-0.2.3/test/test_writer.py +215 -0
- log2frame-0.2.1/src/log2frame/__init__.py +0 -74
- log2frame-0.2.1/src/log2frame/debug.py +0 -5
- log2frame-0.2.1/src/log2frame/dictionaries/units.py +0 -28
- {log2frame-0.2.1 → log2frame-0.2.3}/LICENSE +0 -0
- {log2frame-0.2.1 → log2frame-0.2.3}/setup.cfg +0 -0
- {log2frame-0.2.1 → log2frame-0.2.3}/src/log2frame/dictionaries/__init__.py +0 -0
- {log2frame-0.2.1 → log2frame-0.2.3}/src/log2frame/dictionaries/units_dictionary.json +0 -0
- {log2frame-0.2.1 → log2frame-0.2.3}/src/log2frame.egg-info/dependency_links.txt +0 -0
- {log2frame-0.2.1 → log2frame-0.2.3}/src/log2frame.egg-info/requires.txt +0 -0
- {log2frame-0.2.1 → log2frame-0.2.3}/src/log2frame.egg-info/top_level.txt +0 -0
- {log2frame-0.2.1 → log2frame-0.2.3}/test/test_log.py +0 -0
- {log2frame-0.2.1 → log2frame-0.2.3}/test/test_pack.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: log2frame
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary: A function to facilitate loading well log data, in las or dlis format, as dataframe.
|
|
5
5
|
Author-email: Martin Carlos Araya <martinaraya@gmail.com>
|
|
6
6
|
License: MIT License
|
|
@@ -51,8 +51,8 @@ Dynamic: license-file
|
|
|
51
51
|
|
|
52
52
|
A function to read well log data in LAS, LIS and DLIS formats and extract curves data as DataFrame.
|
|
53
53
|
|
|
54
|
-
The curves and header data are stored in an instance of
|
|
55
|
-
If multiple log files are read together, the __Log__ instances are packed in a
|
|
54
|
+
The curves and header data are stored in an instance of ``Log`` class, designed to hold this data and operate with it. It provides convenient access to metadata like well name, UWI, and curve units (via ``.units`` or the safer ``.units_dict()``).
|
|
55
|
+
If multiple log files are read together, the __Log__ instances are packed in a ``Pack`` instance, designed to hold ``Log`` instances and operate with them.
|
|
56
56
|
|
|
57
57
|
# unit-aware mode
|
|
58
58
|
When `simpandas` is installed, `log2frame` can preserve curve units and index units through the `use_simpandas` option.
|
|
@@ -63,6 +63,12 @@ When `simpandas` is installed, `log2frame` can preserve curve units and index un
|
|
|
63
63
|
# to read log files
|
|
64
64
|
Simply call the function `read()` with the path or path pattern as argument:
|
|
65
65
|
|
|
66
|
+
# to export / write LAS files
|
|
67
|
+
Use `write_las()` to export a `Log` back to a LAS file:
|
|
68
|
+
- `log2frame.write_las( log, path_to_file )` — writes LAS 2.0 by default
|
|
69
|
+
- `log2frame.write_las( log, path_to_file, version=1.2 )` — LAS 1.2
|
|
70
|
+
- `log.to_las( path_to_file )` — equivalent convenience method on `Log`
|
|
71
|
+
|
|
66
72
|
## a single log file
|
|
67
73
|
To load a single _.las_, _.lis_ or _.dlis_ file. The function will return a `Log` instance containing the data:
|
|
68
74
|
- `log2frame.read( path_to_file )`
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
A function to read well log data in LAS, LIS and DLIS formats and extract curves data as DataFrame.
|
|
6
6
|
|
|
7
|
-
The curves and header data are stored in an instance of
|
|
8
|
-
If multiple log files are read together, the __Log__ instances are packed in a
|
|
7
|
+
The curves and header data are stored in an instance of ``Log`` class, designed to hold this data and operate with it. It provides convenient access to metadata like well name, UWI, and curve units (via ``.units`` or the safer ``.units_dict()``).
|
|
8
|
+
If multiple log files are read together, the __Log__ instances are packed in a ``Pack`` instance, designed to hold ``Log`` instances and operate with them.
|
|
9
9
|
|
|
10
10
|
# unit-aware mode
|
|
11
11
|
When `simpandas` is installed, `log2frame` can preserve curve units and index units through the `use_simpandas` option.
|
|
@@ -16,6 +16,12 @@ When `simpandas` is installed, `log2frame` can preserve curve units and index un
|
|
|
16
16
|
# to read log files
|
|
17
17
|
Simply call the function `read()` with the path or path pattern as argument:
|
|
18
18
|
|
|
19
|
+
# to export / write LAS files
|
|
20
|
+
Use `write_las()` to export a `Log` back to a LAS file:
|
|
21
|
+
- `log2frame.write_las( log, path_to_file )` — writes LAS 2.0 by default
|
|
22
|
+
- `log2frame.write_las( log, path_to_file, version=1.2 )` — LAS 1.2
|
|
23
|
+
- `log.to_las( path_to_file )` — equivalent convenience method on `Log`
|
|
24
|
+
|
|
19
25
|
## a single log file
|
|
20
26
|
To load a single _.las_, _.lis_ or _.dlis_ file. The function will return a `Log` instance containing the data:
|
|
21
27
|
- `log2frame.read( path_to_file )`
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""log2frame — well-log data reader and writer.
|
|
3
|
+
|
|
4
|
+
This package reads well-log files in LAS, DLIS, and LIS formats and
|
|
5
|
+
returns the curve data as unit-aware :class:`Log` (single file) or
|
|
6
|
+
:class:`Pack` (multiple files) containers built on top of
|
|
7
|
+
:class:`pandas.DataFrame` (or :class:`simpandas.SimDataFrame` when the
|
|
8
|
+
optional ``simpandas`` package is installed).
|
|
9
|
+
|
|
10
|
+
The package also provides :func:`write_las` to export any :class:`Log`
|
|
11
|
+
back to a LAS 1.2 / 2.0 file.
|
|
12
|
+
|
|
13
|
+
Public API
|
|
14
|
+
----------
|
|
15
|
+
read(path, ...) Read one or many log files.
|
|
16
|
+
write_las(log, path) Write a Log to a LAS file.
|
|
17
|
+
concat(logs) Concatenate a list of Log objects into a single DataFrame.
|
|
18
|
+
"""
|
|
19
|
+
# Created on Thu Jan 19 19:01:21 2023
|
|
20
|
+
# @author: Martín Carlos Araya <martinaraya@gmail.com>
|
|
21
|
+
from .las import las2frame
|
|
22
|
+
from .dlis import dlis2frame
|
|
23
|
+
from .lis import lis2frame
|
|
24
|
+
from .pack import Pack, concat
|
|
25
|
+
from .log import Log
|
|
26
|
+
from .writer import write_las
|
|
27
|
+
import glob
|
|
28
|
+
import os.path
|
|
29
|
+
import logging
|
|
30
|
+
from .rft import *
|
|
31
|
+
|
|
32
|
+
for name in ("lasio", "lasio.reader", "lasio.las", "unyts"):
|
|
33
|
+
logging.getLogger(name).setLevel(logging.WARNING)
|
|
34
|
+
|
|
35
|
+
__version__ = '0.2.3'
|
|
36
|
+
__release__ = 20260503
|
|
37
|
+
__all__ = ['read', 'concat', 'write_las']
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class _Log2FrameParams(object):
|
|
41
|
+
"""Package-level singleton holding runtime configuration flags.
|
|
42
|
+
|
|
43
|
+
Attributes
|
|
44
|
+
----------
|
|
45
|
+
simpandas_ : bool
|
|
46
|
+
``True`` when the optional ``simpandas`` package is importable.
|
|
47
|
+
Used by all readers to decide whether to return a
|
|
48
|
+
:class:`simpandas.SimDataFrame` or a plain
|
|
49
|
+
:class:`pandas.DataFrame`.
|
|
50
|
+
raise_error_ : bool
|
|
51
|
+
Default value for the *raise_error* parameter of :func:`read`.
|
|
52
|
+
Set to ``True`` to propagate parse errors; ``False`` silently
|
|
53
|
+
skips unreadable files.
|
|
54
|
+
"""
|
|
55
|
+
def __init__(self):
|
|
56
|
+
try:
|
|
57
|
+
import simpandas as spd
|
|
58
|
+
self.simpandas_ = True
|
|
59
|
+
except ModuleNotFoundError:
|
|
60
|
+
self.simpandas_ = False
|
|
61
|
+
self.raise_error_ = True
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
_params_ = _Log2FrameParams()
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _read_one(path: str, raise_error=True, use_simpandas=None, correct_units=True):
|
|
68
|
+
"""Dispatch a single file path to the appropriate format reader.
|
|
69
|
+
|
|
70
|
+
Parameters
|
|
71
|
+
----------
|
|
72
|
+
path : str
|
|
73
|
+
Path to a ``.las``, ``.dlis``, or ``.lis`` / ``.lti`` file.
|
|
74
|
+
raise_error : bool, default True
|
|
75
|
+
Whether to propagate parse errors or return ``None``.
|
|
76
|
+
use_simpandas : bool | None, default None
|
|
77
|
+
Passed through to the format-specific reader.
|
|
78
|
+
correct_units : bool, default True
|
|
79
|
+
Passed through to the format-specific reader.
|
|
80
|
+
|
|
81
|
+
Returns
|
|
82
|
+
-------
|
|
83
|
+
Log | None
|
|
84
|
+
"""
|
|
85
|
+
if use_simpandas is not None and use_simpandas is True and not _params_.simpandas_:
|
|
86
|
+
raise ModuleNotFoundError("SimPandas is not installed, please install it or set parameter `use_simpandas` to False.")
|
|
87
|
+
use_simpandas = _params_.simpandas_ if use_simpandas is None else bool(use_simpandas)
|
|
88
|
+
if not os.path.isfile(path):
|
|
89
|
+
if raise_error:
|
|
90
|
+
raise FileNotFoundError("The provided path can't be found:\n" + str(path))
|
|
91
|
+
else:
|
|
92
|
+
logging.warning("The provided path can't be found:\n" + str(path))
|
|
93
|
+
if path.split('.')[-1].lower() == 'las':
|
|
94
|
+
return las2frame(path, use_simpandas=use_simpandas, raise_error=raise_error, correct_units=correct_units)
|
|
95
|
+
elif path.split('.')[-1].lower() == 'dlis':
|
|
96
|
+
return dlis2frame(path, use_simpandas=use_simpandas, raise_error=raise_error, correct_units=correct_units)
|
|
97
|
+
elif path.split('.')[-1].lower() in ['lis', 'lti']:
|
|
98
|
+
return lis2frame(path, use_simpandas=use_simpandas, raise_error=raise_error, correct_units=correct_units)
|
|
99
|
+
elif not raise_error:
|
|
100
|
+
# logging.warning("Not a valid log file: " + str(path))
|
|
101
|
+
return None
|
|
102
|
+
else:
|
|
103
|
+
raise ValueError("`path` should be a '.las' or '.dlis' file")
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def read(path: str, recursive=True, raise_error=False, squeeze=True, use_simpandas=None, correct_units=True):
|
|
107
|
+
"""Read a well-log file (LAS, DLIS, LIS) into a units-aware :class:`Log` container.
|
|
108
|
+
|
|
109
|
+
Accepts individual file paths or a list of paths/globs. If multiple files are
|
|
110
|
+
read, they are returned grouped in a :class:`Pack` container.
|
|
111
|
+
|
|
112
|
+
Parameters
|
|
113
|
+
----------
|
|
114
|
+
path : str | list[str]
|
|
115
|
+
Path to the ``.las``, ``.dlis``, or ``.lis`` file(s). Glob
|
|
116
|
+
expressions (``**/*.*``) and bare directory paths are supported.
|
|
117
|
+
recursive : bool, default True
|
|
118
|
+
Whether ``**`` glob patterns recurse into subdirectories.
|
|
119
|
+
squeeze : bool, default True
|
|
120
|
+
Return a single :class:`Log` directly when the result would be a
|
|
121
|
+
one-entry :class:`Pack`.
|
|
122
|
+
use_simpandas : bool | None, default None
|
|
123
|
+
Use :class:`simpandas.SimDataFrame` for curve storage when
|
|
124
|
+
``True``; plain :class:`pandas.DataFrame` when ``False``;
|
|
125
|
+
auto-detect when ``None`` (default).
|
|
126
|
+
raise_error : bool, default False
|
|
127
|
+
Raise exceptions for unreadable files. When ``False`` (default)
|
|
128
|
+
unreadable files are silently skipped.
|
|
129
|
+
correct_units : bool, default True
|
|
130
|
+
Normalise unit strings using the built-in correction dictionary
|
|
131
|
+
(e.g. ``GAPI`` → ``gAPI``).
|
|
132
|
+
|
|
133
|
+
Returns
|
|
134
|
+
-------
|
|
135
|
+
Log | Pack
|
|
136
|
+
A :class:`Log` if a single file was resolved; a :class:`Pack`
|
|
137
|
+
otherwise (unless *squeeze* collapses a one-entry Pack).
|
|
138
|
+
|
|
139
|
+
Examples
|
|
140
|
+
--------
|
|
141
|
+
>>> import log2frame
|
|
142
|
+
>>> log = log2frame.read('well.las')
|
|
143
|
+
>>> pack = log2frame.read('data/**/*.las')
|
|
144
|
+
>>> log = log2frame.read(['run1.las', 'run2.las'], squeeze=False)
|
|
145
|
+
"""
|
|
146
|
+
if use_simpandas is not None and use_simpandas is True and not _params_.simpandas_:
|
|
147
|
+
raise ModuleNotFoundError("SimPandas is not installed, please install it or set parameter `use_simpandas` to False.")
|
|
148
|
+
use_simpandas = _params_.simpandas_ if use_simpandas is None else bool(use_simpandas)
|
|
149
|
+
raise_error = _params_.raise_error_ if raise_error is None else bool(raise_error)
|
|
150
|
+
if os.path.isfile(path):
|
|
151
|
+
return _read_one(path, raise_error=raise_error, use_simpandas=use_simpandas, correct_units=correct_units)
|
|
152
|
+
if type(path) is str:
|
|
153
|
+
path = [path]
|
|
154
|
+
if type(path) is not str and hasattr(path, '__iter__'):
|
|
155
|
+
result = Pack()
|
|
156
|
+
for each in path:
|
|
157
|
+
for file in glob.iglob(each, recursive=recursive):
|
|
158
|
+
result.append(_read_one(file, raise_error=raise_error, use_simpandas=use_simpandas, correct_units=correct_units))
|
|
159
|
+
if squeeze:
|
|
160
|
+
return result.squeeze()
|
|
161
|
+
else:
|
|
162
|
+
return result
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""Unit-name correction mapping for LAS/DLIS/LIS mnemonics.
|
|
3
|
+
|
|
4
|
+
Loads ``units_dictionary.json`` (stored alongside this file) and
|
|
5
|
+
exposes :func:`correct_units` to normalise non-standard unit strings to
|
|
6
|
+
their canonical forms (e.g. ``'GAPI'`` → ``'gAPI'``,
|
|
7
|
+
``'OHMM'`` → ``'ohm.m'``).
|
|
8
|
+
|
|
9
|
+
Module-level attributes
|
|
10
|
+
-----------------------
|
|
11
|
+
units_correction_dict_ : dict
|
|
12
|
+
The raw ``{non_canonical: canonical}`` mapping loaded from the JSON
|
|
13
|
+
dictionary. An empty dict is used as a safe fallback when the file
|
|
14
|
+
is missing or malformed (a :class:`UserWarning` is emitted).
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
from os.path import dirname
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
this_path_ = Path(__file__).with_name('units_dictionary.json').absolute()
|
|
22
|
+
this_path_ = dirname(this_path_) + '/'
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
with open(this_path_ + 'units_dictionary.json', 'r') as f:
|
|
26
|
+
units_correction_dict_ = json.load(f)
|
|
27
|
+
except (FileNotFoundError, json.JSONDecodeError) as e:
|
|
28
|
+
import warnings
|
|
29
|
+
warnings.warn(
|
|
30
|
+
f"log2frame: units-correction dictionary not found "
|
|
31
|
+
f"({type(e).__name__}: {e}); proceeding with unit-correction disabled.",
|
|
32
|
+
UserWarning,
|
|
33
|
+
)
|
|
34
|
+
units_correction_dict_ = {}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def correct_units(units):
|
|
38
|
+
"""Normalise unit strings using the built-in correction dictionary.
|
|
39
|
+
|
|
40
|
+
Parameters
|
|
41
|
+
----------
|
|
42
|
+
units : str | dict
|
|
43
|
+
A single unit string, or a ``{mnemonic: unit}`` dict whose
|
|
44
|
+
values are corrected. Any other type is returned unchanged.
|
|
45
|
+
|
|
46
|
+
Returns
|
|
47
|
+
-------
|
|
48
|
+
str | dict
|
|
49
|
+
The corrected unit string (when *units* is a ``str``) or a new
|
|
50
|
+
dict with corrected values (when *units* is a ``dict``). Returns
|
|
51
|
+
the input unchanged when *units* is neither.
|
|
52
|
+
|
|
53
|
+
Examples
|
|
54
|
+
--------
|
|
55
|
+
>>> from log2frame.dictionaries.units import correct_units
|
|
56
|
+
>>> correct_units('GAPI')
|
|
57
|
+
'gAPI'
|
|
58
|
+
>>> correct_units({'GR': 'GAPI', 'RHOB': 'G/C3'})
|
|
59
|
+
{'GR': 'gAPI', 'RHOB': 'g/c3'}
|
|
60
|
+
"""
|
|
61
|
+
if type(units) is dict:
|
|
62
|
+
return {k: (units_correction_dict_[u]
|
|
63
|
+
if u in units_correction_dict_
|
|
64
|
+
else u)
|
|
65
|
+
for k, u in units.items()}
|
|
66
|
+
elif type(units) is str and units in units_correction_dict_:
|
|
67
|
+
return units_correction_dict_[units]
|
|
68
|
+
else:
|
|
69
|
+
return units
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
|
-
"""
|
|
3
|
-
|
|
2
|
+
"""DLIS reader — parse DLIS files into :class:`Log` or :class:`Pack` containers.
|
|
3
|
+
|
|
4
|
+
This module wraps ``dlisio`` to provide curve data and metadata as
|
|
5
|
+
unit-aware :class:`Log` objects. When a DLIS file contains multiple
|
|
6
|
+
logical files or frames, a :class:`Pack` grouping all frames is returned.
|
|
4
7
|
|
|
5
|
-
|
|
8
|
+
Public API
|
|
9
|
+
----------
|
|
10
|
+
dlis2frame(path, ...) Read a ``.dlis`` file.
|
|
6
11
|
"""
|
|
7
12
|
|
|
8
13
|
import logging
|
|
@@ -19,23 +24,47 @@ try:
|
|
|
19
24
|
except ModuleNotFoundError:
|
|
20
25
|
pass
|
|
21
26
|
|
|
22
|
-
__version__ = '0.2.
|
|
23
|
-
__release__ =
|
|
27
|
+
__version__ = '0.2.3'
|
|
28
|
+
__release__ = 20260503
|
|
24
29
|
|
|
25
30
|
logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.INFO)
|
|
26
31
|
|
|
27
32
|
|
|
28
33
|
class DLISIOError(Exception):
|
|
29
|
-
"""
|
|
30
|
-
Error raised while reading LAS file.
|
|
31
|
-
"""
|
|
34
|
+
"""Raised when ``dlisio`` fails to parse a DLIS file."""
|
|
32
35
|
def __init__(self, message='raised by dlisio.'):
|
|
33
36
|
self.message = 'ERROR: reading DLIS file ' + message
|
|
34
37
|
|
|
35
38
|
|
|
36
39
|
def dlis2frame(path: str, use_simpandas=None, raise_error=True, correct_units=True):
|
|
40
|
+
"""Read a DLIS file into a :class:`Log` container.
|
|
41
|
+
|
|
42
|
+
Parameters
|
|
43
|
+
----------
|
|
44
|
+
path : str
|
|
45
|
+
Path to the ``.dlis`` file.
|
|
46
|
+
use_simpandas : bool | None, default None
|
|
47
|
+
If True, the returned :class:`Log` wraps a
|
|
48
|
+
:class:`simpandas.SimDataFrame` and units propagate through
|
|
49
|
+
operations. If False, plain :class:`pandas.DataFrame` is used.
|
|
50
|
+
``None`` falls back to True when SimPandas is installed.
|
|
51
|
+
raise_error : bool, default True
|
|
52
|
+
If False, parse errors are caught and ``None`` is returned
|
|
53
|
+
instead of raising.
|
|
54
|
+
correct_units : bool, default True
|
|
55
|
+
Apply the LAS-mnemonic-to-canonical-unit corrections (GR's
|
|
56
|
+
``GAPI`` ↔ ``gAPI`` etc.) defined in
|
|
57
|
+
:data:`dictionaries.units_correction_dict_`.
|
|
58
|
+
|
|
59
|
+
Returns
|
|
60
|
+
-------
|
|
61
|
+
Log | Pack | None
|
|
62
|
+
The parsed log; or a ``Pack`` of logs if multiple frames are present;
|
|
63
|
+
``None`` if parsing failed and ``raise_error=False``.
|
|
64
|
+
"""
|
|
37
65
|
if not os.path.isfile(path):
|
|
38
66
|
raise FileNotFoundError("The provided path can't be found:\n" + str(path))
|
|
67
|
+
original_path = path
|
|
39
68
|
if type(path) is str:
|
|
40
69
|
path = path.replace('\\', '/')
|
|
41
70
|
|
|
@@ -129,10 +158,10 @@ def dlis2frame(path: str, use_simpandas=None, raise_error=True, correct_units=Tr
|
|
|
129
158
|
data=data[0] if not use_simpandas else spd.SimDataFrame(data=data[0], units=data[2].to_dict(),
|
|
130
159
|
name=data[3],
|
|
131
160
|
meta=data[1],
|
|
132
|
-
source=str(
|
|
161
|
+
source=str(original_path)),
|
|
133
162
|
header=data[1],
|
|
134
163
|
units=data[2],
|
|
135
|
-
source=str(
|
|
164
|
+
source=str(original_path),
|
|
136
165
|
well=data[3]) for name, data in frames.items()}
|
|
137
166
|
return frames[list(frames.keys())[0]]
|
|
138
167
|
else:
|
|
@@ -142,11 +171,11 @@ def dlis2frame(path: str, use_simpandas=None, raise_error=True, correct_units=Tr
|
|
|
142
171
|
meta=data[1],
|
|
143
172
|
source='logical file ' + str(name[0]) +
|
|
144
173
|
', frame ' + str(name[1]) +
|
|
145
|
-
' in: ' +
|
|
174
|
+
' in: ' + str(original_path)),
|
|
146
175
|
header=data[1],
|
|
147
176
|
units=data[2],
|
|
148
177
|
source='logical file ' + str(name[0]) +
|
|
149
178
|
', frame ' + str(name[1]) +
|
|
150
|
-
' in: ' +
|
|
179
|
+
' in: ' + str(original_path),
|
|
151
180
|
well=data[3]) for name, data in frames.items()}
|
|
152
181
|
return Pack(frames)
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
|
-
"""
|
|
3
|
-
|
|
2
|
+
"""LAS reader — parse LAS 1.2, 2.0, and 3.0 files into :class:`Log` containers.
|
|
3
|
+
|
|
4
|
+
This module wraps ``lasio`` to provide curve data and metadata as
|
|
5
|
+
unit-aware :class:`Log` objects.
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
Public API
|
|
8
|
+
----------
|
|
9
|
+
las2frame(path, ...) Read a ``.las`` file.
|
|
6
10
|
"""
|
|
7
11
|
import logging
|
|
8
12
|
import lasio
|
|
@@ -18,23 +22,55 @@ try:
|
|
|
18
22
|
except ModuleNotFoundError:
|
|
19
23
|
pass
|
|
20
24
|
|
|
21
|
-
__version__ = '0.2.
|
|
22
|
-
__release__ =
|
|
25
|
+
__version__ = '0.2.3'
|
|
26
|
+
__release__ = 20260503
|
|
23
27
|
|
|
24
28
|
logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.INFO)
|
|
25
29
|
|
|
26
30
|
|
|
27
31
|
class LASIOError(Exception):
|
|
28
|
-
"""
|
|
29
|
-
Error raised while reading LAS file.
|
|
30
|
-
"""
|
|
32
|
+
"""Raised when ``lasio`` fails to parse a LAS file."""
|
|
31
33
|
def __init__(self, message='raised by lasio.'):
|
|
32
34
|
self.message = 'ERROR: reading LAS file ' + message
|
|
33
35
|
|
|
34
36
|
|
|
35
37
|
def las2frame(path: str, use_simpandas=None, raise_error=True, correct_units=True):
|
|
38
|
+
"""Read a LAS file (1.2 / 2.0 / 3.0) into a :class:`Log` container.
|
|
39
|
+
|
|
40
|
+
Parameters
|
|
41
|
+
----------
|
|
42
|
+
path : str
|
|
43
|
+
Path to the ``.las`` file.
|
|
44
|
+
use_simpandas : bool | None, default None
|
|
45
|
+
If True, the returned :class:`Log` wraps a
|
|
46
|
+
:class:`simpandas.SimDataFrame` and units propagate through
|
|
47
|
+
operations. If False, plain :class:`pandas.DataFrame` is used.
|
|
48
|
+
``None`` falls back to True when SimPandas is installed.
|
|
49
|
+
raise_error : bool, default True
|
|
50
|
+
If False, parse errors are caught and ``None`` is returned
|
|
51
|
+
instead of raising.
|
|
52
|
+
correct_units : bool, default True
|
|
53
|
+
Apply the LAS-mnemonic-to-canonical-unit corrections (GR's
|
|
54
|
+
``GAPI`` ↔ ``gAPI`` etc.) defined in
|
|
55
|
+
:data:`dictionaries.units_correction_dict_`.
|
|
56
|
+
|
|
57
|
+
Returns
|
|
58
|
+
-------
|
|
59
|
+
Log | None
|
|
60
|
+
The parsed log; ``None`` if parsing failed and
|
|
61
|
+
``raise_error=False``.
|
|
62
|
+
|
|
63
|
+
Examples
|
|
64
|
+
--------
|
|
65
|
+
>>> log = log2frame.las2frame("well.las")
|
|
66
|
+
>>> log.curves
|
|
67
|
+
['GR', 'NPHI', 'RHOB', 'DT']
|
|
68
|
+
>>> log.units_dict()['GR']
|
|
69
|
+
'gAPI'
|
|
70
|
+
"""
|
|
36
71
|
if not os.path.isfile(path):
|
|
37
72
|
raise FileNotFoundError("The provided path can't be found:\n" + str(path))
|
|
73
|
+
original_path = path
|
|
38
74
|
if type(path) is str:
|
|
39
75
|
path = path.replace('\\', '/')
|
|
40
76
|
|
|
@@ -92,10 +128,10 @@ def las2frame(path: str, use_simpandas=None, raise_error=True, correct_units=Tru
|
|
|
92
128
|
units=las_units,
|
|
93
129
|
name=well_name,
|
|
94
130
|
meta=las_header,
|
|
95
|
-
source=
|
|
131
|
+
source=original_path)
|
|
96
132
|
|
|
97
133
|
return Log(data=data,
|
|
98
134
|
header=las_header,
|
|
99
135
|
units=pd.Series(las_units, name='curves_units'),
|
|
100
|
-
source=
|
|
136
|
+
source=original_path,
|
|
101
137
|
well=well_name)
|
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
|
-
"""
|
|
3
|
-
|
|
2
|
+
"""LIS reader — parse LIS files into :class:`Log` or :class:`Pack` containers.
|
|
3
|
+
|
|
4
|
+
This module wraps ``dlisio.lis`` to provide curve data and metadata as
|
|
5
|
+
unit-aware :class:`Log` objects. When a LIS file contains multiple
|
|
6
|
+
logical files or format specs with different sample rates, each
|
|
7
|
+
combination is returned as a separate :class:`Log` grouped in a
|
|
8
|
+
:class:`Pack`.
|
|
4
9
|
|
|
5
|
-
|
|
10
|
+
Public API
|
|
11
|
+
----------
|
|
12
|
+
lis2frame(path, ...) Read a ``.lis`` or ``.lti`` file.
|
|
6
13
|
"""
|
|
7
14
|
|
|
8
15
|
import logging
|
|
@@ -18,22 +25,46 @@ try:
|
|
|
18
25
|
except ModuleNotFoundError:
|
|
19
26
|
pass
|
|
20
27
|
|
|
21
|
-
__version__ = '0.2.
|
|
22
|
-
__release__ =
|
|
28
|
+
__version__ = '0.2.3'
|
|
29
|
+
__release__ = 20260503
|
|
23
30
|
|
|
24
31
|
logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.INFO)
|
|
25
32
|
|
|
26
33
|
|
|
27
34
|
class DLISIOError(Exception):
|
|
28
|
-
"""
|
|
29
|
-
Error raised while reading LAS file.
|
|
30
|
-
"""
|
|
35
|
+
"""Raised when ``dlisio`` fails to parse a LIS file."""
|
|
31
36
|
def __init__(self, message='raised by dlisio.'):
|
|
32
37
|
self.message = 'ERROR: reading LIS file ' + message
|
|
33
38
|
|
|
34
39
|
def lis2frame(path: str, use_simpandas=None, raise_error=True, correct_units=True):
|
|
40
|
+
"""Read a LIS file into a :class:`Log` container.
|
|
41
|
+
|
|
42
|
+
Parameters
|
|
43
|
+
----------
|
|
44
|
+
path : str
|
|
45
|
+
Path to the ``.lis`` or ``.lti`` file.
|
|
46
|
+
use_simpandas : bool | None, default None
|
|
47
|
+
If True, the returned :class:`Log` wraps a
|
|
48
|
+
:class:`simpandas.SimDataFrame` and units propagate through
|
|
49
|
+
operations. If False, plain :class:`pandas.DataFrame` is used.
|
|
50
|
+
``None`` falls back to True when SimPandas is installed.
|
|
51
|
+
raise_error : bool, default True
|
|
52
|
+
If False, parse errors are caught and ``None`` is returned
|
|
53
|
+
instead of raising.
|
|
54
|
+
correct_units : bool, default True
|
|
55
|
+
Apply the LAS-mnemonic-to-canonical-unit corrections (GR's
|
|
56
|
+
``GAPI`` ↔ ``gAPI`` etc.) defined in
|
|
57
|
+
:data:`dictionaries.units_correction_dict_`.
|
|
58
|
+
|
|
59
|
+
Returns
|
|
60
|
+
-------
|
|
61
|
+
Log | Pack | None
|
|
62
|
+
The parsed log; or a ``Pack`` of logs if multiple frames are present;
|
|
63
|
+
``None`` if parsing failed and ``raise_error=False``.
|
|
64
|
+
"""
|
|
35
65
|
if not os.path.isfile(path):
|
|
36
66
|
raise FileNotFoundError("The provided path can't be found:\n" + str(path))
|
|
67
|
+
original_path = path
|
|
37
68
|
if type(path) is str:
|
|
38
69
|
path = path.replace('\\', '/')
|
|
39
70
|
|
|
@@ -44,6 +75,28 @@ def lis2frame(path: str, use_simpandas=None, raise_error=True, correct_units=Tru
|
|
|
44
75
|
raise ModuleNotFoundError("SimPandas is not installed, please install it or set parameter `use_simpandas` to False.")
|
|
45
76
|
|
|
46
77
|
def _get_frame(data, l_count, i, sr):
|
|
78
|
+
"""Return the curve data for one format spec and sample rate.
|
|
79
|
+
|
|
80
|
+
When *use_simpandas* is ``True``, wraps the raw structured array
|
|
81
|
+
in a :class:`simpandas.SimDataFrame` with units and index
|
|
82
|
+
settings applied. Otherwise returns a plain
|
|
83
|
+
:class:`pandas.DataFrame` with the index set to the depth column.
|
|
84
|
+
|
|
85
|
+
Parameters
|
|
86
|
+
----------
|
|
87
|
+
data : pandas.DataFrame
|
|
88
|
+
Raw DataFrame produced from :func:`dlisio.lis.curves`.
|
|
89
|
+
l_count : int
|
|
90
|
+
Logical-file counter key into ``frames``.
|
|
91
|
+
i : int
|
|
92
|
+
Format-spec index within the logical file.
|
|
93
|
+
sr : int | float
|
|
94
|
+
Sample-rate key within the format spec.
|
|
95
|
+
|
|
96
|
+
Returns
|
|
97
|
+
-------
|
|
98
|
+
pandas.DataFrame | simpandas.SimDataFrame
|
|
99
|
+
"""
|
|
47
100
|
if frames[l_count][i]['index_name'] in data:
|
|
48
101
|
index_name = frames[l_count][i]['index_name']
|
|
49
102
|
elif len([c for c in data.columns if c.startswith(frames[l_count][i]['index_name'])]) > 0:
|
|
@@ -65,12 +118,31 @@ def lis2frame(path: str, use_simpandas=None, raise_error=True, correct_units=Tru
|
|
|
65
118
|
if frames[l_count]['header']['name'] is not None
|
|
66
119
|
and len(frames[l_count]['header']['name']) > 0 else None),
|
|
67
120
|
meta=_make_header(l_count=l_count, i=i, sr=sr),
|
|
68
|
-
source='logical file ' + str(l_count) + ', frame ' + str(i) + ', sample rate ' + str(sr) + ' in: ' + str(
|
|
121
|
+
source='logical file ' + str(l_count) + ', frame ' + str(i) + ', sample rate ' + str(sr) + ' in: ' + str(original_path))
|
|
69
122
|
else:
|
|
70
123
|
return data.set_index(index_name)
|
|
71
124
|
|
|
72
125
|
|
|
73
126
|
def _make_header(l_count, i, sr):
|
|
127
|
+
"""Build a merged header DataFrame for one logical file / frame / sample rate.
|
|
128
|
+
|
|
129
|
+
Concatenates curve units, wellsite data, and LIS file/reel header
|
|
130
|
+
into a single :class:`pandas.DataFrame` indexed by mnemonic.
|
|
131
|
+
|
|
132
|
+
Parameters
|
|
133
|
+
----------
|
|
134
|
+
l_count : int
|
|
135
|
+
Logical-file index.
|
|
136
|
+
i : int
|
|
137
|
+
Format-spec index within the logical file.
|
|
138
|
+
sr : int | float
|
|
139
|
+
Sample-rate key.
|
|
140
|
+
|
|
141
|
+
Returns
|
|
142
|
+
-------
|
|
143
|
+
pandas.DataFrame
|
|
144
|
+
Combined header table; empty cells are replaced with ``''``.
|
|
145
|
+
"""
|
|
74
146
|
to_concat = []
|
|
75
147
|
if 'curves_units' in frames[l_count][i] and sr in frames[l_count][i]['curves_units']:
|
|
76
148
|
to_concat.append(pd.DataFrame(frames[l_count][i]['curves_units'][sr],
|
|
@@ -85,6 +157,18 @@ def lis2frame(path: str, use_simpandas=None, raise_error=True, correct_units=Tru
|
|
|
85
157
|
return pd.concat(to_concat, axis=0).fillna(value='')
|
|
86
158
|
|
|
87
159
|
def _try_frame(data):
|
|
160
|
+
"""Attempt to wrap *data* in a :class:`pandas.DataFrame`.
|
|
161
|
+
|
|
162
|
+
Parameters
|
|
163
|
+
----------
|
|
164
|
+
data : structured numpy array or similar
|
|
165
|
+
Raw curve data returned by :func:`dlisio.lis.curves`.
|
|
166
|
+
|
|
167
|
+
Returns
|
|
168
|
+
-------
|
|
169
|
+
pandas.DataFrame | None
|
|
170
|
+
``None`` when the conversion raises any exception.
|
|
171
|
+
"""
|
|
88
172
|
try:
|
|
89
173
|
return pd.DataFrame(data)
|
|
90
174
|
except:
|
|
@@ -161,7 +245,7 @@ def lis2frame(path: str, use_simpandas=None, raise_error=True, correct_units=Tru
|
|
|
161
245
|
source='logical file ' + str(l_count) +
|
|
162
246
|
', frame ' + str(i) +
|
|
163
247
|
', sample rate ' + str(sr) +
|
|
164
|
-
' in: ' + str(
|
|
248
|
+
' in: ' + str(original_path),
|
|
165
249
|
well=(
|
|
166
250
|
frames[l_count]['header']['service_name'] if frames[l_count]['header']['service_name'] is not None and len(
|
|
167
251
|
frames[l_count]['header']['service_name']) > 0 else
|