segyio 2.0.0a1__cp312-cp312-win_amd64.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.
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, segyfile, labels, length, stride, offsets, name):
65
+ self.segyfd = segyfile.segyfd
66
+ self.lines = labels
67
+ self.length = length
68
+ self.stride = stride
69
+ self.shape = (length, len(segyfile.samples))
70
+ self.dtype = segyfile.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.segyfd.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.segyfd.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.segyfd.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.segyfd.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.segyfile,
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