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/tools.py ADDED
@@ -0,0 +1,731 @@
1
+ import segyio
2
+ from . import TraceSortingFormat
3
+ from . import SegySampleFormat
4
+
5
+ import numpy as np
6
+ import textwrap
7
+
8
+
9
+ def dt(f, fallback_dt=4000.0):
10
+ """Delta-time
11
+
12
+ Infer a ``dt``, the sample rate, from the file. If none is found, use the
13
+ fallback.
14
+
15
+ Parameters
16
+ ----------
17
+
18
+ f : segyio.SegyFile
19
+ fallback_dt : float
20
+ delta-time to fall back to, in microseconds
21
+
22
+ Returns
23
+ -------
24
+ dt : float
25
+
26
+ Notes
27
+ -----
28
+
29
+ .. versionadded:: 1.1
30
+
31
+ """
32
+ return f.xfd.getdt(fallback_dt)
33
+
34
+
35
+ def sample_indexes(segyfile, t0=0.0, dt_override=None):
36
+ """
37
+ Creates a list of values representing the samples in a trace at depth or time.
38
+ The list starts at *t0* and is incremented with am*dt* for the number of samples.
39
+ If a *dt_override* is not provided it will try to find a *dt* in the file.
40
+
41
+
42
+ Parameters
43
+ ----------
44
+ segyfile : segyio.SegyFile
45
+ t0 : float
46
+ initial sample, or delay-recording-time
47
+ dt_override : float or None
48
+
49
+ Returns
50
+ -------
51
+ samples : array_like of float
52
+
53
+ Notes
54
+ -----
55
+
56
+ .. versionadded:: 1.1
57
+
58
+ """
59
+ if dt_override is None:
60
+ dt_override = dt(segyfile)
61
+
62
+ return [t0 + t * dt_override for t in range(len(segyfile.samples))]
63
+
64
+
65
+ def create_text_header(lines):
66
+ """Format textual header
67
+
68
+ Create a "correct" SEG-Y textual header. Every line will be prefixed with
69
+ C## and there are 40 lines. The input must be a dictionary with the line
70
+ number[1-40] as a key. The value for each key should be up to 76 character
71
+ long string.
72
+
73
+ Parameters
74
+ ----------
75
+
76
+ lines : dict
77
+ `lines` dictionary with fields:
78
+
79
+ - ``no`` : line number (`int`)
80
+ - ``line`` : line (`str`)
81
+
82
+ Returns
83
+ -------
84
+
85
+ text : str
86
+
87
+ """
88
+
89
+ rows = []
90
+ for line_no in range(1, 41):
91
+ line = ""
92
+ if line_no in lines:
93
+ line = lines[line_no]
94
+ row = "C{0:>2} {1:76}".format(line_no, line)
95
+ rows.append(row)
96
+
97
+ rows = ''.join(rows)
98
+ return rows
99
+
100
+ def wrap(s, width=80):
101
+ """
102
+ Formats the text input with newlines given the user specified width for
103
+ each line. `wrap` will attempt to decode the input as ascii, ignoring any
104
+ errors, which occurs with many headers in ebcdic. To consider encoding
105
+ errors, decode the textual header before passing it to wrap.
106
+
107
+ Parameters
108
+ ----------
109
+ s : text or bytearray or str
110
+ width : int
111
+
112
+ Returns
113
+ -------
114
+ text : str
115
+
116
+ Notes
117
+ -----
118
+ .. versionadded:: 1.1
119
+
120
+ """
121
+ try:
122
+ s = s.decode(errors = 'ignore')
123
+ except AttributeError:
124
+ # Already str-like enough, e.g. wrap(f.text[0].decode()), so just try
125
+ # to wrap-join
126
+ pass
127
+
128
+ return '\n'.join(textwrap.wrap(s, width=width))
129
+
130
+
131
+ def native(data,
132
+ format = segyio.SegySampleFormat.IBM_FLOAT_4_BYTE,
133
+ copy = True):
134
+ """Convert numpy array to native float
135
+
136
+ Converts a numpy array from raw segy trace data to native floats. Works for numpy ndarrays.
137
+
138
+ Parameters
139
+ ----------
140
+
141
+ data : numpy.ndarray
142
+ format : int or segyio.SegySampleFormat
143
+ copy : bool
144
+ If True, convert on a copy, and leave the input array unmodified
145
+
146
+ Returns
147
+ -------
148
+
149
+ data : numpy.ndarray
150
+
151
+ Notes
152
+ -----
153
+
154
+ .. versionadded:: 1.1
155
+
156
+ Examples
157
+ --------
158
+
159
+ Convert mmap'd trace to native float:
160
+
161
+ >>> d = np.memmap('file.sgy', offset = 3600, dtype = np.uintc)
162
+ >>> samples = 1500
163
+ >>> trace = segyio.tools.native(d[240:240+samples])
164
+
165
+ """
166
+
167
+ data = data.view( dtype = np.single )
168
+ if copy:
169
+ data = np.copy( data )
170
+
171
+ format = int(segyio.SegySampleFormat(format))
172
+ return segyio._segyio.native(data, format)
173
+
174
+ def collect(itr):
175
+ """Collect traces or lines into one ndarray
176
+
177
+ Eagerly copy a series of traces, lines or depths into one numpy ndarray. If
178
+ collecting traces or fast-direction over a post-stacked file, reshaping the
179
+ resulting array is equivalent to calling ``segyio.tools.cube``.
180
+
181
+ Parameters
182
+ ----------
183
+
184
+ itr : iterable of numpy.ndarray
185
+
186
+ Returns
187
+ -------
188
+
189
+ data : numpy.ndarray
190
+
191
+ Notes
192
+ -----
193
+
194
+ .. versionadded:: 1.1
195
+
196
+ Examples
197
+ --------
198
+
199
+ collect-cube identity:
200
+
201
+ >>> with segyio.open('post-stack.sgy') as f:
202
+ >>> x = segyio.tools.collect(f.trace[:])
203
+ >>> x = x.reshape((len(f.ilines), len(f.xlines), f.samples))
204
+ >>> numpy.all(x == segyio.tools.cube(f))
205
+
206
+ """
207
+ return np.stack([np.copy(x) for x in itr])
208
+
209
+ def cube(f):
210
+ """Read a full cube from a file
211
+
212
+ Takes an open segy file (created with segyio.open) or a file name.
213
+
214
+ If the file is a prestack file, the cube returned has the dimensions
215
+ ``(fast, slow, offset, sample)``. If it is post-stack (only the one
216
+ offset), the dimensions are normalised to ``(fast, slow, sample)``
217
+
218
+ Parameters
219
+ ----------
220
+
221
+ f : str or segyio.SegyFile
222
+
223
+ Returns
224
+ -------
225
+
226
+ cube : numpy.ndarray
227
+
228
+ Notes
229
+ -----
230
+
231
+ .. versionadded:: 1.1
232
+
233
+ """
234
+
235
+ if not isinstance(f, segyio.SegyFile):
236
+ with segyio.open(f) as fl:
237
+ return cube(fl)
238
+
239
+ ilsort = f.sorting == segyio.TraceSortingFormat.INLINE_SORTING
240
+ fast = f.ilines if ilsort else f.xlines
241
+ slow = f.xlines if ilsort else f.ilines
242
+ fast, slow, offs = len(fast), len(slow), len(f.offsets)
243
+ smps = len(f.samples)
244
+ dims = (fast, slow, smps) if offs == 1 else (fast, slow, offs, smps)
245
+ return f.trace.raw[:].reshape(dims)
246
+
247
+ def rotation(f, line = 'fast'):
248
+ """ Find rotation of the survey
249
+
250
+ Find the clock-wise rotation and origin of `line` as ``(rot, cdpx, cdpy)``
251
+
252
+ The clock-wise rotation is defined as the angle in radians between line
253
+ given by the first and last trace of the first line and the axis that gives
254
+ increasing CDP-Y, in the direction that gives increasing CDP-X.
255
+
256
+ By default, the first line is the 'fast' direction, which is inlines if the
257
+ file is inline sorted, and crossline if it's crossline sorted.
258
+
259
+
260
+ Parameters
261
+ ----------
262
+
263
+ f : SegyFile
264
+ line : { 'fast', 'slow', 'iline', 'xline' }
265
+
266
+ Returns
267
+ -------
268
+
269
+ rotation : float
270
+ cdpx : int
271
+ cdpy : int
272
+
273
+
274
+ Notes
275
+ -----
276
+
277
+ .. versionadded:: 1.2
278
+
279
+ """
280
+
281
+ if f.unstructured:
282
+ raise ValueError("Rotation requires a structured file")
283
+
284
+ lines = { 'fast': f.fast,
285
+ 'slow': f.slow,
286
+ 'iline': f.iline,
287
+ 'xline': f.xline,
288
+ }
289
+
290
+ if line not in lines:
291
+ error = "Unknown line {}".format(line)
292
+ solution = "Must be any of: {}".format(' '.join(lines.keys()))
293
+ raise ValueError('{} {}'.format(error, solution))
294
+
295
+ l = lines[line]
296
+ origin = f.header[0][segyio.su.cdpx, segyio.su.cdpy]
297
+ cdpx, cdpy = origin[segyio.su.cdpx], origin[segyio.su.cdpy]
298
+
299
+ rot = f.xfd.rotation( len(l),
300
+ l.stride,
301
+ len(f.offsets),
302
+ np.fromiter(l.keys(), dtype = np.intc) )
303
+ return rot, cdpx, cdpy
304
+
305
+ def metadata(f):
306
+ """Get survey structural properties and metadata
307
+
308
+ Create a description object that, when passed to ``segyio.create()``, would
309
+ create a new file with the same structure, dimensions, and metadata as
310
+ ``f``.
311
+
312
+ Takes an open segy file (created with segyio.open) or a file name.
313
+
314
+ Parameters
315
+ ----------
316
+
317
+ f : str or segyio.SegyFile
318
+
319
+ Returns
320
+ -------
321
+ spec : segyio.spec
322
+
323
+ Notes
324
+ -----
325
+
326
+ .. versionadded:: 1.4
327
+
328
+ """
329
+
330
+ if not isinstance(f, segyio.SegyFile):
331
+ with segyio.open(f) as fl:
332
+ return metadata(fl)
333
+
334
+ spec = segyio.spec()
335
+
336
+ spec.iline = f._il
337
+ spec.xline = f._xl
338
+ spec.samples = f.samples
339
+ spec.format = f.format
340
+
341
+ spec.ilines = f.ilines
342
+ spec.xlines = f.xlines
343
+ spec.offsets = f.offsets
344
+ spec.sorting = f.sorting
345
+
346
+ spec.tracecount = f.tracecount
347
+
348
+ spec.ext_headers = f.ext_headers
349
+ spec.endian = f.endian
350
+
351
+ return spec
352
+
353
+ def resample(f, rate = None, delay = None, micro = False,
354
+ trace = True,
355
+ binary = True):
356
+ """Resample a file
357
+
358
+ Resample all data traces, and update the file handle to reflect the new
359
+ sample rate. No actual samples (data traces) are modified, only the header
360
+ fields and interpretation.
361
+
362
+ By default, the rate and the delay are in millseconds - if you need higher
363
+ resolution, passing micro=True interprets rate as microseconds (as it is
364
+ represented in the file). Delay is always milliseconds.
365
+
366
+ By default, both the global binary header and the trace headers are updated
367
+ to reflect this. If preserving either the trace header interval field or
368
+ the binary header interval field is important, pass trace=False and
369
+ binary=False respectively, to not have that field updated. This only apply
370
+ to sample rates - the recording delay is only found in trace headers and
371
+ will be written unconditionally, if delay is not None.
372
+
373
+ .. warning::
374
+ This function requires an open file handle and is **DESTRUCTIVE**. It
375
+ will modify the file, and if an exception is raised then partial writes
376
+ might have happened and the file might be corrupted.
377
+
378
+ This function assumes all traces have uniform delays and frequencies.
379
+
380
+ Parameters
381
+ ----------
382
+
383
+ f : SegyFile
384
+ rate : int
385
+ delay : int
386
+ micro : bool
387
+ if True, interpret rate as microseconds
388
+ trace : bool
389
+ Update the trace header if True
390
+ binary : bool
391
+ Update the binary header if True
392
+
393
+ Notes
394
+ -----
395
+
396
+ .. versionadded:: 1.4
397
+
398
+ """
399
+
400
+ if rate is not None:
401
+ if not micro: rate *= 1000
402
+
403
+ if binary: f.bin[segyio.su.hdt] = rate
404
+ if trace: f.header = { segyio.su.dt: rate}
405
+
406
+ if delay is not None:
407
+ f.header = { segyio.su.delrt: delay }
408
+
409
+ t0 = delay if delay is not None else f.samples[0]
410
+ rate = rate / 1000 if rate is not None else f.samples[1] - f.samples[0]
411
+
412
+ f._samples = (np.arange(len(f.samples)) * rate) + t0
413
+
414
+ return f
415
+
416
+
417
+ def from_array(filename, data, iline=189,
418
+ xline=193,
419
+ format=SegySampleFormat.IBM_FLOAT_4_BYTE,
420
+ dt=4000,
421
+ delrt=0):
422
+ """ Create a new SEGY file from an n-dimentional array. Create a structured
423
+ SEGY file with defaulted headers from a 2-, 3- or 4-dimensional array.
424
+ ilines, xlines, offsets and samples are inferred from the size of the
425
+ array. Please refer to the documentation for functions from_array2D,
426
+ from_array3D and from_array4D to see how the arrays are interpreted.
427
+
428
+ Structure-defining fields in the binary header and in the traceheaders are
429
+ set accordingly. Such fields include, but are not limited to iline, xline
430
+ and offset. The file also contains a defaulted textual header.
431
+
432
+ Parameters
433
+ ----------
434
+ filename : string-like
435
+ Path to new file
436
+ data : 2-,3- or 4-dimensional array-like
437
+ iline : int or segyio.TraceField
438
+ Inline number field in the trace headers. Defaults to 189 as per the
439
+ SEG-Y rev1 specification
440
+ xline : int or segyio.TraceField
441
+ Crossline number field in the trace headers. Defaults to 193 as per the
442
+ SEG-Y rev1 specification
443
+ format : int or segyio.SegySampleFormat
444
+ Sample format field in the trace header. Defaults to IBM float 4 byte
445
+ dt : int-like
446
+ sample interval
447
+ delrt : int-like
448
+
449
+ Notes
450
+ -----
451
+ .. versionadded:: 1.8
452
+
453
+ Examples
454
+ --------
455
+ Create a file from a 3D array, open it and read an iline:
456
+
457
+ >>> segyio.tools.from_array(path, array3d)
458
+ >>> segyio.open(path, mode) as f:
459
+ ... iline = f.iline[0]
460
+ ...
461
+ """
462
+
463
+ dt = int(dt)
464
+ delrt = int(delrt)
465
+
466
+ data = np.asarray(data)
467
+ dimensions = len(data.shape)
468
+
469
+ if dimensions not in range(2, 5):
470
+ problem = "Expected 2, 3, or 4 dimensions, {} was given".format(dimensions)
471
+ raise ValueError(problem)
472
+
473
+ spec = segyio.spec()
474
+ spec.iline = iline
475
+ spec.xline = xline
476
+ spec.format = format
477
+ spec.sorting = TraceSortingFormat.INLINE_SORTING
478
+
479
+ if dimensions == 2:
480
+ spec.ilines = [1]
481
+ spec.xlines = list(range(1, np.size(data,0) + 1))
482
+ spec.samples = list(range(np.size(data,1)))
483
+ spec.tracecount = np.size(data, 1)
484
+
485
+ if dimensions == 3:
486
+ spec.ilines = list(range(1, np.size(data, 0) + 1))
487
+ spec.xlines = list(range(1, np.size(data, 1) + 1))
488
+ spec.samples = list(range(np.size(data, 2)))
489
+
490
+ if dimensions == 4:
491
+ spec.ilines = list(range(1, np.size(data, 0) + 1))
492
+ spec.xlines = list(range(1, np.size(data, 1) + 1))
493
+ spec.offsets = list(range(1, np.size(data, 2)+ 1))
494
+ spec.samples = list(range(np.size(data,3)))
495
+
496
+ samplecount = len(spec.samples)
497
+
498
+ with segyio.create(filename, spec) as f:
499
+ tr = 0
500
+ for ilno, il in enumerate(spec.ilines):
501
+ for xlno, xl in enumerate(spec.xlines):
502
+ for offno, off in enumerate(spec.offsets):
503
+ f.header[tr] = {
504
+ segyio.su.tracf : tr,
505
+ segyio.su.cdpt : tr,
506
+ segyio.su.offset : off,
507
+ segyio.su.ns : samplecount,
508
+ segyio.su.dt : dt,
509
+ segyio.su.delrt : delrt,
510
+ segyio.su.iline : il,
511
+ segyio.su.xline : xl
512
+ }
513
+ if dimensions == 2: f.trace[tr] = data[tr, :]
514
+ if dimensions == 3: f.trace[tr] = data[ilno, xlno, :]
515
+ if dimensions == 4: f.trace[tr] = data[ilno, xlno, offno, :]
516
+ tr += 1
517
+
518
+ f.bin.update(
519
+ tsort=TraceSortingFormat.INLINE_SORTING,
520
+ hdt=dt,
521
+ dto=dt
522
+ )
523
+
524
+
525
+ def from_array2D(filename, data, iline=189,
526
+ xline=193,
527
+ format=SegySampleFormat.IBM_FLOAT_4_BYTE,
528
+ dt=4000,
529
+ delrt=0):
530
+ """ Create a new SEGY file from a 2D array
531
+ Create an structured SEGY file with defaulted headers from a 2-dimensional
532
+ array. The file is inline-sorted and structured as a slice, i.e. it has one
533
+ iline and the xlinecount equals the tracecount. The tracecount and
534
+ samplecount are inferred from the size of the array. Structure-defining
535
+ fields in the binary header and in the traceheaders are set accordingly.
536
+ Such fields include, but are not limited to iline, xline and offset. The
537
+ file also contains a defaulted textual header.
538
+
539
+ The 2 dimensional array is interpreted as::
540
+
541
+ samples
542
+ --------------------
543
+ trace 0 | s0 | s1 | ... | sn |
544
+ --------------------
545
+ trace 1 | s0 | s1 | ... | sn |
546
+ --------------------
547
+ .
548
+ .
549
+ --------------------
550
+ trace n | s0 | s1 | ... | sn |
551
+ --------------------
552
+
553
+ traces = [0, len(axis(0)]
554
+ samples = [0, len(axis(1)]
555
+
556
+ Parameters
557
+ ----------
558
+ filename : string-like
559
+ Path to new file
560
+ data : 2-dimensional array-like
561
+ iline : int or segyio.TraceField
562
+ Inline number field in the trace headers. Defaults to 189 as per the
563
+ SEG-Y rev1 specification
564
+ xline : int or segyio.TraceField
565
+ Crossline number field in the trace headers. Defaults to 193 as per the
566
+ SEG-Y rev1 specification
567
+ format : int or segyio.SegySampleFormat
568
+ Sample format field in the trace header. Defaults to IBM float 4 byte
569
+ dt : int-like
570
+ sample interval
571
+ delrt : int-like
572
+
573
+ Notes
574
+ -----
575
+ .. versionadded:: 1.8
576
+
577
+ Examples
578
+ --------
579
+ Create a file from a 2D array, open it and read a trace:
580
+
581
+ >>> segyio.tools.from_array2D(path, array2d)
582
+ >>> segyio.open(path, mode, strict=False) as f:
583
+ ... tr = f.trace[0]
584
+ """
585
+
586
+ data = np.asarray(data)
587
+ dimensions = len(data.shape)
588
+
589
+ if dimensions != 2:
590
+ problem = "Expected 2 dimensions, {} was given".format(dimensions)
591
+ raise ValueError(problem)
592
+
593
+ from_array(filename, data, iline=iline, xline=xline, format=format,
594
+ dt=dt,
595
+ delrt=delrt)
596
+
597
+
598
+ def from_array3D(filename, data, iline=189,
599
+ xline=193,
600
+ format=SegySampleFormat.IBM_FLOAT_4_BYTE,
601
+ dt=4000,
602
+ delrt=0):
603
+ """ Create a new SEGY file from a 3D array
604
+ Create an structured SEGY file with defaulted headers from a 3-dimensional
605
+ array. The file is inline-sorted. ilines, xlines and samples are inferred
606
+ from the array. Structure-defining fields in the binary header and
607
+ in the traceheaders are set accordingly. Such fields include, but are not
608
+ limited to iline, xline and offset. The file also contains a defaulted
609
+ textual header.
610
+
611
+ The 3-dimensional array is interpreted as::
612
+
613
+ xl0 xl1 xl2
614
+ -----------------
615
+ / | tr0 | tr1 | tr2 | il0
616
+ -----------------
617
+ | / | tr3 | tr4 | tr5 | il1
618
+ -----------------
619
+ | / | tr6 | tr7 | tr8 | il2
620
+ -----------------
621
+ | / / / / n-samples
622
+ ------------------
623
+
624
+ ilines = [1, len(axis(0) + 1]
625
+ xlines = [1, len(axis(1) + 1]
626
+ samples = [0, len(axis(2)]
627
+
628
+ Parameters
629
+ ----------
630
+ filename : string-like
631
+ Path to new file
632
+ data : 3-dimensional array-like
633
+ iline : int or segyio.TraceField
634
+ Inline number field in the trace headers. Defaults to 189 as per the
635
+ SEG-Y rev1 specification
636
+ xline : int or segyio.TraceField
637
+ Crossline number field in the trace headers. Defaults to 193 as per the
638
+ SEG-Y rev1 specification
639
+ format : int or segyio.SegySampleFormat
640
+ Sample format field in the trace header. Defaults to IBM float 4 byte
641
+ dt : int-like
642
+ sample interval
643
+ delrt : int-like
644
+
645
+ Notes
646
+ -----
647
+ .. versionadded:: 1.8
648
+
649
+ Examples
650
+ --------
651
+ Create a file from a 3D array, open it and read an iline:
652
+
653
+ >>> segyio.tools.from_array3D(path, array3d)
654
+ >>> segyio.open(path, mode) as f:
655
+ ... iline = f.iline[0]
656
+ ...
657
+ """
658
+
659
+ data = np.asarray(data)
660
+ dimensions = len(data.shape)
661
+
662
+ if dimensions != 3:
663
+ problem = "Expected 3 dimensions, {} was given".format(dimensions)
664
+ raise ValueError(problem)
665
+
666
+ from_array(filename, data, iline=iline, xline=xline, format=format,
667
+ dt=dt,
668
+ delrt=delrt)
669
+
670
+
671
+ def from_array4D(filename, data, iline=189,
672
+ xline=193,
673
+ format=SegySampleFormat.IBM_FLOAT_4_BYTE,
674
+ dt=4000,
675
+ delrt=0):
676
+ """ Create a new SEGY file from a 4D array
677
+ Create an structured SEGY file with defaulted headers from a 4-dimensional
678
+ array. The file is inline-sorted. ilines, xlines, offsets and samples are
679
+ inferred from the array. Structure-defining fields in the binary header and
680
+ in the traceheaders are set accordingly. Such fields include, but are not
681
+ limited to iline, xline and offset. The file also contains a defaulted
682
+ textual header.
683
+
684
+ The 4D array is interpreted:
685
+
686
+ ilines = [1, len(axis(0) + 1]
687
+ xlines = [1, len(axis(1) + 1]
688
+ offsets = [1, len(axis(2) + 1]
689
+ samples = [0, len(axis(3)]
690
+
691
+ Parameters
692
+ ----------
693
+ filename : string-like
694
+ Path to new file
695
+ data : 4-dimensional array-like
696
+ iline : int or segyio.TraceField
697
+ Inline number field in the trace headers. Defaults to 189 as per the
698
+ SEG-Y rev1 specification
699
+ xline : int or segyio.TraceField
700
+ Crossline number field in the trace headers. Defaults to 193 as per the
701
+ SEG-Y rev1 specification
702
+ format : int or segyio.SegySampleFormat
703
+ Sample format field in the trace header. Defaults to IBM float 4 byte
704
+ dt : int-like
705
+ sample interval
706
+ delrt : int-like
707
+
708
+ Notes
709
+ -----
710
+ .. versionadded:: 1.8
711
+
712
+ Examples
713
+ --------
714
+ Create a file from a 3D array, open it and read an iline:
715
+
716
+ >>> segyio.tools.create_from_array4D(path, array4d)
717
+ >>> segyio.open(path, mode) as f:
718
+ ... iline = f.iline[0]
719
+ ...
720
+ """
721
+
722
+ data = np.asarray(data)
723
+ dimensions = len(data.shape)
724
+
725
+ if dimensions != 4:
726
+ problem = "Expected 4 dimensions, {} was given".format(dimensions)
727
+ raise ValueError(problem)
728
+
729
+ from_array(filename, data, iline=iline, xline=xline, format=format,
730
+ dt=dt,
731
+ delrt=delrt)