1- """Test for create_empty_mdio function."""
1+ """Test for create_empty_mdio function.
2+
3+ This set of tests has to run after the segy_roundtrip_teapot tests have run because
4+ the teapot dataset is used as the input for the create_empty_like test.
5+
6+ NOTE: The only reliable way to ensure the test order (including the case when the
7+ test are run in parallel) is to use the alphabetical order of the test names.
8+ """
29
310from __future__ import annotations
411
2229 from xarray import Dataset as xr_Dataset
2330
2431
25- from tests .integration .test_segy_roundtrip_teapot import get_teapot_segy_spec
26- from tests .integration .testing_helpers import get_values
32+ from tests .integration .testing_helpers import UNITS_NONE , UNITS_SECOND , UNITS_METER , UNITS_FOOT , UNITS_METER_PER_SECOND , UNITS_FEET_PER_SECOND
33+ from tests .integration .testing_helpers import get_teapot_segy_spec
2734from tests .integration .testing_helpers import validate_xr_variable
35+ from tests .integration .testing_helpers import get_values
36+
2837
2938from mdio import __version__
3039from mdio .api .create import create_empty
3746from mdio .converters .mdio import mdio_to_segy
3847from mdio .core import Dimension
3948
40- UNITS_NONE = None
41- UNITS_METER = LengthUnitModel (length = LengthUnitEnum .METER )
42- UNITS_SECOND = TimeUnitModel (time = TimeUnitEnum .SECOND )
43- UNITS_METER_PER_SECOND = SpeedUnitModel (speed = SpeedUnitEnum .METER_PER_SECOND )
44- UNITS_FOOT = LengthUnitModel (length = LengthUnitEnum .FOOT )
45- UNITS_FEET_PER_SECOND = SpeedUnitModel (speed = SpeedUnitEnum .FEET_PER_SECOND )
46-
47-
4849class PostStack3DVelocityTemplate (Seismic3DPostStackTemplate ):
4950 """Custom template that uses 'velocity' as the default variable name instead of 'amplitude'."""
5051
@@ -80,13 +81,72 @@ def _name(self) -> str:
8081 domain_suffix = self ._data_domain .capitalize ()
8182 return f"PostStack3DVelocity{ domain_suffix } "
8283
83-
84- @pytest .mark .order (1000 )
8584class TestCreateEmptyMdio :
8685 """Tests for create_empty_mdio function."""
8786
8887 @classmethod
89- def _validate_empty_mdio_dataset (cls , ds : xr_Dataset , has_headers : bool , is_velocity : bool ) -> None :
88+ def _create_empty_mdio (cls , create_headers : bool , output_path : Path , overwrite : bool = True ) -> None :
89+ """Create a temporary empty MDIO file for testing."""
90+ # Create the grid with the specified dimensions
91+ dims = [
92+ Dimension (name = "inline" , coords = range (1 , 346 , 1 )), # 100-300 with step 1
93+ Dimension (name = "crossline" , coords = range (1 , 189 , 1 )), # 1000-1600 with step 2
94+ Dimension (name = "time" , coords = range (0 , 3002 , 2 )), # 0-3 seconds 4ms sample rate
95+ ]
96+
97+ # If later on, we want to export to SEG-Y, we need to provide the trace header spec.
98+ # The HeaderSpec can be either standard or customized.
99+ headers = get_teapot_segy_spec ().trace .header if create_headers else None
100+ # Create an empty MDIO v1 metric post-stack 3D time velocity dataset
101+ create_empty (
102+ mdio_template = PostStack3DVelocityTemplate (data_domain = "time" , is_metric = True ),
103+ dimensions = dims ,
104+ output_path = output_path ,
105+ headers = headers ,
106+ overwrite = overwrite ,
107+ )
108+
109+ @classmethod
110+ def validate_teapod_dataset_metadata (cls , ds : xr_Dataset , is_velocity : bool ) -> None :
111+ """Validate the dataset metadata."""
112+ if is_velocity :
113+ assert ds .name == "PostStack3DVelocityTime"
114+ else :
115+ assert ds .name == "PostStack3DTime"
116+
117+ # Check basic metadata attributes
118+ expected_attrs = {
119+ "apiVersion" : __version__ ,
120+ "name" : ds .name ,
121+ }
122+ actual_attrs_json = ds .attrs
123+
124+ # Compare one by one due to ever changing createdOn
125+ for key , value in expected_attrs .items ():
126+ assert key in actual_attrs_json
127+ if key == "createdOn" :
128+ assert actual_attrs_json [key ] is not None
129+ else :
130+ assert actual_attrs_json [key ] == value
131+
132+ # Check that createdOn exists
133+ assert "createdOn" in actual_attrs_json
134+ assert actual_attrs_json ["createdOn" ] is not None
135+
136+ # Validate template attributes
137+ attributes = ds .attrs ["attributes" ]
138+ assert attributes is not None
139+ assert len (attributes ) == 3
140+ # Validate all attributes provided by the abstract template
141+ if is_velocity :
142+ assert attributes ["defaultVariableName" ] == "velocity"
143+ else :
144+ assert attributes ["defaultVariableName" ] == "amplitude"
145+ assert attributes ["surveyType" ] == "3D"
146+ assert attributes ["gatherType" ] == "stacked"
147+
148+ @classmethod
149+ def validate_teapod_dataset_variables (cls , ds : xr_Dataset , header_dtype : np .dtype | None , is_velocity : bool ) -> None :
90150 """Validate an empty MDIO dataset structure and content."""
91151 # Check that the dataset has the expected shape
92152 assert ds .sizes == {"inline" : 345 , "crossline" : 188 , "time" : 1501 }
@@ -102,10 +162,10 @@ def _validate_empty_mdio_dataset(cls, ds: xr_Dataset, has_headers: bool, is_velo
102162 validate_xr_variable (ds , "cdp_x" , {"inline" : 345 , "crossline" : 188 }, UNITS_METER , np .float64 )
103163 validate_xr_variable (ds , "cdp_y" , {"inline" : 345 , "crossline" : 188 }, UNITS_METER , np .float64 )
104164
105- if has_headers :
165+ if header_dtype is not None :
106166 # Validate the headers (should be empty for empty dataset)
107167 # Infer the dtype from segy_spec and ignore endianness
108- header_dtype = get_teapot_segy_spec (). trace . header . dtype .newbyteorder ("native" )
168+ header_dtype = header_dtype .newbyteorder ("native" )
109169 validate_xr_variable (ds , "headers" , {"inline" : 345 , "crossline" : 188 }, UNITS_NONE , header_dtype )
110170 validate_xr_variable (ds , "segy_file_header" , dims = {}, units = UNITS_NONE , data_type = np .dtype ("U1" ))
111171 else :
@@ -127,28 +187,6 @@ def _validate_empty_mdio_dataset(cls, ds: xr_Dataset, has_headers: bool, is_velo
127187 ds , "amplitude" , {"inline" : 345 , "crossline" : 188 , "time" : 1501 }, UNITS_NONE , np .float32
128188 )
129189
130- @classmethod
131- def _create_empty_mdio (cls , create_headers : bool , output_path : Path , overwrite : bool = True ) -> None :
132- """Create a temporary empty MDIO file for testing."""
133- # Create the grid with the specified dimensions
134- dims = [
135- Dimension (name = "inline" , coords = range (1 , 346 , 1 )), # 100-300 with step 1
136- Dimension (name = "crossline" , coords = range (1 , 189 , 1 )), # 1000-1600 with step 2
137- Dimension (name = "time" , coords = range (0 , 3002 , 2 )), # 0-3 seconds 4ms sample rate
138- ]
139-
140- # If later on, we want to export to SEG-Y, we need to provide the trace header spec.
141- # The HeaderSpec can be either standard or customized.
142- headers = get_teapot_segy_spec ().trace .header if create_headers else None
143- # Create an empty MDIO v1 metric post-stack 3D time velocity dataset
144- create_empty (
145- mdio_template = PostStack3DVelocityTemplate (data_domain = "time" , is_metric = True ),
146- dimensions = dims ,
147- output_path = output_path ,
148- headers = headers ,
149- overwrite = overwrite ,
150- )
151-
152190 @pytest .fixture (scope = "class" )
153191 def mdio_with_headers (self , empty_mdio_dir : Path ) -> Path :
154192 """Create a temporary empty MDIO file for testing.
@@ -171,56 +209,22 @@ def mdio_no_headers(self, empty_mdio_dir: Path) -> Path:
171209 self ._create_empty_mdio (create_headers = False , output_path = empty_mdio )
172210 return empty_mdio
173211
174- def validate_dataset_metadata (self , ds : xr_Dataset , is_velocity : bool ) -> None :
175- """Validate the dataset metadata."""
176- if is_velocity :
177- assert ds .name == "PostStack3DVelocityTime"
178- else :
179- assert ds .name == "PostStack3DTime"
180-
181- # Check basic metadata attributes
182- expected_attrs = {
183- "apiVersion" : __version__ ,
184- "name" : ds .name ,
185- }
186- actual_attrs_json = ds .attrs
187-
188- # Compare one by one due to ever changing createdOn
189- for key , value in expected_attrs .items ():
190- assert key in actual_attrs_json
191- if key == "createdOn" :
192- assert actual_attrs_json [key ] is not None
193- else :
194- assert actual_attrs_json [key ] == value
195-
196- # Check that createdOn exists
197- assert "createdOn" in actual_attrs_json
198- assert actual_attrs_json ["createdOn" ] is not None
199-
200- # Validate template attributes
201- attributes = ds .attrs ["attributes" ]
202- assert attributes is not None
203- assert len (attributes ) == 3
204- # Validate all attributes provided by the abstract template
205- if is_velocity :
206- assert attributes ["defaultVariableName" ] == "velocity"
207- else :
208- assert attributes ["defaultVariableName" ] == "amplitude"
209- assert attributes ["surveyType" ] == "3D"
210- assert attributes ["gatherType" ] == "stacked"
211212
212213 def test_dataset_metadata (self , mdio_with_headers : Path ) -> None :
213214 """Test dataset metadata for empty MDIO file."""
214215 ds = open_mdio (mdio_with_headers )
215- self .validate_dataset_metadata (ds , is_velocity = True )
216+ self .validate_teapod_dataset_metadata (ds , is_velocity = True )
217+
216218
217219 def test_variables (self , mdio_with_headers : Path , mdio_no_headers : Path ) -> None :
218220 """Test grid validation for empty MDIO file."""
221+
219222 ds = open_mdio (mdio_with_headers )
220- self ._validate_empty_mdio_dataset (ds , has_headers = True , is_velocity = True )
223+ header_dtype = get_teapot_segy_spec ().trace .header .dtype
224+ self .validate_teapod_dataset_variables (ds , header_dtype = header_dtype , is_velocity = True )
221225
222226 ds = open_mdio (mdio_no_headers )
223- self ._validate_empty_mdio_dataset (ds , has_headers = False , is_velocity = True )
227+ self .validate_teapod_dataset_variables (ds , header_dtype = None , is_velocity = True )
224228
225229 def test_overwrite_behavior (self , empty_mdio_dir : Path ) -> None :
226230 """Test overwrite parameter behavior in create_empty_mdio."""
@@ -246,7 +250,9 @@ def test_overwrite_behavior(self, empty_mdio_dir: Path) -> None:
246250
247251 # Validate that the MDIO file can be loaded correctly using the helper function
248252 ds = open_mdio (empty_mdio )
249- self ._validate_empty_mdio_dataset (ds , has_headers = True , is_velocity = True )
253+ self .validate_teapod_dataset_metadata (ds , is_velocity = True )
254+ header_dtype = get_teapot_segy_spec ().trace .header .dtype
255+ self .validate_teapod_dataset_variables (ds , header_dtype = header_dtype , is_velocity = True )
250256
251257 # Verify the garbage data was overwritten (should not exist)
252258 assert not garbage_file .exists (), "Garbage file should have been overwritten"
@@ -375,16 +381,21 @@ def test_populate_empty_dataset(self, mdio_with_headers: Path) -> None:
375381 output_path = mdio_with_headers .parent / "populated_empty.sgy" ,
376382 )
377383
378- @pytest .mark .order (1001 )
379- @pytest .mark .dependency
380- def test_create_empty_like (self , teapot_mdio_tmp : Path , mdio_with_headers : Path ) -> None :
381- """Create an empty MDIO file like the input file."""
382- _ = mdio_with_headers
384+
385+ def test_create_empty_like (self , teapot_mdio_tmp : Path ) -> None :
386+ """Create an empty MDIO file like the input MDIO file.
387+
388+ This test has to run after the segy_roundtrip_teapot tests have run because
389+ its uses 'teapot_mdio_tmp' created by the segy_roundtrip_teapot tests as the input.
390+ """
391+
383392 ds = create_empty_like (
384393 input_path = teapot_mdio_tmp ,
394+ # TODO: write to a file
385395 output_path = None , # We don't want to write to disk for now
386396 keep_coordinates = True ,
387397 overwrite = True ,
388398 )
389- self .validate_dataset_metadata (ds , is_velocity = False )
390- self ._validate_empty_mdio_dataset (ds , has_headers = True , is_velocity = False )
399+ self .validate_teapod_dataset_metadata (ds , is_velocity = False )
400+ header_dtype = get_teapot_segy_spec ().trace .header .dtype
401+ self .validate_teapod_dataset_variables (ds , header_dtype = header_dtype , is_velocity = False )
0 commit comments