integrate_module 0.99.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- integrate/__init__.py +144 -0
- integrate/gex.py +402 -0
- integrate/integrate.py +4063 -0
- integrate/integrate_borehole.py +1127 -0
- integrate/integrate_hdf5_info_cli.py +122 -0
- integrate/integrate_io.py +5293 -0
- integrate/integrate_plot.py +4986 -0
- integrate/integrate_query.py +1609 -0
- integrate/integrate_rejection.py +1836 -0
- integrate/integrate_rejection_cli.py +210 -0
- integrate/integrate_rejection_jax.py +494 -0
- integrate/integrate_timing_cli.py +407 -0
- integrate/integrate_www_cli.py +8 -0
- integrate_module-0.99.1.dist-info/METADATA +229 -0
- integrate_module-0.99.1.dist-info/RECORD +19 -0
- integrate_module-0.99.1.dist-info/WHEEL +5 -0
- integrate_module-0.99.1.dist-info/entry_points.txt +5 -0
- integrate_module-0.99.1.dist-info/licenses/LICENSE +21 -0
- integrate_module-0.99.1.dist-info/top_level.txt +1 -0
integrate/__init__.py
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import multiprocessing
|
|
2
|
+
multiprocessing.freeze_support()
|
|
3
|
+
|
|
4
|
+
# Import rejection sampling functions from new module
|
|
5
|
+
from integrate.integrate_rejection import integrate_rejection
|
|
6
|
+
from integrate.integrate_rejection import integrate_rejection_range
|
|
7
|
+
from integrate.integrate_rejection import integrate_posterior_chunk
|
|
8
|
+
from integrate.integrate_rejection import integrate_posterior_main
|
|
9
|
+
from integrate.integrate_rejection import likelihood_gaussian_diagonal
|
|
10
|
+
from integrate.integrate_rejection import likelihood_gaussian_diagonal_old
|
|
11
|
+
from integrate.integrate_rejection import likelihood_gaussian_full
|
|
12
|
+
from integrate.integrate_rejection import likelihood_multinomial
|
|
13
|
+
from integrate.integrate_rejection import likelihood_multinomial_old
|
|
14
|
+
from integrate.integrate_rejection import select_subset_for_inversion
|
|
15
|
+
from integrate.integrate_rejection import cleanup_shared_memory
|
|
16
|
+
from integrate.integrate_rejection import reconstruct_shared_arrays
|
|
17
|
+
from integrate.integrate_rejection import create_shared_memory
|
|
18
|
+
from integrate.integrate_rejection import compute_hypothesis_probability
|
|
19
|
+
|
|
20
|
+
# Import other functions from main module
|
|
21
|
+
from integrate.integrate import integrate_update_prior_attributes
|
|
22
|
+
from integrate.integrate import integrate_posterior_stats
|
|
23
|
+
from integrate.integrate import logl_T_est
|
|
24
|
+
from integrate.integrate import lu_post_sample_logl
|
|
25
|
+
from integrate.integrate import prior_data
|
|
26
|
+
from integrate.integrate import prior_data_gaaem
|
|
27
|
+
from integrate.integrate import prior_data_identity
|
|
28
|
+
from integrate.integrate import forward_gaaem
|
|
29
|
+
from integrate.integrate import synthetic_case
|
|
30
|
+
from integrate.integrate import prior_model_layered
|
|
31
|
+
from integrate.integrate import prior_model_workbench
|
|
32
|
+
from integrate.integrate import prior_model_workbench_direct
|
|
33
|
+
from integrate.integrate import posterior_cumulative_thickness
|
|
34
|
+
#from integrate.integrate import integrate_rejection_multi
|
|
35
|
+
from integrate.integrate import use_parallel
|
|
36
|
+
from integrate.integrate import kl_divergence
|
|
37
|
+
from integrate.integrate import entropy
|
|
38
|
+
from integrate.integrate import class_id_to_idx
|
|
39
|
+
from integrate.integrate import is_notebook
|
|
40
|
+
from integrate.integrate import get_hypothesis_probability
|
|
41
|
+
from integrate.integrate import sample_posterior_multiple_hypotheses
|
|
42
|
+
from integrate.integrate import timing_compute
|
|
43
|
+
from integrate.integrate import timing_plot
|
|
44
|
+
#from integrate.integrate import compute_P_obs_from_log
|
|
45
|
+
#from integrate.integrate import rescale_P_obs_temperature
|
|
46
|
+
#from integrate.integrate import Pobs_to_datagrid
|
|
47
|
+
|
|
48
|
+
# Import matplotlib backend setup
|
|
49
|
+
from integrate.integrate_plot import setup_matplotlib_backend
|
|
50
|
+
|
|
51
|
+
from integrate.integrate_io import copy_prior
|
|
52
|
+
from integrate.integrate_io import load_prior
|
|
53
|
+
from integrate.integrate_io import load_prior_data
|
|
54
|
+
from integrate.integrate_io import save_prior_data
|
|
55
|
+
from integrate.integrate_io import load_prior_model
|
|
56
|
+
from integrate.integrate_io import save_prior_model
|
|
57
|
+
from integrate.integrate_io import load_data
|
|
58
|
+
from integrate.integrate_io import get_geometry
|
|
59
|
+
from integrate.integrate_io import extract_feature_at_elevation
|
|
60
|
+
from integrate.integrate_io import get_discrete_classes
|
|
61
|
+
from integrate.integrate_io import get_number_of_datasets
|
|
62
|
+
from integrate.integrate_io import get_number_of_data
|
|
63
|
+
from integrate.integrate_io import read_gex
|
|
64
|
+
from integrate.integrate_io import read_gex_workbench
|
|
65
|
+
from integrate.integrate_io import gex_to_stm
|
|
66
|
+
from integrate.integrate_io import get_gex_file_from_data
|
|
67
|
+
from integrate.integrate_io import write_stm_files
|
|
68
|
+
from integrate.integrate_io import post_to_csv
|
|
69
|
+
from integrate.integrate_io import copy_hdf5_file
|
|
70
|
+
from integrate.integrate_io import hdf5_scan
|
|
71
|
+
from integrate.integrate_io import get_case_data
|
|
72
|
+
from integrate.integrate_io import save_data_gaussian
|
|
73
|
+
from integrate.integrate_io import xyz_to_h5
|
|
74
|
+
from integrate.integrate_io import save_data_multinomial
|
|
75
|
+
from integrate.integrate_io import write_data_gaussian # Deprecated - use save_data_gaussian
|
|
76
|
+
from integrate.integrate_io import write_data_multinomial # Deprecated - use save_data_multinomial
|
|
77
|
+
from integrate.integrate_io import check_data
|
|
78
|
+
from integrate.integrate_io import merge_prior
|
|
79
|
+
from integrate.integrate_io import merge_data
|
|
80
|
+
from integrate.integrate_io import merge_posterior
|
|
81
|
+
from integrate.integrate_io import filter_prior
|
|
82
|
+
from integrate.integrate_io import read_usf
|
|
83
|
+
from integrate.integrate_io import read_usf_mul
|
|
84
|
+
from integrate.integrate_io import test_read_usf
|
|
85
|
+
from integrate.integrate_io import hdf5_info
|
|
86
|
+
from integrate.integrate_io import read_borehole
|
|
87
|
+
from integrate.integrate_io import write_borehole
|
|
88
|
+
|
|
89
|
+
from integrate.integrate_plot import plot_geometry
|
|
90
|
+
from integrate.integrate_plot import plot_profile
|
|
91
|
+
from integrate.integrate_plot import plot_profile_continuous
|
|
92
|
+
from integrate.integrate_plot import plot_profile_discrete
|
|
93
|
+
from integrate.integrate_plot import plot_cumulative_probability_profile
|
|
94
|
+
from integrate.integrate_plot import plot_T_EV
|
|
95
|
+
from integrate.integrate_plot import plot_data_xy
|
|
96
|
+
from integrate.integrate_plot import plot_data
|
|
97
|
+
from integrate.integrate_plot import plot_data_prior_post
|
|
98
|
+
from integrate.integrate_plot import plot_data_prior
|
|
99
|
+
from integrate.integrate_plot import plot_prior_stats
|
|
100
|
+
from integrate.integrate_plot import plot_post_stats
|
|
101
|
+
from integrate.integrate_plot import plot_feature_2d
|
|
102
|
+
from integrate.integrate_plot import plot_posterior_cumulative_thickness
|
|
103
|
+
from integrate.integrate_plot import h5_get_clim_cmap
|
|
104
|
+
from integrate.integrate_plot import get_colormap_and_limits
|
|
105
|
+
from integrate.integrate_plot import plot_xy
|
|
106
|
+
from integrate.integrate_plot import find_points_along_line_segments
|
|
107
|
+
from integrate.integrate_plot import plot_boreholes
|
|
108
|
+
|
|
109
|
+
# Import from borehole module
|
|
110
|
+
from integrate.integrate_borehole import compute_P_obs_discrete
|
|
111
|
+
from integrate.integrate_borehole import compute_P_obs_discrete as Pobs_discrete_compute
|
|
112
|
+
from integrate.integrate_borehole import welllog_compute_P_obs_class_mode
|
|
113
|
+
from integrate.integrate_borehole import rescale_P_obs_temperature
|
|
114
|
+
from integrate.integrate_borehole import rescale_P_obs_temperature as Pobs_rescale_temperature
|
|
115
|
+
from integrate.integrate_borehole import Pobs_to_datagrid
|
|
116
|
+
from integrate.integrate_borehole import get_weight_from_position
|
|
117
|
+
from integrate.integrate_borehole import prior_data_borehole
|
|
118
|
+
from integrate.integrate_borehole import prior_data_borehole_class_mode
|
|
119
|
+
from integrate.integrate_borehole import prior_data_borehole_class_layer
|
|
120
|
+
from integrate.integrate_borehole import save_borehole_data
|
|
121
|
+
|
|
122
|
+
# Import query functions
|
|
123
|
+
from integrate.integrate_query import query
|
|
124
|
+
from integrate.integrate_query import query_probability
|
|
125
|
+
from integrate.integrate_query import query_percentile
|
|
126
|
+
from integrate.integrate_query import query_plot
|
|
127
|
+
from integrate.integrate_query import query_percentile_plot
|
|
128
|
+
from integrate.integrate_query import save_query
|
|
129
|
+
from integrate.integrate_query import load_query
|
|
130
|
+
from integrate.integrate_query import get_prior_model_info
|
|
131
|
+
from integrate.integrate_query import prior_describe
|
|
132
|
+
from integrate.integrate_query import query_from_text
|
|
133
|
+
from integrate.integrate_query import title_from_json
|
|
134
|
+
from integrate.integrate_query import query_test_llm
|
|
135
|
+
|
|
136
|
+
# Import gex module functions
|
|
137
|
+
from integrate.gex import read_gex as read_gex2
|
|
138
|
+
from integrate.gex import describe_gex as describe_gex2
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
# REMOVE CLI IMPORTS - These cause circular dependencies
|
|
142
|
+
# from . import integrate_cli
|
|
143
|
+
# from . import integrate_timing
|
|
144
|
+
|
integrate/gex.py
ADDED
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import re
|
|
3
|
+
from typing import Dict, List, Tuple, Union, Any
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def read_gex(file_gex: str) -> Tuple[Dict[str, Any], List[Dict[str, Any]]]:
|
|
7
|
+
"""
|
|
8
|
+
Read GEX file and return GENERAL section and CHANNELS list.
|
|
9
|
+
|
|
10
|
+
Parameters:
|
|
11
|
+
-----------
|
|
12
|
+
file_gex : str
|
|
13
|
+
Path to the GEX file
|
|
14
|
+
|
|
15
|
+
Returns:
|
|
16
|
+
--------
|
|
17
|
+
GENERAL : dict
|
|
18
|
+
Dictionary containing all key-value pairs from [General] section
|
|
19
|
+
CHANNELS : list
|
|
20
|
+
List of dictionaries, one per channel section
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def parse_value(value_str: str) -> Union[str, int, float, np.ndarray]:
|
|
24
|
+
"""Parse a value string into appropriate data type."""
|
|
25
|
+
value_str = value_str.strip()
|
|
26
|
+
|
|
27
|
+
# Try to split into multiple values (space-separated)
|
|
28
|
+
parts = value_str.split()
|
|
29
|
+
|
|
30
|
+
if len(parts) == 1:
|
|
31
|
+
# Single value
|
|
32
|
+
try:
|
|
33
|
+
# Try integer first
|
|
34
|
+
if '.' not in value_str and 'E' not in value_str.upper() and 'e' not in value_str:
|
|
35
|
+
return int(value_str)
|
|
36
|
+
else:
|
|
37
|
+
# Try float
|
|
38
|
+
return float(value_str)
|
|
39
|
+
except ValueError:
|
|
40
|
+
# Return as string
|
|
41
|
+
return value_str
|
|
42
|
+
else:
|
|
43
|
+
# Multiple values - try to convert to numpy array of floats
|
|
44
|
+
try:
|
|
45
|
+
return np.array([float(p) for p in parts])
|
|
46
|
+
except ValueError:
|
|
47
|
+
# If conversion fails, return as string
|
|
48
|
+
return value_str
|
|
49
|
+
|
|
50
|
+
def group_numbered_keys(section_dict: Dict[str, Any]) -> Dict[str, Any]:
|
|
51
|
+
"""Group numbered keys into 2D numpy arrays."""
|
|
52
|
+
grouped = {}
|
|
53
|
+
numbered_groups = {}
|
|
54
|
+
|
|
55
|
+
for key, value in section_dict.items():
|
|
56
|
+
# Check if key ends with a number
|
|
57
|
+
match = re.match(r'^(.+?)(\d+)$', key)
|
|
58
|
+
if match:
|
|
59
|
+
base_name = match.group(1)
|
|
60
|
+
number = int(match.group(2))
|
|
61
|
+
|
|
62
|
+
if base_name not in numbered_groups:
|
|
63
|
+
numbered_groups[base_name] = {}
|
|
64
|
+
numbered_groups[base_name][number] = value
|
|
65
|
+
else:
|
|
66
|
+
grouped[key] = value
|
|
67
|
+
|
|
68
|
+
# Convert numbered groups to 2D arrays
|
|
69
|
+
for base_name, numbered_dict in numbered_groups.items():
|
|
70
|
+
# Sort by number and collect values
|
|
71
|
+
sorted_items = sorted(numbered_dict.items())
|
|
72
|
+
values = [item[1] for item in sorted_items]
|
|
73
|
+
|
|
74
|
+
# If all values are numpy arrays of the same length, stack them
|
|
75
|
+
if all(isinstance(v, np.ndarray) for v in values):
|
|
76
|
+
try:
|
|
77
|
+
grouped[base_name] = np.array(values)
|
|
78
|
+
except:
|
|
79
|
+
# If stacking fails, keep as list
|
|
80
|
+
grouped[base_name] = values
|
|
81
|
+
else:
|
|
82
|
+
# Keep as list if not all numpy arrays
|
|
83
|
+
grouped[base_name] = values
|
|
84
|
+
|
|
85
|
+
return grouped
|
|
86
|
+
|
|
87
|
+
# Read the file
|
|
88
|
+
with open(file_gex, 'r') as f:
|
|
89
|
+
lines = f.readlines()
|
|
90
|
+
|
|
91
|
+
GENERAL = {}
|
|
92
|
+
CHANNELS = []
|
|
93
|
+
current_section = None
|
|
94
|
+
current_dict = None
|
|
95
|
+
|
|
96
|
+
for line in lines:
|
|
97
|
+
line = line.strip()
|
|
98
|
+
|
|
99
|
+
# Skip empty lines and comments
|
|
100
|
+
if not line or line.startswith('/'):
|
|
101
|
+
continue
|
|
102
|
+
|
|
103
|
+
# Check for section headers
|
|
104
|
+
if line.startswith('[') and line.endswith(']'):
|
|
105
|
+
# Save previous section if it exists
|
|
106
|
+
if current_section is not None and current_dict is not None:
|
|
107
|
+
if current_section.lower() == 'general':
|
|
108
|
+
GENERAL = group_numbered_keys(current_dict)
|
|
109
|
+
elif current_section.lower().startswith('channel'):
|
|
110
|
+
CHANNELS.append(group_numbered_keys(current_dict))
|
|
111
|
+
|
|
112
|
+
# Start new section
|
|
113
|
+
current_section = line[1:-1] # Remove brackets
|
|
114
|
+
current_dict = {}
|
|
115
|
+
continue
|
|
116
|
+
|
|
117
|
+
# Parse key-value pairs
|
|
118
|
+
if '=' in line and current_dict is not None:
|
|
119
|
+
key, value = line.split('=', 1)
|
|
120
|
+
key = key.strip()
|
|
121
|
+
value = value.strip()
|
|
122
|
+
|
|
123
|
+
if value: # Only process non-empty values
|
|
124
|
+
current_dict[key] = parse_value(value)
|
|
125
|
+
|
|
126
|
+
# Handle the last section
|
|
127
|
+
if current_section is not None and current_dict is not None:
|
|
128
|
+
if current_section.lower() == 'general':
|
|
129
|
+
GENERAL = group_numbered_keys(current_dict)
|
|
130
|
+
elif current_section.lower().startswith('channel'):
|
|
131
|
+
CHANNELS.append(group_numbered_keys(current_dict))
|
|
132
|
+
|
|
133
|
+
return GENERAL, CHANNELS
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def describe_gex(input_data, channels_data=None):
|
|
137
|
+
"""
|
|
138
|
+
Describe a GEX system configuration in a human-readable format.
|
|
139
|
+
|
|
140
|
+
Parameters:
|
|
141
|
+
-----------
|
|
142
|
+
input_data : str or dict
|
|
143
|
+
Either a GEX filename (str) or a GENERAL dictionary
|
|
144
|
+
channels_data : list of dict, optional
|
|
145
|
+
List of channel dictionaries. Required if input_data is a dict.
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
--------
|
|
149
|
+
None
|
|
150
|
+
Prints description to screen
|
|
151
|
+
"""
|
|
152
|
+
|
|
153
|
+
# Handle input - either filename or dictionaries
|
|
154
|
+
if isinstance(input_data, str):
|
|
155
|
+
# It's a filename
|
|
156
|
+
filename = input_data
|
|
157
|
+
general, channels = read_gex(filename)
|
|
158
|
+
print(f"GEX System Description: {filename}")
|
|
159
|
+
else:
|
|
160
|
+
# It's a dictionary
|
|
161
|
+
general = input_data
|
|
162
|
+
channels = channels_data or []
|
|
163
|
+
print("GEX System Description")
|
|
164
|
+
|
|
165
|
+
print("=" * 60)
|
|
166
|
+
|
|
167
|
+
# System identification
|
|
168
|
+
description = general.get('Description', 'Unknown')
|
|
169
|
+
data_type = general.get('DataType', 'Unknown')
|
|
170
|
+
print(f"System: {description}")
|
|
171
|
+
if data_type != 'Unknown':
|
|
172
|
+
print(f"Data Type: {data_type}")
|
|
173
|
+
|
|
174
|
+
print()
|
|
175
|
+
|
|
176
|
+
# Transmitter loop information
|
|
177
|
+
loop_type = general.get('LoopType', 'Unknown')
|
|
178
|
+
tx_area = general.get('TxLoopArea', 'Unknown')
|
|
179
|
+
turns_lm = general.get('NumberOfTurnsLM', 'Unknown')
|
|
180
|
+
turns_hm = general.get('NumberOfTurnsHM', 'Unknown')
|
|
181
|
+
|
|
182
|
+
print("TRANSMITTER CONFIGURATION:")
|
|
183
|
+
print(f" Loop Type: {loop_type}")
|
|
184
|
+
print(f" Loop Area: {tx_area} m²")
|
|
185
|
+
print(f" Turns (Low Moment): {turns_lm}")
|
|
186
|
+
print(f" Turns (High Moment): {turns_hm}")
|
|
187
|
+
|
|
188
|
+
# Transmitter loop geometry
|
|
189
|
+
if 'TxLoopPoint' in general:
|
|
190
|
+
tx_points = general['TxLoopPoint']
|
|
191
|
+
if isinstance(tx_points, np.ndarray):
|
|
192
|
+
n_points = len(tx_points)
|
|
193
|
+
if n_points == 4:
|
|
194
|
+
loop_shape = "Rectangular"
|
|
195
|
+
elif n_points == 8:
|
|
196
|
+
loop_shape = "Octagonal"
|
|
197
|
+
else:
|
|
198
|
+
loop_shape = f"{n_points}-sided polygon"
|
|
199
|
+
|
|
200
|
+
print(f" Loop Shape: {loop_shape} ({n_points} points)")
|
|
201
|
+
|
|
202
|
+
# Calculate approximate dimensions
|
|
203
|
+
x_coords = tx_points[:, 0]
|
|
204
|
+
y_coords = tx_points[:, 1]
|
|
205
|
+
x_range = np.max(x_coords) - np.min(x_coords)
|
|
206
|
+
y_range = np.max(y_coords) - np.min(y_coords)
|
|
207
|
+
print(f" Dimensions: {x_range:.2f} x {y_range:.2f} m")
|
|
208
|
+
|
|
209
|
+
print()
|
|
210
|
+
|
|
211
|
+
# Receiver configuration
|
|
212
|
+
print("RECEIVER CONFIGURATION:")
|
|
213
|
+
if 'RxCoilPosition' in general:
|
|
214
|
+
rx_pos = general['RxCoilPosition']
|
|
215
|
+
if isinstance(rx_pos, np.ndarray) and len(rx_pos) > 0:
|
|
216
|
+
if len(rx_pos) == 1:
|
|
217
|
+
x, y, z = rx_pos[0]
|
|
218
|
+
print(f" Receiver Position: ({x:.2f}, {y:.2f}, {z:.2f}) m")
|
|
219
|
+
# Calculate offset from transmitter
|
|
220
|
+
if 'TxCoilPosition' in general:
|
|
221
|
+
tx_pos = general['TxCoilPosition'][0]
|
|
222
|
+
offset = np.sqrt((x - tx_pos[0])**2 + (y - tx_pos[1])**2)
|
|
223
|
+
print(f" Tx-Rx Offset: {offset:.2f} m")
|
|
224
|
+
else:
|
|
225
|
+
print(f" Multiple Receivers: {len(rx_pos)} positions")
|
|
226
|
+
|
|
227
|
+
print()
|
|
228
|
+
|
|
229
|
+
# Timing configuration
|
|
230
|
+
print("TIMING CONFIGURATION:")
|
|
231
|
+
front_gate_delay = general.get('FrontGateDelay', 'Unknown')
|
|
232
|
+
print(f" Front Gate Delay: {front_gate_delay}")
|
|
233
|
+
|
|
234
|
+
if 'GateTime' in general:
|
|
235
|
+
gate_times = general['GateTime']
|
|
236
|
+
if isinstance(gate_times, np.ndarray):
|
|
237
|
+
n_gates = len(gate_times)
|
|
238
|
+
if gate_times.shape[1] >= 3:
|
|
239
|
+
# Assume format: [center, start, end]
|
|
240
|
+
earliest = np.min(gate_times[:, 1]) # earliest start time
|
|
241
|
+
latest = np.max(gate_times[:, 2]) # latest end time
|
|
242
|
+
print(f" Number of Gates: {n_gates}")
|
|
243
|
+
print(f" Time Window: {earliest:.2e} to {latest:.2e} seconds")
|
|
244
|
+
|
|
245
|
+
# Show early and late time examples
|
|
246
|
+
print(f" Early Gates: {gate_times[0, 0]:.2e}, {gate_times[1, 0]:.2e}, {gate_times[2, 0]:.2e} s")
|
|
247
|
+
if n_gates > 3:
|
|
248
|
+
print(f" Late Gates: {gate_times[-3, 0]:.2e}, {gate_times[-2, 0]:.2e}, {gate_times[-1, 0]:.2e} s")
|
|
249
|
+
|
|
250
|
+
print()
|
|
251
|
+
|
|
252
|
+
# Waveform information
|
|
253
|
+
print("WAVEFORM CONFIGURATION:")
|
|
254
|
+
waveform_types = []
|
|
255
|
+
if 'WaveformLMPoint' in general:
|
|
256
|
+
wf_lm = general['WaveformLMPoint']
|
|
257
|
+
if isinstance(wf_lm, np.ndarray):
|
|
258
|
+
n_points_lm = len(wf_lm)
|
|
259
|
+
waveform_types.append(f"Low Moment ({n_points_lm} points)")
|
|
260
|
+
|
|
261
|
+
if 'WaveformHMPoint' in general:
|
|
262
|
+
wf_hm = general['WaveformHMPoint']
|
|
263
|
+
if isinstance(wf_hm, np.ndarray):
|
|
264
|
+
n_points_hm = len(wf_hm)
|
|
265
|
+
waveform_types.append(f"High Moment ({n_points_hm} points)")
|
|
266
|
+
|
|
267
|
+
if waveform_types:
|
|
268
|
+
print(f" Available Waveforms: {', '.join(waveform_types)}")
|
|
269
|
+
else:
|
|
270
|
+
print(" Waveform Data: Not available")
|
|
271
|
+
|
|
272
|
+
print()
|
|
273
|
+
|
|
274
|
+
# Channel information
|
|
275
|
+
print("CHANNEL CONFIGURATION:")
|
|
276
|
+
n_channels = len(channels)
|
|
277
|
+
print(f" Number of Channels: {n_channels}")
|
|
278
|
+
|
|
279
|
+
if n_channels > 0:
|
|
280
|
+
for i, channel in enumerate(channels):
|
|
281
|
+
print(f"\n Channel {i+1}:")
|
|
282
|
+
|
|
283
|
+
# Channel-specific parameters
|
|
284
|
+
rx_coil_num = channel.get('RxCoilNumber', 'Unknown')
|
|
285
|
+
print(f" Receiver Coil: {rx_coil_num}")
|
|
286
|
+
|
|
287
|
+
no_gates = channel.get('NoGates', 'Unknown')
|
|
288
|
+
if no_gates != 'Unknown':
|
|
289
|
+
print(f" Number of Gates: {no_gates}")
|
|
290
|
+
|
|
291
|
+
rep_freq = channel.get('RepFreq', 'Unknown')
|
|
292
|
+
if rep_freq != 'Unknown':
|
|
293
|
+
print(f" Repetition Frequency: {rep_freq} Hz")
|
|
294
|
+
|
|
295
|
+
front_gate_time = channel.get('FrontGateTime', 'Unknown')
|
|
296
|
+
if front_gate_time != 'Unknown':
|
|
297
|
+
print(f" Front Gate Time: {front_gate_time} s")
|
|
298
|
+
|
|
299
|
+
# Data processing parameters
|
|
300
|
+
uniform_std = channel.get('UniformDataSTD', 'Unknown')
|
|
301
|
+
if uniform_std != 'Unknown':
|
|
302
|
+
print(f" Uniform Data STD: {uniform_std}")
|
|
303
|
+
|
|
304
|
+
remove_gates = channel.get('RemoveInitialGates', 'Unknown')
|
|
305
|
+
if remove_gates != 'Unknown':
|
|
306
|
+
print(f" Remove Initial Gates: {remove_gates}")
|
|
307
|
+
|
|
308
|
+
print()
|
|
309
|
+
|
|
310
|
+
# Position information
|
|
311
|
+
if any(key in general for key in ['GPSPosition', 'TxCoilPosition', 'AltimeterPosition']):
|
|
312
|
+
print("POSITION INFORMATION:")
|
|
313
|
+
|
|
314
|
+
if 'GPSPosition' in general:
|
|
315
|
+
gps_pos = general['GPSPosition']
|
|
316
|
+
if isinstance(gps_pos, np.ndarray) and len(gps_pos) > 0:
|
|
317
|
+
if len(gps_pos) == 1:
|
|
318
|
+
x, y, z = gps_pos[0]
|
|
319
|
+
print(f" GPS Position: ({x:.2f}, {y:.2f}, {z:.2f})")
|
|
320
|
+
else:
|
|
321
|
+
print(f" GPS Positions: {len(gps_pos)} locations")
|
|
322
|
+
|
|
323
|
+
if 'TxCoilPosition' in general:
|
|
324
|
+
tx_pos = general['TxCoilPosition']
|
|
325
|
+
if isinstance(tx_pos, np.ndarray) and len(tx_pos) > 0:
|
|
326
|
+
x, y, z = tx_pos[0]
|
|
327
|
+
print(f" Transmitter Position: ({x:.2f}, {y:.2f}, {z:.2f}) m")
|
|
328
|
+
|
|
329
|
+
if 'AltimeterPosition' in general:
|
|
330
|
+
alt_pos = general['AltimeterPosition']
|
|
331
|
+
if isinstance(alt_pos, np.ndarray) and len(alt_pos) > 0:
|
|
332
|
+
if not np.allclose(alt_pos, 0): # Only show if not all zeros
|
|
333
|
+
x, y, z = alt_pos[0]
|
|
334
|
+
print(f" Altimeter Position: ({x:.2f}, {y:.2f}, {z:.2f}) m")
|
|
335
|
+
|
|
336
|
+
print()
|
|
337
|
+
|
|
338
|
+
# Summary
|
|
339
|
+
print("SYSTEM SUMMARY:")
|
|
340
|
+
summary_points = []
|
|
341
|
+
|
|
342
|
+
if data_type != 'Unknown':
|
|
343
|
+
summary_points.append(f"{data_type} system")
|
|
344
|
+
|
|
345
|
+
if 'TxLoopPoint' in general:
|
|
346
|
+
tx_points = general['TxLoopPoint']
|
|
347
|
+
if isinstance(tx_points, np.ndarray):
|
|
348
|
+
n_points = len(tx_points)
|
|
349
|
+
if n_points == 4:
|
|
350
|
+
summary_points.append("rectangular transmitter loop")
|
|
351
|
+
elif n_points == 8:
|
|
352
|
+
summary_points.append("octagonal transmitter loop")
|
|
353
|
+
else:
|
|
354
|
+
summary_points.append(f"{n_points}-sided transmitter loop")
|
|
355
|
+
|
|
356
|
+
if tx_area != 'Unknown':
|
|
357
|
+
summary_points.append(f"{tx_area} m² loop area")
|
|
358
|
+
|
|
359
|
+
summary_points.append(f"{n_channels} channel{'s' if n_channels != 1 else ''}")
|
|
360
|
+
|
|
361
|
+
if 'GateTime' in general:
|
|
362
|
+
gate_times = general['GateTime']
|
|
363
|
+
if isinstance(gate_times, np.ndarray):
|
|
364
|
+
n_gates = len(gate_times)
|
|
365
|
+
summary_points.append(f"{n_gates} time gates")
|
|
366
|
+
|
|
367
|
+
print(f" • {', '.join(summary_points).capitalize()}")
|
|
368
|
+
|
|
369
|
+
print("=" * 60)
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
if __name__ == "__main__":
|
|
373
|
+
# Test the functions
|
|
374
|
+
print("Testing read_gex() and describe_gex() functions...")
|
|
375
|
+
|
|
376
|
+
# Test files
|
|
377
|
+
test_files = [
|
|
378
|
+
'202500404_DEN_Diamond_Soeballe_mergedGates_SR.gex',
|
|
379
|
+
'TX07_20230731_2x4_RC20-33.gex'
|
|
380
|
+
]
|
|
381
|
+
|
|
382
|
+
for filename in test_files:
|
|
383
|
+
print(f"\n{'='*80}")
|
|
384
|
+
print(f"Testing describe_gex() with filename: {filename}")
|
|
385
|
+
print(f"{'='*80}")
|
|
386
|
+
|
|
387
|
+
try:
|
|
388
|
+
# Test with filename
|
|
389
|
+
describe_gex(filename)
|
|
390
|
+
|
|
391
|
+
print(f"\n{'='*80}")
|
|
392
|
+
print(f"Testing describe_gex() with dictionaries from: {filename}")
|
|
393
|
+
print(f"{'='*80}")
|
|
394
|
+
|
|
395
|
+
# Test with dictionaries
|
|
396
|
+
general, channels = read_gex(filename)
|
|
397
|
+
describe_gex(general, channels)
|
|
398
|
+
|
|
399
|
+
except Exception as e:
|
|
400
|
+
print(f"Error testing {filename}: {e}")
|
|
401
|
+
import traceback
|
|
402
|
+
traceback.print_exc()
|