Skip to content

Commit 2b3be75

Browse files
authored
Merge pull request #632 from achaikou/python_datasource
Expose datasource to python
2 parents 0cb2a54 + 3b960a7 commit 2b3be75

18 files changed

Lines changed: 1259 additions & 48 deletions

File tree

.github/workflows/build.yaml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,3 +134,31 @@ jobs:
134134
uses: matlab-actions/run-command@v2
135135
with:
136136
command: addpath('build/mex'), try, run('build/mex/test/segyspec.m'), exit(0), catch ME, disp(getReport(ME, 'extended')), exit(-1), end;
137+
138+
python:
139+
runs-on: ubuntu-latest
140+
name: Build and test python on example stream
141+
strategy:
142+
fail-fast: false
143+
matrix:
144+
stream: [pyfile, pymemory]
145+
146+
steps:
147+
- uses: actions/checkout@v4
148+
149+
- name: Install build/test dependencies
150+
working-directory: python
151+
run: |
152+
python3 -m pip install -r requirements-dev.txt
153+
154+
- name: Configure and build
155+
shell: bash
156+
run: |
157+
cmake -S . -B build -DBUILD_PYTHON=ON -DBUILD_BIN=OFF
158+
cmake --build build
159+
160+
- name: Test
161+
shell: bash
162+
run: |
163+
cd python
164+
pytest -rP --datasource ${{ matrix.stream }} test/stream.py

python/segyio/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,8 @@ def enums(cls):
8888
from .tracesortingformat import TraceSortingFormat
8989
from .tracefield import TraceField
9090
from . import su
91-
from .open import open
92-
from .create import create
91+
from .open import open, open_with, open_from_memory
92+
from .create import create, create_with
9393
from .segy import SegyFile, spec
9494
from .tools import dt, sample_indexes, create_text_header, native
9595
from .tools import collect, cube

python/segyio/create.py

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
import segyio
44

55
from . import TraceSortingFormat
6-
from .utils import c_endianness
6+
from .utils import (
7+
FileDatasourceDescriptor,
8+
StreamDatasourceDescriptor,
9+
)
710

811
def default_text_header(iline, xline, offset):
912
lines = {
@@ -178,9 +181,42 @@ def create(filename, spec):
178181
... dst.header = src.header
179182
... dst.trace = src.trace
180183
"""
184+
return _create(FileDatasourceDescriptor(filename, "w+"), spec)
181185

182-
from . import _segyio
183186

187+
def create_with(stream, spec, minimize_requests_number=True):
188+
"""
189+
Creates a segy file on stream.
190+
191+
Function behaves the same as `segyio.create`, but outputs data to a
192+
finite stream instead of a file. Stream's close() will be called when
193+
SegyFile is closed.
194+
195+
Note that `segyio.create_with` can be very slow. `segyio.create` is
196+
generally a preferred option when speed matters.
197+
198+
Parameters
199+
----------
200+
201+
stream : file-like object
202+
Data destination. It is up to the user to assure stream is opened in w+b mode.
203+
spec : segyio.spec
204+
Structure of the segy file
205+
minimize_requests_number : bool
206+
Configuration for some internal algorithms. True to minimize number of
207+
requests to the stream at the cost of higher memory usage. False to
208+
minimize memory usage at the cost of more requests to the stream.
209+
"""
210+
return _create(
211+
StreamDatasourceDescriptor(
212+
stream,
213+
minimize_requests_number
214+
),
215+
spec
216+
)
217+
218+
219+
def _create(datasource_descriptor, spec):
184220
if not structured(spec):
185221
tracecount = spec.tracecount
186222
else:
@@ -193,9 +229,7 @@ def create(filename, spec):
193229
if endian is None:
194230
endian = 'big'
195231

196-
fd = _segyio.segyfd(
197-
filename=str(filename), mode='w+', endianness=c_endianness(endian)
198-
)
232+
fd = datasource_descriptor.make_segyfile_descriptor(endian)
199233
fd.segymake(
200234
samples = len(samples),
201235
tracecount = tracecount,
@@ -204,8 +238,7 @@ def create(filename, spec):
204238
)
205239

206240
f = segyio.SegyFile(fd,
207-
filename = str(filename),
208-
mode = 'w+',
241+
datasource_descriptor,
209242
iline = int(spec.iline),
210243
xline = int(spec.xline),
211244
endian = endian,

python/segyio/open.py

Lines changed: 89 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@
22

33
import segyio
44

5-
from .utils import c_endianness
5+
from .utils import (
6+
FileDatasourceDescriptor,
7+
StreamDatasourceDescriptor,
8+
MemoryBufferDatasourceDescriptor
9+
)
610

711
def infer_geometry(f, metrics, iline, xline, strict):
812
try:
@@ -148,16 +152,95 @@ def open(filename, mode="r", iline = 189,
148152
solution = 'use r+ to open in read-write'
149153
raise ValueError(', '.join((problem, solution)))
150154

151-
from . import _segyio
152-
fd = _segyio.segyfd(
153-
filename=str(filename), mode=mode, endianness=c_endianness(endian)
155+
return _open(
156+
FileDatasourceDescriptor(filename, mode),
157+
iline, xline, strict, ignore_geometry, endian
154158
)
159+
160+
161+
def open_with(stream,
162+
iline=189,
163+
xline=193,
164+
strict=True,
165+
ignore_geometry=False,
166+
endian='big',
167+
minimize_requests_number=True
168+
):
169+
"""
170+
Opens a segy file from a stream.
171+
172+
Function behaves the same as `segyio.open`, but sources data from a finite
173+
stream instead of a file. Stream's close() will be called when SegyFile is
174+
closed.
175+
176+
Note that `segyio.open_with` can be very slow. `segyio.open` is generally a
177+
preferred option when speed matters.
178+
179+
Parameters
180+
----------
181+
182+
stream : file-like object
183+
Source of the data. It is up to the user to assure stream is opened in rb
184+
mode for reading and r+b mode for updating.
185+
minimize_requests_number : bool
186+
Configuration for some internal algorithms. True to minimize number of
187+
requests to the stream at the cost of higher memory usage. False to
188+
minimize memory usage at the cost of more requests to the stream.
189+
190+
See other common parameters at `segyio.open`.
191+
"""
192+
return _open(
193+
StreamDatasourceDescriptor(
194+
stream,
195+
minimize_requests_number
196+
),
197+
iline, xline, strict, ignore_geometry, endian
198+
)
199+
200+
201+
def open_from_memory(memory_buffer,
202+
iline=189,
203+
xline=193,
204+
strict=True,
205+
ignore_geometry=False,
206+
endian='big'):
207+
"""
208+
Opens a segy file from memory.
209+
210+
Function behaves the same as `segyio.open`, but expects file to fit
211+
completely into memory. Performance is expected to be significantly faster
212+
than using `segyio.open` or `segyio.open_with`.
213+
214+
Parameters
215+
----------
216+
217+
buffer : contiguous memory object
218+
Source of the data: complete SEGY file loaded into memory. Update
219+
operations assume memory is writable.
220+
221+
See other common parameters at `segyio.open`.
222+
"""
223+
return _open(
224+
MemoryBufferDatasourceDescriptor(
225+
memory_buffer
226+
),
227+
iline, xline, strict, ignore_geometry, endian
228+
)
229+
230+
231+
def _open(datasource_descriptor,
232+
iline=189,
233+
xline=193,
234+
strict=True,
235+
ignore_geometry=False,
236+
endian='big'):
237+
238+
fd = datasource_descriptor.make_segyfile_descriptor(endian)
155239
fd.segyopen()
156240
metrics = fd.metrics()
157241

158242
f = segyio.SegyFile(fd,
159-
filename = str(filename),
160-
mode = mode,
243+
datasource_descriptor,
161244
iline = int(iline),
162245
xline = int(xline),
163246
endian = endian,

python/segyio/segy.py

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,16 @@ class SegyFile(object):
2323

2424
_unstructured_errmsg = "File opened in unstructured mode."
2525

26-
def __init__(self, fd, filename, mode, iline=189,
27-
xline=193,
28-
endian='big',
29-
):
30-
31-
self._filename = filename
32-
self._mode = mode
26+
def __init__(
27+
self,
28+
fd,
29+
datasource_descriptor,
30+
iline=189,
31+
xline=193,
32+
endian='big'
33+
):
34+
35+
self._datasource_descriptor = datasource_descriptor
3336
self._il = iline
3437
self._xl = xline
3538

@@ -90,7 +93,7 @@ def __init__(self, fd, filename, mode, iline=189,
9093
super(SegyFile, self).__init__()
9194

9295
def __str__(self):
93-
f = "SegyFile {}:".format(self._filename)
96+
f = "SegyFile {}:".format(str(self._datasource_descriptor))
9497

9598
if self.unstructured:
9699
il = " inlines: None"
@@ -115,8 +118,8 @@ def __str__(self):
115118

116119

117120
def __repr__(self):
118-
return "SegyFile('{}', '{}', iline = {}, xline = {})".format(
119-
self._filename, self._mode, self._il, self._xl)
121+
return "SegyFile('{}', iline = {}, xline = {})".format(
122+
self._datasource_descriptor.__repr__(), self._il, self._xl)
120123

121124
def __enter__(self):
122125
return self
@@ -853,7 +856,7 @@ def readonly(self):
853856
.. versionadded:: 1.6
854857
"""
855858

856-
return '+' not in self._mode
859+
return self._datasource_descriptor.readonly()
857860

858861

859862
def interpret(self, ilines, xlines, offsets=None, sorting=TraceSortingFormat.INLINE_SORTING):

python/segyio/segyio.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -466,6 +466,10 @@ int init( segyfd* self, PyObject* args, PyObject* kwargs ) {
466466

467467
autods ds;
468468
if( stream ) {
469+
if (stream == Py_None) {
470+
ValueError( "stream must not be None" );
471+
return -1;
472+
}
469473
if( minimize_requests_number < 0 ) {
470474
ValueError( "minimize requests number is not set" );
471475
return -1;
@@ -474,6 +478,10 @@ int init( segyfd* self, PyObject* args, PyObject* kwargs ) {
474478
stream, minimize_requests_number
475479
);
476480
} else if( memory_buffer ) {
481+
if (memory_buffer == Py_None) {
482+
ValueError( "buffer must not be None" );
483+
return -1;
484+
}
477485
auto flags = PyBUF_CONTIG | PyBUF_WRITABLE;
478486
const int err = PyObject_GetBuffer( memory_buffer, &ds.memory_ds_buffer, flags );
479487
if( err ) {

python/segyio/su/file.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from ..open import infer_geometry
22
from ..segy import SegyFile
33
from . import words
4-
from ..utils import c_endianness
4+
from ..utils import FileDatasourceDescriptor
55

66
import numpy
77

@@ -81,17 +81,26 @@ def open(filename, mode = 'r', iline = 189,
8181
solution = 'use r+ to open in read-write'
8282
raise ValueError(', '.join((problem, solution)))
8383

84-
from .. import _segyio
85-
fd = _segyio.segyfd(
86-
filename=str(filename), mode=mode, endianness=c_endianness(endian)
84+
return _open(
85+
FileDatasourceDescriptor(filename, mode),
86+
iline, xline, strict, ignore_geometry, endian
8787
)
88+
89+
90+
def _open(datasource_descriptor,
91+
iline=189,
92+
xline=193,
93+
strict=True,
94+
ignore_geometry=False,
95+
endian='big'):
96+
97+
fd = datasource_descriptor.make_segyfile_descriptor(endian)
8898
fd.suopen()
8999
metrics = fd.metrics()
90100

91101
f = sufile(
92102
fd,
93-
filename = str(filename),
94-
mode = mode,
103+
datasource_descriptor,
95104
iline = iline,
96105
xline = xline,
97106
)

0 commit comments

Comments
 (0)