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/segyio.cpp ADDED
@@ -0,0 +1,3281 @@
1
+ #define PY_SSIZE_T_CLEAN
2
+ #if defined(_DEBUG) && defined(_MSC_VER)
3
+ # define _CRT_NOFORCE_MAINFEST 1
4
+ # undef _DEBUG
5
+ # include <Python.h>
6
+ # include <bytesobject.h>
7
+ # define _DEBUG 1
8
+ #else
9
+ # include <Python.h>
10
+ # include <bytesobject.h>
11
+ #endif
12
+
13
+ #include <segyio/segy.h>
14
+
15
+ #include <algorithm>
16
+ #include <array>
17
+ #include <cstdint>
18
+ #include <cstring>
19
+ #include <memory>
20
+ #include <sstream>
21
+ #include <stdexcept>
22
+ #include <string>
23
+ #include <unordered_map>
24
+ #include <vector>
25
+
26
+ #if PY_MAJOR_VERSION >= 3
27
+ #define IS_PY3K
28
+ #endif
29
+
30
+ #if defined(__GNUC__) && !defined(__clang__)
31
+ #define IS_GCC
32
+ #endif
33
+ #if defined(__clang__)
34
+ #define IS_CLANG
35
+ #endif
36
+
37
+ namespace {
38
+
39
+ std::string segy_errstr( int err ) {
40
+ std::stringstream ss;
41
+
42
+ switch( err ) {
43
+ case SEGY_OK: return "segyio.ok";
44
+ case SEGY_FOPEN_ERROR: return "segyio.fopen";
45
+ case SEGY_FSEEK_ERROR: return "segyio.fseek";
46
+ case SEGY_FREAD_ERROR: return "segyio.fread";
47
+ case SEGY_FWRITE_ERROR: return "segyio.fwrite";
48
+ case SEGY_INVALID_FIELD: return "segyio.invalid.field";
49
+ case SEGY_INVALID_SORTING: return "segyio.invalid.sorting";
50
+ case SEGY_MISSING_LINE_INDEX: return "segyio.missing.lineindex";
51
+ case SEGY_INVALID_OFFSETS: return "segyio.invalid.offsets";
52
+ case SEGY_TRACE_SIZE_MISMATCH: return "segyio.trace.size.mismatch";
53
+ case SEGY_INVALID_ARGS: return "segyio.invalid.args";
54
+ case SEGY_MMAP_ERROR: return "segyio.mmap.error";
55
+ case SEGY_MMAP_INVALID: return "segyio.mmap.invalid";
56
+ case SEGY_READONLY: return "segyio.readonly";
57
+ case SEGY_NOTFOUND: return "segyio.notfound";
58
+
59
+ default:
60
+ ss << "code " << err << "";
61
+ return ss.str();
62
+ }
63
+ }
64
+
65
+ template< typename T1 >
66
+ PyObject* TypeError( const char* msg, T1 t1 ) {
67
+ return PyErr_Format( PyExc_TypeError, msg, t1 );
68
+ }
69
+
70
+ PyObject* ValueError( const char* msg ) {
71
+ PyErr_SetString( PyExc_ValueError, msg );
72
+ return NULL;
73
+ }
74
+
75
+ template< typename... Args >
76
+ PyObject* ValueError( const char* msg, Args... args ) {
77
+ return PyErr_Format( PyExc_ValueError, msg, args... );
78
+ }
79
+
80
+ template< typename T1, typename T2 >
81
+ PyObject* IndexError( const char* msg, T1 t1, T2 t2 ) {
82
+ return PyErr_Format( PyExc_IndexError, msg, t1, t2 );
83
+ }
84
+
85
+ template< typename T1, typename T2, typename T3 >
86
+ PyObject* IndexError( const char* msg, T1 t1, T2 t2, T3 t3 ) {
87
+ return PyErr_Format( PyExc_IndexError, msg, t1, t2, t3 );
88
+ }
89
+
90
+ PyObject* BufferError( const char* msg ) {
91
+ PyErr_SetString( PyExc_BufferError, msg );
92
+ return NULL;
93
+ }
94
+
95
+ PyObject* RuntimeError( const char* msg ) {
96
+ PyErr_SetString( PyExc_RuntimeError, msg );
97
+ return NULL;
98
+ }
99
+
100
+ PyObject* RuntimeError( int err ) {
101
+ const std::string msg = "uncaught exception: " + segy_errstr( err );
102
+ return RuntimeError( msg.c_str() );
103
+ }
104
+
105
+ template< typename... Args >
106
+ PyObject* RuntimeError( const char* msg, Args... args ) {
107
+ return PyErr_Format( PyExc_RuntimeError, msg, args... );
108
+ }
109
+
110
+ PyObject* IOErrno() {
111
+ return PyErr_SetFromErrno( PyExc_IOError );
112
+ }
113
+
114
+ PyObject* IOError( const char* msg ) {
115
+ PyErr_SetString( PyExc_IOError, msg );
116
+ return NULL;
117
+ }
118
+
119
+ template< typename T1, typename T2 >
120
+ PyObject* IOError( const char* msg, T1 t1, T2 t2 ) {
121
+ return PyErr_Format( PyExc_IOError, msg, t1, t2 );
122
+ }
123
+
124
+ template< typename T1 >
125
+ PyObject* IOError( const char* msg, T1 t1 ) {
126
+ return PyErr_Format( PyExc_IOError, msg, t1 );
127
+ }
128
+
129
+ template< typename T1 >
130
+ PyObject* KeyError( const char* msg, T1 t1 ) {
131
+ return PyErr_Format( PyExc_KeyError, msg, t1 );
132
+ }
133
+
134
+ template< typename T1, typename T2 >
135
+ PyObject* KeyError( const char* msg, T1 t1, T2 t2 ) {
136
+ return PyErr_Format( PyExc_KeyError, msg, t1, t2 );
137
+ }
138
+
139
+ PyObject* Error( int err ) {
140
+ /*
141
+ * a default error handler. The fseek errors are sufficiently described
142
+ * with errno, and all cases that raise fwrite and fread errors get
143
+ * sufficient context from stack trace to be generalised with a better
144
+ * message.
145
+ *
146
+ * Anything else falls through to a generic RuntimeError "uncaught
147
+ * exception"
148
+ */
149
+ switch( err ) {
150
+ case SEGY_FSEEK_ERROR: return IOErrno();
151
+ case SEGY_FWRITE_ERROR: // fallthrough
152
+ case SEGY_FREAD_ERROR: return IOError( "I/O operation failed, "
153
+ "likely corrupted file" );
154
+ case SEGY_READONLY: return IOError( "file not open for writing. "
155
+ "open with 'r+'" );
156
+ case SEGY_DS_FLUSH_ERROR:
157
+ return IOError( "Datasource flush failed" );
158
+ case SEGY_DS_CLOSE_ERROR:
159
+ return IOError( "Datasource close failed" );
160
+ default: return RuntimeError( err );
161
+ }
162
+ }
163
+
164
+
165
+ namespace ds {
166
+
167
+ int py_read( segy_datasource* self, void* buffer, size_t size ) {
168
+ int err = SEGY_DS_READ_ERROR;
169
+ PyObject* stream = (PyObject*)self->stream;
170
+
171
+ PyObject* result = PyObject_CallMethod( stream, "read", "n", size );
172
+ if( !result ) {
173
+ if (PyErr_Occurred()) {
174
+ PyErr_Print();
175
+ }
176
+ return err;
177
+ }
178
+ const char* data = PyBytes_AsString( result );
179
+ if( data ) {
180
+ Py_ssize_t result_size = PyBytes_Size( result );
181
+ if( static_cast<size_t>( result_size ) == size ) {
182
+ memcpy( buffer, data, size );
183
+ err = SEGY_OK;
184
+ }
185
+ }
186
+
187
+ Py_DECREF( result );
188
+ return err;
189
+ }
190
+
191
+ int py_write( segy_datasource* self, const void* buffer, size_t size ) {
192
+ int err = SEGY_DS_WRITE_ERROR;
193
+ PyObject* stream = (PyObject*)self->stream;
194
+
195
+ PyObject* result = PyObject_CallMethod( stream, "write", "y#", buffer, size );
196
+ if( !result ) {
197
+ if (PyErr_Occurred()) {
198
+ PyErr_Print();
199
+ }
200
+ return err;
201
+ }
202
+
203
+ Py_ssize_t result_size = PyLong_AsSsize_t( result );
204
+ if( static_cast<size_t>( result_size ) == size ) {
205
+ err = SEGY_OK;
206
+ }
207
+ Py_DECREF( result );
208
+ return err;
209
+ }
210
+
211
+ int py_seek( segy_datasource* self, long long offset, int whence ) {
212
+ PyObject* stream = (PyObject*)self->stream;
213
+ PyObject* result = PyObject_CallMethod( stream, "seek", "Li", offset, whence );
214
+ if( !result ) {
215
+ if (PyErr_Occurred()) {
216
+ PyErr_Print();
217
+ }
218
+ return SEGY_DS_SEEK_ERROR;
219
+ }
220
+ Py_DECREF( result );
221
+ return SEGY_OK;
222
+ }
223
+
224
+ int py_tell( segy_datasource* self, long long* pos ) {
225
+ PyObject* stream = (PyObject*)self->stream;
226
+ PyObject* result = PyObject_CallMethod( stream, "tell", NULL );
227
+ if( !result ) {
228
+ if (PyErr_Occurred()) {
229
+ PyErr_Print();
230
+ }
231
+ return SEGY_DS_ERROR;
232
+ }
233
+ *pos = PyLong_AsLongLong( result );
234
+ Py_DECREF( result );
235
+ return SEGY_OK;
236
+ }
237
+
238
+ int py_size( segy_datasource* self, long long* out ) {
239
+ long long original_tell;
240
+ int err = py_tell( self, &original_tell );
241
+ if( err != SEGY_OK ) return err;
242
+
243
+ err = py_seek( self, 0, SEEK_END );
244
+ if( err != SEGY_OK ) return err;
245
+
246
+ err = py_tell( self, out );
247
+ if( err != SEGY_OK ) return err;
248
+
249
+ err = py_seek( self, original_tell, SEEK_SET );
250
+ if( err != SEGY_OK ) return err;
251
+ return SEGY_OK;
252
+ }
253
+
254
+ int py_flush( segy_datasource* self ) {
255
+ PyObject* stream = (PyObject*)self->stream;
256
+ PyObject* result = PyObject_CallMethod( stream, "flush", NULL );
257
+ if( !result ) {
258
+ if (PyErr_Occurred()) {
259
+ PyErr_Print();
260
+ }
261
+ return SEGY_DS_FLUSH_ERROR;
262
+ }
263
+ Py_DECREF( result );
264
+ return SEGY_OK;
265
+ }
266
+
267
+ int py_close( segy_datasource* self ) {
268
+ PyObject* stream = (PyObject*)self->stream;
269
+ PyObject* result = PyObject_CallMethod( stream, "close", NULL );
270
+ if( !result ) {
271
+ if (PyErr_Occurred()) {
272
+ PyErr_Print();
273
+ }
274
+ return SEGY_DS_CLOSE_ERROR;
275
+ }
276
+ Py_DECREF( result );
277
+ Py_DECREF( stream );
278
+ return SEGY_OK;
279
+ }
280
+
281
+ int py_set_writable( segy_datasource* self ) {
282
+ PyObject* stream = (PyObject*)self->stream;
283
+ PyObject* result = PyObject_CallMethod( stream, "writable", NULL );
284
+ if( !result ) {
285
+ if( PyErr_Occurred() ) {
286
+ PyErr_Print();
287
+ }
288
+ return SEGY_DS_ERROR;
289
+ }
290
+ self->writable = PyObject_IsTrue( result );
291
+ Py_DECREF( result );
292
+ return SEGY_OK;
293
+ }
294
+
295
+ static void init_traceheader_mapping(
296
+ segy_header_mapping* dst_mapping,
297
+ const uint8_t* src_name_map,
298
+ const segy_entry_definition* src_entry_definition_map,
299
+ const char* name
300
+ ) {
301
+ memcpy( dst_mapping->name, name, 8 );
302
+ memcpy(
303
+ dst_mapping->name_to_offset,
304
+ src_name_map,
305
+ sizeof( dst_mapping->name_to_offset )
306
+ );
307
+ memcpy(
308
+ dst_mapping->offset_to_entry_definition,
309
+ src_entry_definition_map,
310
+ sizeof( dst_mapping->offset_to_entry_definition )
311
+ );
312
+ }
313
+
314
+ segy_datasource* create_py_stream_datasource(
315
+ PyObject* py_stream, bool minimize_requests_number
316
+ ) {
317
+ segy_datasource* ds = (segy_datasource*)malloc( sizeof( segy_datasource ) );
318
+ if( !ds ) return NULL;
319
+
320
+ /* current requirements on file-like-object stream: read, write, seek, tell,
321
+ * flush, close, writable
322
+ */
323
+ ds->stream = py_stream;
324
+
325
+ ds->read = py_read;
326
+ ds->write = py_write;
327
+ ds->seek = py_seek;
328
+ ds->tell = py_tell;
329
+ ds->size = py_size;
330
+ ds->flush = py_flush;
331
+ ds->close = py_close;
332
+
333
+ // writable is set only on init, assuming stream does not change it during
334
+ // operation
335
+ const int err = py_set_writable( ds );
336
+ if( err ) return NULL;
337
+
338
+ ds->memory_speedup = false;
339
+ ds->minimize_requests_number = minimize_requests_number;
340
+
341
+ ds->metadata.endianness = SEGY_MSB;
342
+ ds->metadata.encoding = SEGY_EBCDIC;
343
+ ds->metadata.format = SEGY_IBM_FLOAT_4_BYTE;
344
+ ds->metadata.elemsize = 4;
345
+ ds->metadata.ext_textheader_count = 0;
346
+ ds->metadata.trace0 = -1;
347
+ ds->metadata.samplecount = -1;
348
+ ds->metadata.trace_bsize = -1;
349
+ ds->metadata.traceheader_count = 1;
350
+ ds->metadata.tracecount = -1;
351
+
352
+ init_traceheader_mapping(
353
+ &ds->traceheader_mapping_standard,
354
+ segy_traceheader_default_name_map(),
355
+ segy_traceheader_default_map(),
356
+ "SEG00000"
357
+ );
358
+
359
+ init_traceheader_mapping(
360
+ &ds->traceheader_mapping_extension1,
361
+ segy_ext1_traceheader_default_name_map(),
362
+ segy_ext1_traceheader_default_map(),
363
+ "SEG00001"
364
+ );
365
+
366
+ /* keep additional reference to assure object does not get deleted before
367
+ * segy_datasource is closed
368
+ */
369
+ Py_INCREF( py_stream );
370
+ return ds;
371
+ }
372
+
373
+ } // namespace ds
374
+
375
+ struct stanza_header {
376
+ std::string name;
377
+ int headerindex;
378
+ int headercount;
379
+
380
+ stanza_header() = default;
381
+ stanza_header( std::string n, int i, int c )
382
+ : name( std::move( n ) ),
383
+ headerindex( i ),
384
+ headercount( c ) {}
385
+
386
+ std::string normalized_name() const {
387
+ std::string normalized;
388
+ for( auto c : this->name ) {
389
+ if( c != ' ' ) {
390
+ normalized += std::tolower( static_cast<unsigned char>( c ) );
391
+ }
392
+ }
393
+ return normalized;
394
+ }
395
+
396
+ int end_index() const { return headerindex + headercount; }
397
+
398
+ size_t header_length() const {
399
+ const size_t parentheses_count = 4;
400
+ return this->name.size() + parentheses_count;
401
+ }
402
+
403
+ size_t data_size() const {
404
+ return this->headercount * SEGY_TEXT_HEADER_SIZE - this->header_length();
405
+ }
406
+ };
407
+
408
+ struct buffer_guard {
409
+ /* automate Py_buffer handling.
410
+ *
411
+ * the python documentation does not mention any exception guarantees when
412
+ * PyArg_ParseTuple, so assume that whenever the function fails, the buffer
413
+ * object is either zero'd or untouched. That means checking if a
414
+ * PyBuffer_Release should be called boils down to checking if underlying
415
+ * buffer is a nullptr or not
416
+ */
417
+ buffer_guard() { Py_buffer b = {}; this->buffer = b; }
418
+ explicit buffer_guard( const Py_buffer& b ) : buffer( b ) {}
419
+ buffer_guard( PyObject* o, int flags = PyBUF_CONTIG_RO ) {
420
+ Py_buffer b = {};
421
+ this->buffer = b;
422
+
423
+ if( !PyObject_CheckBuffer( o ) ) {
424
+ TypeError( "'%s' does not expose buffer interface",
425
+ o->ob_type->tp_name );
426
+ return;
427
+ }
428
+
429
+ const int cont = PyBUF_C_CONTIGUOUS;
430
+ if( PyObject_GetBuffer( o, &this->buffer, flags | cont ) == 0 )
431
+ return;
432
+
433
+ if( (flags & PyBUF_WRITABLE) == PyBUF_WRITABLE )
434
+ BufferError( "buffer must be contiguous and writable" );
435
+ else
436
+ BufferError( "buffer must be contiguous and readable" );
437
+ }
438
+
439
+ ~buffer_guard() { if( *this ) PyBuffer_Release( &this->buffer ); }
440
+
441
+ operator bool() const { return this->buffer.buf; }
442
+ Py_ssize_t len() const { return this->buffer.len; }
443
+ Py_buffer* operator&() { return &this->buffer; }
444
+
445
+ template< typename T >
446
+ T* buf() const { return static_cast< T* >( this->buffer.buf ); }
447
+ char* buf() const { return this->buf< char >(); }
448
+
449
+ Py_buffer buffer;
450
+ };
451
+
452
+ struct autods {
453
+ autods() : ds( nullptr ), memory_ds_buffer() {}
454
+
455
+ ~autods() {
456
+ this->close();
457
+ }
458
+
459
+ operator segy_datasource*() const;
460
+ operator bool() const;
461
+ void swap( autods& other );
462
+ int close();
463
+
464
+ segy_datasource* ds;
465
+ // only for memory datasource. Field is not directly used (it's .buf member
466
+ // is), it's only purpose is to stay alive so that .buf is available for the
467
+ // whole lifetime of segyfd.
468
+ Py_buffer memory_ds_buffer;
469
+ };
470
+
471
+ autods::operator segy_datasource*() const {
472
+ if( this->ds ) return this->ds;
473
+
474
+ ValueError( "I/O operation on closed datasource" );
475
+ return NULL;
476
+ }
477
+
478
+ autods::operator bool() const { return this->ds; }
479
+
480
+ void autods::swap( autods& other ) {
481
+ std::swap( this->ds, other.ds );
482
+ std::swap( this->memory_ds_buffer, other.memory_ds_buffer );
483
+ }
484
+
485
+ int autods::close() {
486
+ int err = SEGY_OK;
487
+ if( this->ds ) {
488
+ err = segy_close( this->ds );
489
+ }
490
+ this->ds = NULL;
491
+ if ( this->memory_ds_buffer.buf ) {
492
+ PyBuffer_Release( &this->memory_ds_buffer );
493
+ this->memory_ds_buffer = Py_buffer();
494
+ }
495
+ return err;
496
+ }
497
+
498
+ struct segyfd {
499
+ PyObject_HEAD
500
+ autods ds;
501
+ unsigned long long trace0;
502
+ int trace_bsize;
503
+ int tracecount;
504
+ int traceheader_count;
505
+ int samplecount;
506
+ int format;
507
+ int elemsize;
508
+
509
+ std::vector<stanza_header> stanzas;
510
+ std::vector<segy_header_mapping> traceheader_mappings;
511
+ };
512
+
513
+ namespace {
514
+ /** Parse extended text headers, find out their number and determine the list of
515
+ * stanza headers. Updates stanzas field. */
516
+ int parse_extended_text_headers( segyfd* self );
517
+
518
+ /** Frees segy_header_mapping names allocated on heap. */
519
+ int free_header_mappings_names( segy_header_mapping* mappings, size_t mappings_length );
520
+
521
+ /** Updates traceheader_mappings field from provided xml_stanza_data and user
522
+ * defined iline/xline. If required mappings are not found, default are used.
523
+ * Returns SEGY_OK if requested mapping override was successful, sets error and
524
+ * returns error code otherwise.*/
525
+ int set_traceheader_mappings(
526
+ segyfd* self,
527
+ std::vector<char>& xml_stanza_data,
528
+ PyObject* py_iline,
529
+ PyObject* py_xline
530
+ );
531
+
532
+ /** Extracts xml into layout_stanza_data vector from the stanza with name
533
+ * "seg:layout", regardless of how many extended headers it takes. Returns
534
+ * SEGY_OK both when data was successfully extracted or not found. */
535
+ int extract_layout_stanza( segyfd* self, std::vector<char>& layout_stanza_data );
536
+
537
+ /**
538
+ * Returns the dictionary {header_name: TraceHeaderLayout.}
539
+ */
540
+ PyObject* traceheader_layouts( segyfd* self );
541
+
542
+ } // namespace
543
+
544
+ namespace fd {
545
+
546
+ int init( segyfd* self, PyObject* args, PyObject* kwargs ) {
547
+ char* filename = NULL;
548
+ char* mode = NULL;
549
+ PyObject* stream = NULL;
550
+ PyObject* memory_buffer = NULL;
551
+ int minimize_requests_number = -1;
552
+
553
+ static const char* keywords[] = {
554
+ "filename",
555
+ "mode",
556
+ "stream",
557
+ "memory_buffer",
558
+ "minimize_requests_number",
559
+ NULL
560
+ };
561
+
562
+ if( !PyArg_ParseTupleAndKeywords(
563
+ args, kwargs, "|ssOOp",
564
+ const_cast<char**>( keywords ),
565
+ &filename,
566
+ &mode,
567
+ &stream,
568
+ &memory_buffer,
569
+ &minimize_requests_number
570
+ ) ) {
571
+ ValueError( "could not parse arguments" );
572
+ return -1;
573
+ }
574
+
575
+ autods ds;
576
+ if( stream ) {
577
+ if (stream == Py_None) {
578
+ ValueError( "stream must not be None" );
579
+ return -1;
580
+ }
581
+ if( minimize_requests_number < 0 ) {
582
+ ValueError( "minimize requests number is not set" );
583
+ return -1;
584
+ }
585
+ ds.ds = ds::create_py_stream_datasource(
586
+ stream, minimize_requests_number
587
+ );
588
+ } else if( memory_buffer ) {
589
+ if (memory_buffer == Py_None) {
590
+ ValueError( "buffer must not be None" );
591
+ return -1;
592
+ }
593
+ auto flags = PyBUF_CONTIG | PyBUF_WRITABLE;
594
+ const int err = PyObject_GetBuffer( memory_buffer, &ds.memory_ds_buffer, flags );
595
+ if( err ) {
596
+ ValueError( "Could not export buffer of requested type" );
597
+ return -1;
598
+ }
599
+ ds.ds = segy_memopen(
600
+ static_cast<unsigned char*>( ds.memory_ds_buffer.buf ),
601
+ ds.memory_ds_buffer.len
602
+ );
603
+ } else if( filename && mode ) {
604
+ if( std::strlen( mode ) == 0 ) {
605
+ ValueError( "mode string must be non-empty" );
606
+ return -1;
607
+ }
608
+
609
+ if( std::strlen( mode ) > 3 ) {
610
+ ValueError( "invalid mode string '%s', good strings are %s",
611
+ mode, "'r' (read-only) and 'r+' (read-write)" );
612
+ return -1;
613
+ }
614
+
615
+ segy_datasource* file = segy_open( filename, mode );
616
+ if( !file && !strstr( "rb" "wb" "ab" "r+b" "w+b" "a+b", mode ) ) {
617
+ ValueError( "invalid mode string '%s', good strings are %s",
618
+ mode, "'r' (read-only) and 'r+' (read-write)" );
619
+ return -1;
620
+ }
621
+
622
+ if( !file ) {
623
+ IOErrno();
624
+ }
625
+ ds.ds = file;
626
+ } else {
627
+ ValueError( "unknown input configuration" );
628
+ return -1;
629
+ }
630
+
631
+ if( !ds ) {
632
+ return -1;
633
+ }
634
+
635
+ /*
636
+ * init can be called multiple times, which is treated as opening a new
637
+ * file on the same object. That means the previous file handle must be
638
+ * properly closed before the new file is set
639
+ */
640
+ self->ds.swap( ds );
641
+
642
+ return 0;
643
+ }
644
+
645
+ PyObject* segyopen( segyfd* self, PyObject* args, PyObject* kwargs ) {
646
+ segy_datasource* ds = self->ds;
647
+ if( !ds ) return NULL;
648
+
649
+ int endianness = -1; // change to Py_None later
650
+ int encoding = -1;
651
+ PyObject *py_iline = Py_None;
652
+ PyObject *py_xline = Py_None;
653
+ PyObject* py_layout_xml = Py_None;
654
+
655
+ static const char* keywords[] = {
656
+ "endianness",
657
+ "encoding",
658
+ "iline",
659
+ "xline",
660
+ "layout_xml",
661
+ NULL
662
+ };
663
+
664
+ if( !PyArg_ParseTupleAndKeywords(
665
+ args, kwargs, "|iiOOO",
666
+ const_cast<char**>( keywords ),
667
+ &endianness,
668
+ &encoding,
669
+ &py_iline,
670
+ &py_xline,
671
+ &py_layout_xml
672
+ ) ) {
673
+ return NULL;
674
+ }
675
+
676
+ // endianness must be set before any other header values are read
677
+ if( endianness != SEGY_LSB && endianness != SEGY_MSB ) {
678
+ int err = segy_endianness( ds, &endianness );
679
+ if( err ) return Error( err );
680
+ }
681
+ ds->metadata.endianness = endianness;
682
+
683
+ int err = parse_extended_text_headers( self );
684
+ if( err ) return Error( err );
685
+
686
+ std::vector<char> layout_stanza_data;
687
+ if( py_layout_xml != Py_None ) {
688
+ buffer_guard layout_xml( py_layout_xml, PyBUF_CONTIG );
689
+ layout_stanza_data.resize( layout_xml.len() );
690
+ std::memcpy(
691
+ layout_stanza_data.data(),
692
+ layout_xml.buf<char>(),
693
+ layout_xml.len()
694
+ );
695
+ } else {
696
+ err = extract_layout_stanza( self, layout_stanza_data );
697
+ if( err ) return Error( err );
698
+ }
699
+
700
+ err = set_traceheader_mappings( self, layout_stanza_data, py_iline, py_xline );
701
+ if( err ) return NULL;
702
+
703
+ int ext_textheader_count =
704
+ self->stanzas.empty() ? 0 : self->stanzas.back().end_index();
705
+
706
+ err = segy_collect_metadata( ds, endianness, encoding, ext_textheader_count );
707
+ if( err != SEGY_OK ) {
708
+ std::ostringstream msg;
709
+ msg << "unable to gather basic metadata from the file, error " << segy_errstr( err )
710
+ << ". Intermediate state:\n"
711
+ << " endianness=" << ds->metadata.endianness << "\n"
712
+ << " encoding=" << ds->metadata.encoding << "\n"
713
+ << " format=" << ds->metadata.format << "\n"
714
+ << " elemsize=" << ds->metadata.elemsize << "\n"
715
+ << " ext_textheader_count=" << ds->metadata.ext_textheader_count << "\n"
716
+ << " trace0=" << ds->metadata.trace0 << "\n"
717
+ << " samplecount=" << ds->metadata.samplecount << "\n"
718
+ << " trace_bsize=" << ds->metadata.trace_bsize << "\n"
719
+ << " traceheader_count=" << ds->metadata.traceheader_count << "\n"
720
+ << " tracecount=" << ds->metadata.tracecount;
721
+ return RuntimeError( msg.str().c_str() );
722
+ }
723
+
724
+ self->format = ds->metadata.format;
725
+ self->elemsize = ds->metadata.elemsize;
726
+ self->trace0 = ds->metadata.trace0;
727
+ self->samplecount = ds->metadata.samplecount;
728
+ self->trace_bsize = ds->metadata.trace_bsize;
729
+ self->traceheader_count = ds->metadata.traceheader_count;
730
+ self->tracecount = ds->metadata.tracecount;
731
+
732
+
733
+ std::vector<std::array<char, 8>> traceheader_names(self->traceheader_count);
734
+ err = segy_traceheader_names(ds, reinterpret_cast<char(*)[8]>(traceheader_names.data()));
735
+ if( err ) return Error( err );
736
+
737
+ std::vector<segy_header_mapping> ordered_mappings;
738
+ for (const auto& header_name : traceheader_names) {
739
+ bool found = false;
740
+ for (const auto& mapping : self->traceheader_mappings) {
741
+ if (std::strncmp(mapping.name, header_name.data(), 8) == 0) {
742
+ ordered_mappings.push_back(mapping);
743
+ found = true;
744
+ break;
745
+ }
746
+ }
747
+ if (!found) {
748
+ return KeyError("traceheader mapping for '%8.8s' not found", header_name.data());
749
+ }
750
+ }
751
+ self->traceheader_mappings = std::move(ordered_mappings);
752
+
753
+ Py_INCREF( self );
754
+ return (PyObject*)self;
755
+ }
756
+
757
+ PyObject* segycreate( segyfd* self, PyObject* args, PyObject* kwargs ) {
758
+ segy_datasource* ds = self->ds;
759
+ if( !ds ) return NULL;
760
+
761
+ int endianness = -1;
762
+ int encoding = -1;
763
+ int samples;
764
+ int tracecount;
765
+ int ext_headers = 0;
766
+ int traceheader_count = 1;
767
+ int format = SEGY_IBM_FLOAT_4_BYTE;
768
+ PyObject* py_layout_xml = Py_None;
769
+
770
+ // https://mail.python.org/pipermail/python-dev/2006-February/060689.html
771
+ // python3 fixes the non-constness of the kwlist arg in
772
+ // ParseTupleAndKeywords, since C++ really prefers writing string literals
773
+ // as const
774
+ //
775
+ // Remove the const_cast when python2 support is dropped
776
+ static const char* kwlist[] = {
777
+ "samples",
778
+ "tracecount",
779
+ "endianness",
780
+ "encoding",
781
+ "traceheader_count",
782
+ "format",
783
+ "ext_headers",
784
+ "layout_xml",
785
+ NULL,
786
+ };
787
+
788
+ if( !PyArg_ParseTupleAndKeywords( args, kwargs,
789
+ "ii|iiiiiO",
790
+ const_cast< char** >(kwlist),
791
+ &samples,
792
+ &tracecount,
793
+ &endianness,
794
+ &encoding,
795
+ &traceheader_count,
796
+ &format,
797
+ &ext_headers,
798
+ &py_layout_xml
799
+ ) )
800
+ return NULL;
801
+
802
+ if( endianness != SEGY_MSB && endianness != SEGY_LSB ) {
803
+ endianness = SEGY_MSB;
804
+ }
805
+
806
+ if( encoding != SEGY_EBCDIC && encoding != SEGY_ASCII ) {
807
+ encoding = SEGY_EBCDIC;
808
+ }
809
+
810
+ ds->metadata.endianness = endianness;
811
+ ds->metadata.encoding = encoding;
812
+
813
+ std::vector<char> layout_stanza_data;
814
+
815
+ if( py_layout_xml != Py_None ) {
816
+ buffer_guard layout_xml( py_layout_xml, PyBUF_CONTIG );
817
+ layout_stanza_data.resize( layout_xml.len() );
818
+ std::memcpy(
819
+ layout_stanza_data.data(),
820
+ layout_xml.buf<char>(),
821
+ layout_xml.len()
822
+ );
823
+ }
824
+
825
+ int err = set_traceheader_mappings(
826
+ self, layout_stanza_data, Py_None, Py_None
827
+ );
828
+ if( err ) return NULL;
829
+
830
+ if( samples <= 0 )
831
+ return ValueError( "expected samples > 0" );
832
+
833
+ if( tracecount <= 0 )
834
+ return ValueError( "expected tracecount > 0" );
835
+
836
+ if( ext_headers < 0 )
837
+ return ValueError( "ext_headers must be non-negative" );
838
+
839
+ switch( format ) {
840
+ case SEGY_IBM_FLOAT_4_BYTE:
841
+ case SEGY_SIGNED_INTEGER_4_BYTE:
842
+ case SEGY_SIGNED_SHORT_2_BYTE:
843
+ case SEGY_FIXED_POINT_WITH_GAIN_4_BYTE:
844
+ case SEGY_IEEE_FLOAT_4_BYTE:
845
+ case SEGY_IEEE_FLOAT_8_BYTE:
846
+ case SEGY_SIGNED_CHAR_3_BYTE:
847
+ case SEGY_SIGNED_CHAR_1_BYTE:
848
+ case SEGY_SIGNED_INTEGER_8_BYTE:
849
+ case SEGY_UNSIGNED_INTEGER_4_BYTE:
850
+ case SEGY_UNSIGNED_SHORT_2_BYTE:
851
+ case SEGY_UNSIGNED_INTEGER_8_BYTE:
852
+ case SEGY_UNSIGNED_INTEGER_3_BYTE:
853
+ case SEGY_UNSIGNED_CHAR_1_BYTE:
854
+ break;
855
+
856
+ default:
857
+ return ValueError( "unknown format identifier" );
858
+ }
859
+
860
+ int elemsize = segy_formatsize( format );
861
+ ds->metadata.elemsize = elemsize;
862
+
863
+ ds->metadata.format = format;
864
+ ds->metadata.trace0 = SEGY_TEXT_HEADER_SIZE + SEGY_BINARY_HEADER_SIZE +
865
+ SEGY_TEXT_HEADER_SIZE * ext_headers;
866
+ ds->metadata.samplecount = samples;
867
+ ds->metadata.trace_bsize = segy_trsize( format, samples );
868
+ ds->metadata.traceheader_count = traceheader_count;
869
+ ds->metadata.tracecount = tracecount;
870
+
871
+ self->trace0 = ds->metadata.trace0;
872
+ self->trace_bsize = ds->metadata.trace_bsize;
873
+ self->format = format;
874
+ self->elemsize = elemsize;
875
+ self->samplecount = samples;
876
+ self->tracecount = tracecount;
877
+ self->traceheader_count = traceheader_count;
878
+
879
+ Py_INCREF( self );
880
+ return (PyObject*) self;
881
+ }
882
+
883
+ PyObject* suopen( segyfd* self, PyObject* args, PyObject* kwargs ) {
884
+ segy_datasource* ds = self->ds;
885
+ if( !ds ) return NULL;
886
+
887
+ int endianness = -1;
888
+ int encoding = -1;
889
+ PyObject *py_iline = Py_None;
890
+ PyObject *py_xline = Py_None;
891
+ static const char* keywords[] = {
892
+ "endianness",
893
+ "encoding",
894
+ "iline",
895
+ "xline",
896
+ NULL
897
+ };
898
+
899
+ if( !PyArg_ParseTupleAndKeywords(
900
+ args, kwargs, "|iiOO",
901
+ const_cast<char**>( keywords ),
902
+ &endianness,
903
+ &encoding,
904
+ &py_iline,
905
+ &py_xline
906
+ ) ) {
907
+ return NULL;
908
+ }
909
+
910
+ if( endianness != SEGY_MSB && endianness != SEGY_LSB ) {
911
+ ValueError( "endianness must be set to a valid value" );
912
+ return NULL;
913
+ }
914
+
915
+ if( encoding != SEGY_EBCDIC && encoding != SEGY_ASCII ) {
916
+ int err = segy_encoding( ds, &encoding );
917
+ if( err != SEGY_OK ) return NULL;
918
+ }
919
+ ds->metadata.endianness = endianness;
920
+ ds->metadata.encoding = encoding;
921
+
922
+ std::vector<char> layout_stanza_data;
923
+ int err = set_traceheader_mappings(
924
+ self, layout_stanza_data, py_iline, py_xline
925
+ );
926
+ if( err ) return NULL;
927
+
928
+ ds->metadata.format = SEGY_IEEE_FLOAT_4_BYTE;
929
+ ds->metadata.elemsize = segy_formatsize( ds->metadata.format );
930
+ ds->metadata.trace0 = 0;
931
+
932
+ char header[SEGY_TRACE_HEADER_SIZE] = {};
933
+ err = segy_read_standard_traceheader( ds, 0, header );
934
+ if( err )
935
+ return IOError( "unable to read first trace header in SU file" );
936
+
937
+ segy_field_data fd;
938
+ segy_get_tracefield(
939
+ header,
940
+ ds->traceheader_mapping_standard.offset_to_entry_definition,
941
+ SEGY_TR_SAMPLE_COUNT,
942
+ &fd
943
+ );
944
+ ds->metadata.samplecount = fd.value.u16;
945
+ ds->metadata.trace_bsize = ds->metadata.samplecount * ds->metadata.elemsize;
946
+
947
+ err = segy_traces( ds, &ds->metadata.tracecount );
948
+ if( err == SEGY_OK ) {
949
+ self->format = ds->metadata.format;
950
+ self->elemsize = ds->metadata.elemsize;
951
+ self->trace0 = ds->metadata.trace0;
952
+ self->samplecount = ds->metadata.samplecount;
953
+ self->trace_bsize = ds->metadata.trace_bsize;
954
+ self->traceheader_count = ds->metadata.traceheader_count;
955
+ self->tracecount = ds->metadata.tracecount;
956
+ Py_INCREF( self );
957
+ return (PyObject*)self;
958
+ }
959
+
960
+ std::ostringstream msg;
961
+ msg << "unable to gather basic metadata from the file, error " << segy_errstr( err )
962
+ << ". Intermediate state:\n"
963
+ << " endianness=" << ds->metadata.endianness << "\n"
964
+ << " encoding=" << ds->metadata.encoding << "\n"
965
+ << " format=" << ds->metadata.format << "\n"
966
+ << " elemsize=" << ds->metadata.elemsize << "\n"
967
+ << " ext_textheader_count=" << ds->metadata.ext_textheader_count << "\n"
968
+ << " trace0=" << ds->metadata.trace0 << "\n"
969
+ << " samplecount=" << ds->metadata.samplecount << "\n"
970
+ << " trace_bsize=" << ds->metadata.trace_bsize << "\n"
971
+ << " traceheader_count=" << ds->metadata.traceheader_count << "\n"
972
+ << " tracecount=" << ds->metadata.tracecount;
973
+ return RuntimeError( msg.str().c_str() );
974
+ }
975
+
976
+ void dealloc( segyfd* self ) {
977
+ free_header_mappings_names(
978
+ self->traceheader_mappings.data(),
979
+ self->traceheader_mappings.size()
980
+ );
981
+ self->ds.close();
982
+ Py_TYPE( self )->tp_free( (PyObject*) self );
983
+ }
984
+
985
+ PyObject* close( segyfd* self ) {
986
+ /* multiple close() is a no-op */
987
+ if( !self->ds ) return Py_BuildValue( "" );
988
+
989
+ errno = 0;
990
+ const int err = self->ds.close();
991
+ if ( err ) {
992
+ return Error( err );
993
+ }
994
+
995
+ if( errno ) return IOErrno();
996
+
997
+ return Py_BuildValue( "" );
998
+ }
999
+
1000
+ PyObject* flush( segyfd* self ) {
1001
+ segy_datasource* ds = self->ds;
1002
+ if( !ds ) return NULL;
1003
+
1004
+ errno = 0;
1005
+ segy_flush( self->ds );
1006
+ if( errno ) return IOErrno();
1007
+
1008
+ return Py_BuildValue( "" );
1009
+ }
1010
+
1011
+ PyObject* mmap( segyfd* self ) {
1012
+ segy_datasource* ds = self->ds;
1013
+
1014
+ if( !ds ) return NULL;
1015
+
1016
+ const int err = segy_mmap( ds );
1017
+
1018
+ if( err != SEGY_OK )
1019
+ Py_RETURN_FALSE;
1020
+
1021
+ Py_RETURN_TRUE;
1022
+ }
1023
+
1024
+ /*
1025
+ * No C++11, so no std::vector::data. single-alloc automatic heap buffer,
1026
+ * without resize
1027
+ */
1028
+ struct heapbuffer {
1029
+ explicit heapbuffer( int sz ) : ptr( new( std::nothrow ) char[ sz ] ) {
1030
+ if( !this->ptr ) {
1031
+ PyErr_SetString( PyExc_MemoryError, "unable to alloc buffer" );
1032
+ return;
1033
+ }
1034
+
1035
+ std::memset( this->ptr, 0, sz );
1036
+ }
1037
+
1038
+ ~heapbuffer() { delete[] this->ptr; }
1039
+
1040
+ operator char*() { return this->ptr; }
1041
+ operator const char*() const { return this->ptr; }
1042
+
1043
+ char* ptr;
1044
+
1045
+ private:
1046
+ heapbuffer( const heapbuffer& );
1047
+ };
1048
+
1049
+ PyObject* gettext( segyfd* self, PyObject* args ) {
1050
+ segy_datasource* ds = self->ds;
1051
+ if( !ds ) return NULL;
1052
+
1053
+ int index = 0;
1054
+ if( !PyArg_ParseTuple( args, "i", &index ) ) return NULL;
1055
+
1056
+ heapbuffer buffer( segy_textheader_size() );
1057
+ if( !buffer ) return NULL;
1058
+
1059
+ const int err = index == 0
1060
+ ? segy_read_textheader( ds, buffer )
1061
+ : segy_read_ext_textheader( ds, index - 1, buffer );
1062
+
1063
+ if( err ) return Error( err );
1064
+ /*
1065
+ * segy-textheader-size returns sizeof(header)+1 to have space for
1066
+ * string-terminating null-byte, but here just the raw buffer is returned
1067
+ */
1068
+ return PyByteArray_FromStringAndSize(buffer, segy_textheader_size() - 1);
1069
+ }
1070
+
1071
+ PyObject* puttext( segyfd* self, PyObject* args ) {
1072
+ segy_datasource* ds = self->ds;
1073
+ if( !ds ) return NULL;
1074
+
1075
+ int index;
1076
+ buffer_guard buffer;
1077
+
1078
+ if( !PyArg_ParseTuple( args, "is*", &index, &buffer ) )
1079
+ return NULL;
1080
+
1081
+ int size = std::min( int(buffer.len()), SEGY_TEXT_HEADER_SIZE );
1082
+ heapbuffer buf( SEGY_TEXT_HEADER_SIZE );
1083
+ if( !buf ) return NULL;
1084
+
1085
+ const char* src = buffer.buf< const char >();
1086
+ std::copy( src, src + size, buf.ptr );
1087
+
1088
+ const int err = segy_write_textheader( ds, index, buf );
1089
+
1090
+ if( err ) return Error( err );
1091
+
1092
+ return Py_BuildValue( "" );
1093
+ }
1094
+
1095
+ PyObject* getstanza( segyfd* self, PyObject* args ) {
1096
+ segy_datasource* ds = self->ds;
1097
+ if( !ds ) return NULL;
1098
+
1099
+ int index = 0;
1100
+ if( !PyArg_ParseTuple( args, "i", &index ) ) return NULL;
1101
+
1102
+ stanza_header stanza = self->stanzas[index];
1103
+
1104
+ heapbuffer buffer( stanza.data_size() );
1105
+ if( !buffer ) return NULL;
1106
+
1107
+ const int err = segy_read_stanza_data(
1108
+ ds,
1109
+ stanza.header_length(),
1110
+ stanza.headerindex,
1111
+ stanza.data_size(),
1112
+ buffer
1113
+ );
1114
+ if( err ) return Error( err );
1115
+
1116
+ return PyByteArray_FromStringAndSize( buffer, stanza.data_size() );
1117
+ }
1118
+
1119
+ PyObject* getbin( segyfd* self ) {
1120
+ segy_datasource* ds = self->ds;
1121
+ if( !ds ) return NULL;
1122
+
1123
+ char buffer[ SEGY_BINARY_HEADER_SIZE ] = {};
1124
+
1125
+ const int err = segy_binheader( ds, buffer );
1126
+ if( err ) return Error( err );
1127
+
1128
+ return PyByteArray_FromStringAndSize( buffer, sizeof( buffer ) );
1129
+ }
1130
+
1131
+ PyObject* putbin( segyfd* self, PyObject* args ) {
1132
+ segy_datasource* ds = self->ds;
1133
+ if( !ds ) return NULL;
1134
+
1135
+ buffer_guard buffer;
1136
+ if( !PyArg_ParseTuple(args, "s*", &buffer ) ) return NULL;
1137
+
1138
+ if( buffer.len() < SEGY_BINARY_HEADER_SIZE )
1139
+ return ValueError( "internal: binary buffer too small, "
1140
+ "expected %i, was %zd",
1141
+ SEGY_BINARY_HEADER_SIZE, buffer.len() );
1142
+
1143
+ const int err = segy_write_binheader( ds, buffer.buf< const char >() );
1144
+
1145
+ if( err == SEGY_INVALID_ARGS )
1146
+ return IOError( "file not open for writing. open with 'r+'" );
1147
+ if( err ) return Error( err );
1148
+
1149
+ return Py_BuildValue( "" );
1150
+ }
1151
+
1152
+ PyObject* getth( segyfd* self, PyObject *args ) {
1153
+ segy_datasource* ds = self->ds;
1154
+ if( !ds ) return NULL;
1155
+
1156
+ int traceno;
1157
+ uint16_t traceheader_index;
1158
+ PyObject* bufferobj;
1159
+
1160
+ if( !PyArg_ParseTuple( args, "iHO", &traceno, &traceheader_index, &bufferobj ) ) return NULL;
1161
+
1162
+ buffer_guard buffer( bufferobj, PyBUF_CONTIG );
1163
+ if( !buffer ) return NULL;
1164
+
1165
+ if( buffer.len() < SEGY_TRACE_HEADER_SIZE )
1166
+ return ValueError( "internal: trace header buffer too small, "
1167
+ "expected %i, was %zd",
1168
+ SEGY_TRACE_HEADER_SIZE, buffer.len() );
1169
+
1170
+ if( traceheader_index >= self->traceheader_mappings.size() ) {
1171
+ return KeyError(
1172
+ "no trace header mapping available for index %d", traceheader_index
1173
+ );
1174
+ }
1175
+
1176
+ int err = segy_read_traceheader(
1177
+ ds,
1178
+ traceno,
1179
+ traceheader_index,
1180
+ self->traceheader_mappings[traceheader_index].offset_to_entry_definition,
1181
+ buffer.buf()
1182
+ );
1183
+
1184
+ switch( err ) {
1185
+ case SEGY_OK:
1186
+ Py_INCREF( bufferobj );
1187
+ return bufferobj;
1188
+
1189
+ case SEGY_FREAD_ERROR:
1190
+ return IOError( "I/O operation failed on trace header %d",
1191
+ traceno );
1192
+
1193
+ default:
1194
+ return Error( err );
1195
+ }
1196
+ }
1197
+
1198
+ PyObject* putth( segyfd* self, PyObject* args ) {
1199
+ segy_datasource* ds = self->ds;
1200
+ if( !ds ) return NULL;
1201
+
1202
+ int traceno;
1203
+ uint16_t traceheader_index;
1204
+ buffer_guard buf;
1205
+ if( !PyArg_ParseTuple( args, "iHs*", &traceno, &traceheader_index, &buf ) ) return NULL;
1206
+
1207
+ if( buf.len() < SEGY_TRACE_HEADER_SIZE )
1208
+ return ValueError( "internal: trace header buffer too small, "
1209
+ "expected %i, was %zd",
1210
+ SEGY_TRACE_HEADER_SIZE, buf.len() );
1211
+
1212
+ if( traceheader_index >= self->traceheader_mappings.size() ) {
1213
+ return KeyError(
1214
+ "no trace header mapping available for index %d", traceheader_index
1215
+ );
1216
+ }
1217
+
1218
+ const char* buffer = buf.buf< const char >();
1219
+
1220
+ int err = segy_write_traceheader(
1221
+ ds,
1222
+ traceno,
1223
+ traceheader_index,
1224
+ self->traceheader_mappings[traceheader_index].offset_to_entry_definition,
1225
+ buffer
1226
+ );
1227
+
1228
+ switch( err ) {
1229
+ case SEGY_OK:
1230
+ return Py_BuildValue( "" );
1231
+
1232
+ case SEGY_FWRITE_ERROR:
1233
+ return IOError( "I/O operation failed on trace header %d",
1234
+ traceno );
1235
+ default:
1236
+ return Error( err );
1237
+ }
1238
+ }
1239
+
1240
+ PyObject* getfield( segyfd* self, PyObject* args ) {
1241
+ buffer_guard buffer;
1242
+ int field;
1243
+ PyObject* py_traceheader_index;
1244
+
1245
+ if( !PyArg_ParseTuple(
1246
+ args,
1247
+ "s*Oi",
1248
+ &buffer,
1249
+ &py_traceheader_index,
1250
+ &field
1251
+ ) )
1252
+ return NULL;
1253
+
1254
+ segy_field_data fd;
1255
+ int err;
1256
+ switch( buffer.len() ) {
1257
+ case SEGY_BINARY_HEADER_SIZE:
1258
+ err = segy_get_binfield(
1259
+ buffer.buf<const char>(), field, &fd
1260
+ );
1261
+ break;
1262
+ case SEGY_TRACE_HEADER_SIZE: {
1263
+ unsigned long traceheader_index = PyLong_AsUnsignedLong( py_traceheader_index );
1264
+ if( PyErr_Occurred() ) {
1265
+ return ValueError(
1266
+ "traceheader_index must be a non-negative integer"
1267
+ );
1268
+ }
1269
+ if( traceheader_index >= self->traceheader_mappings.size() ) {
1270
+ return KeyError(
1271
+ "no trace header mapping available for index %d", traceheader_index
1272
+ );
1273
+ }
1274
+ const segy_entry_definition* map =
1275
+ self->traceheader_mappings[traceheader_index].offset_to_entry_definition;
1276
+ err = segy_get_tracefield( buffer.buf<const char>(), map, field, &fd );
1277
+ break;
1278
+ }
1279
+ default:
1280
+ return BufferError( "buffer too small" );
1281
+ }
1282
+ if( err != SEGY_OK )
1283
+ return KeyError( "Got error code %d when requesting field %d", err, field );
1284
+
1285
+ uint8_t datatype = segy_entry_type_to_datatype( fd.entry_type );
1286
+ switch( datatype ) {
1287
+
1288
+ case SEGY_SIGNED_INTEGER_8_BYTE:
1289
+ return PyLong_FromLongLong( fd.value.i64 );
1290
+ case SEGY_SIGNED_INTEGER_4_BYTE:
1291
+ return PyLong_FromLong( fd.value.i32 );
1292
+ case SEGY_SIGNED_SHORT_2_BYTE:
1293
+ return PyLong_FromLong( fd.value.i16 );
1294
+ case SEGY_SIGNED_CHAR_1_BYTE:
1295
+ return PyLong_FromLong( fd.value.i8 );
1296
+
1297
+ case SEGY_UNSIGNED_INTEGER_8_BYTE:
1298
+ return PyLong_FromUnsignedLongLong( fd.value.u64 );
1299
+ case SEGY_UNSIGNED_INTEGER_4_BYTE:
1300
+ return PyLong_FromUnsignedLong( fd.value.u32 );
1301
+ case SEGY_UNSIGNED_SHORT_2_BYTE:
1302
+ return PyLong_FromUnsignedLong( fd.value.u16 );
1303
+ case SEGY_UNSIGNED_CHAR_1_BYTE:
1304
+ return PyLong_FromUnsignedLong( fd.value.u8 );
1305
+
1306
+ case SEGY_IBM_FLOAT_4_BYTE:
1307
+ case SEGY_IEEE_FLOAT_4_BYTE:
1308
+ return PyFloat_FromDouble( fd.value.f32 );
1309
+ case SEGY_IEEE_FLOAT_8_BYTE:
1310
+ return PyFloat_FromDouble( fd.value.f64 );
1311
+
1312
+ case SEGY_STRING_8_BYTE: {
1313
+ return PyBytes_FromStringAndSize( fd.value.str8, 8 );
1314
+ }
1315
+
1316
+ default:
1317
+ return KeyError( "Unhandled entry type %d for field %d", fd.entry_type, field );
1318
+ }
1319
+ }
1320
+
1321
+ PyObject* putfield( segyfd* self, PyObject *args ) {
1322
+ PyObject* buffer_arg;
1323
+ PyObject* py_traceheader_index;
1324
+ int field;
1325
+ PyObject* value_arg;
1326
+
1327
+
1328
+ if( !PyArg_ParseTuple(
1329
+ args,
1330
+ "OOiO",
1331
+ &buffer_arg,
1332
+ &py_traceheader_index,
1333
+ &field,
1334
+ &value_arg
1335
+ ) ) return NULL;
1336
+
1337
+ buffer_guard buffer;
1338
+ if( !PyArg_Parse( buffer_arg, "w*", &buffer ) )
1339
+ return NULL;
1340
+
1341
+ segy_field_data fd;
1342
+ const segy_entry_definition* map;
1343
+
1344
+ /*
1345
+ * We repeat some of internal logic here because Python does not keep type
1346
+ * information the same way as C does. As we do not know the type of the
1347
+ * data we have, we assume it to be the same as expected type in the
1348
+ * mapping.
1349
+ */
1350
+ switch( buffer.len() ) {
1351
+ case SEGY_BINARY_HEADER_SIZE: {
1352
+ const int offset = field - SEGY_TEXT_HEADER_SIZE - 1;
1353
+ if( offset < 0 || offset >= SEGY_BINARY_HEADER_SIZE ) {
1354
+ return KeyError( "Invalid field %d", field );
1355
+ }
1356
+ map = segy_binheader_map();
1357
+ fd.entry_type = map[offset].entry_type;
1358
+ break;
1359
+ }
1360
+ case SEGY_TRACE_HEADER_SIZE: {
1361
+ const int offset = field - 1;
1362
+ if( offset < 0 || offset >= SEGY_TRACE_HEADER_SIZE ) {
1363
+ return KeyError( "Invalid field %d", field );
1364
+ }
1365
+
1366
+ unsigned long traceheader_index = PyLong_AsUnsignedLong( py_traceheader_index );
1367
+ if( PyErr_Occurred() ) {
1368
+ return ValueError(
1369
+ "traceheader_index must be a non-negative integer"
1370
+ );
1371
+ }
1372
+
1373
+ if( traceheader_index >= self->traceheader_mappings.size() ) {
1374
+ return KeyError(
1375
+ "no trace header mapping available for index %d", traceheader_index
1376
+ );
1377
+ }
1378
+ map = self->traceheader_mappings[traceheader_index].offset_to_entry_definition;
1379
+ fd.entry_type = map[offset].entry_type;
1380
+ break;
1381
+ }
1382
+ default:
1383
+ return BufferError( "buffer too small" );
1384
+ }
1385
+
1386
+ uint8_t datatype = segy_entry_type_to_datatype( fd.entry_type );
1387
+ switch( datatype ) {
1388
+ case SEGY_UNSIGNED_INTEGER_8_BYTE:
1389
+ {
1390
+ unsigned long long val = PyLong_AsUnsignedLongLong( value_arg );
1391
+ if (PyErr_Occurred() || val > UINT64_MAX) {
1392
+ return ValueError( "Value out of range for unsigned long at field %d", field );
1393
+ }
1394
+ fd.value.u64 = val;
1395
+ }
1396
+ break;
1397
+ case SEGY_UNSIGNED_INTEGER_4_BYTE:
1398
+ {
1399
+ unsigned long val = PyLong_AsUnsignedLong( value_arg );
1400
+ if( PyErr_Occurred() || val > UINT32_MAX ) {
1401
+ return ValueError( "Value out of range for unsigned int at field %d", field );
1402
+ }
1403
+ fd.value.u32 = val;
1404
+ }
1405
+ break;
1406
+ case SEGY_UNSIGNED_SHORT_2_BYTE:
1407
+ {
1408
+ unsigned long val = PyLong_AsUnsignedLong( value_arg );
1409
+ if( PyErr_Occurred() || val > UINT16_MAX ) {
1410
+ return ValueError( "Value out of range for unsigned short at field %d", field );
1411
+ }
1412
+ fd.value.u16 = static_cast<uint16_t>( val );
1413
+ }
1414
+ break;
1415
+ case SEGY_UNSIGNED_CHAR_1_BYTE:
1416
+ {
1417
+ unsigned long val = PyLong_AsUnsignedLong( value_arg );
1418
+ if( PyErr_Occurred() || val > UINT8_MAX ) {
1419
+ return ValueError( "Value out of range for unsigned char at field %d", field );
1420
+ }
1421
+ fd.value.u8 = static_cast<uint8_t>( val );
1422
+ }
1423
+ break;
1424
+
1425
+ case SEGY_SIGNED_INTEGER_8_BYTE:
1426
+ {
1427
+ long long val = PyLong_AsLongLong( value_arg );
1428
+ if (PyErr_Occurred() || val > INT64_MAX || val < INT64_MIN ) {
1429
+ return ValueError( "Value out of range for signed long at field %d", field );
1430
+ }
1431
+ fd.value.i64 = val;
1432
+ }
1433
+ break;
1434
+ case SEGY_SIGNED_INTEGER_4_BYTE:
1435
+ {
1436
+ long val = PyLong_AsLong( value_arg );
1437
+ if( PyErr_Occurred() || val > INT32_MAX || val < INT32_MIN ) {
1438
+ return ValueError( "Value out of range for signed int at field %d", field );
1439
+ }
1440
+ fd.value.i32 = val;
1441
+ }
1442
+ break;
1443
+ case SEGY_SIGNED_SHORT_2_BYTE:
1444
+ {
1445
+ long val = PyLong_AsLong( value_arg );
1446
+ if( PyErr_Occurred() || val > INT16_MAX || val < INT16_MIN ) {
1447
+ return ValueError( "Value out of range for signed short at field %d", field );
1448
+ }
1449
+ fd.value.i16 = static_cast<int16_t>( val );
1450
+ }
1451
+ break;
1452
+ case SEGY_SIGNED_CHAR_1_BYTE:
1453
+ {
1454
+ long val = PyLong_AsLong( value_arg );
1455
+ if( PyErr_Occurred() || val > INT8_MAX || val < INT8_MIN ) {
1456
+ return ValueError( "Value out of range for signed char at field %d", field );
1457
+ }
1458
+ fd.value.u8 = static_cast<uint8_t>( val );
1459
+ }
1460
+ break;
1461
+
1462
+ case SEGY_IEEE_FLOAT_8_BYTE:
1463
+ {
1464
+ double val = PyFloat_AsDouble( value_arg );
1465
+ if( PyErr_Occurred() ) {
1466
+ return ValueError( "Value out of range for double at field %d", field );
1467
+ }
1468
+ fd.value.f64 = val;
1469
+ break;
1470
+ }
1471
+ case SEGY_IBM_FLOAT_4_BYTE:
1472
+ case SEGY_IEEE_FLOAT_4_BYTE:
1473
+ {
1474
+ float val = static_cast<float>( PyFloat_AsDouble( value_arg ) );
1475
+ if( PyErr_Occurred() ) {
1476
+ return ValueError( "Value out of range for float at field %d", field );
1477
+ }
1478
+ fd.value.f32 = val;
1479
+ break;
1480
+ }
1481
+ break;
1482
+ case SEGY_STRING_8_BYTE: {
1483
+ char* ptr = nullptr;
1484
+ Py_ssize_t len = 0;
1485
+ if( PyBytes_AsStringAndSize( value_arg, &ptr, &len ) == -1 ) {
1486
+ return ValueError( "Value must be a bytes object of length 8 at field %d", field );
1487
+ }
1488
+ if( len != 8 ) {
1489
+ return ValueError( "Value must be exactly 8 bytes, got %zd", len );
1490
+ }
1491
+ memcpy( fd.value.str8, ptr, len );
1492
+ break;
1493
+ }
1494
+ default:
1495
+ return KeyError( "Field %d has unknown entry type %d", field, fd.entry_type );
1496
+ }
1497
+
1498
+ int err;
1499
+ switch (buffer.len()) {
1500
+ case SEGY_BINARY_HEADER_SIZE:
1501
+ err = segy_set_binfield(
1502
+ buffer.buf<char>(), field, fd
1503
+ );
1504
+ break;
1505
+ case SEGY_TRACE_HEADER_SIZE:
1506
+ err = segy_set_tracefield(
1507
+ buffer.buf<char>(), map, field, fd
1508
+ );
1509
+ break;
1510
+ default:
1511
+ return ValueError( "unhandled buffer type" );
1512
+ }
1513
+
1514
+ switch( err ) {
1515
+ case SEGY_OK:
1516
+ return Py_BuildValue("");
1517
+ case SEGY_INVALID_FIELD: return KeyError( "No such field %d", field );
1518
+ default: return Error( err );
1519
+ }
1520
+ }
1521
+
1522
+ PyObject* field_forall( segyfd* self, PyObject* args ) {
1523
+ segy_datasource* ds = self->ds;
1524
+ if( !ds ) return NULL;
1525
+
1526
+ PyObject* bufferobj;
1527
+ int start, stop, step;
1528
+ uint16_t traceheader_index;
1529
+ int field;
1530
+
1531
+ if( !PyArg_ParseTuple(
1532
+ args,
1533
+ "OHiiii",
1534
+ &bufferobj,
1535
+ &traceheader_index,
1536
+ &start,
1537
+ &stop,
1538
+ &step,
1539
+ &field
1540
+ ) )
1541
+ return NULL;
1542
+
1543
+ if( step == 0 ) return ValueError( "slice step cannot be zero" );
1544
+
1545
+ buffer_guard buffer( bufferobj, PyBUF_CONTIG );
1546
+ if( !buffer ) return NULL;
1547
+
1548
+ if( traceheader_index >= self->traceheader_mappings.size() ) {
1549
+ return KeyError(
1550
+ "no trace header mapping available for index %d", traceheader_index
1551
+ );
1552
+ }
1553
+ const segy_entry_definition* map =
1554
+ self->traceheader_mappings[traceheader_index].offset_to_entry_definition;
1555
+
1556
+ const int err = segy_field_forall( ds,
1557
+ traceheader_index,
1558
+ map,
1559
+ field,
1560
+ start,
1561
+ stop,
1562
+ step,
1563
+ buffer.buf< char >() );
1564
+
1565
+ if( err ) return Error( err );
1566
+
1567
+ Py_INCREF( bufferobj );
1568
+ return bufferobj;
1569
+ }
1570
+
1571
+ PyObject* field_foreach( segyfd* self, PyObject* args ) {
1572
+ segy_datasource* ds = self->ds;
1573
+ if( !ds ) return NULL;
1574
+
1575
+ PyObject* bufferobj;
1576
+ uint16_t traceheader_index;
1577
+ buffer_guard indices; //int32 is expected, but not really assured
1578
+ int field;
1579
+ if( !PyArg_ParseTuple(
1580
+ args,
1581
+ "OHs*i",
1582
+ &bufferobj,
1583
+ &traceheader_index,
1584
+ &indices,
1585
+ &field
1586
+ ) )
1587
+ return NULL;
1588
+
1589
+ buffer_guard bufout( bufferobj, PyBUF_CONTIG );
1590
+ if( !bufout ) return NULL;
1591
+
1592
+ if( traceheader_index >= self->traceheader_mappings.size() ) {
1593
+ return KeyError(
1594
+ "no trace header mapping available for index %d", traceheader_index
1595
+ );
1596
+ }
1597
+ const segy_entry_definition* map =
1598
+ self->traceheader_mappings[traceheader_index].offset_to_entry_definition;
1599
+
1600
+ int field_size = segy_formatsize( segy_entry_type_to_datatype(
1601
+ map[field - 1].entry_type ));
1602
+
1603
+ int buffer_length = bufout.len() / field_size;
1604
+ int indices_length = indices.len() / sizeof( int32_t );
1605
+
1606
+ if( buffer_length != indices_length )
1607
+ return ValueError( "internal: array size mismatch "
1608
+ "(output %zd, indices %zd)",
1609
+ buffer_length, indices_length );
1610
+
1611
+ const int* ind = indices.buf< const int >();
1612
+ char* out = bufout.buf< char >();
1613
+ int err = 0;
1614
+ for( int i = 0; err == 0 && i < buffer_length; ++i ) {
1615
+ err = segy_field_forall( ds, traceheader_index, map, field,
1616
+ ind[ i ],
1617
+ ind[ i ] + 1,
1618
+ 1,
1619
+ out + i * field_size );
1620
+ }
1621
+
1622
+ if( err ) return Error( err );
1623
+
1624
+ Py_INCREF( bufferobj );
1625
+ return bufferobj;
1626
+ }
1627
+
1628
+ PyObject* metrics( segyfd* self ) {
1629
+ static const int text = SEGY_TEXT_HEADER_SIZE;
1630
+ static const int bin = SEGY_BINARY_HEADER_SIZE;
1631
+ const int ext = (self->trace0 - (text + bin)) / text;
1632
+ segy_datasource* ds = self->ds;
1633
+ int encoding = ds->metadata.encoding;
1634
+ return Py_BuildValue( "{s:i, s:K, s:i, s:i, s:i, s:i, s:i, s:i}",
1635
+ "tracecount", self->tracecount,
1636
+ "trace0", self->trace0,
1637
+ "trace_bsize", self->trace_bsize,
1638
+ "samplecount", self->samplecount,
1639
+ "format", self->format,
1640
+ "encoding", encoding,
1641
+ "traceheader_count", self->traceheader_count,
1642
+ "ext_headers", ext );
1643
+ }
1644
+
1645
+ struct metrics_errmsg {
1646
+ int il, xl, of;
1647
+ PyObject* operator()( int err ) const {
1648
+ switch( err ) {
1649
+ case SEGY_INVALID_FIELD:
1650
+ return IndexError( "invalid iline, (%i), xline (%i), "
1651
+ "or offset (%i) field", il, xl, of );
1652
+
1653
+ case SEGY_INVALID_FIELD_DATATYPE:
1654
+ return ValueError( "invalid field datatype for "
1655
+ "iline, (%i), xline (%i) or offset (%i) field",
1656
+ il, xl, of );
1657
+
1658
+ case SEGY_INVALID_SORTING:
1659
+ return RuntimeError( "unable to find sorting."
1660
+ "Check iline, (%i) and xline (%i) "
1661
+ "in case you are sure the file is "
1662
+ "a 3D sorted volume", il, xl);
1663
+
1664
+ default:
1665
+ return Error( err );
1666
+ }
1667
+ }
1668
+ };
1669
+
1670
+ PyObject* cube_metrics( segyfd* self ) {
1671
+ segy_datasource* ds = self->ds;
1672
+ if( !ds ) return NULL;
1673
+
1674
+ segy_header_mapping& mapping = ds->traceheader_mapping_standard;
1675
+
1676
+ int il = mapping.name_to_offset[SEGY_TR_INLINE];
1677
+ int xl = mapping.name_to_offset[SEGY_TR_CROSSLINE];
1678
+ int offset = mapping.name_to_offset[SEGY_TR_OFFSET];
1679
+
1680
+ metrics_errmsg errmsg = { il, xl, offset };
1681
+
1682
+ int sorting = -1;
1683
+ int err = segy_sorting( ds, il,
1684
+ xl,
1685
+ offset,
1686
+ &sorting );
1687
+
1688
+ if( err ) return errmsg( err );
1689
+
1690
+ int offset_count = -1;
1691
+ err = segy_offsets( ds, il,
1692
+ xl,
1693
+ self->tracecount,
1694
+ &offset_count );
1695
+
1696
+ if( err ) return errmsg( err );
1697
+
1698
+ int xl_count = 0;
1699
+ int il_count = 0;
1700
+ err = segy_lines_count( ds, il,
1701
+ xl,
1702
+ sorting,
1703
+ offset_count,
1704
+ &il_count,
1705
+ &xl_count );
1706
+
1707
+ if( err == SEGY_NOTFOUND )
1708
+ return ValueError( "could not parse geometry, "
1709
+ "open with strict=False" );
1710
+
1711
+ if( err ) return errmsg( err );
1712
+
1713
+ return Py_BuildValue( "{s:i, s:i, s:i, s:i, s:i, s:i, s:i}",
1714
+ "sorting", sorting,
1715
+ "iline_field", il,
1716
+ "xline_field", xl,
1717
+ "offset_field", offset,
1718
+ "offset_count", offset_count,
1719
+ "iline_count", il_count,
1720
+ "xline_count", xl_count );
1721
+ }
1722
+
1723
+ long getitem( PyObject* dict, const char* key ) {
1724
+ return PyLong_AsLong( PyDict_GetItemString( dict, key ) );
1725
+ }
1726
+
1727
+ PyObject* indices( segyfd* self, PyObject* args ) {
1728
+ segy_datasource* ds = self->ds;
1729
+ if( !ds ) return NULL;
1730
+
1731
+ PyObject* metrics;
1732
+ buffer_guard iline_out;
1733
+ buffer_guard xline_out;
1734
+ buffer_guard offset_out;
1735
+
1736
+ if( !PyArg_ParseTuple( args, "O!w*w*w*", &PyDict_Type, &metrics,
1737
+ &iline_out,
1738
+ &xline_out,
1739
+ &offset_out ) )
1740
+ return NULL;
1741
+
1742
+ const int iline_count = getitem( metrics, "iline_count" );
1743
+ const int xline_count = getitem( metrics, "xline_count" );
1744
+ const int offset_count = getitem( metrics, "offset_count" );
1745
+
1746
+ if( iline_out.len() < Py_ssize_t(iline_count * sizeof( int )) )
1747
+ return ValueError( "internal: inline indices buffer too small, "
1748
+ "expected %i, was %zd",
1749
+ iline_count, iline_out.len() );
1750
+
1751
+ if( xline_out.len() < Py_ssize_t(xline_count * sizeof( int )) )
1752
+ return ValueError( "internal: crossline indices buffer too small, "
1753
+ "expected %i, was %zd",
1754
+ xline_count, xline_out.len() );
1755
+
1756
+ if( offset_out.len() < Py_ssize_t(offset_count * sizeof( int )) )
1757
+ return ValueError( "internal: offset indices buffer too small, "
1758
+ "expected %i, was %zd",
1759
+ offset_count, offset_out.len() );
1760
+
1761
+ const int il_field = getitem( metrics, "iline_field" );
1762
+ const int xl_field = getitem( metrics, "xline_field" );
1763
+ const int offset_field = getitem( metrics, "offset_field" );
1764
+ const int sorting = getitem( metrics, "sorting" );
1765
+ if( PyErr_Occurred() ) return NULL;
1766
+
1767
+ metrics_errmsg errmsg = { il_field, xl_field, offset_field };
1768
+
1769
+ int err = segy_inline_indices( ds, il_field,
1770
+ sorting,
1771
+ iline_count,
1772
+ xline_count,
1773
+ offset_count,
1774
+ iline_out.buf< int >() );
1775
+ if( err ) return errmsg( err );
1776
+
1777
+ err = segy_crossline_indices( ds, xl_field,
1778
+ sorting,
1779
+ iline_count,
1780
+ xline_count,
1781
+ offset_count,
1782
+ xline_out.buf< int >() );
1783
+ if( err ) return errmsg( err );
1784
+
1785
+ err = segy_offset_indices( ds, offset_field,
1786
+ offset_count,
1787
+ offset_out.buf< int >() );
1788
+ if( err ) return errmsg( err );
1789
+
1790
+ return Py_BuildValue( "" );
1791
+ }
1792
+
1793
+ PyObject* gettr( segyfd* self, PyObject* args ) {
1794
+ segy_datasource* ds = self->ds;
1795
+ if( !ds ) return NULL;
1796
+
1797
+ PyObject* bufferobj;
1798
+ int start, length, step, sample_start, sample_stop, sample_step, samples;
1799
+
1800
+ if( !PyArg_ParseTuple( args, "Oiiiiiii", &bufferobj, &start, &step, &length,
1801
+ &sample_start, &sample_stop, &sample_step, &samples ) )
1802
+ return NULL;
1803
+
1804
+ buffer_guard buffer( bufferobj, PyBUF_CONTIG );
1805
+ if( !buffer) return NULL;
1806
+
1807
+ const int skip = samples * self->elemsize;
1808
+ const long long bufsize = (long long) length * samples;
1809
+
1810
+ if( buffer.len() < bufsize )
1811
+ return ValueError( "internal: data trace buffer too small, "
1812
+ "expected %zi, was %zd",
1813
+ bufsize, buffer.len() );
1814
+
1815
+ int err = 0;
1816
+ int i = 0;
1817
+ char* buf = buffer.buf();
1818
+ for( ; err == 0 && i < length; ++i, buf += skip ) {
1819
+ err = segy_readsubtr( ds, start + (i * step),
1820
+ sample_start,
1821
+ sample_stop,
1822
+ sample_step,
1823
+ buf,
1824
+ NULL );
1825
+ }
1826
+
1827
+ if( err == SEGY_FREAD_ERROR )
1828
+ return IOError( "I/O operation failed on data trace %d", i );
1829
+
1830
+ if( err ) return Error( err );
1831
+
1832
+ segy_to_native( self->format, bufsize, buffer.buf() );
1833
+
1834
+ Py_INCREF( bufferobj );
1835
+ return bufferobj;
1836
+ }
1837
+
1838
+ PyObject* puttr( segyfd* self, PyObject* args ) {
1839
+ segy_datasource* ds = self->ds;
1840
+ if( !ds ) return NULL;
1841
+
1842
+ int traceno;
1843
+ char* buffer;
1844
+ Py_ssize_t buflen;
1845
+
1846
+ if( !PyArg_ParseTuple( args, "is#", &traceno, &buffer, &buflen ) )
1847
+ return NULL;
1848
+
1849
+ if( self->trace_bsize > buflen )
1850
+ return ValueError("trace too short: expected %d bytes, got %d",
1851
+ self->trace_bsize,
1852
+ buflen );
1853
+
1854
+ segy_from_native( self->format, self->samplecount, buffer );
1855
+
1856
+ int err = segy_writetrace( ds, traceno,
1857
+ buffer );
1858
+
1859
+ segy_to_native( self->format, self->samplecount, buffer );
1860
+
1861
+ switch( err ) {
1862
+ case SEGY_OK:
1863
+ return Py_BuildValue("");
1864
+
1865
+ case SEGY_FREAD_ERROR:
1866
+ return IOError( "I/O operation failed on data trace %d", traceno );
1867
+
1868
+ default:
1869
+ return Error( err );
1870
+ }
1871
+ }
1872
+
1873
+ PyObject* getline( segyfd* self, PyObject* args) {
1874
+ segy_datasource* ds = self->ds;
1875
+ if( !ds ) return NULL;
1876
+
1877
+ int line_trace0;
1878
+ int line_length;
1879
+ int stride;
1880
+ int offsets;
1881
+ PyObject* bufferobj;
1882
+
1883
+ if( !PyArg_ParseTuple( args, "iiiiO", &line_trace0,
1884
+ &line_length,
1885
+ &stride,
1886
+ &offsets,
1887
+ &bufferobj ) )
1888
+ return NULL;
1889
+
1890
+ buffer_guard buffer( bufferobj, PyBUF_CONTIG );
1891
+ if( !buffer ) return NULL;
1892
+
1893
+ int err = segy_read_line( ds, line_trace0,
1894
+ line_length,
1895
+ stride,
1896
+ offsets,
1897
+ buffer.buf() );
1898
+ if( err ) return Error( err );
1899
+
1900
+ segy_to_native( self->format,
1901
+ self->samplecount * line_length,
1902
+ buffer.buf() );
1903
+
1904
+ Py_INCREF( bufferobj );
1905
+ return bufferobj;
1906
+ }
1907
+
1908
+ PyObject* putline( segyfd* self, PyObject* args) {
1909
+ segy_datasource* ds = self->ds;
1910
+ if( !ds ) return NULL;
1911
+
1912
+ int line_trace0;
1913
+ int line_length;
1914
+ int stride;
1915
+ int offsets;
1916
+ int index;
1917
+ int offset;
1918
+ PyObject* val;
1919
+
1920
+ if( !PyArg_ParseTuple( args, "iiiiiiO", &line_trace0,
1921
+ &line_length,
1922
+ &stride,
1923
+ &offsets,
1924
+ &index,
1925
+ &offset,
1926
+ &val ) )
1927
+ return NULL;
1928
+
1929
+ buffer_guard buffer( val, PyBUF_CONTIG );
1930
+
1931
+ if( self->trace_bsize * line_length > buffer.len() )
1932
+ return ValueError("line too short: expected %d elements, got %zd",
1933
+ self->samplecount * line_length,
1934
+ buffer.len() / self->elemsize );
1935
+
1936
+ const int elems = line_length * self->samplecount;
1937
+ segy_from_native( self->format, elems, buffer.buf() );
1938
+
1939
+ int err = segy_write_line( ds, line_trace0,
1940
+ line_length,
1941
+ stride,
1942
+ offsets,
1943
+ buffer.buf() );
1944
+
1945
+ segy_to_native( self->format, elems, buffer.buf() );
1946
+
1947
+ switch( err ) {
1948
+ case SEGY_OK:
1949
+ return Py_BuildValue("");
1950
+
1951
+ case SEGY_FWRITE_ERROR:
1952
+ return IOError( "I/O operation failed on line %d, offset %d",
1953
+ index, offset );
1954
+
1955
+ default:
1956
+ return Error( err );
1957
+ }
1958
+ }
1959
+
1960
+ PyObject* getdepth( segyfd* self, PyObject* args ) {
1961
+ segy_datasource* ds = self->ds;
1962
+ if( !ds ) return NULL;
1963
+
1964
+ int depth;
1965
+ int count;
1966
+ int offsets;
1967
+ PyObject* bufferobj;
1968
+
1969
+ if( !PyArg_ParseTuple( args, "iiiO", &depth,
1970
+ &count,
1971
+ &offsets,
1972
+ &bufferobj ) )
1973
+ return NULL;
1974
+
1975
+ buffer_guard buffer( bufferobj, PyBUF_CONTIG );
1976
+ if( !buffer ) return NULL;
1977
+
1978
+ int traceno = 0;
1979
+ int err = 0;
1980
+ char* buf = buffer.buf();
1981
+ const int skip = self->elemsize;
1982
+
1983
+ for( ; err == 0 && traceno < count; ++traceno, buf += skip ) {
1984
+ err = segy_readsubtr( ds,
1985
+ traceno * offsets,
1986
+ depth,
1987
+ depth + 1,
1988
+ 1,
1989
+ buf,
1990
+ NULL );
1991
+ }
1992
+
1993
+ if( err == SEGY_FREAD_ERROR )
1994
+ return IOError( "I/O operation failed on data trace %d at depth %d",
1995
+ traceno, depth );
1996
+
1997
+ if( err ) return Error( err );
1998
+
1999
+ segy_to_native( self->format, count, buffer.buf() );
2000
+
2001
+ Py_INCREF( bufferobj );
2002
+ return bufferobj;
2003
+ }
2004
+
2005
+ PyObject* putdepth( segyfd* self, PyObject* args ) {
2006
+ segy_datasource* ds = self->ds;
2007
+ if( !ds ) return NULL;
2008
+
2009
+ int depth;
2010
+ int count;
2011
+ int offsets;
2012
+ PyObject* val;
2013
+
2014
+ if( !PyArg_ParseTuple( args, "iiiO", &depth,
2015
+ &count,
2016
+ &offsets,
2017
+ &val ) )
2018
+ return NULL;
2019
+
2020
+ buffer_guard buffer( val, PyBUF_CONTIG );
2021
+ if( !buffer ) return NULL;
2022
+
2023
+ if( count * self->elemsize > buffer.len() )
2024
+ return ValueError("slice too short: expected %d elements, got %zd",
2025
+ count, buffer.len() / self->elemsize );
2026
+
2027
+ int traceno = 0;
2028
+ int err = 0;
2029
+ const char* buf = buffer.buf();
2030
+ const int skip = self->elemsize;
2031
+
2032
+ segy_from_native( self->format, count, buffer.buf() );
2033
+
2034
+ for( ; err == 0 && traceno < count; ++traceno, buf += skip ) {
2035
+ err = segy_writesubtr( ds,
2036
+ traceno * offsets,
2037
+ depth,
2038
+ depth + 1,
2039
+ 1,
2040
+ buf,
2041
+ NULL );
2042
+ }
2043
+
2044
+ segy_to_native( self->format, count, buffer.buf() );
2045
+
2046
+ if( err == SEGY_FREAD_ERROR )
2047
+ return IOError( "I/O operation failed on data trace %d at depth %d",
2048
+ traceno, depth );
2049
+
2050
+ if( err ) return Error( err );
2051
+
2052
+ return Py_BuildValue( "" );
2053
+ }
2054
+
2055
+ PyObject* getdt( segyfd* self, PyObject* args ) {
2056
+ segy_datasource* ds = self->ds;
2057
+ if( !ds ) return NULL;
2058
+
2059
+ float fallback;
2060
+ if( !PyArg_ParseTuple(args, "f", &fallback ) ) return NULL;
2061
+
2062
+ float dt;
2063
+ int err = segy_sample_interval( ds, fallback, &dt );
2064
+
2065
+ if( err == SEGY_OK )
2066
+ return PyFloat_FromDouble( dt );
2067
+
2068
+ if( err != SEGY_FREAD_ERROR && err != SEGY_FSEEK_ERROR )
2069
+ return Error( err );
2070
+
2071
+ /*
2072
+ * Figure out if the problem is reading the trace header
2073
+ * or the binary header
2074
+ */
2075
+ char buffer[ SEGY_BINARY_HEADER_SIZE ];
2076
+ err = segy_binheader( ds, buffer );
2077
+
2078
+ if( err )
2079
+ return IOError( "I/O operation failed on binary header, "
2080
+ "likely corrupted file" );
2081
+
2082
+ err = segy_read_standard_traceheader( ds, 0, buffer );
2083
+
2084
+ if( err == SEGY_FREAD_ERROR )
2085
+ return IOError( "I/O operation failed on trace header 0, "
2086
+ "likely corrupted file" );
2087
+
2088
+ return Error( err );
2089
+ }
2090
+
2091
+ PyObject* getdelay( segyfd* self ) {
2092
+ segy_datasource* ds = self->ds;
2093
+ if( !ds ) return NULL;
2094
+
2095
+ float delay;
2096
+ int err = segy_delay_recoding_time( ds, &delay );
2097
+
2098
+ if( err == SEGY_OK )
2099
+ return PyFloat_FromDouble( delay );
2100
+
2101
+ return Error( err );
2102
+ }
2103
+
2104
+ PyObject* rotation( segyfd* self, PyObject* args ) {
2105
+ segy_datasource* ds = self->ds;
2106
+ if( !ds ) return NULL;
2107
+
2108
+ int line_length;
2109
+ int stride;
2110
+ int offsets;
2111
+ buffer_guard linenos;
2112
+
2113
+ if( !PyArg_ParseTuple( args, "iiis*", &line_length,
2114
+ &stride,
2115
+ &offsets,
2116
+ &linenos ) )
2117
+ return NULL;
2118
+
2119
+ float rotation;
2120
+ int linenos_sz = static_cast<int>( linenos.len() / sizeof( int ) );
2121
+ int err = segy_rotation_cw( ds, line_length,
2122
+ stride,
2123
+ offsets,
2124
+ linenos.buf< const int >(),
2125
+ linenos_sz,
2126
+ &rotation );
2127
+
2128
+ if( err ) return Error( err );
2129
+
2130
+ return PyFloat_FromDouble( rotation );
2131
+ }
2132
+
2133
+ PyObject* stanza_names( segyfd* self ) {
2134
+ PyObject* names = PyList_New( self->stanzas.size() );
2135
+ if( !names ) {
2136
+ PyErr_Print();
2137
+ return NULL;
2138
+ }
2139
+ for( size_t i = 0; i < self->stanzas.size(); ++i ) {
2140
+ const std::string& name = self->stanzas[i].name;
2141
+ PyObject* py_name = PyUnicode_FromStringAndSize( name.c_str(), name.size() );
2142
+ if( !py_name ) {
2143
+ PyErr_Print();
2144
+ Py_DECREF( names );
2145
+ return NULL;
2146
+ }
2147
+ PyList_SET_ITEM( names, i, py_name );
2148
+ }
2149
+ return names;
2150
+ }
2151
+
2152
+ #ifdef IS_CLANG
2153
+ #pragma clang diagnostic push
2154
+ #pragma clang diagnostic ignored "-Wcast-function-type"
2155
+ #pragma clang diagnostic ignored "-Wmissing-field-initializers"
2156
+ #endif
2157
+ #ifdef IS_GCC
2158
+ #pragma GCC diagnostic push
2159
+ #pragma GCC diagnostic ignored "-Wcast-function-type"
2160
+ #pragma GCC diagnostic ignored "-Wmissing-field-initializers"
2161
+ #endif
2162
+ PyMethodDef methods [] = {
2163
+ { "segyopen", (PyCFunction) fd::segyopen,
2164
+ METH_VARARGS | METH_KEYWORDS, "Open file." },
2165
+ { "segymake", (PyCFunction) fd::segycreate,
2166
+ METH_VARARGS | METH_KEYWORDS, "Create file." },
2167
+
2168
+ { "suopen", (PyCFunction) fd::suopen,
2169
+ METH_VARARGS | METH_KEYWORDS, "Open SU file." },
2170
+
2171
+ { "close", (PyCFunction) fd::close, METH_VARARGS, "Close file." },
2172
+ { "flush", (PyCFunction) fd::flush, METH_VARARGS, "Flush file." },
2173
+ { "mmap", (PyCFunction) fd::mmap, METH_NOARGS, "mmap file." },
2174
+
2175
+ { "gettext", (PyCFunction) fd::gettext, METH_VARARGS, "Get text header." },
2176
+ { "puttext", (PyCFunction) fd::puttext, METH_VARARGS, "Put text header." },
2177
+ { "getstanza", (PyCFunction) fd::getstanza, METH_VARARGS, "Get stanza data." },
2178
+
2179
+ { "getbin", (PyCFunction) fd::getbin, METH_VARARGS, "Get binary header." },
2180
+ { "putbin", (PyCFunction) fd::putbin, METH_VARARGS, "Put binary header." },
2181
+
2182
+ { "getth", (PyCFunction) fd::getth, METH_VARARGS, "Get trace header." },
2183
+ { "putth", (PyCFunction) fd::putth, METH_VARARGS, "Put trace header." },
2184
+
2185
+ { "getfield", (PyCFunction) fd::getfield, METH_VARARGS, "Get a header field." },
2186
+ { "putfield", (PyCFunction) fd::putfield, METH_VARARGS, "Put a header field." },
2187
+
2188
+ { "field_forall", (PyCFunction) fd::field_forall, METH_VARARGS, "Field for-all." },
2189
+ { "field_foreach", (PyCFunction) fd::field_foreach, METH_VARARGS, "Field for-each." },
2190
+
2191
+ { "gettr", (PyCFunction) fd::gettr, METH_VARARGS, "Get trace." },
2192
+ { "puttr", (PyCFunction) fd::puttr, METH_VARARGS, "Put trace." },
2193
+
2194
+ { "getline", (PyCFunction) fd::getline, METH_VARARGS, "Get line." },
2195
+ { "putline", (PyCFunction) fd::putline, METH_VARARGS, "Put line." },
2196
+ { "getdepth", (PyCFunction) fd::getdepth, METH_VARARGS, "Get depth." },
2197
+ { "putdepth", (PyCFunction) fd::putdepth, METH_VARARGS, "Put depth." },
2198
+
2199
+ { "getdt", (PyCFunction) fd::getdt, METH_VARARGS, "Get sample interval (dt)." },
2200
+ { "getdelay", (PyCFunction) fd::getdelay, METH_NOARGS, "Get recording delay." },
2201
+ { "rotation", (PyCFunction) fd::rotation, METH_VARARGS, "Get clockwise rotation." },
2202
+
2203
+ { "metrics", (PyCFunction) fd::metrics, METH_NOARGS, "Metrics." },
2204
+ { "cube_metrics", (PyCFunction) fd::cube_metrics, METH_NOARGS, "Cube metrics." },
2205
+ { "indices", (PyCFunction) fd::indices, METH_VARARGS, "Indices." },
2206
+
2207
+ { "stanza_names", (PyCFunction) fd::stanza_names, METH_NOARGS, "Stanza names in order." },
2208
+
2209
+ { "traceheader_layouts", (PyCFunction) traceheader_layouts, METH_VARARGS, "Layout of all traceheaders." },
2210
+
2211
+ { NULL }
2212
+ };
2213
+ #ifdef IS_GCC
2214
+ #pragma GCC diagnostic pop
2215
+ #endif
2216
+ #ifdef IS_CLANG
2217
+ #pragma clang diagnostic pop
2218
+ #endif
2219
+
2220
+ }
2221
+
2222
+ #ifdef IS_CLANG
2223
+ #pragma clang diagnostic push
2224
+ #pragma clang diagnostic ignored "-Wmissing-field-initializers"
2225
+ #endif
2226
+ #ifdef IS_GCC
2227
+ #pragma GCC diagnostic push
2228
+ #pragma GCC diagnostic ignored "-Wmissing-field-initializers"
2229
+ #endif
2230
+ PyTypeObject Segyfd = {
2231
+ PyVarObject_HEAD_INIT( NULL, 0 )
2232
+ "_segyio.segyfd", /* name */
2233
+ sizeof( segyfd ), /* basic size */
2234
+ 0, /* tp_itemsize */
2235
+ (destructor)fd::dealloc, /* tp_dealloc */
2236
+ 0, /* tp_print */
2237
+ 0, /* tp_getattr */
2238
+ 0, /* tp_setattr */
2239
+ 0, /* tp_compare */
2240
+ 0, /* tp_repr */
2241
+ 0, /* tp_as_number */
2242
+ 0, /* tp_as_sequence */
2243
+ 0, /* tp_as_mapping */
2244
+ 0, /* tp_hash */
2245
+ 0, /* tp_call */
2246
+ 0, /* tp_str */
2247
+ 0, /* tp_getattro */
2248
+ 0, /* tp_setattro */
2249
+ 0, /* tp_as_buffer */
2250
+ Py_TPFLAGS_DEFAULT, /* tp_flags */
2251
+ "segyio file descriptor", /* tp_doc */
2252
+ 0, /* tp_traverse */
2253
+ 0, /* tp_clear */
2254
+ 0, /* tp_richcompare */
2255
+ 0, /* tp_weaklistoffset */
2256
+ 0, /* tp_iter */
2257
+ 0, /* tp_iternext */
2258
+ fd::methods, /* tp_methods */
2259
+ 0, /* tp_members */
2260
+ 0, /* tp_getset */
2261
+ 0, /* tp_base */
2262
+ 0, /* tp_dict */
2263
+ 0, /* tp_descr_get */
2264
+ 0, /* tp_descr_set */
2265
+ 0, /* tp_dictoffset */
2266
+ (initproc)fd::init, /* tp_init */
2267
+ };
2268
+ #ifdef IS_GCC
2269
+ #pragma GCC diagnostic pop
2270
+ #endif
2271
+ #ifdef IS_CLANG
2272
+ #pragma clang diagnostic pop
2273
+ #endif
2274
+
2275
+ PyObject* binsize( PyObject* ) {
2276
+ return PyLong_FromLong( segy_binheader_size() );
2277
+ }
2278
+
2279
+ PyObject* thsize( PyObject* ) {
2280
+ return PyLong_FromLong( SEGY_TRACE_HEADER_SIZE );
2281
+ }
2282
+
2283
+ PyObject* textsize(PyObject* ) {
2284
+ return PyLong_FromLong( SEGY_TEXT_HEADER_SIZE );
2285
+ }
2286
+
2287
+ PyObject* trbsize( PyObject*, PyObject* args ) {
2288
+ int sample_count;
2289
+ if( !PyArg_ParseTuple( args, "i", &sample_count ) ) return NULL;
2290
+ return PyLong_FromLong( segy_trace_bsize( sample_count ) );
2291
+ }
2292
+
2293
+ PyObject* line_metrics( PyObject*, PyObject *args) {
2294
+ SEGY_SORTING sorting;
2295
+ int trace_count;
2296
+ int inline_count;
2297
+ int crossline_count;
2298
+ int offset_count;
2299
+
2300
+ if( !PyArg_ParseTuple( args, "iiiii", &sorting,
2301
+ &trace_count,
2302
+ &inline_count,
2303
+ &crossline_count,
2304
+ &offset_count ) )
2305
+ return NULL;
2306
+
2307
+ int iline_length = segy_inline_length( crossline_count );
2308
+ int xline_length = segy_crossline_length( inline_count );
2309
+
2310
+ int iline_stride = 0;
2311
+ int err = segy_inline_stride( sorting, inline_count, &iline_stride );
2312
+
2313
+ // only check first call since the only error that can occur is
2314
+ // SEGY_INVALID_SORTING
2315
+ if( err == SEGY_INVALID_SORTING )
2316
+ return ValueError( "internal: invalid sorting." );
2317
+
2318
+ if( err )
2319
+ return Error( err );
2320
+
2321
+ int xline_stride;
2322
+ segy_crossline_stride( sorting, crossline_count, &xline_stride );
2323
+
2324
+ return Py_BuildValue( "{s:i, s:i, s:i, s:i}",
2325
+ "xline_length", xline_length,
2326
+ "xline_stride", xline_stride,
2327
+ "iline_length", iline_length,
2328
+ "iline_stride", iline_stride );
2329
+ }
2330
+
2331
+ PyObject* fread_trace0( PyObject* , PyObject* args ) {
2332
+ int lineno;
2333
+ int other_line_length;
2334
+ int stride;
2335
+ int offsets;
2336
+ int* indices;
2337
+ Py_ssize_t indiceslen;
2338
+ char* linetype;
2339
+
2340
+ if( !PyArg_ParseTuple( args, "iiiis#s", &lineno,
2341
+ &other_line_length,
2342
+ &stride,
2343
+ &offsets,
2344
+ &indices,
2345
+ &indiceslen,
2346
+ &linetype ) )
2347
+ return NULL;
2348
+
2349
+ int trace_no = 0;
2350
+ int linenos_sz = static_cast<int>( indiceslen / sizeof( int ) );
2351
+ int err = segy_line_trace0( lineno,
2352
+ other_line_length,
2353
+ stride,
2354
+ offsets,
2355
+ indices,
2356
+ linenos_sz,
2357
+ &trace_no );
2358
+
2359
+ if( err == SEGY_MISSING_LINE_INDEX )
2360
+ return KeyError( "no such %s %d", linetype, lineno );
2361
+
2362
+ if( err )
2363
+ return Error( err );
2364
+
2365
+ return PyLong_FromLong( trace_no );
2366
+ }
2367
+
2368
+ PyObject* format( PyObject* , PyObject* args ) {
2369
+ PyObject *out;
2370
+ int format;
2371
+
2372
+ if( !PyArg_ParseTuple( args, "Oi", &out, &format ) ) return NULL;
2373
+
2374
+ buffer_guard buffer( out, PyBUF_CONTIG );
2375
+
2376
+ const int len = static_cast<int>( buffer.len() / sizeof( float ) );
2377
+ segy_to_native( format, len, buffer.buf() );
2378
+
2379
+ Py_INCREF( out );
2380
+ return out;
2381
+ }
2382
+
2383
+ #ifdef IS_CLANG
2384
+ #pragma clang diagnostic push
2385
+ #pragma clang diagnostic ignored "-Wcast-function-type"
2386
+ #pragma clang diagnostic ignored "-Wmissing-field-initializers"
2387
+ #endif
2388
+ #ifdef IS_GCC
2389
+ #pragma GCC diagnostic push
2390
+ #pragma GCC diagnostic ignored "-Wcast-function-type"
2391
+ #pragma GCC diagnostic ignored "-Wmissing-field-initializers"
2392
+ #endif
2393
+ PyMethodDef SegyMethods[] = {
2394
+ { "binsize", (PyCFunction) binsize, METH_NOARGS, "Size of the binary header." },
2395
+ { "thsize", (PyCFunction) thsize, METH_NOARGS, "Size of the trace header." },
2396
+ { "textsize", (PyCFunction) textsize, METH_NOARGS, "Size of the text header." },
2397
+
2398
+ { "trace_bsize", (PyCFunction) trbsize, METH_VARARGS, "Size of a trace (in bytes)." },
2399
+
2400
+ { "line_metrics", (PyCFunction) line_metrics, METH_VARARGS, "Find the length and stride of lines." },
2401
+ { "fread_trace0", (PyCFunction) fread_trace0, METH_VARARGS, "Find trace0 of a line." },
2402
+ { "native", (PyCFunction) format, METH_VARARGS, "Convert to native float." },
2403
+
2404
+ { NULL }
2405
+ };
2406
+ #ifdef IS_GCC
2407
+ #pragma GCC diagnostic pop
2408
+ #endif
2409
+ #ifdef IS_CLANG
2410
+ #pragma clang diagnostic pop
2411
+ #endif
2412
+
2413
+ namespace {
2414
+
2415
+ struct NameMapEntry {
2416
+ std::string spec_entry_name;
2417
+ int segyio_entry_name;
2418
+ };
2419
+
2420
+ struct TypeMapEntry {
2421
+ std::string spec_entry_type;
2422
+ SEGY_ENTRY_TYPE segyio_entry_type;
2423
+ };
2424
+
2425
+ /** D8 names to SEGY names. Contains all the known names, not only the ones used
2426
+ * internally in the library. scale6 type entries are not fully supported as we
2427
+ * have separate names for mantissa and exponent parts of the type. */
2428
+ static const NameMapEntry standard_name_map[] = {
2429
+ { "linetrc", SEGY_TR_SEQ_LINE },
2430
+ { "reeltrc", SEGY_TR_SEQ_FILE },
2431
+ { "ffid", SEGY_TR_FIELD_RECORD },
2432
+ { "chan", SEGY_TR_NUMBER_ORIG_FIELD },
2433
+ { "espnum", SEGY_TR_ENERGY_SOURCE_POINT },
2434
+ { "cdp", SEGY_TR_ENSEMBLE },
2435
+ { "cdptrc", SEGY_TR_NUM_IN_ENSEMBLE },
2436
+ { "trctype", SEGY_TR_TRACE_ID },
2437
+ { "vstack", SEGY_TR_SUMMED_TRACES },
2438
+ { "fold", SEGY_TR_STACKED_TRACES },
2439
+ { "rectype", SEGY_TR_DATA_USE },
2440
+ { "offset", SEGY_TR_OFFSET },
2441
+ { "relev", SEGY_TR_RECV_GROUP_ELEV },
2442
+ { "selev", SEGY_TR_SOURCE_SURF_ELEV },
2443
+ { "sdepth", SEGY_TR_SOURCE_DEPTH },
2444
+ { "rdatum", SEGY_TR_RECV_DATUM_ELEV },
2445
+ { "sdatum", SEGY_TR_SOURCE_DATUM_ELEV },
2446
+ { "wdepthso", SEGY_TR_SOURCE_WATER_DEPTH },
2447
+ { "wdepthrc", SEGY_TR_GROUP_WATER_DEPTH },
2448
+ { "ed_scal", SEGY_TR_ELEV_SCALAR },
2449
+ { "co_scal", SEGY_TR_SOURCE_GROUP_SCALAR },
2450
+ { "sht_x", SEGY_TR_SOURCE_X },
2451
+ { "sht_y", SEGY_TR_SOURCE_Y },
2452
+ { "rec_x", SEGY_TR_GROUP_X },
2453
+ { "rec_y", SEGY_TR_GROUP_Y },
2454
+ { "coorunit", SEGY_TR_COORD_UNITS },
2455
+ { "wvel", SEGY_TR_WEATHERING_VELO },
2456
+ { "subwvel", SEGY_TR_SUBWEATHERING_VELO },
2457
+ { "shuphole", SEGY_TR_SOURCE_UPHOLE_TIME },
2458
+ { "rcuphole", SEGY_TR_GROUP_UPHOLE_TIME },
2459
+ { "shstat", SEGY_TR_SOURCE_STATIC_CORR },
2460
+ { "rcstat", SEGY_TR_GROUP_STATIC_CORR },
2461
+ { "stapply", SEGY_TR_TOT_STATIC_APPLIED },
2462
+ { "lagtimea", SEGY_TR_LAG_A },
2463
+ { "lagtimeb", SEGY_TR_LAG_B },
2464
+ { "delay", SEGY_TR_DELAY_REC_TIME },
2465
+ { "mutestrt", SEGY_TR_MUTE_TIME_START },
2466
+ { "muteend", SEGY_TR_MUTE_TIME_END },
2467
+ { "nsamps", SEGY_TR_SAMPLE_COUNT },
2468
+ { "dt", SEGY_TR_SAMPLE_INTER },
2469
+ { "gaintype", SEGY_TR_GAIN_TYPE },
2470
+ { "ingconst", SEGY_TR_INSTR_GAIN_CONST },
2471
+ { "initgain", SEGY_TR_INSTR_INIT_GAIN },
2472
+ { "corrflag", SEGY_TR_CORRELATED },
2473
+ { "sweepsrt", SEGY_TR_SWEEP_FREQ_START },
2474
+ { "sweepend", SEGY_TR_SWEEP_FREQ_END },
2475
+ { "sweeplng", SEGY_TR_SWEEP_LENGTH },
2476
+ { "sweeptyp", SEGY_TR_SWEEP_TYPE },
2477
+ { "sweepstp", SEGY_TR_SWEEP_TAPERLEN_START },
2478
+ { "sweepetp", SEGY_TR_SWEEP_TAPERLEN_END },
2479
+ { "tapertyp", SEGY_TR_TAPER_TYPE },
2480
+ { "aliasfil", SEGY_TR_ALIAS_FILT_FREQ },
2481
+ { "aliaslop", SEGY_TR_ALIAS_FILT_SLOPE },
2482
+ { "notchfil", SEGY_TR_NOTCH_FILT_FREQ },
2483
+ { "notchslp", SEGY_TR_NOTCH_FILT_SLOPE },
2484
+ { "lowcut", SEGY_TR_LOW_CUT_FREQ },
2485
+ { "highcut", SEGY_TR_HIGH_CUT_FREQ },
2486
+ { "lowcslop", SEGY_TR_LOW_CUT_SLOPE },
2487
+ { "hicslop", SEGY_TR_HIGH_CUT_SLOPE },
2488
+ { "year", SEGY_TR_YEAR_DATA_REC },
2489
+ { "day", SEGY_TR_DAY_OF_YEAR },
2490
+ { "hour", SEGY_TR_HOUR_OF_DAY },
2491
+ { "minute", SEGY_TR_MIN_OF_HOUR },
2492
+ { "second", SEGY_TR_SEC_OF_MIN },
2493
+ { "timebase", SEGY_TR_TIME_BASE_CODE },
2494
+ { "trweight", SEGY_TR_WEIGHTING_FAC },
2495
+ { "rstaswp1", SEGY_TR_GEOPHONE_GROUP_ROLL1 },
2496
+ { "rstatrc1", SEGY_TR_GEOPHONE_GROUP_FIRST },
2497
+ { "rstatrcn", SEGY_TR_GEOPHONE_GROUP_LAST },
2498
+ { "gapsize", SEGY_TR_GAP_SIZE },
2499
+ { "overtrvl", SEGY_TR_OVER_TRAVEL },
2500
+ { "cdp_x", SEGY_TR_CDP_X },
2501
+ { "cdp_y", SEGY_TR_CDP_Y },
2502
+ { "iline", SEGY_TR_INLINE },
2503
+ { "xline", SEGY_TR_CROSSLINE },
2504
+ { "sp", SEGY_TR_SHOT_POINT },
2505
+ { "sp_scal", SEGY_TR_SHOT_POINT_SCALAR },
2506
+ { "samp_unit", SEGY_TR_MEASURE_UNIT },
2507
+ { "trans_const", SEGY_TR_TRANSDUCTION_MANT }, // and SEGY_TR_TRANSDUCTION_EXP
2508
+ { "trans_unit", SEGY_TR_TRANSDUCTION_UNIT },
2509
+ { "dev_id", SEGY_TR_DEVICE_ID },
2510
+ { "tm_scal", SEGY_TR_SCALAR_TRACE_HEADER },
2511
+ { "src_type", SEGY_TR_SOURCE_TYPE },
2512
+ { "src_dir1", SEGY_TR_SOURCE_ENERGY_DIR_VERT },
2513
+ { "src_dir2", SEGY_TR_SOURCE_ENERGY_DIR_XLINE },
2514
+ { "src_dir3", SEGY_TR_SOURCE_ENERGY_DIR_ILINE },
2515
+ { "smeasure", SEGY_TR_SOURCE_MEASURE_MANT }, // and SEGY_TR_SOURCE_MEASURE_EXP
2516
+ { "sm_unit", SEGY_TR_SOURCE_MEASURE_UNIT },
2517
+ };
2518
+
2519
+ /** D8 names to SEGY names. Contains all the known names, not only the ones used
2520
+ * internally in the library. */
2521
+ static const NameMapEntry ext1_name_map[] = {
2522
+ { "linetrc", SEGY_EXT1_SEQ_LINE },
2523
+ { "reeltrc", SEGY_EXT1_SEQ_FILE },
2524
+ { "ffid", SEGY_EXT1_FIELD_RECORD },
2525
+ { "cdp", SEGY_EXT1_ENSEMBLE },
2526
+ { "relev", SEGY_EXT1_RECV_GROUP_ELEV },
2527
+ { "rdepth", SEGY_EXT1_RECV_GROUP_DEPTH },
2528
+ { "selev", SEGY_EXT1_SOURCE_SURF_ELEV },
2529
+ { "sdepth", SEGY_EXT1_SOURCE_DEPTH },
2530
+ { "rdatum", SEGY_EXT1_RECV_DATUM_ELEV },
2531
+ { "sdatum", SEGY_EXT1_SOURCE_DATUM_ELEV },
2532
+ { "wdepthso", SEGY_EXT1_SOURCE_WATER_DEPTH },
2533
+ { "wdepthrc", SEGY_EXT1_GROUP_WATER_DEPTH },
2534
+ { "sht_x", SEGY_EXT1_SOURCE_X },
2535
+ { "sht_y", SEGY_EXT1_SOURCE_Y },
2536
+ { "rec_x", SEGY_EXT1_GROUP_X },
2537
+ { "rec_y", SEGY_EXT1_GROUP_Y },
2538
+ { "offset", SEGY_EXT1_OFFSET },
2539
+ { "nsamps", SEGY_EXT1_SAMPLE_COUNT },
2540
+ { "nanosecs", SEGY_EXT1_NANOSEC_OF_SEC },
2541
+ { "dt", SEGY_EXT1_SAMPLE_INTER },
2542
+ { "cable_num", SEGY_EXT1_RECORDING_DEVICE_NR },
2543
+ { "last_trc", SEGY_EXT1_LAST_TRACE_FLAG },
2544
+ { "cdp_x", SEGY_EXT1_CDP_X },
2545
+ { "cdp_y", SEGY_EXT1_CDP_Y },
2546
+
2547
+ // segyio private
2548
+ { "header_name", SEGY_EXT1_TRACE_HEADER_NAME },
2549
+ };
2550
+
2551
+ /** D8 entry type names to SEGY entry type names. scale6 type is not fully
2552
+ * supported as we have separate types for mantissa and exponent. */
2553
+ static const TypeMapEntry entry_type_map[] = {
2554
+ { "int2", SEGY_ENTRY_TYPE_INT2 },
2555
+ { "int4", SEGY_ENTRY_TYPE_INT4 },
2556
+ { "int8", SEGY_ENTRY_TYPE_INT8 },
2557
+ { "uint2", SEGY_ENTRY_TYPE_UINT2 },
2558
+ { "uint4", SEGY_ENTRY_TYPE_UINT4 },
2559
+ { "uint8", SEGY_ENTRY_TYPE_UINT8 },
2560
+ { "ibmfp", SEGY_ENTRY_TYPE_IBMFP },
2561
+ { "ieee32", SEGY_ENTRY_TYPE_IEEE32 },
2562
+ { "ieee64", SEGY_ENTRY_TYPE_IEEE64 },
2563
+ { "linetrc", SEGY_ENTRY_TYPE_LINETRC },
2564
+ { "reeltrc", SEGY_ENTRY_TYPE_REELTRC },
2565
+ { "linetrc8", SEGY_ENTRY_TYPE_LINETRC8 },
2566
+ { "reeltrc8", SEGY_ENTRY_TYPE_REELTRC8 },
2567
+ { "coor4", SEGY_ENTRY_TYPE_COOR4 },
2568
+ { "elev4", SEGY_ENTRY_TYPE_ELEV4 },
2569
+ { "time2", SEGY_ENTRY_TYPE_TIME2 },
2570
+ { "spnum4", SEGY_ENTRY_TYPE_SPNUM4 },
2571
+ { "scale6", SEGY_ENTRY_TYPE_SCALE6_MANT },
2572
+
2573
+ // segyio private
2574
+ { "string8", SEGY_ENTRY_TYPE_STRING8 },
2575
+ };
2576
+
2577
+ static const std::unordered_map<SEGY_ENTRY_TYPE, std::string> segytype_to_spectype_map = [] {
2578
+ std::unordered_map<SEGY_ENTRY_TYPE, std::string> map;
2579
+ for( const auto& e : entry_type_map ) {
2580
+ map[e.segyio_entry_type] = e.spec_entry_type;
2581
+ }
2582
+ return map;
2583
+ }();
2584
+
2585
+ static const std::unordered_map<std::string, SEGY_ENTRY_TYPE> spectype_to_segytype_map = [] {
2586
+ std::unordered_map<std::string, SEGY_ENTRY_TYPE> map;
2587
+ for( const auto& e : entry_type_map ) {
2588
+ map[e.spec_entry_type] = e.segyio_entry_type;
2589
+ }
2590
+ return map;
2591
+ }();
2592
+
2593
+ int overwrite_field_offset(
2594
+ segy_header_mapping& mapping,
2595
+ uint8_t segyio_field_name,
2596
+ const char* spec_entry_name,
2597
+ SEGY_ENTRY_TYPE entry_type,
2598
+ PyObject* py_entry_byte
2599
+ ) {
2600
+ if( py_entry_byte == Py_None ) return SEGY_OK;
2601
+
2602
+ unsigned long long raw_entry_byte = PyLong_AsUnsignedLongLong( py_entry_byte );
2603
+ if( PyErr_Occurred() || raw_entry_byte < 1 || raw_entry_byte > SEGY_TRACE_HEADER_SIZE ) {
2604
+ ValueError( "Custom field offset %llu out of range: ", raw_entry_byte );
2605
+ return SEGY_INVALID_ARGS;
2606
+ }
2607
+ uint8_t entry_byte = static_cast<uint8_t>( raw_entry_byte );
2608
+ mapping.name_to_offset[segyio_field_name] = entry_byte;
2609
+
2610
+ segy_entry_definition& def = mapping.offset_to_entry_definition[entry_byte - 1];
2611
+ def.entry_type = entry_type;
2612
+ def.requires_nonzero_value = false;
2613
+
2614
+ if( def.name ) {
2615
+ delete[] def.name;
2616
+ }
2617
+ def.name = new char[6];
2618
+ std::strcpy( def.name, spec_entry_name );
2619
+
2620
+ return SEGY_OK;
2621
+ }
2622
+
2623
+ int initialize_traceheader_mappings(
2624
+ segyfd* self,
2625
+ std::vector<char>& layout_stanza_data
2626
+ ) {
2627
+ segy_datasource* ds = self->ds;
2628
+
2629
+ if( layout_stanza_data.empty() ) {
2630
+ /* Entry names provided by xml are of unknown length, so we must
2631
+ * allocate them on heap and delete them afterwards. Default C maps have
2632
+ * their names set to NULL. If we provided default names for entries
2633
+ * inside of the C code, then:
2634
+ * - we can't allocate them on heap statically, so there has to be a
2635
+ * separate function doing the job and user would have to deallocate
2636
+ * appropriately. This is complex and doesn't solve much.
2637
+ * - if we allocated them as static strings, then we would have to
2638
+ * somehow keep track where strings were allocated to know if they
2639
+ * should be freed or not.
2640
+ *
2641
+ * So we allocate names only here, in C++, where we know they always
2642
+ * will be freed.
2643
+ **/
2644
+ auto add_names = [](
2645
+ const NameMapEntry* name_map,
2646
+ size_t name_map_size,
2647
+ segy_header_mapping& mapping
2648
+ ) {
2649
+ for (size_t i = 0; i < name_map_size; ++i) {
2650
+ const NameMapEntry& entry = name_map[i];
2651
+ int offset = mapping.name_to_offset[entry.segyio_entry_name];
2652
+ if (offset != 0) {
2653
+ char* name = new char[entry.spec_entry_name.size() + 1];
2654
+ std::strcpy(name, entry.spec_entry_name.c_str());
2655
+ mapping.offset_to_entry_definition[offset - 1].name = name;
2656
+ }
2657
+ }
2658
+ };
2659
+
2660
+ const size_t standard_map_size = sizeof(standard_name_map) / sizeof(standard_name_map[0]);
2661
+ add_names( standard_name_map, standard_map_size, ds->traceheader_mapping_standard );
2662
+ self->traceheader_mappings.insert(
2663
+ self->traceheader_mappings.begin(), ds->traceheader_mapping_standard
2664
+ );
2665
+
2666
+ const size_t ext1_map_size = sizeof(ext1_name_map) / sizeof(ext1_name_map[0]);
2667
+ add_names( ext1_name_map, ext1_map_size, ds->traceheader_mapping_extension1 );
2668
+ self->traceheader_mappings.insert(
2669
+ self->traceheader_mappings.begin() + 1, ds->traceheader_mapping_extension1
2670
+ );
2671
+ return SEGY_OK;
2672
+ }
2673
+
2674
+ segy_header_mapping* mappings = nullptr;
2675
+ size_t mappings_length = 0;
2676
+ int err = segy_parse_layout_xml(
2677
+ layout_stanza_data.data(),
2678
+ layout_stanza_data.size(),
2679
+ &mappings,
2680
+ &mappings_length
2681
+ );
2682
+ if( err != SEGY_OK ) return err;
2683
+
2684
+ self->traceheader_mappings =
2685
+ std::vector<segy_header_mapping>( mappings, mappings + mappings_length );
2686
+ delete[] mappings;
2687
+
2688
+ int standard_index = -1;
2689
+ int extension1_index = -1;
2690
+
2691
+ for( size_t i = 0; i < self->traceheader_mappings.size(); ++i ) {
2692
+ const auto& mapping = self->traceheader_mappings[i];
2693
+ if( strncmp( mapping.name, "SEG00000", 8 ) == 0 ) {
2694
+ standard_index = i;
2695
+ }
2696
+ if( strncmp( mapping.name, "SEG00001", 8 ) == 0 ) {
2697
+ extension1_index = i;
2698
+ }
2699
+ }
2700
+
2701
+ // standard header definition is obligatory, so it anyway should have failed earlier
2702
+ if( standard_index != 0 ) return SEGY_NOTFOUND;
2703
+
2704
+ if( extension1_index != -1 ) {
2705
+ if( extension1_index != 1 ) {
2706
+ auto extension1_mapping = self->traceheader_mappings[extension1_index];
2707
+ self->traceheader_mappings.erase(
2708
+ self->traceheader_mappings.begin() + extension1_index
2709
+ );
2710
+ self->traceheader_mappings.insert(
2711
+ self->traceheader_mappings.begin() + 1, extension1_mapping
2712
+ );
2713
+ }
2714
+ }
2715
+
2716
+ return SEGY_OK;
2717
+ }
2718
+
2719
+ int set_traceheader_mappings(
2720
+ segyfd* self,
2721
+ std::vector<char>& layout_stanza_data,
2722
+ PyObject* py_iline,
2723
+ PyObject* py_xline
2724
+ ) {
2725
+ int err = initialize_traceheader_mappings( self, layout_stanza_data );
2726
+ if( err != SEGY_OK ) {
2727
+ if( PyErr_Occurred() ) {
2728
+ PyErr_Clear();
2729
+ }
2730
+ ValueError( "error parsing trace header mappings" );
2731
+ return err;
2732
+ }
2733
+
2734
+ segy_header_mapping& mapping = self->traceheader_mappings[0];
2735
+ err = overwrite_field_offset(
2736
+ mapping, SEGY_TR_INLINE, "iline", SEGY_ENTRY_TYPE_INT4, py_iline
2737
+ );
2738
+ if( err != SEGY_OK ) return err;
2739
+
2740
+ err = overwrite_field_offset(
2741
+ mapping, SEGY_TR_CROSSLINE, "xline", SEGY_ENTRY_TYPE_INT4, py_xline
2742
+ );
2743
+ if( err != SEGY_OK ) return err;
2744
+
2745
+ segy_datasource* ds = self->ds;
2746
+ memcpy(
2747
+ &ds->traceheader_mapping_standard,
2748
+ &self->traceheader_mappings[0],
2749
+ sizeof( ds->traceheader_mapping_standard )
2750
+ );
2751
+ if( self->traceheader_mappings.size() >= 2 &&
2752
+ strncmp( self->traceheader_mappings[1].name, "SEG00001", 8 ) == 0 ) {
2753
+ memcpy(
2754
+ &ds->traceheader_mapping_extension1,
2755
+ &self->traceheader_mappings[1],
2756
+ sizeof( ds->traceheader_mapping_extension1 )
2757
+ );
2758
+ }
2759
+
2760
+ return SEGY_OK;
2761
+ }
2762
+
2763
+ int parse_extended_text_headers( segyfd* self ) {
2764
+ segy_datasource* ds = self->ds;
2765
+
2766
+ char binheader[SEGY_BINARY_HEADER_SIZE] = {};
2767
+ int err = segy_binheader( ds, binheader );
2768
+ if( err != SEGY_OK ) return err;
2769
+
2770
+ segy_field_data fd;
2771
+ segy_get_binfield( binheader, SEGY_BIN_EXT_HEADERS, &fd );
2772
+ const int extra_headers_count = fd.value.i16;
2773
+
2774
+ int i = 0;
2775
+ while( extra_headers_count != i ) {
2776
+ char* c_stanza_name = NULL;
2777
+ size_t stanza_name_length;
2778
+
2779
+ err = segy_read_stanza_header(
2780
+ ds, i, &c_stanza_name, &stanza_name_length
2781
+ );
2782
+ if( err != SEGY_OK ) return err;
2783
+
2784
+ if( stanza_name_length != 0 || self->stanzas.empty() ) {
2785
+ self->stanzas.emplace_back(
2786
+ std::string( c_stanza_name, stanza_name_length ),
2787
+ i,
2788
+ 1
2789
+ );
2790
+ } else {
2791
+ self->stanzas.back().headercount += 1;
2792
+ }
2793
+ if( c_stanza_name ) free( c_stanza_name );
2794
+ if( self->stanzas.back().normalized_name() == "seg:endtext" ) break;
2795
+
2796
+ ++i;
2797
+ }
2798
+ return SEGY_OK;
2799
+ }
2800
+
2801
+ int free_header_mappings_names(
2802
+ segy_header_mapping* mappings,
2803
+ size_t mappings_length
2804
+ ) {
2805
+ if( !mappings ) return SEGY_OK;
2806
+
2807
+ for( size_t i = 0; i < mappings_length; ++i ) {
2808
+ segy_header_mapping* mapping = &mappings[i];
2809
+ for( int j = 0; j < SEGY_TRACE_HEADER_SIZE; ++j ) {
2810
+ char*& name = mapping->offset_to_entry_definition[j].name;
2811
+ if( name ) {
2812
+ delete[] name;
2813
+ }
2814
+ name = nullptr;
2815
+ }
2816
+ }
2817
+ return SEGY_OK;
2818
+ }
2819
+
2820
+ int set_mapping_name_to_offset(
2821
+ segy_header_mapping* mapping,
2822
+ std::string spec_entry_name,
2823
+ int byte
2824
+ ) {
2825
+ NameMapEntry* entry_name_map;
2826
+ size_t entry_name_map_size;
2827
+ const char* header_name = mapping->name;
2828
+
2829
+ if( strncmp( header_name, "SEG00000", 8 ) == 0 ) {
2830
+ entry_name_map = const_cast<NameMapEntry*>( standard_name_map );
2831
+ entry_name_map_size = sizeof( standard_name_map ) / sizeof( standard_name_map[0] );
2832
+ } else if( strncmp( header_name, "SEG00001", 8 ) == 0 ) {
2833
+ entry_name_map = const_cast<NameMapEntry*>( ext1_name_map );
2834
+ entry_name_map_size = sizeof( ext1_name_map ) / sizeof( ext1_name_map[0] );
2835
+ } else {
2836
+ // no name maps for proprietary headers
2837
+ entry_name_map = nullptr;
2838
+ entry_name_map_size = 0;
2839
+ }
2840
+
2841
+ int entry_name = -1;
2842
+ for( size_t i = 0; i < entry_name_map_size; ++i ) {
2843
+ if( spec_entry_name == entry_name_map[i].spec_entry_name ) {
2844
+ entry_name = entry_name_map[i].segyio_entry_name;
2845
+ break;
2846
+ }
2847
+ }
2848
+ if( entry_name >= 0 ) {
2849
+ mapping->name_to_offset[entry_name] = byte;
2850
+ }
2851
+
2852
+ return SEGY_OK;
2853
+ }
2854
+
2855
+ int set_mapping_offset_to_entry_defintion(
2856
+ segy_header_mapping* mapping,
2857
+ std::string spec_entry_name,
2858
+ int byte,
2859
+ std::string spec_entry_type,
2860
+ bool requires_nonzero_value
2861
+ ) {
2862
+ std::unique_ptr<char[]> spec_entry_name_ptr(
2863
+ new char[spec_entry_name.size() + 1]
2864
+ );
2865
+ if( !spec_entry_name_ptr ) return SEGY_MEMORY_ERROR;
2866
+ std::strcpy( spec_entry_name_ptr.get(), spec_entry_name.c_str() );
2867
+
2868
+ SEGY_ENTRY_TYPE entry_type = SEGY_ENTRY_TYPE_UNDEFINED;
2869
+ if( spectype_to_segytype_map.count( spec_entry_type ) ) {
2870
+ entry_type = spectype_to_segytype_map.at( spec_entry_type );
2871
+ }
2872
+ if( entry_type == SEGY_ENTRY_TYPE_UNDEFINED ) return SEGY_INVALID_ARGS;
2873
+
2874
+ if( entry_type == SEGY_ENTRY_TYPE_SCALE6_MANT ) {
2875
+ const int byte_mant = byte;
2876
+ const int byte_exp = byte + 4;
2877
+ if( byte_exp < 1 || byte_exp > SEGY_TRACE_HEADER_SIZE ) {
2878
+ return SEGY_INVALID_ARGS;
2879
+ }
2880
+
2881
+ std::string mant_name = spec_entry_name + "_mant";
2882
+ std::unique_ptr<char[]> mant_name_ptr( new char[mant_name.size() + 1] );
2883
+ if( !mant_name_ptr ) return SEGY_MEMORY_ERROR;
2884
+ std::strcpy( mant_name_ptr.get(), mant_name.c_str() );
2885
+
2886
+ std::string exp_name = spec_entry_name + "_exp";
2887
+ std::unique_ptr<char[]> exp_name_ptr( new char[exp_name.size() + 1] );
2888
+ if( !exp_name_ptr ) return SEGY_MEMORY_ERROR;
2889
+ std::strcpy( exp_name_ptr.get(), exp_name.c_str() );
2890
+
2891
+ segy_entry_definition def_mant = {
2892
+ SEGY_ENTRY_TYPE_SCALE6_MANT,
2893
+ requires_nonzero_value,
2894
+ mant_name_ptr.release()
2895
+ };
2896
+ segy_entry_definition def_exp = {
2897
+ SEGY_ENTRY_TYPE_SCALE6_EXP,
2898
+ requires_nonzero_value,
2899
+ exp_name_ptr.release()
2900
+ };
2901
+
2902
+ mapping->offset_to_entry_definition[byte_mant - 1] = def_mant;
2903
+ mapping->offset_to_entry_definition[byte_exp - 1] = def_exp;
2904
+ return SEGY_OK;
2905
+ }
2906
+
2907
+ segy_entry_definition def = {
2908
+ entry_type,
2909
+ requires_nonzero_value,
2910
+ spec_entry_name_ptr.release()
2911
+ };
2912
+ mapping->offset_to_entry_definition[byte - 1] = def;
2913
+ return SEGY_OK;
2914
+ }
2915
+
2916
+ int parse_py_TraceHeaderLayoutEntry_list( PyObject* entries, segy_header_mapping* mapping ) {
2917
+ Py_ssize_t entries_length = PyList_Size( entries );
2918
+ for( Py_ssize_t i = 0; i < entries_length; ++i ) {
2919
+ PyObject* entry = PyList_GetItem( entries, i );
2920
+ if( !entry ) return SEGY_INVALID_ARGS;
2921
+
2922
+ PyObject* name_obj = PyObject_GetAttrString( entry, "name" );
2923
+ PyObject* byte_obj = PyObject_GetAttrString( entry, "byte" );
2924
+ PyObject* type_obj = PyObject_GetAttrString( entry, "type" );
2925
+ PyObject* zero_obj = PyObject_GetAttrString( entry, "requires_nonzero_value" );
2926
+ if( !name_obj || !byte_obj || !type_obj || !zero_obj ) {
2927
+ Py_XDECREF( name_obj );
2928
+ Py_XDECREF( byte_obj );
2929
+ Py_XDECREF( type_obj );
2930
+ Py_XDECREF( zero_obj );
2931
+ return SEGY_INVALID_ARGS;
2932
+ }
2933
+ std::string spec_entry_name( PyUnicode_AsUTF8( name_obj ) );
2934
+ int byte = (int)PyLong_AsLong( byte_obj );
2935
+ std::string spec_entry_type( PyUnicode_AsUTF8( type_obj ) );
2936
+ bool requires_nonzero_value = PyObject_IsTrue( zero_obj );
2937
+
2938
+ Py_DECREF( name_obj );
2939
+ Py_DECREF( byte_obj );
2940
+ Py_DECREF( type_obj );
2941
+ Py_DECREF( zero_obj );
2942
+
2943
+ if( byte < 1 || byte > SEGY_TRACE_HEADER_SIZE ) {
2944
+ return SEGY_INVALID_ARGS;
2945
+ }
2946
+
2947
+ int err = set_mapping_name_to_offset(
2948
+ mapping, spec_entry_name, byte
2949
+ );
2950
+ if( err != SEGY_OK ) return err;
2951
+
2952
+ err = set_mapping_offset_to_entry_defintion(
2953
+ mapping, spec_entry_name, byte, spec_entry_type, requires_nonzero_value
2954
+ );
2955
+ if( err != SEGY_OK ) return err;
2956
+ }
2957
+
2958
+ /* Last 8 bytes of each trace header should contain a header name, which is
2959
+ * not in the map. To be able to deal with header names the same way we deal
2960
+ * with other fields, we need to add them to the map.
2961
+ *
2962
+ * The main header is an exception as there header name in bytes 233-240 is
2963
+ * optional. Previous versions of segyio assumed those fields could contain
2964
+ * custom user information. To be compliant with those versions, we allow
2965
+ * any field types in SEG00000 bytes 233-240.
2966
+ */
2967
+ const int header_name_offset = 233;
2968
+ const segy_entry_definition header_name_entry =
2969
+ mapping->offset_to_entry_definition[header_name_offset - 1];
2970
+ if( header_name_entry.entry_type == SEGY_ENTRY_TYPE_UNDEFINED ) {
2971
+ int err = set_mapping_offset_to_entry_defintion(
2972
+ mapping, "header_name", header_name_offset, "string8", false
2973
+ );
2974
+ if( err != SEGY_OK ) return err;
2975
+ } else {
2976
+ // we accept that users could have written their own values here only for SEG00000
2977
+ if( strncmp( mapping->name, "SEG00000", 8 ) != 0 ) {
2978
+ return SEGY_INVALID_ARGS;
2979
+ }
2980
+ }
2981
+
2982
+ return SEGY_OK;
2983
+ }
2984
+
2985
+ int parse_py_traceheader_layout_dict(
2986
+ PyObject* headers,
2987
+ segy_header_mapping* mappings
2988
+ ) {
2989
+ PyObject *py_header_name, *py_header_entities;
2990
+ Py_ssize_t pos = 0; // must be intialized to 0 per PyDict_Next docs
2991
+
2992
+ while( PyDict_Next( headers, &pos, &py_header_name, &py_header_entities ) ) {
2993
+ if( !py_header_entities || !PyList_Check( py_header_entities ) ) {
2994
+ return SEGY_INVALID_ARGS;
2995
+ }
2996
+ segy_header_mapping mapping = {};
2997
+
2998
+ Py_ssize_t header_size;
2999
+ const char* header_name = PyUnicode_AsUTF8AndSize( py_header_name, &header_size );
3000
+ if( !header_name || header_size > 8 ) {
3001
+ return SEGY_INVALID_ARGS;
3002
+ }
3003
+ strncpy( mapping.name, header_name, 8 );
3004
+
3005
+ int err = parse_py_TraceHeaderLayoutEntry_list( py_header_entities, &mapping );
3006
+ if( err != SEGY_OK ) return err;
3007
+
3008
+ mappings[pos - 1] = mapping;
3009
+ }
3010
+ return SEGY_OK;
3011
+ }
3012
+
3013
+ extern "C" int segy_parse_layout_xml(
3014
+ const char* xml,
3015
+ size_t xml_size,
3016
+ segy_header_mapping** mappings,
3017
+ size_t* mappings_length
3018
+ ) {
3019
+
3020
+ PyObject* module = PyImport_ImportModule( "segyio.utils" );
3021
+ if( !module ) {
3022
+ PyErr_Print();
3023
+ return SEGY_NOTFOUND;
3024
+ }
3025
+
3026
+ PyObject* parse_func = PyObject_GetAttrString( module, "parse_trace_headers_layout" );
3027
+ if( !parse_func || !PyCallable_Check( parse_func ) ) {
3028
+ PyErr_Print();
3029
+ Py_DECREF( module );
3030
+ return SEGY_NOTFOUND;
3031
+ }
3032
+
3033
+ PyObject* py_xml = PyUnicode_FromStringAndSize( xml, xml_size );
3034
+ if( !py_xml ) {
3035
+ PyErr_Print();
3036
+ Py_DECREF( parse_func );
3037
+ Py_DECREF( module );
3038
+ return SEGY_INVALID_ARGS;
3039
+ }
3040
+ PyObject* args = PyTuple_Pack( 1, py_xml );
3041
+ if( !args ) {
3042
+ PyErr_Print();
3043
+ Py_DECREF( py_xml );
3044
+ Py_DECREF( parse_func );
3045
+ Py_DECREF( module );
3046
+ return SEGY_INVALID_ARGS;
3047
+ }
3048
+
3049
+ PyObject* headers = PyObject_CallObject( parse_func, args );
3050
+
3051
+ Py_DECREF( args );
3052
+ Py_DECREF( py_xml );
3053
+ Py_DECREF( parse_func );
3054
+ Py_DECREF( module );
3055
+
3056
+ if( !headers || !PyDict_Check( headers ) ) {
3057
+ if( PyErr_Occurred() ) {
3058
+ PyErr_Print();
3059
+ }
3060
+ Py_XDECREF( headers );
3061
+ return SEGY_INVALID_ARGS;
3062
+ }
3063
+
3064
+ Py_ssize_t headers_number = PyDict_Size( headers );
3065
+ *mappings_length = static_cast<size_t>( headers_number );
3066
+ *mappings = new segy_header_mapping[*mappings_length]();
3067
+ if( !*mappings ) {
3068
+ return SEGY_MEMORY_ERROR;
3069
+ }
3070
+
3071
+ const int err = parse_py_traceheader_layout_dict( headers, *mappings );
3072
+ Py_DECREF( headers );
3073
+ if( err != SEGY_OK ) {
3074
+ if( *mappings ) {
3075
+ free_header_mappings_names( *mappings, *mappings_length );
3076
+ delete[] *mappings;
3077
+ }
3078
+ if( PyErr_Occurred() ) {
3079
+ PyErr_Print();
3080
+ }
3081
+ return err;
3082
+ }
3083
+
3084
+ return SEGY_OK;
3085
+ }
3086
+
3087
+ int entry_defintion_to_py_TraceHeaderLayoutEntry(
3088
+ PyObject* entries,
3089
+ int offset,
3090
+ const segy_entry_definition& def,
3091
+ PyObject* entry_class
3092
+ ) {
3093
+ std::string entry_type;
3094
+ if( segytype_to_spectype_map.count( def.entry_type ) ) {
3095
+ entry_type = segytype_to_spectype_map.at( def.entry_type );
3096
+ }
3097
+
3098
+ char* name = NULL;
3099
+ if( def.name != NULL ) {
3100
+ name = def.name;
3101
+ }
3102
+
3103
+ PyObject* args = Py_BuildValue(
3104
+ "sisi",
3105
+ name,
3106
+ offset,
3107
+ entry_type.c_str(),
3108
+ def.requires_nonzero_value
3109
+ );
3110
+ if( !args ) {
3111
+ return SEGY_INVALID_ARGS;
3112
+ }
3113
+
3114
+ PyObject* entry = PyObject_CallObject( entry_class, args );
3115
+ Py_DECREF( args );
3116
+
3117
+ if( !entry ) {
3118
+ return SEGY_INVALID_ARGS;
3119
+ }
3120
+ PyList_Append( entries, entry );
3121
+ Py_DECREF( entry );
3122
+ return SEGY_OK;
3123
+ }
3124
+
3125
+ int mapping_to_py_TraceHeaderLayout(
3126
+ PyObject* layout_dict,
3127
+ const segy_header_mapping& mapping,
3128
+ PyObject* layout_class,
3129
+ PyObject* entry_class
3130
+ ) {
3131
+ PyObject* header_name = PyUnicode_FromStringAndSize( mapping.name, strnlen( mapping.name, 8 ) );
3132
+ if( !header_name ) return SEGY_INVALID_ARGS;
3133
+
3134
+ PyObject* entries = PyList_New( 0 );
3135
+ if( !entries ) {
3136
+ Py_DECREF( header_name );
3137
+ return SEGY_INVALID_ARGS;
3138
+ }
3139
+
3140
+ for( int i = 0; i < SEGY_TRACE_HEADER_SIZE; ++i ) {
3141
+ const segy_entry_definition& def = mapping.offset_to_entry_definition[i];
3142
+ if( def.entry_type == SEGY_ENTRY_TYPE_UNDEFINED ) continue;
3143
+
3144
+ int err = entry_defintion_to_py_TraceHeaderLayoutEntry( entries, i + 1, def, entry_class );
3145
+ if( err != SEGY_OK ) {
3146
+ Py_DECREF( header_name );
3147
+ Py_DECREF( entries );
3148
+ return err;
3149
+ }
3150
+ }
3151
+
3152
+ PyObject* trace_header_layout = PyObject_CallFunctionObjArgs( layout_class, entries, NULL );
3153
+ if( !trace_header_layout ) {
3154
+ Py_DECREF( header_name );
3155
+ Py_DECREF( entries );
3156
+ return SEGY_INVALID_ARGS;
3157
+ }
3158
+
3159
+ PyDict_SetItem( layout_dict, header_name, trace_header_layout );
3160
+ Py_DECREF( header_name );
3161
+ Py_DECREF( entries );
3162
+ Py_DECREF( trace_header_layout );
3163
+
3164
+ return SEGY_OK;
3165
+ }
3166
+
3167
+ PyObject* traceheader_layouts( segyfd* self ) {
3168
+ PyObject* mod = PyImport_ImportModule( "segyio.utils" );
3169
+ if( !mod ) {
3170
+ return NULL;
3171
+ }
3172
+
3173
+ PyObject* entry_class = PyObject_GetAttrString( mod, "TraceHeaderLayoutEntry" );
3174
+ PyObject* layout_class = PyObject_GetAttrString( mod, "TraceHeaderLayout" );
3175
+ Py_DECREF( mod );
3176
+
3177
+ if( !entry_class || !layout_class ) {
3178
+ return NULL;
3179
+ }
3180
+
3181
+ if( !PyCallable_Check( entry_class ) || !PyCallable_Check( layout_class ) ) {
3182
+ return NULL;
3183
+ }
3184
+
3185
+ PyObject* layout_dict = PyDict_New();
3186
+ if( !layout_dict ) return NULL;
3187
+
3188
+ for( const auto& mapping : self->traceheader_mappings ) {
3189
+ int err = mapping_to_py_TraceHeaderLayout( layout_dict, mapping, layout_class, entry_class );
3190
+ if( err != SEGY_OK ) {
3191
+ Py_DECREF( layout_class );
3192
+ Py_DECREF( entry_class );
3193
+ Py_DECREF( layout_dict );
3194
+ return NULL;
3195
+ }
3196
+ }
3197
+
3198
+ Py_DECREF( layout_class );
3199
+ Py_DECREF( entry_class );
3200
+ return layout_dict;
3201
+ }
3202
+
3203
+ int extract_layout_stanza(
3204
+ segyfd* self,
3205
+ std::vector<char>& layout_stanza_data
3206
+ ) {
3207
+ segy_datasource* ds = self->ds;
3208
+
3209
+ auto is_layout_stanza = []( const stanza_header& s ) {
3210
+ const std::string layout_stanza_name = "seg:layout";
3211
+ return s.normalized_name().compare( 0, layout_stanza_name.size(), layout_stanza_name ) == 0;
3212
+ };
3213
+ auto it = find_if( self->stanzas.begin(), self->stanzas.end(), is_layout_stanza );
3214
+ if( it == self->stanzas.end() ) return SEGY_OK;
3215
+
3216
+ stanza_header stanza = *it;
3217
+ layout_stanza_data.resize( stanza.data_size() );
3218
+
3219
+ return segy_read_stanza_data(
3220
+ ds,
3221
+ stanza.header_length(),
3222
+ stanza.headerindex,
3223
+ stanza.data_size(),
3224
+ layout_stanza_data.data()
3225
+ );
3226
+ }
3227
+
3228
+ } // namespace
3229
+ }
3230
+
3231
+ /* module initialization */
3232
+ #ifdef IS_PY3K
3233
+ #ifdef IS_CLANG
3234
+ #pragma clang diagnostic push
3235
+ #pragma clang diagnostic ignored "-Wmissing-field-initializers"
3236
+ #endif
3237
+ #ifdef IS_GCC
3238
+ #pragma GCC diagnostic push
3239
+ #pragma GCC diagnostic ignored "-Wmissing-field-initializers"
3240
+ #endif
3241
+ static struct PyModuleDef segyio_module = {
3242
+ PyModuleDef_HEAD_INIT,
3243
+ "_segyio", /* name of module */
3244
+ NULL, /* module documentation, may be NULL */
3245
+ -1, /* size of per-interpreter state of the module, or -1 if the module keeps state in global variables. */
3246
+ SegyMethods
3247
+ };
3248
+ #ifdef IS_GCC
3249
+ #pragma GCC diagnostic pop
3250
+ #endif
3251
+ #ifdef IS_CLANG
3252
+ #pragma clang diagnostic pop
3253
+ #endif
3254
+
3255
+ PyMODINIT_FUNC
3256
+ PyInit__segyio(void) {
3257
+
3258
+ Segyfd.tp_new = PyType_GenericNew;
3259
+ if( PyType_Ready( &Segyfd ) < 0 ) return NULL;
3260
+
3261
+ PyObject* m = PyModule_Create(&segyio_module);
3262
+
3263
+ if( !m ) return NULL;
3264
+
3265
+ Py_INCREF( &Segyfd );
3266
+ PyModule_AddObject( m, "segyfd", (PyObject*)&Segyfd );
3267
+
3268
+ return m;
3269
+ }
3270
+ #else
3271
+ PyMODINIT_FUNC
3272
+ init_segyio(void) {
3273
+ Segyfd.tp_new = PyType_GenericNew;
3274
+ if( PyType_Ready( &Segyfd ) < 0 ) return;
3275
+
3276
+ PyObject* m = Py_InitModule("_segyio", SegyMethods);
3277
+
3278
+ Py_INCREF( &Segyfd );
3279
+ PyModule_AddObject( m, "segyfd", (PyObject*)&Segyfd );
3280
+ }
3281
+ #endif