segyio 1.9.13__cp313-cp313-macosx_10_13_x86_64.whl

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.

Potentially problematic release.


This version of segyio might be problematic. Click here for more details.

segyio/line.py ADDED
@@ -0,0 +1,498 @@
1
+ try:
2
+ from collections.abc import Mapping # noqa
3
+ except ImportError:
4
+ from collections import Mapping # noqa
5
+
6
+ import itertools
7
+ try: from future_builtins import zip
8
+ except ImportError: pass
9
+ import numpy as np
10
+
11
+ from .utils import castarray
12
+
13
+ # in order to support [:end] syntax, we must make sure
14
+ # start has a non-None value. lineno.indices() would set it
15
+ # to 0, but we don't know if that's a reasonable value or
16
+ # not. If start is None we set it to the first line
17
+ def sanitize_slice(s, source):
18
+ if all((s.start, s.stop, s.step)):
19
+ return s
20
+
21
+ start, stop, step = s.start, s.stop, s.step
22
+ increasing = step is None or step > 0
23
+
24
+ if start is None:
25
+ start = min(source) if increasing else max(source)
26
+
27
+ if stop is None:
28
+ stop = max(source) + 1 if increasing else min(source) - 1
29
+
30
+ return slice(start, stop, step)
31
+
32
+ class Line(Mapping):
33
+ """
34
+ The Line implements the dict interface, with a fixed set of int_like keys,
35
+ the line numbers/labels. Data is read lazily from disk, so iteration does
36
+ not consume much memory, and are returned as numpy.ndarrays.
37
+
38
+ It provides a convenient interface for reading data in a cartesian grid
39
+ system, provided one exists and is detectable by segyio.
40
+
41
+ Lines can be accessed individually or with slices, and writing is done via
42
+ assignment. Note that accessing lines uses the line numbers, not their
43
+ position, so if a files has lines [2400..2500], accessing line [0..100]
44
+ will be an error. Note that since each line is returned as a numpy.ndarray,
45
+ meaning accessing the intersections of the inline and crossline is
46
+ 0-indexed - orthogonal labels are not preserved.
47
+
48
+ Additionally, the line has a concept of offsets, which is useful when
49
+ dealing with prestack files. Offsets are accessed via sub indexing, meaning
50
+ iline[10, 4] will give you line 10 at offset 4. Please note that offset,
51
+ like lines, are accessed via their labels, not their indices. If your file
52
+ has the offsets [150, 250, 350, 450] and the lines [2400..2500], you can
53
+ access the third offset with [2403, 350]. Please refer to the examples for
54
+ more details. If no offset is specified, segyio will give you the first.
55
+
56
+ Notes
57
+ -----
58
+ .. versionadded:: 1.1
59
+
60
+ .. versionchanged:: 1.6
61
+ common dict operations (Mapping)
62
+ """
63
+
64
+ def __init__(self, filehandle, labels, length, stride, offsets, name):
65
+ self.filehandle = filehandle.xfd
66
+ self.lines = labels
67
+ self.length = length
68
+ self.stride = stride
69
+ self.shape = (length, len(filehandle.samples))
70
+ self.dtype = filehandle.dtype
71
+
72
+ # pre-compute all line beginnings
73
+ from ._segyio import fread_trace0
74
+ self.heads = {
75
+ label: fread_trace0(label,
76
+ length,
77
+ stride,
78
+ len(offsets),
79
+ labels,
80
+ name)
81
+ for label in labels
82
+ }
83
+
84
+ self.offsets = { x: i for i, x in enumerate(offsets) }
85
+ self.default_offset = offsets[0]
86
+
87
+ def ranges(self, index, offset):
88
+ if not isinstance(index, slice):
89
+ index = slice(index, index + 1)
90
+
91
+ if not isinstance(offset, slice):
92
+ offset = slice(offset, offset + 1)
93
+
94
+ index = sanitize_slice(index, self.heads.keys())
95
+ offset = sanitize_slice(offset, self.offsets.keys())
96
+ irange = range(*index.indices(max(self.heads.keys()) + 1))
97
+ orange = range(*offset.indices(max(self.offsets.keys()) + 1))
98
+ irange = filter(self.heads.__contains__, irange)
99
+ orange = filter(self.offsets.__contains__, orange)
100
+ # offset-range is used in inner loops, so make it a list for
101
+ # reusability. offsets are usually few, so no real punishment by using
102
+ # non-generators here
103
+ return irange, list(orange)
104
+
105
+ def __getitem__(self, index):
106
+ """line[i] or line[i, o]
107
+
108
+ The line `i`, or the line `i` at a specific offset `o`. ``line[i]``
109
+ returns a numpy array, and changes to this array will *not* be
110
+ reflected on disk.
111
+
112
+ The `i` and `o` are *keys*, and should correspond to the line- and
113
+ offset labels in your file, and in the `ilines`, `xlines`, and
114
+ `offsets` attributes.
115
+
116
+ Slices can contain lines and offsets not in the file, and like with
117
+ list slicing, these are handled gracefully and ignored.
118
+
119
+ When `i` or `o` is a slice, a generator of numpy arrays is returned. If
120
+ the slice is defaulted (:), segyio knows enough about the structure to
121
+ give you all of the respective labels.
122
+
123
+ When both `i` and `o` are slices, only one generator is returned, and
124
+ the lines are yielded offsets-first, roughly equivalent to the double
125
+ for loop::
126
+
127
+ >>> for line in lines:
128
+ ... for off in offsets:
129
+ ... yield line[line, off]
130
+ ...
131
+
132
+ Parameters
133
+ ----------
134
+ i : int or slice
135
+ o : int or slice
136
+
137
+ Returns
138
+ -------
139
+ line : numpy.ndarray of dtype or generator of numpy.ndarray of dtype
140
+
141
+ Raises
142
+ ------
143
+ KeyError
144
+ If `i` or `o` don't exist
145
+
146
+ Notes
147
+ -----
148
+ .. versionadded:: 1.1
149
+
150
+ Examples
151
+ --------
152
+
153
+ Read an inline:
154
+
155
+ >>> x = line[2400]
156
+
157
+ Copy every inline into a list:
158
+
159
+ >>> l = [numpy.copy(x) for x in iline[:]]
160
+
161
+ Numpy operations on every other inline:
162
+
163
+ >>> for line in line[::2]:
164
+ ... line = line * 2
165
+ ... avg = np.average(line)
166
+
167
+ Read lines up to 2430:
168
+
169
+ >>> for line in line[:2430]:
170
+ ... line.mean()
171
+
172
+ Copy all lines at all offsets:
173
+
174
+ >>> l = [numpy.copy(x) for x in line[:,:]]
175
+
176
+ Copy all offsets of a line:
177
+
178
+ >>> x = numpy.copy(iline[10,:])
179
+
180
+ Copy all lines at a fixed offset:
181
+
182
+ >>> x = numpy.copy(iline[:, 120])
183
+
184
+ Copy every other line and offset:
185
+
186
+ >>> map(numpy.copy, line[::2, ::2])
187
+
188
+ Copy all offsets [200, 250, 300, 350, ...] in the range [200, 800) for
189
+ all lines [2420,2460):
190
+
191
+ >>> l = [numpy.copy(x) for x in line[2420:2460, 200:800:50]]
192
+ """
193
+
194
+ offset = self.default_offset
195
+ try: index, offset = index
196
+ except TypeError: pass
197
+
198
+ # prioritise the code path that's potentially in loops externally
199
+ if not isinstance(index, slice) and not isinstance(offset, slice):
200
+ head = self.heads[index] + self.offsets[offset]
201
+ return self.filehandle.getline(head,
202
+ self.length,
203
+ self.stride,
204
+ len(self.offsets),
205
+ np.empty(self.shape, dtype=self.dtype),
206
+ )
207
+
208
+ # at this point, either offset or index is a slice (or proper
209
+ # type-error), so we're definitely making a generator. make them both
210
+ # slices to unify all code paths
211
+ irange, orange = self.ranges(index, offset)
212
+
213
+ def gen():
214
+ x = np.empty(self.shape, dtype=self.dtype)
215
+ y = np.copy(x)
216
+
217
+ # only fetch lines that exist. the slice can generate both offsets
218
+ # and line numbers that don't exist, so filter out misses before
219
+ # they happen
220
+ for line in irange:
221
+ for off in orange:
222
+ head = self.heads[line] + self.offsets[off]
223
+ self.filehandle.getline(head,
224
+ self.length,
225
+ self.stride,
226
+ len(self.offsets),
227
+ y,
228
+ )
229
+ y, x = x, y
230
+ yield x
231
+
232
+ return gen()
233
+
234
+ def __setitem__(self, index, val):
235
+ """line[i] = val or line[i, o] = val
236
+
237
+ Follows the same rules for indexing and slicing as ``line[i]``.
238
+
239
+ In either case, if the `val` iterable is exhausted before the line(s),
240
+ assignment stops with whatever is written so far. If `val` is longer
241
+ than an individual line, it's essentially truncated.
242
+
243
+ Parameters
244
+ ----------
245
+ i : int or slice
246
+ offset : int or slice
247
+ val : array_like
248
+
249
+ Raises
250
+ ------
251
+ KeyError
252
+ If `i` or `o` don't exist
253
+
254
+ Notes
255
+ -----
256
+ .. versionadded:: 1.1
257
+
258
+ Examples
259
+ --------
260
+ Copy a full line:
261
+
262
+ >>> line[2400] = other[2834]
263
+
264
+ Copy first half of the inlines from g to f:
265
+
266
+ >>> line[:] = other[:labels[len(labels) / 2]]
267
+
268
+ Copy every other line consecutively:
269
+
270
+ >>> line[:] = other[::2]
271
+
272
+ Copy every third offset:
273
+
274
+ >>> line[:,:] = other[:,::3]
275
+
276
+ Copy a line into a set line and offset:
277
+
278
+ >>> line[12, 200] = other[21]
279
+ """
280
+
281
+ offset = self.default_offset
282
+ try: index, offset = index
283
+ except TypeError: pass
284
+
285
+ if not isinstance(index, slice) and not isinstance(offset, slice):
286
+ head = self.heads[index] + self.offsets[offset]
287
+ return self.filehandle.putline(head,
288
+ self.length,
289
+ self.stride,
290
+ len(self.offsets),
291
+ index,
292
+ offset,
293
+ castarray(val, dtype = self.dtype),
294
+ )
295
+
296
+ irange, orange = self.ranges(index, offset)
297
+
298
+ val = iter(val)
299
+ for line in irange:
300
+ for off in orange:
301
+ head = self.heads[line] + self.offsets[off]
302
+ try: self.filehandle.putline(head,
303
+ self.length,
304
+ self.stride,
305
+ len(self.offsets),
306
+ line,
307
+ off,
308
+ next(val),
309
+ )
310
+ except StopIteration: return
311
+
312
+ # can't rely on most Mapping default implementations of
313
+ # dict-like, because iter() does not yield keys for this class, it gives
314
+ # the lines themselves. that violates some assumptions (but segyio's always
315
+ # worked that way), and it's the more natural behaviour for segyio, so it's
316
+ # acceptible. additionally, the default implementations would be very slow
317
+ # and ineffective because they assume __getitem__ is sufficiently cheap,
318
+ # but it isn't here since it involves a disk operation
319
+ def __len__(self):
320
+ """x.__len__() <==> len(x)"""
321
+ return len(self.heads)
322
+
323
+ def __iter__(self):
324
+ """x.__iter__() <==> iter(x)"""
325
+ return self[:]
326
+
327
+ def __contains__(self, key):
328
+ """x.__contains__(y) <==> y in x"""
329
+ return key in self.heads
330
+
331
+ def keys(self):
332
+ """D.keys() -> a set-like object providing a view on D's keys"""
333
+ return sorted(self.heads.keys())
334
+
335
+ def values(self):
336
+ """D.values() -> generator of D's values"""
337
+ return self[:]
338
+
339
+ def items(self):
340
+ """D.values() -> generator of D's (key,values), as 2-tuples"""
341
+ return zip(self.keys(), self[:])
342
+
343
+ class HeaderLine(Line):
344
+ """
345
+ The Line implements the dict interface, with a fixed set of int_like keys,
346
+ the line numbers/labels. The values are iterables of Field objects.
347
+
348
+ Notes
349
+ -----
350
+ .. versionadded:: 1.1
351
+
352
+ .. versionchanged:: 1.6
353
+ common dict operations (Mapping)
354
+ """
355
+ # a lot of implementation details are shared between reading data traces
356
+ # line-by-line and trace headers line-by-line, so (ab)use inheritance for
357
+ # __len__, keys() etc., however, the __getitem__ is way different and is re-implemented
358
+
359
+ def __init__(self, header, base, direction):
360
+ super(HeaderLine, self).__init__(header.segy,
361
+ base.lines,
362
+ base.length,
363
+ base.stride,
364
+ sorted(base.offsets.keys()),
365
+ 'header.' + direction,
366
+ )
367
+ self.header = header
368
+
369
+ def __getitem__(self, index):
370
+ """line[i] or line[i, o]
371
+
372
+ The line `i`, or the line `i` at a specific offset `o`. ``line[i]``
373
+ returns an iterable of `Field` objects, and changes to these *will* be
374
+ reflected on disk.
375
+
376
+ The `i` and `o` are *keys*, and should correspond to the line- and
377
+ offset labels in your file, and in the `ilines`, `xlines`, and
378
+ `offsets` attributes.
379
+
380
+ Slices can contain lines and offsets not in the file, and like with
381
+ list slicing, these are handled gracefully and ignored.
382
+
383
+ When `i` or `o` is a slice, a generator of iterables of headers are
384
+ returned.
385
+
386
+ When both `i` and `o` are slices, one generator is returned for the
387
+ product `i` and `o`, and the lines are yielded offsets-first, roughly
388
+ equivalent to the double for loop::
389
+
390
+ >>> for line in lines:
391
+ ... for off in offsets:
392
+ ... yield line[line, off]
393
+ ...
394
+
395
+ Parameters
396
+ ----------
397
+
398
+ i : int or slice
399
+ o : int or slice
400
+
401
+ Returns
402
+ -------
403
+ line : iterable of Field or generator of iterator of Field
404
+
405
+ Raises
406
+ ------
407
+ KeyError
408
+ If `i` or `o` don't exist
409
+
410
+ Notes
411
+ -----
412
+ .. versionadded:: 1.1
413
+
414
+ """
415
+ offset = self.default_offset
416
+ try: index, offset = index
417
+ except TypeError: pass
418
+
419
+ if not isinstance(index, slice) and not isinstance(offset, slice):
420
+ start = self.heads[index] + self.offsets[offset]
421
+ step = self.stride * len(self.offsets)
422
+ stop = start + step * self.length
423
+ return self.header[start:stop:step]
424
+
425
+ def gen():
426
+ irange, orange = self.ranges(index, offset)
427
+ for line in irange:
428
+ for off in orange:
429
+ yield self[line, off]
430
+
431
+ return gen()
432
+
433
+ def __setitem__(self, index, val):
434
+ """line[i] = val or line[i, o] = val
435
+
436
+ Follows the same rules for indexing and slicing as ``line[i]``. If `i`
437
+ is an int, and `val` is a dict or Field, that value is replicated and
438
+ assigned to every trace header in the line, otherwise it's treated as
439
+ an iterable, and each trace in the line is assigned the ``next()``
440
+ yielded value.
441
+
442
+ If `i` or `o` is a slice, `val` must be an iterable.
443
+
444
+ In either case, if the `val` iterable is exhausted before the line(s),
445
+ assignment stops with whatever is written so far.
446
+
447
+ Parameters
448
+ ----------
449
+ i : int or slice
450
+ offset : int or slice
451
+ val : dict_like or iterable of dict_like
452
+
453
+ Raises
454
+ ------
455
+ KeyError
456
+ If `i` or `o` don't exist
457
+
458
+ Notes
459
+ -----
460
+ .. versionadded:: 1.1
461
+
462
+ Examples
463
+ --------
464
+ Rename the iline 3 to 4:
465
+
466
+ >>> line[3] = { TraceField.INLINE_3D: 4 }
467
+ >>> # please note that rewriting the header won't update the
468
+ >>> # file's interpretation of the file until you reload it, so
469
+ >>> # the new iline 4 will be considered iline 3 until the file
470
+ >>> # is reloaded
471
+
472
+ Set offset line 3 offset 3 to 5:
473
+
474
+ >>> line[3, 3] = { TraceField.offset: 5 }
475
+ """
476
+ offset = self.default_offset
477
+ try: index, offset = index
478
+ except TypeError: pass
479
+
480
+ if not isinstance(index, slice) and not isinstance(offset, slice):
481
+ start = self.heads[index] + self.offsets[offset]
482
+ step = self.stride * len(self.offsets)
483
+ stop = start + step * self.length
484
+ self.header[start:stop:step] = val
485
+ return
486
+
487
+ # if this is a dict-like, just repeat it
488
+ if hasattr(val, 'keys'):
489
+ val = itertools.repeat(val)
490
+
491
+ irange, orange = self.ranges(index, offset)
492
+ val = iter(val)
493
+ for line in irange:
494
+ for off in orange:
495
+ try:
496
+ self[line, off] = next(val)
497
+ except StopIteration:
498
+ return
segyio/open.py ADDED
@@ -0,0 +1,192 @@
1
+ import numpy
2
+
3
+ import segyio
4
+
5
+ def infer_geometry(f, metrics, iline, xline, strict):
6
+ try:
7
+ cube_metrics = f.xfd.cube_metrics(iline, xline)
8
+ f._sorting = cube_metrics['sorting']
9
+ iline_count = cube_metrics['iline_count']
10
+ xline_count = cube_metrics['xline_count']
11
+ offset_count = cube_metrics['offset_count']
12
+ metrics.update(cube_metrics)
13
+
14
+ ilines = numpy.zeros(iline_count, dtype=numpy.intc)
15
+ xlines = numpy.zeros(xline_count, dtype=numpy.intc)
16
+ offsets = numpy.zeros(offset_count, dtype=numpy.intc)
17
+
18
+ f.xfd.indices(metrics, ilines, xlines, offsets)
19
+ f.interpret(ilines, xlines, offsets, f._sorting)
20
+
21
+ except:
22
+ if not strict:
23
+ f._ilines = None
24
+ f._xlines = None
25
+ f._offsets = None
26
+ else:
27
+ f.close()
28
+ raise
29
+
30
+ return f
31
+
32
+
33
+ def open(filename, mode="r", iline = 189,
34
+ xline = 193,
35
+ strict = True,
36
+ ignore_geometry = False,
37
+ endian = 'big'):
38
+ """Open a segy file.
39
+
40
+ Opens a segy file and tries to figure out its sorting, inline numbers,
41
+ crossline numbers, and offsets, and enables reading and writing to this
42
+ file in a simple manner.
43
+
44
+ For reading, the access mode `r` is preferred. All write operations will
45
+ raise an exception. For writing, the mode `r+` is preferred (as `rw` would
46
+ truncate the file). Any mode with `w` will raise an error. The modes used
47
+ are standard C file modes; please refer to that documentation for a
48
+ complete reference.
49
+
50
+ Open should be used together with python's ``with`` statement. Please refer
51
+ to the examples. When the ``with`` statement is used the file will
52
+ automatically be closed when the routine completes or an exception is
53
+ raised.
54
+
55
+ By default, segyio tries to open in ``strict`` mode. This means the file will
56
+ be assumed to represent a geometry with consistent inline, crosslines and
57
+ offsets. If strict is False, segyio will still try to establish a geometry,
58
+ but it won't abort if it fails. When in non-strict mode is opened,
59
+ geometry-dependent modes such as iline will raise an error.
60
+
61
+ If ``ignore_geometry=True``, segyio will *not* try to build iline/xline or
62
+ other geometry related structures, which leads to faster opens. This is
63
+ essentially the same as using ``strict=False`` on a file that has no
64
+ geometry.
65
+
66
+ Parameters
67
+ ----------
68
+
69
+ filename : str
70
+ Path to file to open
71
+
72
+ mode : {'r', 'r+'}
73
+ File access mode, read-only ('r', default) or read-write ('r+')
74
+
75
+ iline : int or segyio.TraceField
76
+ Inline number field in the trace headers. Defaults to 189 as per the
77
+ SEG-Y rev1 specification
78
+
79
+ xline : int or segyio.TraceField
80
+ Crossline number field in the trace headers. Defaults to 193 as per the
81
+ SEG-Y rev1 specification
82
+
83
+ strict : bool, optional
84
+ Abort if a geometry cannot be inferred. Defaults to True.
85
+
86
+ ignore_geometry : bool, optional
87
+ Opt out on building geometry information, useful for e.g. shot
88
+ organised files. Defaults to False.
89
+
90
+ endian : {'big', 'msb', 'little', 'lsb'}
91
+ File endianness, big/msb (default) or little/lsb
92
+
93
+ Returns
94
+ -------
95
+
96
+ file : segyio.SegyFile
97
+ An open segyio file handle
98
+
99
+ Raises
100
+ ------
101
+
102
+ ValueError
103
+ If the mode string contains 'w', as it would truncate the file
104
+
105
+ Notes
106
+ -----
107
+
108
+ .. versionadded:: 1.1
109
+
110
+ .. versionchanged:: 1.8
111
+ endian argument
112
+
113
+ When a file is opened non-strict, only raw traces access is allowed, and
114
+ using modes such as ``iline`` raise an error.
115
+
116
+
117
+ Examples
118
+ --------
119
+
120
+ Open a file in read-only mode:
121
+
122
+ >>> with segyio.open(path, "r") as f:
123
+ ... print(f.ilines)
124
+ ...
125
+ [1, 2, 3, 4, 5]
126
+
127
+ Open a file in read-write mode:
128
+
129
+ >>> with segyio.open(path, "r+") as f:
130
+ ... f.trace = np.arange(100)
131
+
132
+ Open two files at once:
133
+
134
+ >>> with segyio.open(src_path) as src, segyio.open(dst_path, "r+") as dst:
135
+ ... dst.trace = src.trace # copy all traces from src to dst
136
+
137
+ Open a file little-endian file:
138
+
139
+ >>> with segyio.open(path, endian = 'little') as f:
140
+ ... f.trace[0]
141
+
142
+ """
143
+
144
+ if 'w' in mode:
145
+ problem = 'w in mode would truncate the file'
146
+ solution = 'use r+ to open in read-write'
147
+ raise ValueError(', '.join((problem, solution)))
148
+
149
+ endians = {
150
+ 'little': 256, # (1 << 8)
151
+ 'lsb': 256,
152
+ 'big': 0,
153
+ 'msb': 0,
154
+ }
155
+
156
+ if endian not in endians:
157
+ problem = 'unknown endianness {}, expected one of: '
158
+ opts = ' '.join(endians.keys())
159
+ raise ValueError(problem.format(endian) + opts)
160
+
161
+ from . import _segyio
162
+ fd = _segyio.segyiofd(str(filename), mode, endians[endian])
163
+ fd.segyopen()
164
+ metrics = fd.metrics()
165
+
166
+ f = segyio.SegyFile(fd,
167
+ filename = str(filename),
168
+ mode = mode,
169
+ iline = int(iline),
170
+ xline = int(xline),
171
+ endian = endian,
172
+ )
173
+
174
+ try:
175
+ delay_scalar = f.header[0][segyio.TraceField.ScalarTraceHeader]
176
+ if delay_scalar == 0:
177
+ delay_scalar = 1
178
+ elif delay_scalar < 0:
179
+ delay_scalar = 1.0 / delay_scalar
180
+ dt = segyio.tools.dt(f, fallback_dt = 4000.0) / 1000.0
181
+ t0 = f.header[0][segyio.TraceField.DelayRecordingTime] * abs(delay_scalar)
182
+ samples = metrics['samplecount']
183
+ f._samples = (numpy.arange(samples) * dt) + t0
184
+
185
+ except:
186
+ f.close()
187
+ raise
188
+
189
+ if ignore_geometry:
190
+ return f
191
+
192
+ return infer_geometry(f, metrics, int(iline), int(xline), strict)