Skip to content

Commit a8b12f3

Browse files
authored
Add template: Offset + Azimuth binned CDP gathers (COCA) (TGSAI#605)
* update helper to support structured types in variable validation * add Seismic3DPreStackCocaTemplate and corresponding unit tests * register Seismic3DPreStackCocaTemplate in template registry * reorganize template registrations in template_registry and remove depth ones from shots. * use registered templates instead of listing them all by hand. * simplify template instantiation in unit tests * fix default templates and add missing ones * refactor default template assertions using shared constant
1 parent f5ee136 commit a8b12f3

File tree

6 files changed

+293
-84
lines changed

6 files changed

+293
-84
lines changed
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
"""Seismic3DPreStackCocaTemplate MDIO v1 dataset templates."""
2+
3+
from mdio.schemas.dtype import ScalarType
4+
from mdio.schemas.metadata import UserAttributes
5+
from mdio.schemas.v1.templates.abstract_dataset_template import AbstractDatasetTemplate
6+
from mdio.schemas.v1.units import AllUnits
7+
8+
9+
class Seismic3DPreStackCocaTemplate(AbstractDatasetTemplate):
10+
"""Seismic Shot pre-stack 3D time or depth Dataset template."""
11+
12+
def __init__(self, domain: str):
13+
super().__init__(domain=domain)
14+
15+
self._coord_dim_names = ["inline", "crossline", "offset", "azimuth"]
16+
self._dim_names = [*self._coord_dim_names, self._trace_domain]
17+
self._coord_names = ["cdp_x", "cdp_y"]
18+
self._var_chunk_shape = [8, 8, 32, 1, 1024]
19+
20+
@property
21+
def _name(self) -> str:
22+
return f"PreStackCocaGathers3D{self._trace_domain.capitalize()}"
23+
24+
def _load_dataset_attributes(self) -> UserAttributes:
25+
return UserAttributes(
26+
attributes={
27+
"surveyDimensionality": "3D",
28+
"ensembleType": "cdp_coca",
29+
"processingStage": "pre-stack",
30+
}
31+
)
32+
33+
def _add_coordinates(self) -> None:
34+
# Add dimension coordinates
35+
self._builder.add_coordinate(
36+
"inline",
37+
dimensions=["inline"],
38+
data_type=ScalarType.INT32,
39+
)
40+
self._builder.add_coordinate(
41+
"crossline",
42+
dimensions=["crossline"],
43+
data_type=ScalarType.INT32,
44+
)
45+
self._builder.add_coordinate(
46+
"offset",
47+
dimensions=["offset"],
48+
data_type=ScalarType.INT32,
49+
metadata_info=[self._horizontal_coord_unit],
50+
)
51+
angle_unit = AllUnits(units_v1={"angle": "deg"})
52+
self._builder.add_coordinate(
53+
"azimuth",
54+
dimensions=["azimuth"],
55+
data_type=ScalarType.FLOAT32,
56+
metadata_info=[angle_unit],
57+
)
58+
self._builder.add_coordinate(
59+
self.trace_domain,
60+
dimensions=[self.trace_domain],
61+
data_type=ScalarType.INT32,
62+
)
63+
64+
# Add non-dimension coordinates
65+
self._builder.add_coordinate(
66+
"cdp_x",
67+
dimensions=["inline", "crossline"],
68+
data_type=ScalarType.FLOAT64,
69+
metadata_info=[self._horizontal_coord_unit],
70+
)
71+
self._builder.add_coordinate(
72+
"cdp_y",
73+
dimensions=["inline", "crossline"],
74+
data_type=ScalarType.FLOAT64,
75+
metadata_info=[self._horizontal_coord_unit],
76+
)

src/mdio/schemas/v1/templates/template_registry.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from mdio.schemas.v1.templates.seismic_2d_prestack_shot import Seismic2DPreStackShotTemplate
1010
from mdio.schemas.v1.templates.seismic_3d_poststack import Seismic3DPostStackTemplate
1111
from mdio.schemas.v1.templates.seismic_3d_prestack_cdp import Seismic3DPreStackCDPTemplate
12+
from mdio.schemas.v1.templates.seismic_3d_prestack_coca import Seismic3DPreStackCocaTemplate
1213
from mdio.schemas.v1.templates.seismic_3d_prestack_shot import Seismic3DPreStackShotTemplate
1314

1415

@@ -66,25 +67,25 @@ def register(self, instance: AbstractDatasetTemplate) -> str:
6667
def _register_default_templates(self) -> None:
6768
"""Register default templates if needed.
6869
69-
This method can be overridden by subclasses to register default templates.
70+
Subclasses can override this method to register default templates.
7071
"""
72+
# Post-Stack Data
7173
self.register(Seismic2DPostStackTemplate("time"))
7274
self.register(Seismic2DPostStackTemplate("depth"))
73-
74-
self.register(Seismic2DPreStackCDPTemplate("time"))
75-
self.register(Seismic2DPreStackCDPTemplate("depth"))
76-
77-
self.register(Seismic2DPreStackShotTemplate("time"))
78-
self.register(Seismic2DPreStackShotTemplate("depth"))
79-
8075
self.register(Seismic3DPostStackTemplate("time"))
8176
self.register(Seismic3DPostStackTemplate("depth"))
8277

78+
# CDP/CMP Ordered Data
79+
self.register(Seismic2DPreStackCDPTemplate("time"))
80+
self.register(Seismic2DPreStackCDPTemplate("depth"))
8381
self.register(Seismic3DPreStackCDPTemplate("time"))
8482
self.register(Seismic3DPreStackCDPTemplate("depth"))
83+
self.register(Seismic3DPreStackCocaTemplate("time"))
84+
self.register(Seismic3DPreStackCocaTemplate("depth"))
8585

86+
# Field (shot) data
87+
self.register(Seismic2DPreStackShotTemplate("time"))
8688
self.register(Seismic3DPreStackShotTemplate("time"))
87-
self.register(Seismic3DPreStackShotTemplate("depth"))
8889

8990
def get(self, template_name: str) -> AbstractDatasetTemplate:
9091
"""Get a template from the registry by its name.

tests/unit/v1/helpers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ def validate_variable(
5656
name: str,
5757
dims: list[tuple[str, int]],
5858
coords: list[str],
59-
dtype: ScalarType,
59+
dtype: ScalarType | StructuredType,
6060
) -> Variable:
6161
"""Validate existence and the structure of the created variable."""
6262
if isinstance(container, MDIODatasetBuilder):
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
"""Unit tests for Seismic3DPreStackCocaTemplate."""
2+
3+
from tests.unit.v1.helpers import validate_variable
4+
5+
from mdio.schemas.chunk_grid import RegularChunkGrid
6+
from mdio.schemas.compressors import Blosc
7+
from mdio.schemas.dtype import ScalarType
8+
from mdio.schemas.dtype import StructuredType
9+
from mdio.schemas.v1.dataset import Dataset
10+
from mdio.schemas.v1.templates.seismic_3d_prestack_coca import Seismic3DPreStackCocaTemplate
11+
from mdio.schemas.v1.units import AllUnits
12+
from mdio.schemas.v1.units import AngleUnitEnum
13+
from mdio.schemas.v1.units import LengthUnitEnum
14+
from mdio.schemas.v1.units import LengthUnitModel
15+
from mdio.schemas.v1.units import TimeUnitEnum
16+
from mdio.schemas.v1.units import TimeUnitModel
17+
18+
_UNIT_METER = AllUnits(units_v1=LengthUnitModel(length=LengthUnitEnum.METER))
19+
_UNIT_SECOND = AllUnits(units_v1=TimeUnitModel(time=TimeUnitEnum.SECOND))
20+
21+
22+
def _validate_coordinates_headers_trace_mask(dataset: Dataset, headers: StructuredType, domain: str) -> None:
23+
"""Validate the coordinate, headers, trace_mask variables in the dataset."""
24+
# Verify variables
25+
# 5 dim coords + 2 non-dim coords + 1 data + 1 trace mask + 1 headers = 10 variables
26+
assert len(dataset.variables) == 10
27+
28+
# Verify trace headers
29+
validate_variable(
30+
dataset,
31+
name="headers",
32+
dims=[("inline", 256), ("crossline", 256), ("offset", 100), ("azimuth", 6)],
33+
coords=["cdp_x", "cdp_y"],
34+
dtype=headers,
35+
)
36+
37+
validate_variable(
38+
dataset,
39+
name="trace_mask",
40+
dims=[("inline", 256), ("crossline", 256), ("offset", 100), ("azimuth", 6)],
41+
coords=["cdp_x", "cdp_y"],
42+
dtype=ScalarType.BOOL,
43+
)
44+
45+
# Verify dimension coordinate variables
46+
inline = validate_variable(
47+
dataset,
48+
name="inline",
49+
dims=[("inline", 256)],
50+
coords=["inline"],
51+
dtype=ScalarType.INT32,
52+
)
53+
assert inline.metadata is None
54+
55+
crossline = validate_variable(
56+
dataset,
57+
name="crossline",
58+
dims=[("crossline", 256)],
59+
coords=["crossline"],
60+
dtype=ScalarType.INT32,
61+
)
62+
assert crossline.metadata is None
63+
64+
offset = validate_variable(
65+
dataset,
66+
name="offset",
67+
dims=[("offset", 100)],
68+
coords=["offset"],
69+
dtype=ScalarType.INT32,
70+
)
71+
assert offset.metadata.units_v1.length == LengthUnitEnum.METER
72+
73+
azimuth = validate_variable(
74+
dataset,
75+
name="azimuth",
76+
dims=[("azimuth", 6)],
77+
coords=["azimuth"],
78+
dtype=ScalarType.FLOAT32,
79+
)
80+
assert azimuth.metadata.units_v1.angle == AngleUnitEnum.DEGREES
81+
82+
domain = validate_variable(
83+
dataset,
84+
name=domain,
85+
dims=[(domain, 2048)],
86+
coords=[domain],
87+
dtype=ScalarType.INT32,
88+
)
89+
assert domain.metadata is None
90+
91+
# Verify non-dimension coordinate variables
92+
cdp_x = validate_variable(
93+
dataset,
94+
name="cdp_x",
95+
dims=[("inline", 256), ("crossline", 256)],
96+
coords=["cdp_x"],
97+
dtype=ScalarType.FLOAT64,
98+
)
99+
assert cdp_x.metadata.units_v1.length == LengthUnitEnum.METER
100+
101+
cdp_y = validate_variable(
102+
dataset,
103+
name="cdp_y",
104+
dims=[("inline", 256), ("crossline", 256)],
105+
coords=["cdp_y"],
106+
dtype=ScalarType.FLOAT64,
107+
)
108+
assert cdp_y.metadata.units_v1.length == LengthUnitEnum.METER
109+
110+
111+
class TestSeismic3DPreStackCocaTemplate:
112+
"""Unit tests for Seismic3DPreStackCocaTemplate."""
113+
114+
def test_configuration_time(self) -> None:
115+
"""Unit tests for Seismic3DPreStackCocaTemplate in time domain."""
116+
t = Seismic3DPreStackCocaTemplate(domain="time")
117+
118+
# Template attributes
119+
assert t._coord_dim_names == ["inline", "crossline", "offset", "azimuth"]
120+
assert t._dim_names == ["inline", "crossline", "offset", "azimuth", "time"]
121+
assert t._coord_names == ["cdp_x", "cdp_y"]
122+
assert t._var_chunk_shape == [8, 8, 32, 1, 1024]
123+
124+
# Variables instantiated when build_dataset() is called
125+
assert t._builder is None
126+
assert t._dim_sizes == []
127+
assert t._horizontal_coord_unit is None
128+
129+
# Verify dataset attributes
130+
attrs = t._load_dataset_attributes()
131+
assert attrs.attributes == {
132+
"surveyDimensionality": "3D",
133+
"ensembleType": "cdp_coca",
134+
"processingStage": "pre-stack",
135+
}
136+
assert t.trace_variable_name == "amplitude"
137+
138+
def test_build_dataset_time(self, structured_headers: StructuredType) -> None:
139+
"""Unit tests for Seismic3DPreStackShotTemplate build in time domain."""
140+
t = Seismic3DPreStackCocaTemplate(domain="time")
141+
142+
dataset = t.build_dataset(
143+
"Permian Basin 3D CDP Coca Gathers",
144+
sizes=[256, 256, 100, 6, 2048],
145+
horizontal_coord_unit=_UNIT_METER,
146+
headers=structured_headers,
147+
)
148+
149+
assert dataset.metadata.name == "Permian Basin 3D CDP Coca Gathers"
150+
assert dataset.metadata.attributes["surveyDimensionality"] == "3D"
151+
assert dataset.metadata.attributes["ensembleType"] == "cdp_coca"
152+
assert dataset.metadata.attributes["processingStage"] == "pre-stack"
153+
154+
_validate_coordinates_headers_trace_mask(dataset, structured_headers, "time")
155+
156+
# Verify seismic variable (prestack shot depth data)
157+
seismic = validate_variable(
158+
dataset,
159+
name="amplitude",
160+
dims=[("inline", 256), ("crossline", 256), ("offset", 100), ("azimuth", 6), ("time", 2048)],
161+
coords=["cdp_x", "cdp_y"],
162+
dtype=ScalarType.FLOAT32,
163+
)
164+
assert isinstance(seismic.compressor, Blosc)
165+
assert seismic.compressor.algorithm == "zstd"
166+
assert isinstance(seismic.metadata.chunk_grid, RegularChunkGrid)
167+
assert seismic.metadata.chunk_grid.configuration.chunk_shape == [8, 8, 32, 1, 1024]
168+
assert seismic.metadata.stats_v1 is None

tests/unit/v1/templates/test_seismic_templates.py

Lines changed: 14 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from mdio.schemas.v1.templates.seismic_3d_poststack import Seismic3DPostStackTemplate
1111
from mdio.schemas.v1.templates.seismic_3d_prestack_cdp import Seismic3DPreStackCDPTemplate
1212
from mdio.schemas.v1.templates.seismic_3d_prestack_shot import Seismic3DPreStackShotTemplate
13+
from mdio.schemas.v1.templates.template_registry import TemplateRegistry
1314

1415

1516
class TestSeismicTemplates:
@@ -48,44 +49,24 @@ def _name(self) -> str:
4849

4950
def test_get_name_time(self) -> None:
5051
"""Test get_name with domain."""
51-
time_template = Seismic2DPostStackTemplate("time")
52-
dpth_template = Seismic2DPostStackTemplate("depth")
52+
assert Seismic2DPostStackTemplate("time").name == "PostStack2DTime"
53+
assert Seismic2DPostStackTemplate("depth").name == "PostStack2DDepth"
5354

54-
assert time_template.name == "PostStack2DTime"
55-
assert dpth_template.name == "PostStack2DDepth"
55+
assert Seismic3DPostStackTemplate("time").name == "PostStack3DTime"
56+
assert Seismic3DPostStackTemplate("depth").name == "PostStack3DDepth"
5657

57-
time_template = Seismic3DPostStackTemplate("time")
58-
dpth_template = Seismic3DPostStackTemplate("depth")
58+
assert Seismic3DPreStackCDPTemplate("time").name == "PreStackCdpGathers3DTime"
59+
assert Seismic3DPreStackCDPTemplate("depth").name == "PreStackCdpGathers3DDepth"
5960

60-
assert time_template.name == "PostStack3DTime"
61-
assert dpth_template.name == "PostStack3DDepth"
62-
63-
time_template = Seismic3DPreStackCDPTemplate("time")
64-
dpth_template = Seismic3DPreStackCDPTemplate("depth")
65-
66-
assert time_template.name == "PreStackCdpGathers3DTime"
67-
assert dpth_template.name == "PreStackCdpGathers3DDepth"
68-
69-
time_template = Seismic3DPreStackShotTemplate("time")
70-
dpth_template = Seismic3DPreStackShotTemplate("depth")
71-
72-
assert time_template.name == "PreStackShotGathers3DTime"
73-
assert dpth_template.name == "PreStackShotGathers3DDepth"
61+
assert Seismic3DPreStackShotTemplate("time").name == "PreStackShotGathers3DTime"
7462

7563
def test_all_templates_inherit_from_abstract(self) -> None:
7664
"""Test that all concrete templates inherit from AbstractDatasetTemplate."""
77-
templates = [
78-
Seismic2DPostStackTemplate("time"),
79-
Seismic3DPostStackTemplate("time"),
80-
Seismic3DPreStackCDPTemplate("time"),
81-
Seismic3DPreStackShotTemplate("time"),
82-
Seismic2DPostStackTemplate("depth"),
83-
Seismic3DPostStackTemplate("depth"),
84-
Seismic3DPreStackCDPTemplate("depth"),
85-
Seismic3DPreStackShotTemplate("depth"),
86-
]
87-
88-
for template in templates:
65+
registry = TemplateRegistry()
66+
template_names = registry.list_all_templates()
67+
68+
for template_name in template_names:
69+
template = registry.get(template_name)
8970
assert isinstance(template, AbstractDatasetTemplate)
9071
# That each template has the required properties and methods
9172
assert hasattr(template, "name")
@@ -95,5 +76,4 @@ def test_all_templates_inherit_from_abstract(self) -> None:
9576
assert hasattr(template, "coordinate_names")
9677
assert hasattr(template, "build_dataset")
9778

98-
names = [template.name for template in templates]
99-
assert len(names) == len(set(names)), f"Duplicate template names found: {names}"
79+
assert len(template_names) == len(set(template_names)), f"Duplicate template names found: {template_names}"

0 commit comments

Comments
 (0)