@@ -60,6 +60,28 @@ class StreamerShotGeometryType(Enum):
6060 C = auto ()
6161
6262
63+ class ShotGunGeometryType (Enum ):
64+ r"""Shot geometry template types for multi-gun acquisition.
65+
66+ For shot lines with multiple guns, we can have two configurations for
67+ numbering shot_point. The desired index is to have the shot point index
68+ for a given gun to be dense and unique (configuration A). Typically the
69+ shot_point is unique for the line and therefore is not dense for each
70+ gun (configuration B).
71+
72+ Configuration A:
73+ Gun 1 -> 1------------------20
74+ Gun 2 -> 1------------------20
75+
76+ Configuration B:
77+ Gun 1 -> 1------------------39
78+ Gun 2 -> 2------------------40
79+
80+ """
81+ A = auto ()
82+ B = auto ()
83+
84+
6385def analyze_streamer_headers (
6486 index_headers : dict [str , npt .NDArray ],
6587) -> tuple [npt .NDArray , npt .NDArray , npt .NDArray , StreamerShotGeometryType ]:
@@ -91,6 +113,7 @@ def analyze_streamer_headers(
91113
92114 # Check channel numbers do not overlap for case B
93115 geom_type = StreamerShotGeometryType .B
116+
94117 for idx1 , cable1 in enumerate (unique_cables ):
95118 min_val1 = cable_chan_min [idx1 ]
96119 max_val1 = cable_chan_max [idx1 ]
@@ -124,13 +147,65 @@ def analyze_streamer_headers(
124147 return unique_cables , cable_chan_min , cable_chan_max , geom_type
125148
126149
150+ def analyze_shotlines_for_guns (
151+ index_headers : dict [str , npt .NDArray ],
152+ ) -> tuple [npt .NDArray , npt .NDArray , ShotGunGeometryType ]:
153+ """Check input headers for SEG-Y input to help determine geometry of shots and guns.
154+
155+ This function reads in trace_qc_count headers and finds the unique gun values.
156+ The function then checks to ensure shot numbers are dense.
157+
158+ Args:
159+ index_headers: numpy array with index headers
160+
161+ Returns:
162+ tuple of unique_shot_lines, unique_guns_in_shot_line, geom_type
163+ """
164+ # Find unique cable ids
165+ unique_shot_lines = np .sort (np .unique (index_headers ["shot_line" ]))
166+ unique_guns = np .sort (np .unique (index_headers ["gun" ]))
167+ logger .info (f"unique_shot_lines: { unique_shot_lines } " )
168+ logger .info (f"unique_guns: { unique_guns } " )
169+
170+ # Find channel min and max values for each cable
171+ # unique_guns_in_shot_line = np.empty(unique_shot_lines.shape)
172+ unique_guns_in_shot_line = dict ()
173+
174+ geom_type = ShotGunGeometryType .B
175+ # Check shot numbers are still unique if div/num_guns
176+ for shot_line in unique_shot_lines :
177+ shot_line_mask = index_headers ["shot_line" ] == shot_line
178+ shot_current_sl = index_headers ["shot_point" ][shot_line_mask ]
179+ gun_current_sl = index_headers ["gun" ][shot_line_mask ]
180+
181+ unique_guns_sl = np .sort (np .unique (gun_current_sl ))
182+ num_guns_sl = unique_guns_sl .shape [0 ]
183+ # unique_guns_in_shot_line[idx] = list(unique_guns_sl)
184+ unique_guns_in_shot_line [str (shot_line )] = list (unique_guns_sl )
185+
186+ for gun in unique_guns_sl :
187+ gun_mask = gun_current_sl == gun
188+ shots_current_sl_gun = shot_current_sl [gun_mask ]
189+ num_shots_sl = np .unique (shots_current_sl_gun ).shape [0 ]
190+ mod_shots = np .floor (shots_current_sl_gun / num_guns_sl )
191+ if len (np .unique (mod_shots )) != num_shots_sl :
192+ msg = (
193+ f"Shot line { shot_line } has { num_shots_sl } when using div by "
194+ f"{ num_guns_sl } (num_guns) has { np .unique (mod_shots )} unique mod shots."
195+ )
196+ logger .info (msg )
197+ geom_type = ShotGunGeometryType .A
198+ return unique_shot_lines , unique_guns_in_shot_line , geom_type
199+ return unique_shot_lines , unique_guns_in_shot_line , geom_type
200+
201+
127202def create_counter (
128203 depth : int ,
129204 total_depth : int ,
130205 unique_headers : dict [str , npt .NDArray ],
131206 header_names : list [str ],
132207):
133- """Helper funtion to create dictionary tree for counting trace key for auto index."""
208+ """Helper function to create dictionary tree for counting trace key for auto index."""
134209 if depth == total_depth :
135210 return 0
136211
@@ -490,6 +565,54 @@ def transform(
490565 return index_headers
491566
492567
568+ class AutoShotWrap (GridOverrideCommand ):
569+ """Automatically determine ShotGun acquisition type."""
570+
571+ required_keys = {"shot_line" , "gun" , "shot_point" , "cable" , "channel" }
572+ required_parameters = None
573+
574+ def validate (
575+ self ,
576+ index_headers : dict [str , npt .NDArray ],
577+ grid_overrides : dict [str , bool | int ],
578+ ) -> None :
579+ """Validate if this transform should run on the type of data."""
580+ self .check_required_keys (index_headers )
581+ self .check_required_params (grid_overrides )
582+
583+ def transform (
584+ self ,
585+ index_headers : dict [str , npt .NDArray ],
586+ grid_overrides : dict [str , bool | int ],
587+ ) -> dict [str , npt .NDArray ]:
588+ """Perform the grid transform."""
589+ self .validate (index_headers , grid_overrides )
590+
591+ result = analyze_shotlines_for_guns (index_headers )
592+ unique_shot_lines , unique_guns_in_shot_line , geom_type = result
593+ logger .info (f"Ingesting dataset as shot type: { geom_type .name } " )
594+
595+ # TODO: Add strict=True and remove noqa when min Python is 3.10
596+ max_num_guns = 1
597+ for shot_line in unique_shot_lines :
598+ logger .info (
599+ f"shot_line: { shot_line } has guns: { unique_guns_in_shot_line [str (shot_line )]} "
600+ )
601+ num_guns = len (unique_guns_in_shot_line [str (shot_line )])
602+ if num_guns > max_num_guns :
603+ max_num_guns = num_guns
604+
605+ # This might be slow and potentially could be improved with a rewrite
606+ # to prevent so many lookups
607+ if geom_type == ShotGunGeometryType .B :
608+ for shot_line in unique_shot_lines :
609+ shot_line_idxs = np .where (index_headers ["shot_line" ][:] == shot_line )
610+ index_headers ["shot_point" ][shot_line_idxs ] = np .floor (
611+ index_headers ["shot_point" ][shot_line_idxs ] / max_num_guns
612+ )
613+ return index_headers
614+
615+
493616class GridOverrider :
494617 """Executor for grid overrides.
495618
@@ -503,6 +626,7 @@ def __init__(self):
503626 """Define allowed overrides and parameters here."""
504627 self .commands = {
505628 "AutoChannelWrap" : AutoChannelWrap (),
629+ "AutoShotWrap" : AutoShotWrap (),
506630 "CalculateCable" : CalculateCable (),
507631 "ChannelWrap" : ChannelWrap (),
508632 "NonBinned" : NonBinned (),
0 commit comments