Skip to content

SEG-Y text header operations

Use these functions to view or modify existing SEGY text headers or create new segysak compatable text headers.

get_segy_texthead(segy_file, ext_headers=False, no_richstr=False, **segyio_kwargs)

Return the ebcidc header as a Python string. New lines are separated by the \n char.

Parameters:

Name Type Description Default
segy_file Union[str, PathLike]

Segy File Path

required
ext_headers bool

Return EBCIDC and extended headers in list. Defaults to False

False
no_richstr bool

Defaults to False. If true the returned string will not be updated for pretty HTML printing.

False
segyio_kwargs Dict[str, Any]

Key word arguments to pass to segyio.open

{}

Returns:

Name Type Description
text str

Returns the EBCIDC text as a formatted paragraph.

Source code in segysak/segy/_segy_text.py
Python
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
def get_segy_texthead(
    segy_file: Union[str, os.PathLike],
    ext_headers: bool = False,
    no_richstr: bool = False,
    **segyio_kwargs: Dict[str, Any],
) -> str:
    """Return the ebcidc header as a Python string. New lines are separated by the `\\n` char.

    Args:
        segy_file: Segy File Path
        ext_headers: Return EBCIDC and extended headers in list. Defaults to False
        no_richstr: Defaults to False. If true the returned string
            will not be updated for pretty HTML printing.
        segyio_kwargs: Key word arguments to pass to segyio.open

    Returns:
        text: Returns the EBCIDC text as a formatted paragraph.
    """

    with open(segy_file, mode="rb") as f:
        f.seek(0, 0)  # Locate our position to first byte of file
        data = f.read(3200)  # Read the first 3200 byte from our position

    if _isascii(data) and ext_headers == False:
        encoding = "ascii"
    elif ext_headers == False:
        encoding = "cp500"  # text is ebcidc
    else:
        encoding = "ebcidc"

    if encoding in ["ascii", "cp500"]:
        lines = []
        # doing it this way ensure we split the bytes appropriately across the 40 lines.
        for i in range(0, 3200, 80):
            lines.append(data[i : i + 80].decode("cp500"))
            text = "\n".join(lines)
    else:
        segyio_kwargs["ignore_geometry"] = True
        try:  # pray that the encoding is ebcidc
            with segyio.open(segy_file, "r", **segyio_kwargs) as segyf:
                text = segyf.text[0].decode("ascii", "replace")
                text = _text_fixes(text)
                text = segyio.tools.wrap(text)
                if segyf.ext_headers and ext_headers:
                    text2 = segyf.text[1].decode("ascii", "replace")
                    text = [text, text2]
        except UnicodeDecodeError as err:
            print(err)
            print("The segy text header could not be decoded.")

    text = _text_fixes(text)

    if no_richstr:
        return text
    else:
        return _upgrade_txt_richstr(text)

put_segy_texthead(segy_file, ebcidc, line_counter=True, **segyio_kwargs)

Puts a text header (ebcidc) into a SEG-Y file.

Parameters:

Name Type Description Default
segy_file Union[str, PathLike]

The path to the file to update.

required
ebcidc Union[str, List[str], Dict[int, str], ByteString]

A standard string, new lines will be preserved. A list or lines to add. A dict with numeric keys for line numbers e.g. {1: 'line 1'}. A pre-encoded byte header to add to the SEG-Y file directly.

required
line_counter bool

Add a line counter with format "CXX " to the start of each line. This reduces the maximum content per line to 76 chars.

True
Source code in segysak/segy/_segy_text.py
Python
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
def put_segy_texthead(
    segy_file: Union[str, os.PathLike],
    ebcidc: Union[str, List[str], Dict[int, str], ByteString],
    line_counter: bool = True,
    **segyio_kwargs,
):
    """Puts a text header (ebcidc) into a SEG-Y file.

    Args:
        segy_file: The path to the file to update.
        ebcidc:
            A standard string, new lines will be preserved.
            A list or lines to add.
            A dict with numeric keys for line numbers e.g. {1: 'line 1'}.
            A pre-encoded byte header to add to the SEG-Y file directly.
        line_counter: Add a line counter with format "CXX " to the start of each line.
            This reduces the maximum content per line to 76 chars.
    """
    header = ""
    n = 80

    if not isinstance(ebcidc, (str, list, dict, bytes)):
        raise ValueError(f"Unknown type for ebcidc: {type(ebcidc)}")

    if isinstance(ebcidc, dict):
        lines = _process_dict_texthead(ebcidc, n)
    elif isinstance(ebcidc, str):
        lines = _process_string_texthead(ebcidc, n)
    elif isinstance(ebcidc, list):
        lines = ebcidc
    else:
        lines = []

    if not isinstance(ebcidc, bytes):
        lines = [
            _process_line(line, i, line_counter=line_counter)
            for i, line in enumerate(lines)
        ]
        # convert to bytes line by line to ensure end lines don't get pushed,
        # truncate lines with bad chars instead
        header = b"".join([ln.encode("utf-8")[:n] for ln in lines])
    else:
        header = ebcidc

    # check size
    if len(header) > 3200:
        warn(f"Byte EBCIDC is too large ({len(header)}/3200) - truncating", UserWarning)
        header = header[:3200]

    segyio_kwargs["ignore_geometry"] = True
    with segyio.open(segy_file, "r+", **segyio_kwargs) as segyf:
        segyf.text[0] = header

create_default_texthead(override=None)

Returns a simple default textual header dictionary.

Basic fields are auto populated and a dictionary indexing lines 1-40 can be passed to override keyword for adjustment. By default lines 6-34 are empty.

Line length rules apply, so overrides will be truncated if they have >80 chars.

Parameters:

Name Type Description Default
override Union[Dict[int, str], None]

Override any line with custom values. Defaults to None.

None

Returns:

Name Type Description
text_header Dict[int, str]

Dictionary with keys 1-40 for textual header of SEG-Y file

Example

Override lines 7 and 8 of the default text header.

Python
>>> create_default_texthead(override={7:'Hello', 8:'World!'})
{1: 'segysak SEG-Y Output',
2: 'Data created by: username ',
3: '',
4: 'DATA FORMAT: SEG-Y;  DATE: 2019-06-09 15:14:00',
5: 'DATA DESCRIPTION: SEG-Y format data output from segysak',
6: '',
7: 'Hello',
8: 'World!',
9: '',
...
34: '',
35: '*** BYTE LOCATION OF KEY HEADERS ***',
36: 'CMP UTM-X 181-184, ALL COORDS X100, CMP UTM-Y 185-188',
37: 'INLINE 189-193, XLINE 194-198, ',
38: '',
39: '',
40: 'END TEXTUAL HEADER'}
Source code in segysak/segy/_segy_text.py
Python
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
def create_default_texthead(
    override: Union[Dict[int, str], None] = None
) -> Dict[int, str]:
    """Returns a simple default textual header dictionary.

    Basic fields are auto populated and a dictionary indexing lines 1-40 can
    be passed to override keyword for adjustment. By default lines 6-34 are
    empty.

    Line length rules apply, so overrides will be truncated if they have >80 chars.

    Args:
        override: Override any line with custom values. Defaults to None.

    Returns:
        text_header: Dictionary with keys 1-40 for textual header of SEG-Y file

    !!! example
        Override lines 7 and 8 of the default text header.

        ```python
        >>> create_default_texthead(override={7:'Hello', 8:'World!'})
        {1: 'segysak SEG-Y Output',
        2: 'Data created by: username ',
        3: '',
        4: 'DATA FORMAT: SEG-Y;  DATE: 2019-06-09 15:14:00',
        5: 'DATA DESCRIPTION: SEG-Y format data output from segysak',
        6: '',
        7: 'Hello',
        8: 'World!',
        9: '',
        ...
        34: '',
        35: '*** BYTE LOCATION OF KEY HEADERS ***',
        36: 'CMP UTM-X 181-184, ALL COORDS X100, CMP UTM-Y 185-188',
        37: 'INLINE 189-193, XLINE 194-198, ',
        38: '',
        39: '',
        40: 'END TEXTUAL HEADER'}
        ```

    """
    user = _get_userid()
    today, time = _get_datetime()
    text_dict = {
        #      123456789012345678901234567890123456789012345678901234567890123456
        1: "segysak Python Library SEG-Y Output",
        2: f"Data created by: {user} ",
        4: f"DATA FORMAT: SEG-Y;  DATE: {today} {time}",
        5: "DATA DESCRIPTION: SEG-Y format data output from segysak using segyio",
        6: "",
        40: "END TEXTUAL HEADER",
    }
    if override is not None:
        for key, line in override.items():
            text_dict[key] = line
    return _clean_texthead(text_dict)