beampower 1.0.3__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.
- beampower/__init__.py +14 -0
- beampower/beampower.py +287 -0
- beampower/core.py +169 -0
- beampower/lib/beamform_cpu.so +0 -0
- beampower/src/beamform.c +394 -0
- beampower/src/beamform.cu +661 -0
- beampower/src/beamform_cpu.h +7 -0
- beampower/src/beamform_gpu.h +2 -0
- beampower-1.0.3.dist-info/METADATA +104 -0
- beampower-1.0.3.dist-info/RECORD +13 -0
- beampower-1.0.3.dist-info/WHEEL +5 -0
- beampower-1.0.3.dist-info/licenses/LICENSE +21 -0
- beampower-1.0.3.dist-info/top_level.txt +1 -0
beampower/__init__.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""
|
|
2
|
+
beampower is a Python package wrapping a C and CUDA-C implementation
|
|
3
|
+
of beamforming, sometimes also called back-projection.
|
|
4
|
+
|
|
5
|
+
If you have any question, don't hesitate contacting me at:
|
|
6
|
+
ebeauce@ldeo.columbia.edu
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
__all__ = ["load_library", "beamform"]
|
|
10
|
+
|
|
11
|
+
from .core import load_library
|
|
12
|
+
from .beampower import beamform
|
|
13
|
+
|
|
14
|
+
__version__ = "1.0.3"
|
beampower/beampower.py
ADDED
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
|
|
3
|
+
import ctypes as ct
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
from .core import load_library
|
|
7
|
+
from os import cpu_count
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def beamform(
|
|
11
|
+
waveform_features,
|
|
12
|
+
time_delays,
|
|
13
|
+
weights_phases,
|
|
14
|
+
weights_sources,
|
|
15
|
+
device="cpu",
|
|
16
|
+
reduce="max",
|
|
17
|
+
mode="direct",
|
|
18
|
+
out_of_bounds="strict",
|
|
19
|
+
num_threads=None,
|
|
20
|
+
):
|
|
21
|
+
"""Compute the beamformed network response.
|
|
22
|
+
|
|
23
|
+
This routine computes and returns the whole network response over the
|
|
24
|
+
entire duration of `waveform_features`. Thus, if the input source grid
|
|
25
|
+
is large, the output might be considerably memory-consuming. Therefore,
|
|
26
|
+
this routine is more appropriate for small scale studies such as the
|
|
27
|
+
rupture imaging of an earthquake.
|
|
28
|
+
|
|
29
|
+
Parameters
|
|
30
|
+
----------
|
|
31
|
+
waveform_features: (n_stations, n_channels, n_samples) numpy.ndarray, float
|
|
32
|
+
Any characterization function computed from the continuous seismograms.
|
|
33
|
+
time_delays: (n_sources, n_stations, n_phases) numpy.ndarray, int
|
|
34
|
+
Moveouts, in samples, from each of the `n_sources` theoretical sources
|
|
35
|
+
to each of the `n_stations` seismic stations and for the `n_phases`
|
|
36
|
+
back-projected seismic phases.
|
|
37
|
+
weights_phases: (n_stations, n_channels, n_phases) numpy.ndarray, float
|
|
38
|
+
Weight given to each station and channel for a given phase. For
|
|
39
|
+
example, horizontal components might be given a small or zero
|
|
40
|
+
weight for the P-wave stacking.
|
|
41
|
+
weights_sources: (n_sources, n_stations) numpy.ndarray, float
|
|
42
|
+
Source-receiver-specific weights. For example, based on the
|
|
43
|
+
source-receiver distance.
|
|
44
|
+
device: string, default to 'cpu'
|
|
45
|
+
Either 'cpu' or 'gpu', depending on the available hardware and
|
|
46
|
+
user's preferences.
|
|
47
|
+
reduce: string, default to 'max'
|
|
48
|
+
Reduction operation applied to the beamformed network response. If
|
|
49
|
+
`reduce` is `'max'`, return the maximum network response of the grid at
|
|
50
|
+
each time step, as well as the source indexes. If `reduce` is `'none'`,
|
|
51
|
+
`None` or `'None'`, return the full beamformed network response.
|
|
52
|
+
mode: string, default to 'direct'
|
|
53
|
+
Either 'direct' (default) or 'differential'. If 'direct', the time
|
|
54
|
+
delays are the (relative) source-receiver propagation times. If
|
|
55
|
+
'differential', the time delays are the inter-station differential
|
|
56
|
+
propagation times. The latter requires `waveform_features` to be based
|
|
57
|
+
on inter-station cross-correlations.
|
|
58
|
+
out_of_bounds: string, default to 'strict'
|
|
59
|
+
Either 'strict' (default) or 'flexible'.
|
|
60
|
+
|
|
61
|
+
- 'strict': A beam is computed if and only if the moveouts point to a
|
|
62
|
+
valid sample (that is, within the bounds of the data stream) for every
|
|
63
|
+
channel used in the beam.
|
|
64
|
+
- 'flexible': A beam is computed as long as the moveouts point to a
|
|
65
|
+
valid sample for at least one channel. This option is particularly
|
|
66
|
+
useful for real time applications where an event might have been
|
|
67
|
+
recorded at the closest stations but not yet at the more distant ones.
|
|
68
|
+
num_threads: int or None
|
|
69
|
+
Number of threads for CPU parallelization. If None, uses one thread per
|
|
70
|
+
available (visible) CPU.
|
|
71
|
+
|
|
72
|
+
Returns
|
|
73
|
+
--------
|
|
74
|
+
beam: (n_sources, n_samples) or (n_samples,) numpy.ndarray, float
|
|
75
|
+
Full network response (n_sources, n_samples) or maximum network
|
|
76
|
+
response (n_samples,). See `reduce`.
|
|
77
|
+
beam_argmax: (n_samples,) numpy.ndarray, int, optional
|
|
78
|
+
If `reduce` is `'max'`, return the maximum network response source
|
|
79
|
+
indexes.
|
|
80
|
+
"""
|
|
81
|
+
if out_of_bounds not in ["strict", "flexible"]:
|
|
82
|
+
print("out_of_bounds should be either of 'strict' or 'flexible',"
|
|
83
|
+
f" not {out_of_bounds}")
|
|
84
|
+
return
|
|
85
|
+
elif out_of_bounds == "strict":
|
|
86
|
+
out_of_bounds = 0
|
|
87
|
+
elif out_of_bounds == "flexible":
|
|
88
|
+
out_of_bounds = 1
|
|
89
|
+
|
|
90
|
+
if num_threads is None:
|
|
91
|
+
# set num_threads to -1 so that the C routine
|
|
92
|
+
# understands to use all CPUs
|
|
93
|
+
num_threads = cpu_count()
|
|
94
|
+
|
|
95
|
+
# Load library
|
|
96
|
+
lib = load_library(device)
|
|
97
|
+
|
|
98
|
+
# Get shapes
|
|
99
|
+
n_stations, _, n_samples = waveform_features.shape
|
|
100
|
+
n_sources, _, n_phases = time_delays.shape
|
|
101
|
+
|
|
102
|
+
# Prestack detection traces
|
|
103
|
+
waveform_features = prestack_traces(
|
|
104
|
+
waveform_features, weights_phases, num_threads=num_threads, device="cpu"
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
# Get waveform features
|
|
108
|
+
waveform_features = waveform_features.flatten().astype(np.float32)
|
|
109
|
+
time_delays = time_delays.flatten().astype(np.int32)
|
|
110
|
+
weights_sources = weights_sources.flatten().astype(np.float32)
|
|
111
|
+
|
|
112
|
+
# Essential feature
|
|
113
|
+
if np.random.random() < 1.0e-6:
|
|
114
|
+
print("beampower to the people!")
|
|
115
|
+
|
|
116
|
+
if mode in ["normal", "direct"]:
|
|
117
|
+
# time delays are (relative) source-receiver propagation times
|
|
118
|
+
|
|
119
|
+
# We keep four cases separate in case the signature differs
|
|
120
|
+
if device.lower() == "cpu":
|
|
121
|
+
|
|
122
|
+
if reduce in ["none", "None", None]:
|
|
123
|
+
beam = np.zeros(n_sources * n_samples, dtype=np.float32)
|
|
124
|
+
lib.beamform(
|
|
125
|
+
waveform_features.ctypes.data_as(ct.POINTER(ct.c_float)),
|
|
126
|
+
time_delays.ctypes.data_as(ct.POINTER(ct.c_int)),
|
|
127
|
+
weights_sources.ctypes.data_as(ct.POINTER(ct.c_float)),
|
|
128
|
+
n_samples,
|
|
129
|
+
n_sources,
|
|
130
|
+
n_stations,
|
|
131
|
+
n_phases,
|
|
132
|
+
int(out_of_bounds),
|
|
133
|
+
int(num_threads),
|
|
134
|
+
beam.ctypes.data_as(ct.POINTER(ct.c_float)),
|
|
135
|
+
)
|
|
136
|
+
return beam.reshape(n_sources, n_samples)
|
|
137
|
+
|
|
138
|
+
elif reduce == "max":
|
|
139
|
+
beam_max = np.zeros(n_samples, dtype=np.float32)
|
|
140
|
+
beam_argmax = np.zeros(n_samples, dtype=np.int32)
|
|
141
|
+
lib.beamform_max(
|
|
142
|
+
waveform_features.ctypes.data_as(ct.POINTER(ct.c_float)),
|
|
143
|
+
time_delays.ctypes.data_as(ct.POINTER(ct.c_int)),
|
|
144
|
+
weights_sources.ctypes.data_as(ct.POINTER(ct.c_float)),
|
|
145
|
+
n_samples,
|
|
146
|
+
n_sources,
|
|
147
|
+
n_stations,
|
|
148
|
+
n_phases,
|
|
149
|
+
int(out_of_bounds),
|
|
150
|
+
int(num_threads),
|
|
151
|
+
beam_max.ctypes.data_as(ct.POINTER(ct.c_float)),
|
|
152
|
+
beam_argmax.ctypes.data_as(ct.POINTER(ct.c_int)),
|
|
153
|
+
)
|
|
154
|
+
return beam_max, beam_argmax
|
|
155
|
+
|
|
156
|
+
elif device.lower() == "gpu":
|
|
157
|
+
|
|
158
|
+
if reduce in ["none", "None", None]:
|
|
159
|
+
beam = np.zeros(n_sources * n_samples, dtype=np.float32)
|
|
160
|
+
lib.beamform(
|
|
161
|
+
waveform_features.ctypes.data_as(ct.POINTER(ct.c_float)),
|
|
162
|
+
time_delays.ctypes.data_as(ct.POINTER(ct.c_int)),
|
|
163
|
+
weights_sources.ctypes.data_as(ct.POINTER(ct.c_float)),
|
|
164
|
+
n_samples,
|
|
165
|
+
n_sources,
|
|
166
|
+
n_stations,
|
|
167
|
+
n_phases,
|
|
168
|
+
int(out_of_bounds),
|
|
169
|
+
#int(num_threads),
|
|
170
|
+
beam.ctypes.data_as(ct.POINTER(ct.c_float)),
|
|
171
|
+
)
|
|
172
|
+
return beam.reshape(n_sources, n_samples)
|
|
173
|
+
|
|
174
|
+
elif reduce == "max":
|
|
175
|
+
beam_max = np.zeros(n_samples, dtype=np.float32)
|
|
176
|
+
beam_argmax = np.zeros(n_samples, dtype=np.int32)
|
|
177
|
+
lib.beamform_max(
|
|
178
|
+
waveform_features.ctypes.data_as(ct.POINTER(ct.c_float)),
|
|
179
|
+
time_delays.ctypes.data_as(ct.POINTER(ct.c_int)),
|
|
180
|
+
weights_sources.ctypes.data_as(ct.POINTER(ct.c_float)),
|
|
181
|
+
n_samples,
|
|
182
|
+
n_sources,
|
|
183
|
+
n_stations,
|
|
184
|
+
n_phases,
|
|
185
|
+
int(out_of_bounds),
|
|
186
|
+
#int(num_threads),
|
|
187
|
+
beam_max.ctypes.data_as(ct.POINTER(ct.c_float)),
|
|
188
|
+
beam_argmax.ctypes.data_as(ct.POINTER(ct.c_int)),
|
|
189
|
+
)
|
|
190
|
+
return beam_max, beam_argmax
|
|
191
|
+
|
|
192
|
+
if mode == "differential":
|
|
193
|
+
# time delays are (relative) source-receiver propagation times
|
|
194
|
+
|
|
195
|
+
# We keep four cases separate in case the signature differs
|
|
196
|
+
if device.lower() == "cpu":
|
|
197
|
+
|
|
198
|
+
beam = np.zeros(n_sources, dtype=np.float32)
|
|
199
|
+
lib.beamform_differential(
|
|
200
|
+
waveform_features.ctypes.data_as(ct.POINTER(ct.c_float)),
|
|
201
|
+
time_delays.ctypes.data_as(ct.POINTER(ct.c_int)),
|
|
202
|
+
weights_sources.ctypes.data_as(ct.POINTER(ct.c_float)),
|
|
203
|
+
n_samples,
|
|
204
|
+
n_sources,
|
|
205
|
+
n_stations,
|
|
206
|
+
n_phases,
|
|
207
|
+
num_threads,
|
|
208
|
+
beam.ctypes.data_as(ct.POINTER(ct.c_float)),
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
if reduce in ["none", "None", None]:
|
|
212
|
+
return beam
|
|
213
|
+
elif reduce == "max":
|
|
214
|
+
beam_max = np.max(beam, axis=0)
|
|
215
|
+
beam_argmax = np.argmax(beam, axis=0)
|
|
216
|
+
return beam_max, beam_argmax
|
|
217
|
+
|
|
218
|
+
return beam.reshape(n_sources, n_samples)
|
|
219
|
+
|
|
220
|
+
elif device.lower() == "gpu":
|
|
221
|
+
|
|
222
|
+
print("differential mode not yet implemented on GPU")
|
|
223
|
+
return
|
|
224
|
+
|
|
225
|
+
else:
|
|
226
|
+
print(f"Mode should either be 'direct' or 'differential', not {mode}.")
|
|
227
|
+
return 1
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def prestack_traces(waveform_features, weights_phases, num_threads=None, device="cpu"):
|
|
231
|
+
"""Prestack the detection traces ahead of the beamforming.
|
|
232
|
+
|
|
233
|
+
Channel-wise stacking for each target seismic phase can be done
|
|
234
|
+
once and for all at the beginning of the computation.
|
|
235
|
+
|
|
236
|
+
Parameters
|
|
237
|
+
-----------
|
|
238
|
+
waveform_features: (n_stations, n_channels, n_stations) numpy.ndarray, float
|
|
239
|
+
Any characterization function computed from the continuous seismograms.
|
|
240
|
+
weights_phases: (n_stations, n_channels, n_phases) numpy.ndarray, float
|
|
241
|
+
Weight given to each station and channel for a given phase. For
|
|
242
|
+
example, horizontal components might be given a small or zero
|
|
243
|
+
weight for the P-wave stacking.
|
|
244
|
+
device: string, default to 'cpu'
|
|
245
|
+
Either 'cpu' or 'gpu', depending on the available hardware and
|
|
246
|
+
user's preferences.
|
|
247
|
+
num_threads: int or None
|
|
248
|
+
Number of threads for CPU parallelization. If None, uses one thread per
|
|
249
|
+
available (visible) CPU.
|
|
250
|
+
|
|
251
|
+
Returns
|
|
252
|
+
----------
|
|
253
|
+
prestacked_traces: (n_stations, n_samples, n_phases) numpy.ndarray, float
|
|
254
|
+
Channel-wise stacked detection traces, optimally formatted for the C
|
|
255
|
+
CUDA-C routines.
|
|
256
|
+
"""
|
|
257
|
+
# Load library
|
|
258
|
+
lib = load_library(device)
|
|
259
|
+
|
|
260
|
+
if num_threads is None:
|
|
261
|
+
num_threads = cpu_count()
|
|
262
|
+
|
|
263
|
+
# Get shapes
|
|
264
|
+
n_stations, n_channels, n_samples = waveform_features.shape
|
|
265
|
+
_, _, n_phases = weights_phases.shape
|
|
266
|
+
prestacked_traces = np.zeros(
|
|
267
|
+
(n_stations * n_samples * n_phases), dtype=np.float32
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
# Cast
|
|
271
|
+
waveform_features = waveform_features.flatten().astype(np.float32)
|
|
272
|
+
weights_phases = weights_phases.flatten().astype(np.float32)
|
|
273
|
+
|
|
274
|
+
# Prestack
|
|
275
|
+
if device.lower() == "cpu":
|
|
276
|
+
lib.prestack_waveform_features(
|
|
277
|
+
waveform_features.ctypes.data_as(ct.POINTER(ct.c_float)),
|
|
278
|
+
weights_phases.ctypes.data_as(ct.POINTER(ct.c_float)),
|
|
279
|
+
n_samples,
|
|
280
|
+
n_stations,
|
|
281
|
+
n_channels,
|
|
282
|
+
n_phases,
|
|
283
|
+
int(num_threads),
|
|
284
|
+
prestacked_traces.ctypes.data_as(ct.POINTER(ct.c_float)),
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
return prestacked_traces.reshape((n_stations, n_samples, n_phases))
|
beampower/core.py
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import ctypes as ct
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
DIRPATH_LIBRARIES = os.path.join(os.path.dirname(__file__), "lib")
|
|
8
|
+
|
|
9
|
+
LIBRARIES = {
|
|
10
|
+
"cpu": {
|
|
11
|
+
"filepath_library": os.path.join(DIRPATH_LIBRARIES, "beamform_cpu.so"),
|
|
12
|
+
"is_loaded": False,
|
|
13
|
+
"lib": None,
|
|
14
|
+
"beamform_argtypes": [
|
|
15
|
+
ct.POINTER(ct.c_float), # waveform_features
|
|
16
|
+
ct.POINTER(ct.c_int), # time_delays
|
|
17
|
+
ct.POINTER(ct.c_float), # weights_sources
|
|
18
|
+
ct.c_size_t, # n_samples
|
|
19
|
+
ct.c_size_t, # n_sources
|
|
20
|
+
ct.c_size_t, # n_stations
|
|
21
|
+
ct.c_size_t, # n_phases
|
|
22
|
+
ct.c_int, # out_of_bounds
|
|
23
|
+
ct.c_int, # num_threads
|
|
24
|
+
ct.POINTER(ct.c_float), # beam
|
|
25
|
+
],
|
|
26
|
+
"beamform_differential_argtypes": [
|
|
27
|
+
ct.POINTER(ct.c_float), # waveform_features
|
|
28
|
+
ct.POINTER(ct.c_int), # time_delays
|
|
29
|
+
ct.POINTER(ct.c_float), # weights_sources
|
|
30
|
+
ct.c_size_t, # n_samples
|
|
31
|
+
ct.c_size_t, # n_sources
|
|
32
|
+
ct.c_size_t, # n_stations
|
|
33
|
+
ct.c_size_t, # n_phases
|
|
34
|
+
ct.c_int, # num_threads
|
|
35
|
+
ct.POINTER(ct.c_float), # beam
|
|
36
|
+
],
|
|
37
|
+
"beamform_max_argtypes": [
|
|
38
|
+
ct.POINTER(ct.c_float), # waveform_features
|
|
39
|
+
ct.POINTER(ct.c_int), # time_delays
|
|
40
|
+
ct.POINTER(ct.c_float), # weights_sources
|
|
41
|
+
ct.c_size_t, # n_samples
|
|
42
|
+
ct.c_size_t, # n_sources
|
|
43
|
+
ct.c_size_t, # n_stations
|
|
44
|
+
ct.c_size_t, # n_phases
|
|
45
|
+
ct.c_int, # out_of_bounds
|
|
46
|
+
ct.c_int, # num_threads
|
|
47
|
+
ct.POINTER(ct.c_float), # beam_max
|
|
48
|
+
ct.POINTER(ct.c_int), # beam_argmax
|
|
49
|
+
],
|
|
50
|
+
"prestack_waveform_features_argtypes": [
|
|
51
|
+
ct.POINTER(ct.c_float), # waveform_features
|
|
52
|
+
ct.POINTER(ct.c_float), # weights_phases
|
|
53
|
+
ct.c_size_t, # n_sources
|
|
54
|
+
ct.c_size_t, # n_stations
|
|
55
|
+
ct.c_size_t, # n_channels
|
|
56
|
+
ct.c_size_t, # n_phases
|
|
57
|
+
ct.c_int, # num_threads
|
|
58
|
+
ct.POINTER(ct.c_float), # prestacked_traces
|
|
59
|
+
],
|
|
60
|
+
},
|
|
61
|
+
"gpu": {
|
|
62
|
+
"filepath_library": os.path.join(DIRPATH_LIBRARIES, "beamform_gpu.so"),
|
|
63
|
+
"is_loaded": False,
|
|
64
|
+
"lib": None,
|
|
65
|
+
"beamform_argtypes": [
|
|
66
|
+
ct.POINTER(ct.c_float), # waveform_features
|
|
67
|
+
ct.POINTER(ct.c_int), # time_delays
|
|
68
|
+
ct.POINTER(ct.c_float), # weights_sources
|
|
69
|
+
ct.c_size_t, # n_samples
|
|
70
|
+
ct.c_size_t, # n_sources
|
|
71
|
+
ct.c_size_t, # n_stations
|
|
72
|
+
ct.c_size_t, # n_phases
|
|
73
|
+
ct.c_int, # out_of_bounds
|
|
74
|
+
ct.POINTER(ct.c_float), # beam
|
|
75
|
+
],
|
|
76
|
+
"beamform_max_argtypes": [
|
|
77
|
+
ct.POINTER(ct.c_float), # waveform_features
|
|
78
|
+
ct.POINTER(ct.c_int), # time_delays
|
|
79
|
+
ct.POINTER(ct.c_float), # weights_sources
|
|
80
|
+
ct.c_size_t, # n_samples
|
|
81
|
+
ct.c_size_t, # n_sources
|
|
82
|
+
ct.c_size_t, # n_stations
|
|
83
|
+
ct.c_size_t, # n_phases
|
|
84
|
+
ct.c_int, # out_of_bounds
|
|
85
|
+
ct.POINTER(ct.c_float), # beam_max
|
|
86
|
+
ct.POINTER(ct.c_int), # beam_argmax
|
|
87
|
+
],
|
|
88
|
+
},
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def load_library(device="cpu"):
|
|
93
|
+
"""Load library for device.
|
|
94
|
+
|
|
95
|
+
This function loads the library only once, that is, if the library is
|
|
96
|
+
successfully loaded a first time, it will be stored as a persistent
|
|
97
|
+
variable in the LIBRARIES dictionary.
|
|
98
|
+
|
|
99
|
+
Parameters
|
|
100
|
+
----------
|
|
101
|
+
device: str, optional
|
|
102
|
+
Device-compilated library, either "cpu" or "gpu".
|
|
103
|
+
|
|
104
|
+
Returns
|
|
105
|
+
-------
|
|
106
|
+
lib : ctypes.CDLL
|
|
107
|
+
Loaded shared library object
|
|
108
|
+
|
|
109
|
+
Raises
|
|
110
|
+
------
|
|
111
|
+
NameError
|
|
112
|
+
If device is not "cpu" or "gpu"
|
|
113
|
+
OSError
|
|
114
|
+
If the library file is not found
|
|
115
|
+
RuntimeError
|
|
116
|
+
If GPU library is requested but not available
|
|
117
|
+
"""
|
|
118
|
+
# Get device name
|
|
119
|
+
device_name = device.lower()
|
|
120
|
+
|
|
121
|
+
# Check device name
|
|
122
|
+
if device_name not in LIBRARIES:
|
|
123
|
+
raise NameError(f"Device should be cpu or gpu, not {device_name}")
|
|
124
|
+
|
|
125
|
+
# Check if shared object exists
|
|
126
|
+
library_info = LIBRARIES[device_name]
|
|
127
|
+
filepath_library = library_info["filepath_library"]
|
|
128
|
+
|
|
129
|
+
if os.path.exists(filepath_library):
|
|
130
|
+
# If library was previously loaded, return it
|
|
131
|
+
if library_info["is_loaded"] is True:
|
|
132
|
+
return library_info["lib"]
|
|
133
|
+
|
|
134
|
+
# Otherwise load it
|
|
135
|
+
else:
|
|
136
|
+
# Load
|
|
137
|
+
lib = ct.cdll.LoadLibrary(filepath_library)
|
|
138
|
+
|
|
139
|
+
# Declare types
|
|
140
|
+
lib.beamform.argtypes = library_info["beamform_argtypes"]
|
|
141
|
+
lib.beamform_max.argtypes = library_info["beamform_max_argtypes"]
|
|
142
|
+
if device_name == "cpu":
|
|
143
|
+
lib.prestack_waveform_features.argtypes = library_info[
|
|
144
|
+
"prestack_waveform_features_argtypes"
|
|
145
|
+
]
|
|
146
|
+
lib.beamform_differential.argtypes = library_info[
|
|
147
|
+
"beamform_differential_argtypes"
|
|
148
|
+
]
|
|
149
|
+
|
|
150
|
+
# Store pre-loaded library
|
|
151
|
+
LIBRARIES[device_name]["is_loaded"] = True
|
|
152
|
+
LIBRARIES[device_name]["lib"] = lib
|
|
153
|
+
|
|
154
|
+
return lib
|
|
155
|
+
|
|
156
|
+
else:
|
|
157
|
+
if device_name == "gpu":
|
|
158
|
+
# GPU library is optional - provide helpful message
|
|
159
|
+
raise RuntimeError(
|
|
160
|
+
f"GPU library not available. The shared object {filepath_library} does not exist.\n"
|
|
161
|
+
f"GPU support is optional. You can still use device='cpu' for CPU-based beamforming.\n"
|
|
162
|
+
f"To build GPU support, ensure CUDA is installed and rebuild the package."
|
|
163
|
+
)
|
|
164
|
+
else:
|
|
165
|
+
# CPU library is required
|
|
166
|
+
raise OSError(
|
|
167
|
+
f"CPU library is required but not found at {filepath_library}.\n"
|
|
168
|
+
f"This package requires pre-built binaries. Please reinstall: pip install --upgrade beampower"
|
|
169
|
+
)
|
|
Binary file
|