Skip to content

Commit 7677b25

Browse files
Add Seismic3DOffsetTilesTemplate and Seismic3DReceiverGathersTemplate
Add two new seismic dataset templates: - Seismic3DOffsetTilesTemplate: 5D template for wide-azimuth data organized by CDP location with offset vector tile decomposition. Supports time and depth domains. - Seismic3DReceiverGathersTemplate: Template for fixed receiver surveys (OBN, OBC, land) organized by receiver position. Time domain only. Also fixes duplicate entries in the expected template names test list. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 0632784 commit 7677b25

6 files changed

Lines changed: 595 additions & 8 deletions

File tree

src/mdio/builder/template_registry.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@
2626
from mdio.builder.templates.seismic_3d_cdp import Seismic3DCdpGathersTemplate
2727
from mdio.builder.templates.seismic_3d_coca import Seismic3DCocaGathersTemplate
2828
from mdio.builder.templates.seismic_3d_obn import Seismic3DObnReceiverGathersTemplate
29+
from mdio.builder.templates.seismic_3d_offset_tiles import Seismic3DOffsetTilesTemplate
2930
from mdio.builder.templates.seismic_3d_poststack import Seismic3DPostStackTemplate
31+
from mdio.builder.templates.seismic_3d_receiver_gathers import Seismic3DReceiverGathersTemplate
3032
from mdio.builder.templates.seismic_3d_shot_receiver_line import Seismic3DShotReceiverLineGathersTemplate
3133
from mdio.builder.templates.seismic_3d_streamer_field import Seismic3DStreamerFieldRecordsTemplate
3234
from mdio.builder.templates.seismic_3d_streamer_shot import Seismic3DStreamerShotGathersTemplate
@@ -135,6 +137,13 @@ def _register_default_templates(self) -> None:
135137
self.register(Seismic3DCocaGathersTemplate("time"))
136138
self.register(Seismic3DCocaGathersTemplate("depth"))
137139

140+
# Receiver Gathers (OBN, OBC, land fixed-spread) - time domain only
141+
self.register(Seismic3DReceiverGathersTemplate())
142+
143+
# Offset Tiles
144+
self.register(Seismic3DOffsetTilesTemplate("time"))
145+
self.register(Seismic3DOffsetTilesTemplate("depth"))
146+
138147
# Field (shot) data
139148
self.register(Seismic2DStreamerShotGathersTemplate())
140149
self.register(Seismic3DStreamerShotGathersTemplate())
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
"""Seismic3DOffsetTilesTemplate MDIO v1 dataset templates."""
2+
3+
from typing import Any
4+
5+
from mdio.builder.schemas import compressors
6+
from mdio.builder.schemas.dtype import ScalarType
7+
from mdio.builder.schemas.v1.variable import CoordinateMetadata
8+
from mdio.builder.templates.base import AbstractDatasetTemplate
9+
from mdio.builder.templates.types import SeismicDataDomain
10+
11+
12+
class Seismic3DOffsetTilesTemplate(AbstractDatasetTemplate):
13+
"""Seismic Offset Tiles pre-stack 3D Dataset template.
14+
15+
A 5D template for wide-azimuth seismic data organized by offset vector tiles.
16+
Data is binned by CDP location (inline, crossline) with offset vector
17+
decomposition into inline and crossline offset tile components.
18+
19+
Dimensions:
20+
- inline: Inline bin position
21+
- crossline: Crossline bin position
22+
- inline_offset_tile: Inline component of the offset vector tile
23+
- crossline_offset_tile: Crossline component of the offset vector tile
24+
- time/depth: Sample dimension
25+
26+
This organization is optimal for:
27+
- Wide-azimuth data preservation
28+
- 5D interpolation and regularization
29+
- Azimuthal analysis and processing
30+
- Offset vector filtering
31+
32+
The offset vector tiles partition the offset-azimuth space into a regular grid,
33+
preserving both offset magnitude and azimuth information in a format suitable
34+
for modern wide-azimuth processing workflows.
35+
36+
Args:
37+
data_domain: The domain of the dataset ('time' or 'depth').
38+
"""
39+
40+
def __init__(self, data_domain: SeismicDataDomain = "time"):
41+
super().__init__(data_domain=data_domain)
42+
43+
self._dim_names = (
44+
"inline",
45+
"crossline",
46+
"inline_offset_tile",
47+
"crossline_offset_tile",
48+
self._data_domain,
49+
)
50+
self._physical_coord_names = ("cdp_x", "cdp_y")
51+
self._logical_coord_names = ()
52+
self._var_chunk_shape = (4, 4, 6, 6, 4096)
53+
54+
@property
55+
def _name(self) -> str:
56+
return f"OffsetTiles3D{self._data_domain.capitalize()}"
57+
58+
def _load_dataset_attributes(self) -> dict[str, Any]:
59+
return {"surveyType": "3D", "gatherType": "offset_tiles"}
60+
61+
def _add_coordinates(self) -> None:
62+
# Add dimension coordinates
63+
self._builder.add_coordinate(
64+
"inline",
65+
dimensions=("inline",),
66+
data_type=ScalarType.INT32,
67+
metadata=CoordinateMetadata(units_v1=self.get_unit_by_key("inline")),
68+
)
69+
self._builder.add_coordinate(
70+
"crossline",
71+
dimensions=("crossline",),
72+
data_type=ScalarType.INT32,
73+
metadata=CoordinateMetadata(units_v1=self.get_unit_by_key("crossline")),
74+
)
75+
self._builder.add_coordinate(
76+
"inline_offset_tile",
77+
dimensions=("inline_offset_tile",),
78+
data_type=ScalarType.INT16,
79+
metadata=CoordinateMetadata(units_v1=self.get_unit_by_key("inline_offset_tile")),
80+
)
81+
self._builder.add_coordinate(
82+
"crossline_offset_tile",
83+
dimensions=("crossline_offset_tile",),
84+
data_type=ScalarType.INT16,
85+
metadata=CoordinateMetadata(units_v1=self.get_unit_by_key("crossline_offset_tile")),
86+
)
87+
self._builder.add_coordinate(
88+
self.trace_domain,
89+
dimensions=(self.trace_domain,),
90+
data_type=ScalarType.INT32,
91+
metadata=CoordinateMetadata(units_v1=self.get_unit_by_key(self.trace_domain)),
92+
)
93+
94+
# Add non-dimension coordinates
95+
compressor = compressors.Blosc(cname=compressors.BloscCname.zstd)
96+
97+
# CDP coordinates (vary by inline, crossline)
98+
self._builder.add_coordinate(
99+
"cdp_x",
100+
dimensions=("inline", "crossline"),
101+
data_type=ScalarType.FLOAT64,
102+
compressor=compressor,
103+
metadata=CoordinateMetadata(units_v1=self.get_unit_by_key("cdp_x")),
104+
)
105+
self._builder.add_coordinate(
106+
"cdp_y",
107+
dimensions=("inline", "crossline"),
108+
data_type=ScalarType.FLOAT64,
109+
compressor=compressor,
110+
metadata=CoordinateMetadata(units_v1=self.get_unit_by_key("cdp_y")),
111+
)
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
"""Seismic3DReceiverGathersTemplate MDIO v1 dataset templates."""
2+
3+
from typing import Any
4+
5+
from mdio.builder.schemas import compressors
6+
from mdio.builder.schemas.dtype import ScalarType
7+
from mdio.builder.schemas.v1.variable import CoordinateMetadata
8+
from mdio.builder.templates.base import AbstractDatasetTemplate
9+
10+
11+
class Seismic3DReceiverGathersTemplate(AbstractDatasetTemplate):
12+
"""Seismic receiver gather pre-stack 3D Dataset template.
13+
14+
A template for surveys with fixed receiver positions (OBN, OBC, land fixed-spread)
15+
where data is organized by receiver position for receiver-side processing.
16+
Shots are organized by shot lines with a calculated shot index.
17+
18+
This template is time-domain only as receiver gathers are typically used
19+
for early-stage processing before depth conversion.
20+
21+
Dimensions:
22+
- receiver: Index of the receiver node/station
23+
- shot_line: Shot line or swath identifier
24+
- shot_index: Sequential index of shots within the line (calculated, 0-N)
25+
- time: Sample dimension
26+
27+
This organization is optimal for:
28+
- Receiver-side wavefield separation (up/down)
29+
- Receiver-consistent deconvolution
30+
- Multi-component processing
31+
- Mirror imaging from OBN
32+
"""
33+
34+
def __init__(self) -> None:
35+
super().__init__(data_domain="time")
36+
37+
self._dim_names = ("receiver", "shot_line", "shot_index", "time")
38+
self._calculated_dims = ("shot_index",)
39+
self._physical_coord_names = (
40+
"receiver_x",
41+
"receiver_y",
42+
"source_coord_x",
43+
"source_coord_y",
44+
)
45+
self._logical_coord_names = ("shot_point",)
46+
self._var_chunk_shape = (1, 1, 512, 4096)
47+
48+
@property
49+
def _name(self) -> str:
50+
return "ReceiverGathers3D"
51+
52+
def _load_dataset_attributes(self) -> dict[str, Any]:
53+
return {"surveyType": "3D", "gatherType": "receiver_gathers"}
54+
55+
def _add_coordinates(self) -> None:
56+
# Add dimension coordinates
57+
# Note: shot_index is calculated (0-N), so we don't add a coordinate for it
58+
self._builder.add_coordinate(
59+
"receiver",
60+
dimensions=("receiver",),
61+
data_type=ScalarType.UINT32,
62+
metadata=CoordinateMetadata(units_v1=self.get_unit_by_key("receiver")),
63+
)
64+
self._builder.add_coordinate(
65+
"shot_line",
66+
dimensions=("shot_line",),
67+
data_type=ScalarType.UINT32,
68+
metadata=CoordinateMetadata(units_v1=self.get_unit_by_key("shot_line")),
69+
)
70+
self._builder.add_coordinate(
71+
self.trace_domain,
72+
dimensions=(self.trace_domain,),
73+
data_type=ScalarType.INT32,
74+
metadata=CoordinateMetadata(units_v1=self.get_unit_by_key(self.trace_domain)),
75+
)
76+
77+
# Add non-dimension coordinates
78+
compressor = compressors.Blosc(cname=compressors.BloscCname.zstd)
79+
80+
# Receiver coordinates (fixed per receiver)
81+
self._builder.add_coordinate(
82+
"receiver_x",
83+
dimensions=("receiver",),
84+
data_type=ScalarType.FLOAT64,
85+
compressor=compressor,
86+
metadata=CoordinateMetadata(units_v1=self.get_unit_by_key("receiver_x")),
87+
)
88+
self._builder.add_coordinate(
89+
"receiver_y",
90+
dimensions=("receiver",),
91+
data_type=ScalarType.FLOAT64,
92+
compressor=compressor,
93+
metadata=CoordinateMetadata(units_v1=self.get_unit_by_key("receiver_y")),
94+
)
95+
96+
# Shot point coordinate (actual shot point numbers, varies by shot_line and shot_index)
97+
self._builder.add_coordinate(
98+
"shot_point",
99+
dimensions=("shot_line", "shot_index"),
100+
data_type=ScalarType.UINT32,
101+
compressor=compressor,
102+
metadata=CoordinateMetadata(units_v1=self.get_unit_by_key("shot_point")),
103+
)
104+
105+
# Source coordinates (vary by shot_line and shot_index)
106+
self._builder.add_coordinate(
107+
"source_coord_x",
108+
dimensions=("shot_line", "shot_index"),
109+
data_type=ScalarType.FLOAT64,
110+
compressor=compressor,
111+
metadata=CoordinateMetadata(units_v1=self.get_unit_by_key("source_coord_x")),
112+
)
113+
self._builder.add_coordinate(
114+
"source_coord_y",
115+
dimensions=("shot_line", "shot_index"),
116+
data_type=ScalarType.FLOAT64,
117+
compressor=compressor,
118+
metadata=CoordinateMetadata(units_v1=self.get_unit_by_key("source_coord_y")),
119+
)

0 commit comments

Comments
 (0)