LASReader - SciPy wiki dump (original) (raw)

This cookbook example contains a module that implements a reader for a LAS (Log ASCII Standard) well log file (LAS 2.0). See the Canadian Well Logging Society page about this format for more information.

1 """LAS File Reader 2 3 The main class defined here is LASReader, a class that reads a LAS file 4 and makes the data available as a Python object. 5 """ 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import re 23 import keyword 24 25 import numpy as np 26 27 28 def isidentifier(s): 29 if s in keyword.kwlist: 30 return False 31 return re.match(r'^[a-z_][a-z0-9_]*$', s, re.I) is not None 32 33 34 def _convert_to_value(s): 35 try: 36 value = int(s) 37 except ValueError: 38 try: 39 value = float(s) 40 except ValueError: 41 value = s 42 return value 43 44 45 class LASError(Exception): 46 pass 47 48 49 class LASItem(object): 50 """This class is just a namespace, holding the attributes 'name', 51 'units', 'data', 'value', and 'descr'. 'value' is the numerical 52 value of 'data', if it has a numerical value (specifically, if 53 int() or float() don't raise an exception when given the value 54 of the 'data' attribute). 55 56 A class method, from_line(cls, line), is provided to parse 57 a line from a LAS file and create a LASItem instance. 58 """ 59 def init(self, name, units='', data='', descr=''): 60 self.name = name 61 self.units = units 62 self.data = data 63 self.value = _convert_to_value(data) 64 self.descr = descr 65 66 def str(self): 67 s = ("name='%s', units='%s', data='%s', descr='%s'" % 68 (self.name, self.units, self.data, self.descr)) 69 return s 70 71 def repr(self): 72 s = str(self) 73 return "LASItem(%s)" % s 74 75 @classmethod 76 def from_line(cls, line): 77 first, descr = line.rsplit(':', 1) 78 descr = descr.strip() 79 name, mid = first.split('.', 1) 80 name = name.strip() 81 if mid.startswith(' '): 82
83 units = '' 84 data = mid 85 else: 86 units_data = mid.split(None, 1) 87 if len(units_data) == 1: 88 units = units_data[0] 89 data = '' 90 else: 91 units, data = units_data 92 return LASItem(name=name, units=units, data=data.strip(), 93 descr=descr.strip()) 94 95 96 def _read_wrapped_row(f, n): 97 """Read a "row" of data from the Ascii section of a "wrapped" LAS file. 98 99 f must be a file object opened for reading. 100 n is the number of fields in the row. 101 102 Returns the list of floats read from the file. 103 """ 104 depth = float(f.readline().strip()) 105 values = [depth] 106 while len(values) < n: 107 new_values = [float(s) for s in f.readline().split()] 108 values.extend(new_values) 109 return values 110 111 112 def _read_wrapped_data(f, dt): 113 data = [] 114 ncols = len(dt.names) 115 while True: 116 try: 117 row = _read_wrapped_row(f, ncols) 118 except Exception: 119 break 120 data.append(tuple(row)) 121 data = np.array(data, dtype=dt) 122 return data 123 124 125 class LASSection(object): 126 """Represents a "section" of a LAS file. 127 128 A section is basically a collection of items, where each item has the 129 attributes 'name', 'units', 'data' and 'descr'. 130 131 Any item in the section whose name is a valid Python identifier is 132 also attached to the object as an attribute. For example, if s is a 133 LASSection instance, and the corresponding section in the LAS file 134 contained this line: 135 136 FD .K/M3 999.9999 : Fluid Density 137 138 then the item may be referred to as s.FD (in addition to the longer 139 s.items['FD']). 140 141 Attributes 142 ---------- 143 items : dict 144 The keys are the item names, and the values are LASItem instances. 145 names : list 146 List of item names, in the order they were read from the LAS file. 147 148 """ 149 def init(self): 150
151
152 self.items = dict() 153 self.names = [] 154 155 def add_item(self, item): 156 self.items[item.name] = item 157 self.names.append(item.name) 158 if isidentifier(item.name) and not hasattr(self, item.name): 159 setattr(self, item.name, item) 160 161 def display(self): 162 for name in self.names: 163 item = self.items[name] 164 namestr = name 165 if item.units != '': 166 namestr = namestr + (" (%s)" % item.units) 167 print "%-16s %-30s [%s]" % (namestr, "'" + item.data + "'", 168 item.descr) 169 170 171 class LASReader(object): 172 """The LASReader class holds data from a LAS file. 173 174 This reader only handles LAS 2.0 files (as far as I know). 175 176 Constructor 177 ----------- 178 LASReader(f, null_subs=None) 179 180 f : file object or string 181 If f is a file object, it must be opened for reading. 182 If f is a string, it must be the filename of a LAS file. 183 In that case, the file will be opened and read. 184 185 Attributes for LAS Sections 186 --------------------------- 187 version : LASSection instance 188 This LASSection holds the items from the 'V' section. 189 190 well : LASSection instance 191 This LASSection holds the items from the 'W' section. 192 193 curves : LASection instance 194 This LASSection holds the items from the 'C' section. 195 196 parameters : LASSection instance 197 This LASSection holds the items from the 'P' section. 198 199 other : str 200 Holds the contents of the 'O' section as a single string. 201 202 data : numpy 1D structured array 203 The numerical data from the 'A' section. The data type 204 of the array is constructed from the items in the 'C' 205 section. 206 207 Other attributes 208 ---------------- 209 data2d : numpy 2D array of floats 210 The numerical data from the 'A' section, as a 2D array. 211 This is a view of the same data as in the data attribute. 212 213 wrap : bool 214 True if the LAS file was wrapped. (More specifically, this 215 attribute is True if the data field of the item with the 216 name 'WRAP' in the 'V' section has the value 'YES'.) 217 218 vers : str 219 The LAS version. (More specifically, the value of the data 220 field of the item with the name 'VERS' in the 'V' section). 221 222 null : float or None 223 The numerical value of the 'NULL' item in the 'W' section. 224 The value will be None if the 'NULL' item was missing. 225 226 null_subs : float or None 227 The value given in the constructor, to be used as the 228 replacement value of each occurrence of null_value in 229 the log data. The value will be None (and no substitution 230 will be done) if the null_subs argument is not given to 231 the constructor. 232 233 start : float, or None 234 Numerical value of the 'STRT' item from the 'W' section. 235 The value will be None if 'STRT' was not given in the file. 236 237 start_units : str 238 Units of the 'STRT' item from the 'W' section. 239 The value will be None if 'STRT' was not given in the file. 240 241 stop : float 242 Numerical value of the 'STOP' item from the 'W' section. 243 The value will be None if 'STOP' was not given in the file. 244 245 stop_units : str 246 Units of the 'STOP' item from the 'W' section. 247 The value will be None if 'STOP' was not given in the file. 248 249 step : float 250 Numerical value of the 'STEP' item from the 'W' section. 251 The value will be None if 'STEP' was not given in the file. 252 253 step_units : str 254 Units of the 'STEP' item from the 'W' section. 255 The value will be None if 'STEP' was not given in the file. 256 257 """ 258 259 def init(self, f, null_subs=None): 260 """f can be a filename (str) or a file object. 261 262 If 'null_subs' is not None, its value replaces any values in the data 263 that matches the NULL value specified in the Version section of the LAS 264 file. 265 """ 266 self.null = None 267 self.null_subs = null_subs 268 self.start = None 269 self.start_units = None 270 self.stop = None 271 self.stop_units = None 272 self.step = None 273 self.step_units = None 274 275 self.version = LASSection() 276 self.well = LASSection() 277 self.curves = LASSection() 278 self.parameters = LASSection() 279 self.other = '' 280 self.data = None 281 282 self._read_las(f) 283 284 self.data2d = self.data.view(float).reshape(-1, len(self.curves.items)) 285 if null_subs is not None: 286 self.data2d[self.data2d == self.null] = null_subs 287 288 def _read_las(self, f): 289 """Read a LAS file. 290 291 Returns a dictionary with keys 'V', 'W', 'C', 'P', 'O' and 'A', 292 corresponding to the sections of a LAS file. The values associated 293 with keys 'V', 'W', 'C' and 'P' will be lists of Item instances. The 294 value associated with the 'O' key is a list of strings. The value 295 associated with the 'A' key is a numpy structured array containing the 296 log data. The field names of the array are the mnemonics from the 297 Curve section of the file. 298 """ 299 opened_here = False 300 if isinstance(f, basestring): 301 opened_here = True 302 f = open(f, 'r') 303 304 self.wrap = False 305 306 line = f.readline() 307 current_section = None 308 current_section_label = '' 309 while not line.startswith('A'): 310 if not line.startswith('#'): 311 if line.startswith(''): 312 if len(line) < 2: 313 raise LASError("Missing section character after ''.") 314 current_section_label = line[1:2] 315 other = False 316 if current_section_label == 'V': 317 current_section = self.version 318 elif current_section_label == 'W': 319 current_section = self.well 320 elif current_section_label == 'C': 321 current_section = self.curves 322 elif current_section_label == 'P': 323 current_section = self.parameters 324 elif current_section_label == 'O': 325 current_section = self.other 326 other = True 327 else: 328 raise LASError("Unknown section '%s'" % line) 329 elif current_section is None: 330 raise LASError("Missing first section.") 331 else: 332 if other: 333
334
335 self.other += line 336 current_section = self.other 337 else: 338
339
340 m = LASItem.from_line(line) 341 current_section.add_item(m) 342
343
344 if current_section == self.version: 345 if m.name == 'WRAP': 346 if m.data.strip() == 'YES': 347 self.wrap = True 348 if m.name == 'VERS': 349 self.vers = m.data.strip() 350 if current_section == self.well: 351 if m.name == 'NULL': 352 self.null = float(m.data) 353 elif m.name == 'STRT': 354 self.start = float(m.data) 355 self.start_units = m.units 356 elif m.name == 'STOP': 357 self.stop = float(m.data) 358 self.stop_units = m.units 359 elif m.name == 'STEP': 360 self.step = float(m.data) 361 self.step_units = m.units 362 line = f.readline() 363 364
365
366
367
368
369
370 dt = np.dtype([(name, float) for name in self.curves.names]) 371 if self.wrap: 372 a = _read_wrapped_data(f, dt) 373 else: 374 a = np.loadtxt(f, dtype=dt) 375 self.data = a 376 377 if opened_here: 378 f.close() 379 380 381 if name == "main": 382 import sys 383 384 las = LASReader(sys.argv[1], null_subs=np.nan) 385 print "wrap? ", las.wrap 386 print "vers? ", las.vers 387 print "null =", las.null 388 print "start =", las.start 389 print "stop =", las.stop 390 print "step =", las.step 391 print "Version ---" 392 las.version.display() 393 print "Well ---" 394 las.well.display() 395 print "Curves ---" 396 las.curves.display() 397 print "Parameters ---" 398 las.parameters.display() 399 print "Other ---" 400 print las.other 401 print "Data ---" 402 print las.data2d

Source code: las.py

Here's an example of the use of this module:

import numpy as np from las import LASReader sample3 = LASReader('sample3.las', null_subs=np.nan) print sample3.null -999.25 print sample3.start, sample3.stop, sample3.step 910.0 909.5 -0.125 print sample3.well.PROV.data, sample3.well.UWI.data ALBERTA 100123401234W500 from matplotlib.pyplot import plot, show plot(sample3.data['DEPT'], sample3.data['PHIE']) [<matplotlib.lines.Line2D object at 0x4c2ae90>] show()

It creates the following plot:

sample3plot.png

The sample LAS file is here:

sample3.las