1
|
|
|
import json |
2
|
|
|
import logging |
3
|
|
|
|
4
|
|
|
# The standard library OrderedDict was introduced in Python 2.7 so |
5
|
|
|
# we have a third-party option to support Python 2.6 |
6
|
|
|
|
7
|
|
|
try: |
8
|
|
|
from collections import OrderedDict |
9
|
|
|
except ImportError: |
10
|
|
|
from ordereddict import OrderedDict |
11
|
|
|
|
12
|
|
|
import numpy as np |
13
|
|
|
|
14
|
|
|
logger = logging.getLogger(__name__) |
15
|
|
|
|
16
|
|
|
|
17
|
|
|
class HeaderItem(OrderedDict): |
18
|
|
|
|
19
|
|
|
'''Dictionary/namedtuple-style object for a LAS header line. |
20
|
|
|
|
21
|
|
|
Arguments: |
22
|
|
|
mnemonic (str): the mnemonic |
23
|
|
|
unit (str): the unit (no whitespace!) |
24
|
|
|
value (str): value |
25
|
|
|
descr (str): description |
26
|
|
|
|
27
|
|
|
These arguments are available for use as either items or attributes of the |
28
|
|
|
object. |
29
|
|
|
|
30
|
|
|
''' |
31
|
|
|
def __init__(self, mnemonic='', unit='', value='', descr='', data=None): |
32
|
|
|
super(HeaderItem, self).__init__() |
33
|
|
|
|
34
|
|
|
# The original mnemonic needs to be stored for rewriting a new file. |
35
|
|
|
# it might be nothing - '' - or a duplicate e.g. two 'RHO' curves, |
36
|
|
|
# or unique - 'X11124' - or perhaps invalid?? |
37
|
|
|
# It will be used only when exporting. |
38
|
|
|
|
39
|
|
|
self.original_mnemonic = mnemonic |
40
|
|
|
|
41
|
|
|
# We also need to store a more useful mnemonic, which will be used |
42
|
|
|
# (technically not, but read on) for people to access the curve while |
43
|
|
|
# the LASFile object exists. For example, a curve which is unnamed |
44
|
|
|
# and has an original_mnemonic of '' will be accessed as 'UNKNOWN'. |
45
|
|
|
# It is used in contexts where duplicate mnemonics are acceptable. |
46
|
|
|
|
47
|
|
|
# see property HeaderItem.useful_mnemonic |
48
|
|
|
|
49
|
|
|
# But note that we need to (later) check (repeatedly) for duplicate |
50
|
|
|
# mnemonics. Any duplicates will have ':1', ':2', ':3', etc., appended |
51
|
|
|
# to them. The result of this will be stored as the |
52
|
|
|
# HeaderItem.mnemonic attribute through the below method. |
53
|
|
|
# It is used in contexts where duplicate mnemonics cannot exist. |
54
|
|
|
|
55
|
|
|
self.set_session_mnemonic_only(self.useful_mnemonic) |
56
|
|
|
|
57
|
|
|
self.unit = unit |
58
|
|
|
self.value = value |
59
|
|
|
self.descr = descr |
60
|
|
|
self.data = data |
61
|
|
|
|
62
|
|
|
@property |
63
|
|
|
def useful_mnemonic(self): |
64
|
|
|
if self.original_mnemonic.strip() == '': |
65
|
|
|
return 'UNKNOWN' |
66
|
|
|
else: |
67
|
|
|
return self.original_mnemonic |
68
|
|
|
|
69
|
|
|
@useful_mnemonic.setter |
70
|
|
|
def useful_mnemonic(self, value): |
71
|
|
|
raise ValueError('Cannot set read-only attribute; try .mnemonic instead') |
72
|
|
|
|
73
|
|
|
def set_session_mnemonic_only(self, value): |
74
|
|
|
'''Set the mnemonic for session use. |
75
|
|
|
|
76
|
|
|
See source comments for :class:`lasio.las_items.HeaderItem.__init__` |
77
|
|
|
for a more in-depth explanation. |
78
|
|
|
|
79
|
|
|
''' |
80
|
|
|
super(HeaderItem, self).__setattr__('mnemonic', value) |
81
|
|
|
|
82
|
|
|
def __getitem__(self, key): |
83
|
|
|
'''Provide item dictionary-like access.''' |
84
|
|
|
if key == 'mnemonic': |
85
|
|
|
return self.mnemonic |
86
|
|
|
elif key == 'original_mnemonic': |
87
|
|
|
return self.original_mnemonic |
88
|
|
|
elif key == 'useful_mnemonic': |
89
|
|
|
return self.useful_mnemonic |
90
|
|
|
elif key == 'unit': |
91
|
|
|
return self.unit |
92
|
|
|
elif key == 'value': |
93
|
|
|
return self.value |
94
|
|
|
elif key == 'descr': |
95
|
|
|
return self.descr |
96
|
|
|
else: |
97
|
|
|
raise KeyError( |
98
|
|
|
'CurveItem only has restricted items (not %s)' % key) |
99
|
|
|
|
100
|
|
|
def __setattr__(self, key, value): |
101
|
|
|
|
102
|
|
|
if key == 'mnemonic': |
103
|
|
|
|
104
|
|
|
# The user wants to rename the item! This means we must send their |
105
|
|
|
# new mnemonic to the original_mnemonic attribute. Remember that the |
106
|
|
|
# mnemonic attribute is for session use only. |
107
|
|
|
|
108
|
|
|
self.original_mnemonic = value |
109
|
|
|
self.set_session_mnemonic_only(self.useful_mnemonic) |
110
|
|
|
else: |
111
|
|
|
super(HeaderItem, self).__setattr__(key, value) |
112
|
|
|
|
113
|
|
|
def __repr__(self): |
114
|
|
|
result = ( |
115
|
|
|
'%s(mnemonic=%s, unit=%s, value=%s, ' |
116
|
|
|
'descr=%s)' % ( |
117
|
|
|
self.__class__.__name__, self.mnemonic, self.unit, self.value, |
118
|
|
|
self.descr)) |
119
|
|
|
if len(result) > 80: |
120
|
|
|
return result[:76] + '...)' |
121
|
|
|
else: |
122
|
|
|
return result |
123
|
|
|
|
124
|
|
|
def _repr_pretty_(self, p, cycle): |
125
|
|
|
return p.text(self.__repr__()) |
126
|
|
|
|
127
|
|
|
def __reduce__(self): |
128
|
|
|
return self.__class__, (self.mnemonic, self.unit, self.value, |
129
|
|
|
self.descr, self.data) |
130
|
|
|
|
131
|
|
|
@property |
132
|
|
|
def json(self): |
133
|
|
|
return json.dumps({ |
134
|
|
|
'_type': self.__class__.__name__, |
135
|
|
|
'mnemonic': self.original_mnemonic, |
136
|
|
|
'unit': self.unit, |
137
|
|
|
'value': self.value, |
138
|
|
|
'descr': self.descr |
139
|
|
|
}) |
140
|
|
|
|
141
|
|
|
@json.setter |
142
|
|
|
def json(self, value): |
143
|
|
|
raise Exception('Cannot set objects from JSON') |
144
|
|
|
|
145
|
|
|
|
146
|
|
|
class CurveItem(HeaderItem): |
147
|
|
|
|
148
|
|
|
'''Dictionary/namedtuple-style object for a LAS curve. |
149
|
|
|
|
150
|
|
|
See :class:`lasio.las_items.HeaderItem`` for the (keyword) arguments. |
151
|
|
|
|
152
|
|
|
Keyword Arguments: |
153
|
|
|
data (array-like, 1-D): the curve's data. |
154
|
|
|
|
155
|
|
|
''' |
156
|
|
|
|
157
|
|
|
def __init__(self, mnemonic='', unit='', value='', descr='', data=None): |
158
|
|
|
if data is None: |
159
|
|
|
data = [] |
160
|
|
|
super(CurveItem, self).__init__(mnemonic, unit, value, descr) |
161
|
|
|
self.data = np.asarray(data) |
162
|
|
|
|
163
|
|
|
@property |
164
|
|
|
def API_code(self): |
165
|
|
|
'''Equivalent to the ``value`` attribute.''' |
166
|
|
|
return self.value |
167
|
|
|
|
168
|
|
|
def __repr__(self): |
169
|
|
|
return ( |
170
|
|
|
'%s(mnemonic=%s, unit=%s, value=%s, ' |
171
|
|
|
'descr=%s, original_mnemonic=%s, data.shape=%s)' % ( |
172
|
|
|
self.__class__.__name__, self.mnemonic, self.unit, self.value, |
173
|
|
|
self.descr, self.original_mnemonic, self.data.shape)) |
174
|
|
|
|
175
|
|
|
@property |
176
|
|
|
def json(self): |
177
|
|
|
return json.dumps({ |
178
|
|
|
'_type': self.__class__.__name__, |
179
|
|
|
'mnemonic': self.original_mnemonic, |
180
|
|
|
'unit': self.unit, |
181
|
|
|
'value': self.value, |
182
|
|
|
'descr': self.descr, |
183
|
|
|
'data': list(self.data), |
184
|
|
|
}) |
185
|
|
|
|
186
|
|
|
@json.setter |
187
|
|
|
def json(self, value): |
188
|
|
|
raise Exception('Cannot set objects from JSON') |
189
|
|
|
|
190
|
|
|
|
191
|
|
|
class SectionItems(list): |
192
|
|
|
|
193
|
|
|
'''Variant of a ``list`` which is used to represent a LAS section. |
194
|
|
|
|
195
|
|
|
''' |
196
|
|
|
def __init__(self, *args, **kwargs): |
197
|
|
|
super(SectionItems, self).__init__(*args, **kwargs) |
198
|
|
|
super(SectionItems, self).__setattr__('mnemonic_transforms', False) |
199
|
|
|
|
200
|
|
|
def __str__(self): |
201
|
|
|
rstr_lines = [] |
202
|
|
|
data = [['Mnemonic', 'Unit', 'Value', 'Description'], |
203
|
|
|
['--------', '----', '-----', '-----------']] |
204
|
|
|
data += [[str(x) for x in [item.mnemonic, item.unit, item.value, |
205
|
|
|
item.descr]] for item in self] |
206
|
|
|
col_widths = [] |
207
|
|
|
for i in range(len(data[0])): |
208
|
|
|
col_widths.append(max([len(row[i]) for row in data])) |
209
|
|
|
for row in data: |
210
|
|
|
line_items = [] |
211
|
|
|
for i, item in enumerate(row): |
212
|
|
|
line_items.append(item.ljust(col_widths[i] + 2)) |
213
|
|
|
rstr_lines.append(''.join(line_items)) |
214
|
|
|
return '\n'.join(rstr_lines) |
215
|
|
|
|
216
|
|
|
def mnemonic_compare(self, one, two): |
217
|
|
|
if self.mnemonic_transforms: |
218
|
|
|
try: |
219
|
|
|
if one.upper() == two.upper(): |
220
|
|
|
return True |
221
|
|
|
except AttributeError: |
222
|
|
|
pass |
223
|
|
|
else: |
224
|
|
|
if one == two: |
225
|
|
|
return True |
226
|
|
|
return False |
227
|
|
|
|
228
|
|
|
def __contains__(self, testitem): |
229
|
|
|
'''Check whether a header item or mnemonic is in the section. |
230
|
|
|
|
231
|
|
|
Arguments: |
232
|
|
|
testitem (HeaderItem, CurveItem, str): either an item or a mnemonic |
233
|
|
|
|
234
|
|
|
Returns: |
235
|
|
|
bool |
236
|
|
|
|
237
|
|
|
''' |
238
|
|
|
for item in self: |
239
|
|
|
if self.mnemonic_compare(testitem, item.mnemonic): |
240
|
|
|
return True |
241
|
|
|
elif hasattr(testitem, 'mnemonic'): |
242
|
|
|
if self.mnemonic_compare(testitem.mnemonic, item.mnemonic): |
243
|
|
|
return True |
244
|
|
|
elif testitem is item: |
245
|
|
|
return True |
246
|
|
|
else: |
247
|
|
|
return False |
248
|
|
|
|
249
|
|
|
def keys(self): |
250
|
|
|
'''Return mnemonics of all the HeaderItems in the section.''' |
251
|
|
|
return [item.mnemonic for item in self] |
252
|
|
|
|
253
|
|
|
def values(self): |
254
|
|
|
'''Return HeaderItems in the section.''' |
255
|
|
|
return self |
256
|
|
|
|
257
|
|
|
def items(self): |
258
|
|
|
'''Return pairs of (mnemonic, HeaderItem) from the section.''' |
259
|
|
|
return [(item.mnemonic, item) for item in self] |
260
|
|
|
|
261
|
|
|
def iterkeys(self): |
262
|
|
|
return iter(self.keys()) |
263
|
|
|
|
264
|
|
|
def itervalues(self): |
265
|
|
|
return iter(self) |
266
|
|
|
|
267
|
|
|
def iteritems(self): |
268
|
|
|
return iter(self.items()) |
269
|
|
|
|
270
|
|
|
def __getslice__(self, i0, i1): |
271
|
|
|
'''For Python 2.7 compatibility.''' |
272
|
|
|
return self.__getitem__(slice(i0, i1)) |
273
|
|
|
|
274
|
|
|
def __getitem__(self, key): |
275
|
|
|
'''Item-style access by either mnemonic or index. |
276
|
|
|
|
277
|
|
|
Arguments: |
278
|
|
|
key (str, int, slice): either a mnemonic or the index to the list. |
279
|
|
|
|
280
|
|
|
Returns: |
281
|
|
|
item from the list (either HeaderItem or CurveItem) |
282
|
|
|
|
283
|
|
|
''' |
284
|
|
|
if isinstance(key, slice): |
285
|
|
|
return SectionItems(super(SectionItems, self).__getitem__(key)) |
286
|
|
|
for item in self: |
287
|
|
|
if self.mnemonic_compare(item.mnemonic, key): |
288
|
|
|
return item |
289
|
|
|
if isinstance(key, int): |
290
|
|
|
return super(SectionItems, self).__getitem__(key) |
291
|
|
|
else: |
292
|
|
|
raise KeyError('%s not in %s' % (key, self.keys())) |
293
|
|
|
|
294
|
|
|
def __delitem__(self, key): |
295
|
|
|
'''Delete item by either mnemonic or index. |
296
|
|
|
|
297
|
|
|
Arguments: |
298
|
|
|
key (str, int): either a mnemonic or the index to the list. |
299
|
|
|
|
300
|
|
|
''' |
301
|
|
|
for ix, item in enumerate(self): |
302
|
|
|
if self.mnemonic_compare(item.mnemonic, key): |
303
|
|
|
super(SectionItems, self).__delitem__(ix) |
304
|
|
|
return |
305
|
|
|
if isinstance(key, int): |
306
|
|
|
super(SectionItems, self).__delitem__(key) |
307
|
|
|
return |
308
|
|
|
else: |
309
|
|
|
raise KeyError('%s not in %s' % (key, self.keys())) |
310
|
|
|
|
311
|
|
|
def __setitem__(self, key, newitem): |
312
|
|
|
'''Either replace the item or its value. |
313
|
|
|
|
314
|
|
|
Arguments: |
315
|
|
|
key (int, str): either the mnemonic or the index. |
316
|
|
|
newitem (HeaderItem or str/float/int): the thing to be set. |
317
|
|
|
|
318
|
|
|
If ``newitem`` is a :class:`lasio.las_items.HeaderItem` then the |
319
|
|
|
existing item will be replaced. Otherwise the existing item's ``value`` |
320
|
|
|
attribute will be replaced. |
321
|
|
|
|
322
|
|
|
i.e. this allows us to do |
323
|
|
|
|
324
|
|
|
>>> section.OPERATOR |
325
|
|
|
HeaderItem(mnemonic='OPERATOR', value='John') |
326
|
|
|
>>> section.OPERATOR = 'Kent' |
327
|
|
|
>>> section.OPERATOR |
328
|
|
|
HeaderItem(mnemonic='OPERATOR', value='Kent') |
329
|
|
|
|
330
|
|
|
See :meth:`lasio.las_items.SectionItems.set_item` and |
331
|
|
|
:meth:`lasio.las_items.SectionItems.set_item_value`. |
332
|
|
|
|
333
|
|
|
''' |
334
|
|
|
if isinstance(newitem, HeaderItem): |
335
|
|
|
self.set_item(key, newitem) |
336
|
|
|
else: |
337
|
|
|
self.set_item_value(key, newitem) |
338
|
|
|
|
339
|
|
|
def __getattr__(self, key): |
340
|
|
|
'''Provide attribute access via __contains__ e.g. |
341
|
|
|
|
342
|
|
|
>>> section['VERS'] |
343
|
|
|
HeaderItem(mnemonic='VERS', ...) |
344
|
|
|
>>> 'VERS' in section |
345
|
|
|
True |
346
|
|
|
>>> section.VERS |
347
|
|
|
HeaderItem(mnemonic='VERS', ...) |
348
|
|
|
|
349
|
|
|
''' |
350
|
|
|
known_attrs = ['mnemonic_transforms', ] |
351
|
|
|
if not key in known_attrs: |
352
|
|
|
if key in self: |
353
|
|
|
return self[key] |
354
|
|
|
super(SectionItems, self).__getattr__(key) |
355
|
|
|
|
356
|
|
|
def __setattr__(self, key, value): |
357
|
|
|
'''Allow access to :meth:`lasio.las_items.SectionItems.__setitem__` |
358
|
|
|
via attribute access. |
359
|
|
|
|
360
|
|
|
''' |
361
|
|
|
if key in self: |
362
|
|
|
self[key] = value |
363
|
|
|
else: |
364
|
|
|
super(SectionItems, self).__setattr__(key, value) |
365
|
|
|
|
366
|
|
|
def set_item(self, key, newitem): |
367
|
|
|
'''Replace an item by comparison of session mnemonics. |
368
|
|
|
|
369
|
|
|
Arguments: |
370
|
|
|
key (str): the item mnemonic (or HeaderItem with mnemonic) |
371
|
|
|
you want to replace. |
372
|
|
|
newitem (HeaderItem): the new item |
373
|
|
|
|
374
|
|
|
If **key** is not present, it appends **newitem**. |
375
|
|
|
|
376
|
|
|
''' |
377
|
|
|
for i, item in enumerate(self): |
378
|
|
|
if self.mnemonic_compare(key, item.mnemonic): |
379
|
|
|
|
380
|
|
|
# This is very important. We replace items where |
381
|
|
|
# 'mnemonic' is equal - i.e. we do not check |
382
|
|
|
# against useful_mnemonic or original_mnemonic. |
383
|
|
|
|
384
|
|
|
return super(SectionItems, self).__setitem__(i, newitem) |
385
|
|
|
else: |
386
|
|
|
self.append(newitem) |
387
|
|
|
|
388
|
|
|
def set_item_value(self, key, value): |
389
|
|
|
'''Set the ``value`` attribute of an item. |
390
|
|
|
|
391
|
|
|
Arguments: |
392
|
|
|
key (str): the mnemonic of the item (or HeaderItem with the |
393
|
|
|
mnemonic) you want to edit |
394
|
|
|
value (str, int, float): the new value. |
395
|
|
|
|
396
|
|
|
''' |
397
|
|
|
self[key].value = value |
398
|
|
|
|
399
|
|
|
def append(self, newitem): |
400
|
|
|
'''Append a new HeaderItem to the object.''' |
401
|
|
|
super(SectionItems, self).append(newitem) |
402
|
|
|
self.assign_duplicate_suffixes(newitem.useful_mnemonic) |
403
|
|
|
|
404
|
|
|
def insert(self, i, newitem): |
405
|
|
|
'''Insert a new HeaderItem to the object.''' |
406
|
|
|
super(SectionItems, self).insert(i, newitem) |
407
|
|
|
self.assign_duplicate_suffixes(newitem.useful_mnemonic) |
408
|
|
|
|
409
|
|
|
def assign_duplicate_suffixes(self, test_mnemonic=None): |
410
|
|
|
'''Check and re-assign suffixes for duplicate mnemonics. |
411
|
|
|
|
412
|
|
|
Arguments: |
413
|
|
|
test_mnemonic (str, optional): check for duplicates of |
414
|
|
|
this mnemonic. If it is None, check all mnemonics. |
415
|
|
|
|
416
|
|
|
''' |
417
|
|
|
if test_mnemonic is None: |
418
|
|
|
for test_mnemonic in {i.useful_mnemonic for i in self}: |
419
|
|
|
self.assign_duplicate_suffixes(test_mnemonic) |
420
|
|
|
else: |
421
|
|
|
existing = [item.useful_mnemonic for item in self] |
422
|
|
|
locations = [] |
423
|
|
|
for i, item in enumerate(self): |
424
|
|
|
if self.mnemonic_compare(item.useful_mnemonic, test_mnemonic): |
425
|
|
|
locations.append(i) |
426
|
|
|
if len(locations) > 1: |
427
|
|
|
current_count = 1 |
428
|
|
|
for i, loc in enumerate(locations): |
429
|
|
|
item = self[loc] |
430
|
|
|
item.set_session_mnemonic_only(item.useful_mnemonic + ':%d' |
431
|
|
|
% (i + 1)) |
432
|
|
|
|
433
|
|
|
def dictview(self): |
434
|
|
|
'''View of mnemonics and values as a dict. |
435
|
|
|
|
436
|
|
|
Returns: |
437
|
|
|
dict - keys are the mnemonics and the values are the ``value`` |
438
|
|
|
attributes. |
439
|
|
|
''' |
440
|
|
|
return dict(zip(self.keys(), [i.value for i in self.values()])) |
441
|
|
|
|
442
|
|
|
@property |
443
|
|
|
def json(self): |
444
|
|
|
return json.dumps( |
445
|
|
|
[item.json for item in self.values()]) |
446
|
|
|
|
447
|
|
|
@json.setter |
448
|
|
|
def json(self, value): |
449
|
|
|
raise Exception('Cannot set objects from JSON') |