Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions docs/api_reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,16 @@ and
.. autopydantic_model:: mdio.GridOverrides
```

### SEG-Y File Headers

```{eval-rst}
.. automodule:: mdio.segy.headers
:members:

.. automodule:: mdio.segy.text_header
:members:
```

## Core Functionality

### Dimensions
Expand Down
227 changes: 227 additions & 0 deletions docs/tutorials/file_headers.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Edit SEG-Y File Headers on MDIO\n",
"\n",
"```{article-info}\n",
":author: Brian Michell\n",
":date: \"{sub-ref}`today`\"\n",
":read-time: \"{sub-ref}`wordcount-minutes` min read\"\n",
":class-container: sd-p-0 sd-outline-muted sd-rounded-3 sd-font-weight-light\n",
"```\n",
"\n",
"SEG-Y text and binary file headers live on the scalar `segy_file_header` variable as\n",
"`textHeader` and `binaryHeader` attributes. They are metadata only. Updating them does\n",
"not read or rewrite traces.\n",
"\n",
"Ingest may skip these fields (`MDIO__IMPORT__SAVE_SEGY_FILE_HEADER` defaults to off).\n",
"Export back to SEG-Y needs both attributes. This tutorial shows how to add them when\n",
"they are missing, and how to replace or patch text and binary headers in place. Edits\n",
"range from one character to a completely new header; you always pass the payload you\n",
"want stored.\n",
"\n",
"```{warning}\n",
"Update **values** of existing binary-header keys. Adding or removing keys can break\n",
"`mdio_to_segy`: the export factory only encodes fields from the SEG-Y spec, and it\n",
"requires `sample_interval`, `samples_per_trace`, and revision major/minor.\n",
"```\n"
],
"id": "file-headers-intro"
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## A tiny MDIO file without headers\n",
"\n",
"Build a small in-memory dataset and write it with `to_mdio`. This is the same shape as a\n",
"file ingested with file-header saving turned off.\n"
],
"id": "file-headers-setup"
},
{
"cell_type": "code",
"metadata": {},
"source": [
"from pathlib import Path\n",
"\n",
"from mdio import open_mdio\n",
"from mdio import to_mdio\n",
"from mdio import update_segy_file_headers\n",
"from mdio.builder.template_registry import get_template\n",
"from mdio.builder.xarray_builder import to_xarray_dataset\n",
"\n",
"mdio_path = Path(\"headers_demo.mdio\")\n",
"\n",
"template = get_template(\"PostStack2DTime\")\n",
"template.full_chunk_shape = (4, 8)\n",
"mdio_ds = template.build_dataset(name=\"headers-demo\", sizes=(4, 8))\n",
"to_mdio(to_xarray_dataset(mdio_ds), mdio_path, mode=\"w\")\n",
"\n",
"opened = open_mdio(mdio_path)\n",
"print(\"has segy_file_header:\", \"segy_file_header\" in opened)"
],
"execution_count": null,
"outputs": [],
"id": "file-headers-create"
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Add headers when they are missing\n",
"\n",
"`update_segy_file_headers` creates `segy_file_header` if needed and fills defaults.\n",
"With no `template`, defaults follow SEG-Y Revision 1.0. `samples_per_trace` is taken\n",
"from the default data variable (here, 8 time samples).\n",
"\n",
"Pass `template=get_segy_standard(2.0)` (or any `SegySpec`) when the file should carry\n",
"that revision's binary field set.\n"
],
"id": "file-headers-add"
},
{
"cell_type": "code",
"metadata": {},
"source": [
"headers = update_segy_file_headers(mdio_path)\n",
"\n",
"dataset = open_mdio(mdio_path)\n",
"print(dataset[\"segy_file_header\"].attrs[\"textHeader\"].split(\"\\n\")[0])\n",
"print(\"samples_per_trace\", headers.binary_header[\"samples_per_trace\"])\n",
"print(\"revision\", headers.binary_header[\"segy_revision_major\"], headers.binary_header[\"segy_revision_minor\"])"
],
"execution_count": null,
"outputs": [],
"id": "file-headers-add-code"
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"To seed defaults from a different spec instead of Rev 1.0:\n",
"\n",
"```python\n",
"update_segy_file_headers(mdio_path, template=get_segy_standard(2.0))\n",
"```\n"
],
"id": "file-headers-template"
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Replace or patch in place\n",
"\n",
"Call the same function again. `None` arguments leave that header unchanged.\n",
"\n",
"- **Text:** whatever string you pass replaces `textHeader`. Edit one character, one\n",
" card, or supply a new 40x80 header — same call.\n",
"- **Binary:** keys you pass are merged onto the stored mapping. That is the safe way\n",
" to change a value. A completely new binary header is a full mapping; do not drop\n",
" keys the export spec needs.\n"
],
"id": "file-headers-edit"
},
{
"cell_type": "code",
"metadata": {},
"source": [
"update_segy_file_headers(mdio_path, binary_header={\"job_id\": 42, \"line_num\": 7})\n",
"\n",
"binary = open_mdio(mdio_path)[\"segy_file_header\"].attrs[\"binaryHeader\"]\n",
"print(\"job_id\", binary[\"job_id\"])\n",
"print(\"line_num\", binary[\"line_num\"])\n",
"print(\"samples_per_trace still\", binary[\"samples_per_trace\"])"
],
"execution_count": null,
"outputs": [],
"id": "file-headers-edit-code"
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Edit the text header in Python, then write it back\n",
"\n",
"The stored value is one string (40 cards of 80 characters, joined by newlines).\n",
"Change as little or as much as you want, then pass the whole string.\n"
],
"id": "file-headers-line"
},
{
"cell_type": "code",
"metadata": {},
"source": [
"text = open_mdio(mdio_path)[\"segy_file_header\"].attrs[\"textHeader\"]\n",
"rows = text.split(\"\\n\")\n",
"\n",
"# one character\n",
"rows[0] = rows[0][:4] + \"X\" + rows[0][5:]\n",
"\n",
"# one card\n",
"rows[1] = \"C02 LINE: DEMO-001\".ljust(80)\n",
"\n",
"# or assign a completely new 40-line header to `rows` / `text`\n",
"update_segy_file_headers(mdio_path, text_header=\"\\n\".join(rows))\n",
"\n",
"updated = open_mdio(mdio_path)[\"segy_file_header\"].attrs[\"textHeader\"]\n",
"print(updated.split(\"\\n\")[0])\n",
"print(updated.split(\"\\n\")[1])"
],
"execution_count": null,
"outputs": [],
"id": "file-headers-line-code"
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Binary headers: update values, do not reshape the field set\n",
"\n",
"Safe:\n",
"\n",
"```python\n",
"update_segy_file_headers(mdio_path, binary_header={\"job_id\": 99})\n",
"```\n",
"\n",
"Risky:\n",
"\n",
"- **Adding** a key that is not in the SEG-Y spec used at export. `SegyFactory` raises\n",
" when it cannot place the field.\n",
"- **Removing** a required key (`sample_interval`, `samples_per_trace`,\n",
" `segy_revision_major`, `segy_revision_minor`). Export then fails or writes a bad file.\n",
"- Switching revision by stuffing extra Rev 2 fields into a Rev 1 header (or the reverse)\n",
" without passing a matching `template` the next time defaults are built.\n",
"\n",
"This helper never deletes keys: a partial `binary_header` is merged onto what is already\n",
"stored. To change revision layout, pass `template` when the file still lacks a binary\n",
"header, or replace the whole mapping only if you know the export `SegySpec`.\n"
],
"id": "file-headers-binary-warn"
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Recap\n",
"\n",
"- `update_segy_file_headers` adds or writes `textHeader` / `binaryHeader` without touching traces.\n",
"- Missing fields get Rev 1.0 defaults, or a `SegySpec` you pass as `template`.\n",
"- Text: pass the full string, whether you changed one character or all 40 cards.\n",
"- Binary: merge value updates. Do not add or remove keys unless the export spec matches.\n"
],
"id": "file-headers-recap"
}
],
"metadata": {
"language_info": {
"name": "python"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
1 change: 1 addition & 0 deletions docs/tutorials/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ compression
rechunking
corrupt_files
custom_template
file_headers
```
4 changes: 4 additions & 0 deletions src/mdio/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
from mdio.optimize.access_pattern import OptimizedAccessPatternConfig
from mdio.optimize.access_pattern import optimize_access_patterns
from mdio.segy.geometry import GridOverrides
from mdio.segy.headers import SegyFileHeaders
from mdio.segy.headers import update_segy_file_headers

try:
__version__ = metadata.version("multidimio")
Expand All @@ -22,8 +24,10 @@
__all__ = [
"__version__",
"GridOverrides",
"SegyFileHeaders",
"open_mdio",
"to_mdio",
"update_segy_file_headers",
"mdio_to_segy",
"segy_to_mdio",
"OptimizedAccessPatternConfig",
Expand Down
8 changes: 8 additions & 0 deletions src/mdio/segy/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,9 @@
"""SEG-Y specific implementation module."""

from mdio.segy.headers import SegyFileHeaders
from mdio.segy.headers import update_segy_file_headers

__all__ = [
"SegyFileHeaders",
"update_segy_file_headers",
]
4 changes: 2 additions & 2 deletions src/mdio/segy/creation.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,8 @@ def mdio_spec_to_segy(

if "segy_file_header" not in dataset:
msg = (
"MDIO does not contain SEG-Y file headers to write to output. Please add a dummy segy_file_header "
"variable and fill its metadata (.attrs) with `textHeader` and `binaryHeader`."
"MDIO does not contain SEG-Y file headers to write to output. "
"Call mdio.update_segy_file_headers(...) to add textHeader and binaryHeader."
)
raise MDIOMissingVariableError(msg)

Expand Down
Loading
Loading