Source code for lasio.las_items

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 HeaderItem(OrderedDict): """Dictionary/namedtuple-style object for a LAS header line. Arguments: mnemonic (str): the mnemonic unit (str): the unit (no whitespace!) value (str): value descr (str): description These arguments are available for use as either items or attributes of the object. """ def __init__(self, mnemonic="", unit="", value="", descr="", data=None): super(HeaderItem, self).__init__() # The original mnemonic needs to be stored for rewriting a new file. # it might be nothing - '' - or a duplicate e.g. two 'RHO' curves, # or unique - 'X11124' - or perhaps invalid?? # It will be used only when exporting. self.original_mnemonic = mnemonic # We also need to store a more useful mnemonic, which will be used # (technically not, but read on) for people to access the curve while # the LASFile object exists. For example, a curve which is unnamed # and has an original_mnemonic of '' will be accessed as 'UNKNOWN'. # It is used in contexts where duplicate mnemonics are acceptable. # see property HeaderItem.useful_mnemonic # But note that we need to (later) check (repeatedly) for duplicate # mnemonics. Any duplicates will have ':1', ':2', ':3', etc., appended # to them. The result of this will be stored as the # HeaderItem.mnemonic attribute through the below method. # It is used in contexts where duplicate mnemonics cannot exist. self.set_session_mnemonic_only(self.useful_mnemonic) self.unit = unit self.value = value self.descr = descr self.data = data @property def useful_mnemonic(self): if self.original_mnemonic.strip() == "": return "UNKNOWN" else: return self.original_mnemonic @useful_mnemonic.setter def useful_mnemonic(self, value): raise ValueError("Cannot set read-only attribute; try .mnemonic instead")
[docs] def set_session_mnemonic_only(self, value): """Set the mnemonic for session use. See source comments for :class:`lasio.HeaderItem.__init__` for a more in-depth explanation. """ super(HeaderItem, self).__setattr__("mnemonic", value)
def __getitem__(self, key): """Provide item dictionary-like access.""" if key == "mnemonic": return self.mnemonic elif key == "original_mnemonic": return self.original_mnemonic elif key == "useful_mnemonic": return self.useful_mnemonic elif key == "unit": return self.unit elif key == "value": return self.value elif key == "descr": return self.descr else: raise KeyError("CurveItem only has restricted items (not %s)" % key) def __setattr__(self, key, value): if key == "mnemonic": # The user wants to rename the item! This means we must send their # new mnemonic to the original_mnemonic attribute. Remember that the # mnemonic attribute is for session use only. self.original_mnemonic = value self.set_session_mnemonic_only(self.useful_mnemonic) else: super(HeaderItem, self).__setattr__(key, value) def __repr__(self): result = '%s(mnemonic="%s", unit="%s", value="%s", ' 'descr="%s")' % ( self.__class__.__name__, self.mnemonic, self.unit, self.value, self.descr, ) while len(result) > 80: result = result[:-3] + result[-2:] return result def _repr_pretty_(self, p, cycle): return p.text(self.__repr__()) def __reduce__(self): return self.__class__, ( self.mnemonic, self.unit, self.value, self.descr, self.data, ) @property def json(self): return json.dumps( { "_type": self.__class__.__name__, "mnemonic": self.original_mnemonic, "unit": self.unit, "value": self.value, "descr": self.descr, } ) @json.setter def json(self, value): raise Exception("Cannot set objects from JSON")
[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")