Skip to content

Commit 0632784

Browse files
authored
Add Seismic OBN Dataset Templates (#767)
* Pass 1 of template implementations * Use correct coordinate names * Remove unnecessary docstrings * Fix `surveyType` key * Fix `surveyType` in tests * Update default chunking * Add OBN Data Import documentation and remove single-component template (#12) * Add OBN Data Import documentation and remove single-component template - Introduced a new documentation file for OBN Data Import detailing the `ObnReceiverGathers3D` template and its usage. - Updated the main index documentation to include a reference to the new OBN Data Import guide. - Removed the `Seismic3DObnSingleComponentGathersTemplate` as it is no longer needed. - Adjusted the template registry to reflect the removal of the single-component template. - Enhanced the `Seismic3DObnReceiverGathersTemplate` to clarify the handling of the `shot_index` dimension and component synthesis during data import. - Updated related tests to ensure proper functionality with the new structure. * Update template, streamline grid override, add+enhance docs * Fix pre-commit * Ensure clean environment for coverage data * Fix pre-commit
1 parent 3d01a6d commit 0632784

17 files changed

Lines changed: 1659 additions & 65 deletions

docs/guides/grid_overrides.md

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# Grid Overrides
2+
3+
```{warning}
4+
🚧👷🏻 We are actively working on updating the documentation and adding missing features to v1 release. Please check back later for more updates!
5+
```
6+
7+
Grid overrides are transformations applied during SEG-Y import that modify how trace headers are interpreted and indexed. They handle complex acquisition geometries that cannot be represented by simple header-to-dimension mappings.
8+
9+
## Overview
10+
11+
When importing SEG-Y data, MDIO maps trace header fields to dataset dimensions. However, real-world seismic data often has complexities that require additional processing. Grid overrides address these issues by transforming header values before indexing.
12+
13+
## CalculateShotIndex
14+
15+
Calculates a dense `shot_index` dimension from sparse or interleaved `shot_point` values. Required for the `ObnReceiverGathers3D` template.
16+
17+
**Supported Templates:** `ObnReceiverGathers3D`
18+
19+
**Required Headers:** `shot_line`, `gun`, `shot_point`
20+
21+
**How it works:**
22+
23+
In multi-gun OBN acquisition, shot points are often interleaved across guns:
24+
25+
```
26+
Before (interleaved shot_point):
27+
Gun 1: 1, 3, 5, 7, ...
28+
Gun 2: 2, 4, 6, 8, ...
29+
30+
After (dense shot_index):
31+
Gun 1: 0, 1, 2, 3, ...
32+
Gun 2: 0, 1, 2, 3, ...
33+
```
34+
35+
The override detects the geometry type and only applies the transformation when shot points are actually interleaved (Type B geometry). For non-interleaved data (Type A), shot points are used directly.
36+
37+
**Usage:**
38+
39+
```python
40+
segy_to_mdio(
41+
input_path="obn_data.sgy",
42+
output_path="obn_data.mdio",
43+
segy_spec=obn_spec,
44+
mdio_template=get_template("ObnReceiverGathers3D"),
45+
grid_overrides={"CalculateShotIndex": True},
46+
)
47+
```
48+
49+
```{note}
50+
See [OBN Data Import](obn_data_import.md) for a complete guide on importing OBN data.
51+
```
52+
53+
## Special Behaviors
54+
55+
Some templates have special behaviors that are applied automatically during import, independent of grid overrides.
56+
57+
### Component Synthesis (OBN)
58+
59+
When using the `ObnReceiverGathers3D` template, if the SEG-Y specification does not include a `component` field, MDIO automatically synthesizes it with value `1` for all traces. This allows single-component data (e.g., hydrophone-only) to use the same template without modification.
60+
61+
```{note}
62+
A warning is logged when component is synthesized:
63+
64+
> SEG-Y headers do not contain 'component' field required by template 'ObnReceiverGathers3D'.
65+
> Synthesizing 'component' dimension with constant value 1 for all traces.
66+
```
67+
68+
## Error Handling
69+
70+
Grid overrides validate their requirements and raise specific exceptions:
71+
72+
| Exception | Cause |
73+
| ----------------------------------- | ----------------------------------- |
74+
| `GridOverrideUnknownError` | Unknown override name passed |
75+
| `GridOverrideKeysError` | Required header fields missing |
76+
| `GridOverrideMissingParameterError` | Required parameters not provided |
77+
| `GridOverrideIncompatibleError` | Override incompatible with template |
78+
79+
**Example error message:**
80+
81+
```
82+
GridOverrideKeysError: Grid override 'CalculateShotIndex' requires keys: {'shot_line', 'gun', 'shot_point'}
83+
```
84+
85+
## See Also
86+
87+
- [OBN Data Import](obn_data_import.md) - Complete guide for OBN data
88+
- [Template Registry](../template_registry.md) - Available templates
89+
- [Tutorials](../tutorials/index.md) - Hands-on guides

docs/guides/index.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Guides
2+
3+
Welcome to the MDIO guides. This section provides in-depth documentation on advanced features and specialized workflows.
4+
5+
## Topics
6+
7+
```{toctree}
8+
:maxdepth: 1
9+
:titlesonly:
10+
11+
grid_overrides
12+
obn_data_import
13+
```
14+
15+
## Overview
16+
17+
### Grid Overrides
18+
19+
Grid overrides are transformations applied during SEG-Y import that modify how trace headers are interpreted and indexed. They handle complex acquisition geometries like multi-gun acquisition with interleaved shot points.
20+
21+
See [Grid Overrides](grid_overrides.md) for documentation.
22+
23+
### OBN Data Import
24+
25+
Ocean Bottom Node (OBN) data has unique characteristics requiring specialized handling. The OBN guide covers:
26+
27+
- The `ObnReceiverGathers3D` template
28+
- Required grid overrides for OBN
29+
- Component synthesis for single-component data
30+
31+
See [OBN Data Import](obn_data_import.md) for the complete guide.

docs/guides/obn_data_import.md

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
# OBN Data Import
2+
3+
This guide covers the `ObnReceiverGathers3D` template for importing Ocean Bottom Node (OBN) seismic data into MDIO.
4+
5+
## Template Overview
6+
7+
The `ObnReceiverGathers3D` template organizes data with the following dimensions:
8+
9+
| Dimension | Description |
10+
| -------------- | ------------------------------------------------------------------------------------------ |
11+
| `component` | Sensor component (e.g., 1=X, 2=Y, 3=Z, 4=Hydrophone) |
12+
| `receiver` | Ocean bottom node receiver ID |
13+
| `shot_line` | Shot line identifier |
14+
| `gun` | Gun identifier for multi-gun sources |
15+
| `shot_index` | Calculated dense index for shots (see [Required Grid Overrides](#required-grid-overrides)) |
16+
| `time`/`depth` | Vertical sample axis |
17+
18+
### Coordinates
19+
20+
- **Logical coordinates**: `shot_point` (original values), `orig_field_record_num`
21+
- **Physical coordinates**: `group_coord_x`, `group_coord_y`, `source_coord_x`, `source_coord_y`
22+
23+
```{note}
24+
The `shot_index` dimension is calculated (0 to N-1) from `shot_point` values during ingestion. Original `shot_point` values are preserved as a coordinate indexed by `(shot_line, gun, shot_index)`.
25+
```
26+
27+
## Required Grid Overrides
28+
29+
### CalculateShotIndex (Required)
30+
31+
The `CalculateShotIndex` grid override is **required** for the `ObnReceiverGathers3D` template. It calculates the `shot_index` dimension from `shot_point` values. Without this override, the import will fail with an error:
32+
33+
> Required computed fields ['shot_index'] for template ObnReceiverGathers3D not found after grid overrides.
34+
35+
This override handles multi-gun acquisition where shot points are interleaved across guns, calculating a dense `shot_index` from sparse `shot_point` values:
36+
37+
```
38+
Before (interleaved shot_point):
39+
Gun 1: 1, 3, 5, 7, ...
40+
Gun 2: 2, 4, 6, 8, ...
41+
42+
After (dense shot_index):
43+
Gun 1: 0, 1, 2, 3, ...
44+
Gun 2: 0, 1, 2, 3, ...
45+
```
46+
47+
For `ObnReceiverGathers3D`, the override uses `shot_line` as the line field and requires `shot_line`, `gun`, and `shot_point` headers.
48+
49+
## Special Behaviors
50+
51+
### Component Synthesis
52+
53+
When the SEG-Y spec does not include a `component` field, MDIO automatically synthesizes it with value `1` for all traces. This allows single-component data (e.g., hydrophone-only) to use the same template without modification.
54+
55+
```{note}
56+
A warning is logged when component is synthesized:
57+
> SEG-Y headers do not contain 'component' field required by template 'ObnReceiverGathers3D'.
58+
> Synthesizing 'component' dimension with constant value 1 for all traces.
59+
```
60+
61+
## Usage
62+
63+
### Basic Import
64+
65+
```python
66+
from segy.schema import HeaderField
67+
from segy.standards import get_segy_standard
68+
69+
from mdio import segy_to_mdio
70+
from mdio.builder.template_registry import get_template
71+
72+
# Define SEG-Y header mapping
73+
obn_headers = [
74+
HeaderField(name="orig_field_record_num", byte=9, format="int32"),
75+
HeaderField(name="receiver", byte=13, format="int32"),
76+
HeaderField(name="shot_point", byte=17, format="int32"),
77+
HeaderField(name="shot_line", byte=133, format="int16"),
78+
HeaderField(name="gun", byte=171, format="int16"),
79+
HeaderField(name="component", byte=189, format="int16"),
80+
HeaderField(name="coordinate_scalar", byte=71, format="int16"),
81+
HeaderField(name="source_coord_x", byte=73, format="int32"),
82+
HeaderField(name="source_coord_y", byte=77, format="int32"),
83+
HeaderField(name="group_coord_x", byte=81, format="int32"),
84+
HeaderField(name="group_coord_y", byte=85, format="int32"),
85+
]
86+
87+
obn_spec = get_segy_standard(1.0).customize(trace_header_fields=obn_headers)
88+
89+
segy_to_mdio(
90+
input_path="obn_data.sgy",
91+
output_path="obn_data.mdio",
92+
segy_spec=obn_spec,
93+
mdio_template=get_template("ObnReceiverGathers3D"),
94+
grid_overrides={"CalculateShotIndex": True},
95+
overwrite=True,
96+
)
97+
```
98+
99+
### Single-Component Data
100+
101+
For data without a `component` header field, simply omit it from the spec:
102+
103+
```python
104+
# Same as above, but without the component field
105+
obn_headers = [
106+
HeaderField(name="orig_field_record_num", byte=9, format="int32"),
107+
HeaderField(name="receiver", byte=13, format="int32"),
108+
HeaderField(name="shot_point", byte=17, format="int32"),
109+
HeaderField(name="shot_line", byte=133, format="int16"),
110+
HeaderField(name="gun", byte=171, format="int16"),
111+
# component omitted - will be synthesized
112+
HeaderField(name="coordinate_scalar", byte=71, format="int16"),
113+
HeaderField(name="source_coord_x", byte=73, format="int32"),
114+
HeaderField(name="source_coord_y", byte=77, format="int32"),
115+
HeaderField(name="group_coord_x", byte=81, format="int32"),
116+
HeaderField(name="group_coord_y", byte=85, format="int32"),
117+
]
118+
```
119+
120+
### Exploring the Data
121+
122+
```python
123+
from mdio import open_mdio
124+
125+
ds = open_mdio("obn_data.mdio")
126+
127+
# View dimensions
128+
print(ds.sizes)
129+
# {'component': 4, 'receiver': 100, 'shot_line': 10, 'gun': 2, 'shot_index': 500, 'time': 2001}
130+
131+
# Access original shot_point values (preserved as coordinate)
132+
print(ds["shot_point"].dims) # ('shot_line', 'gun', 'shot_index')
133+
134+
# Select a receiver gather
135+
receiver_gather = ds.sel(receiver=150, component=4)
136+
receiver_gather["amplitude"].plot()
137+
```
138+
139+
## Required Header Fields
140+
141+
| Field | Required | Notes |
142+
| ----------------------- | -------- | ----------------------------------- |
143+
| `receiver` | Yes | |
144+
| `shot_line` | Yes | |
145+
| `gun` | Yes | |
146+
| `shot_point` | Yes | |
147+
| `component` | No | Synthesized with value 1 if missing |
148+
| `coordinate_scalar` | Yes | |
149+
| `source_coord_x` | Yes | |
150+
| `source_coord_y` | Yes | |
151+
| `group_coord_x` | Yes | |
152+
| `group_coord_y` | Yes | |
153+
| `orig_field_record_num` | Yes | |
154+
155+
## See Also
156+
157+
- [Grid Overrides](grid_overrides.md) - All available grid overrides
158+
- [Template Registry](../template_registry.md)
159+
- [Quickstart Tutorial](../tutorials/quickstart.ipynb)

docs/index.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,13 @@ tutorials/index
2727
api_reference
2828
```
2929

30+
```{toctree}
31+
:hidden:
32+
:caption: Guides
33+
34+
guides/index
35+
```
36+
3037
```{toctree}
3138
:hidden:
3239
:caption: Core Concepts and Structures

noxfile.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,9 @@ def tests(session: Session) -> None:
180180
session.notify("coverage", posargs=[])
181181

182182

183-
@session(python=python_versions[0])
183+
# We must pass `--clear` due to different session options during pipeline runs
184+
# https://github.com/TGSAI/mdio-python/blob/3d01a6d8c93cabeaeff1829599327ae83c7d6593/.github/workflows/tests.yml#L123-L130
185+
@session(python=python_versions[0], venv_params=["--clear"])
184186
def coverage(session: Session) -> None:
185187
"""Produce the coverage report."""
186188
args = session.posargs or ["report"]

src/mdio/builder/template_registry.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@
2525
from mdio.builder.templates.seismic_2d_streamer_shot import Seismic2DStreamerShotGathersTemplate
2626
from mdio.builder.templates.seismic_3d_cdp import Seismic3DCdpGathersTemplate
2727
from mdio.builder.templates.seismic_3d_coca import Seismic3DCocaGathersTemplate
28+
from mdio.builder.templates.seismic_3d_obn import Seismic3DObnReceiverGathersTemplate
2829
from mdio.builder.templates.seismic_3d_poststack import Seismic3DPostStackTemplate
30+
from mdio.builder.templates.seismic_3d_shot_receiver_line import Seismic3DShotReceiverLineGathersTemplate
2931
from mdio.builder.templates.seismic_3d_streamer_field import Seismic3DStreamerFieldRecordsTemplate
3032
from mdio.builder.templates.seismic_3d_streamer_shot import Seismic3DStreamerShotGathersTemplate
3133

@@ -138,6 +140,12 @@ def _register_default_templates(self) -> None:
138140
self.register(Seismic3DStreamerShotGathersTemplate())
139141
self.register(Seismic3DStreamerFieldRecordsTemplate())
140142

143+
# OBN (Ocean Bottom Node) data
144+
self.register(Seismic3DObnReceiverGathersTemplate())
145+
146+
# Land/OBC shot-receiver data
147+
self.register(Seismic3DShotReceiverLineGathersTemplate())
148+
141149
def get(self, template_name: str) -> AbstractDatasetTemplate:
142150
"""Get an instance of a template from the registry by its name.
143151

0 commit comments

Comments
 (0)