import json
import logging
# The standard library OrderedDict was introduced in Python 2.7 so
# we have a third-party option to support Python 2.6
try:
from collections import OrderedDict
except ImportError:
from ordereddict import OrderedDict
import numpy as np
logger = logging.getLogger(__name__)
[docs]class CurveItem(HeaderItem):
"""Dictionary/namedtuple-style object for a LAS curve.
See :class:`lasio.HeaderItem`` for the (keyword) arguments.
Keyword Arguments:
data (array-like, 1-D): the curve's data.
"""
def __init__(self, mnemonic="", unit="", value="", descr="", data=None):
if data is None:
data = []
super(CurveItem, self).__init__(mnemonic, unit, value, descr)
self.data = np.asarray(data)
@property
def API_code(self):
"""Equivalent to the ``value`` attribute."""
return self.value
def __repr__(self):
return (
'%s(mnemonic="%s", unit="%s", value="%s", '
'descr="%s", original_mnemonic="%s", data.shape=%s)'
% (
self.__class__.__name__,
self.mnemonic,
self.unit,
self.value,
self.descr,
self.original_mnemonic,
self.data.shape,
)
)
@property
def json(self):
return json.dumps(
{
"_type": self.__class__.__name__,
"mnemonic": self.original_mnemonic,
"unit": self.unit,
"value": self.value,
"descr": self.descr,
"data": list(self.data),
}
)
@json.setter
def json(self, value):
raise Exception("Cannot set objects from JSON")
[docs]class SectionItems(list):
"""Variant of a ``list`` which is used to represent a LAS section."""
def __init__(self, *args, **kwargs):
super(SectionItems, self).__init__(*args, **kwargs)
super(SectionItems, self).__setattr__("mnemonic_transforms", False)
def __str__(self):
rstr_lines = []
data = [
["Mnemonic", "Unit", "Value", "Description"],
["--------", "----", "-----", "-----------"],
]
data += [
[str(x) for x in [item.mnemonic, item.unit, item.value, item.descr]]
for item in self
]
col_widths = []
for i in range(len(data[0])):
col_widths.append(max([len(row[i]) for row in data]))
for row in data:
line_items = []
for i, item in enumerate(row):
line_items.append(item.ljust(col_widths[i] + 2))
rstr_lines.append("".join(line_items))
return "\n".join(rstr_lines)
def mnemonic_compare(self, one, two):
if self.mnemonic_transforms:
try:
if one.upper() == two.upper():
return True
except AttributeError:
pass
else:
if one == two:
return True
return False
def __contains__(self, testitem):
"""Check whether a header item or mnemonic is in the section.
Arguments:
testitem (HeaderItem, CurveItem, str): either an item or a mnemonic
Returns:
bool
"""
for item in self:
if self.mnemonic_compare(testitem, item.mnemonic):
return True
elif hasattr(testitem, "mnemonic"):
if self.mnemonic_compare(testitem.mnemonic, item.mnemonic):
return True
elif testitem is item:
return True
else:
return False
[docs] def keys(self):
"""Return mnemonics of all the HeaderItems in the section."""
return [item.mnemonic for item in self]
[docs] def values(self):
"""Return HeaderItems in the section."""
return self
[docs] def items(self):
"""Return pairs of (mnemonic, HeaderItem) from the section."""
return [(item.mnemonic, item) for item in self]
def iterkeys(self):
return iter(self.keys())
def itervalues(self):
return iter(self)
def iteritems(self):
return iter(self.items())
def __getslice__(self, i0, i1):
"""For Python 2.7 compatibility."""
return self.__getitem__(slice(i0, i1))
def __getitem__(self, key):
"""Item-style access by either mnemonic or index.
Arguments:
key (str, int, slice): either a mnemonic or the index to the list.
Returns:
item from the list (either HeaderItem or CurveItem)
"""
if isinstance(key, slice):
return SectionItems(super(SectionItems, self).__getitem__(key))
for item in self:
if self.mnemonic_compare(item.mnemonic, key):
return item
if isinstance(key, int):
return super(SectionItems, self).__getitem__(key)
else:
raise KeyError("%s not in %s" % (key, self.keys()))
[docs] def get(self, mnemonic, default="", add=False):
"""Get an item, with a default value for the situation when it is missing.
Arguments:
mnemonic (str): mnemonic of item to retrieve
default (str, HeaderItem, or CurveItem): default to provide
if *mnemonic* is missing from the section. If a string is
provided, it will be used as the ``value`` attribute of a new
HeaderItem or the ``descr`` attribute of a new CurveItem.
add (bool): if True, the returned HeaderItem/CurveItem will also
be appended to the SectionItems. By default this is not done.
Returns:
:class:`lasio.HeaderItem`/:class:`lasio.CurveItem`: item from
the section, if it is in there, or
a new item, if it is not. If a CurveItem is returned, the
``data`` attribute will contain ``numpy.nan`` values.
"""
if mnemonic in self:
return self[mnemonic]
else:
if not (
isinstance(default, HeaderItem)
or isinstance(default, CurveItem)
):
default = str(default)
# Determine appropriate type of item to create (HeaderItem
# or CurveItem).
if len(self):
first_item = self[0]
item_type = type(first_item)
else:
item_type = HeaderItem
if item_type is CurveItem:
new_data = np.asarray(first_item.data)
new_data = new_data * np.nan
item = CurveItem(
mnemonic=mnemonic,
descr=default,
data=new_data
)
else:
item = HeaderItem(
mnemonic=mnemonic,
value=default
)
else:
assert type(default) in (HeaderItem, CurveItem)
item = type(default)(
mnemonic=mnemonic,
unit=default.unit,
value=default.value,
descr=default.descr
)
if type(item) is CurveItem:
item.data = np.array(default.data)
if add:
self.append(item)
return item
def __delitem__(self, key):
"""Delete item by either mnemonic or index.
Arguments:
key (str, int): either a mnemonic or the index to the list.
"""
for ix, item in enumerate(self):
if self.mnemonic_compare(item.mnemonic, key):
super(SectionItems, self).__delitem__(ix)
return
if isinstance(key, int):
super(SectionItems, self).__delitem__(key)
return
else:
raise KeyError("%s not in %s" % (key, self.keys()))
def __setitem__(self, key, newitem):
"""Either replace the item or its value.
Arguments:
key (int, str): either the mnemonic or the index.
newitem (HeaderItem or str/float/int): the thing to be set.
If ``newitem`` is a :class:`lasio.HeaderItem` then the
existing item will be replaced. Otherwise the existing item's ``value``
attribute will be replaced.
i.e. this allows us to do
>>> from lasio import SectionItems, HeaderItem
>>> section = SectionItems(
... [HeaderItem(mnemonic="OPERATOR", value="John")]
... )
>>> section.OPERATOR
HeaderItem(mnemonic=OPERATOR, unit=, value=John, descr=)
>>> section.OPERATOR = 'Kent'
>>> section.OPERATOR
HeaderItem(mnemonic=OPERATOR, unit=, value=Kent, descr=)
See :meth:`lasio.SectionItems.set_item` and
:meth:`lasio.SectionItems.set_item_value`.
"""
if isinstance(newitem, HeaderItem):
self.set_item(key, newitem)
else:
self.set_item_value(key, newitem)
def __getattr__(self, key):
"""Provide attribute access via __contains__ e.g.
>>> from lasio import SectionItems, HeaderItem
>>> section = SectionItems(
... [HeaderItem(mnemonic="VERS", value=1.2)]
... )
>>> section['VERS']
HeaderItem(mnemonic=VERS, unit=, value=1.2, descr=)
>>> 'VERS' in section
True
>>> section.VERS
HeaderItem(mnemonic=VERS, unit=, value=1.2, descr=)
"""
known_attrs = [
"mnemonic_transforms",
]
if not key in known_attrs:
if key in self:
return self[key]
super(SectionItems, self).__getattr__(key)
def __setattr__(self, key, value):
"""Allow access to :meth:`lasio.SectionItems.__setitem__`
via attribute access.
"""
if key in self:
self[key] = value
elif isinstance(value, HeaderItem) or isinstance(value, CurveItem):
assert value.mnemonic == key
self.append(value)
else:
super(SectionItems, self).__setattr__(key, value)
[docs] def set_item(self, key, newitem):
"""Replace an item by comparison of session mnemonics.
Arguments:
key (str): the item mnemonic (or HeaderItem with mnemonic)
you want to replace.
newitem (HeaderItem): the new item
If **key** is not present, it appends **newitem**.
"""
for i, item in enumerate(self):
if self.mnemonic_compare(key, item.mnemonic):
# This is very important. We replace items where
# 'mnemonic' is equal - i.e. we do not check
# against useful_mnemonic or original_mnemonic.
return super(SectionItems, self).__setitem__(i, newitem)
else:
self.append(newitem)
[docs] def set_item_value(self, key, value):
"""Set the ``value`` attribute of an item.
Arguments:
key (str): the mnemonic of the item (or HeaderItem with the
mnemonic) you want to edit
value (str, int, float): the new value.
"""
self[key].value = value
[docs] def append(self, newitem):
"""Append a new HeaderItem to the object."""
super(SectionItems, self).append(newitem)
self.assign_duplicate_suffixes(newitem.useful_mnemonic)
[docs] def insert(self, i, newitem):
"""Insert a new HeaderItem to the object."""
super(SectionItems, self).insert(i, newitem)
self.assign_duplicate_suffixes(newitem.useful_mnemonic)
[docs] def assign_duplicate_suffixes(self, test_mnemonic=None):
"""Check and re-assign suffixes for duplicate mnemonics.
Arguments:
test_mnemonic (str, optional): check for duplicates of
this mnemonic. If it is None, check all mnemonics.
"""
if test_mnemonic is None:
for test_mnemonic in {i.useful_mnemonic for i in self}:
self.assign_duplicate_suffixes(test_mnemonic)
else:
existing = [item.useful_mnemonic for item in self]
locations = []
for i, item in enumerate(self):
if self.mnemonic_compare(item.useful_mnemonic, test_mnemonic):
locations.append(i)
if len(locations) > 1:
current_count = 1
for i, loc in enumerate(locations):
item = self[loc]
item.set_session_mnemonic_only(
item.useful_mnemonic + ":%d" % (i + 1)
)
[docs] def dictview(self):
"""View of mnemonics and values as a dict.
Returns:
dict - keys are the mnemonics and the values are the ``value``
attributes.
"""
return dict(zip(self.keys(), [i.value for i in self.values()]))
@property
def json(self):
return json.dumps([item.json for item in self.values()])
@json.setter
def json(self, value):
raise Exception("Cannot set objects from JSON")