fullwave25 1.4.2.dev2__py3-none-any.whl → 1.4.2.dev3__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.
- fullwave/__init__.py +1 -1
- fullwave/beamformer/beamformer.py +12 -3
- fullwave/transducer.py +50 -2
- fullwave/utils/relaxation_parameters.py +51 -0
- {fullwave25-1.4.2.dev2.dist-info → fullwave25-1.4.2.dev3.dist-info}/METADATA +1 -1
- {fullwave25-1.4.2.dev2.dist-info → fullwave25-1.4.2.dev3.dist-info}/RECORD +8 -8
- {fullwave25-1.4.2.dev2.dist-info → fullwave25-1.4.2.dev3.dist-info}/WHEEL +0 -0
- {fullwave25-1.4.2.dev2.dist-info → fullwave25-1.4.2.dev3.dist-info}/licenses/LICENSE +0 -0
fullwave/__init__.py
CHANGED
|
@@ -66,7 +66,7 @@ try:
|
|
|
66
66
|
__version__ = version("fullwave")
|
|
67
67
|
except PackageNotFoundError:
|
|
68
68
|
# Update via bump-my-version, not manually
|
|
69
|
-
__version__ = "1.4.2-
|
|
69
|
+
__version__ = "1.4.2-dev3"
|
|
70
70
|
|
|
71
71
|
VERSION = __version__ # for convenience
|
|
72
72
|
logger.info("Fullwave version: %s", __version__)
|
|
@@ -14,6 +14,13 @@ class Beamformer:
|
|
|
14
14
|
For faster implementations, consider using libraries such as
|
|
15
15
|
mach beamformer: https://github.com/Forest-Neurotech/mach
|
|
16
16
|
|
|
17
|
+
Conventions: ``axial_position_m`` is measured from the top of the grid
|
|
18
|
+
(row 0), not from the array. ``transducer_coordinates`` must be the
|
|
19
|
+
receiver positions in grid points (for example from
|
|
20
|
+
``Transducer.element_id_to_receiver_center``). The time origin is the
|
|
21
|
+
mean receiver row, so the array depth is subtracted from the two-way
|
|
22
|
+
travel time.
|
|
23
|
+
|
|
17
24
|
References
|
|
18
25
|
----------
|
|
19
26
|
- Fullwave2 BMME890 implementation
|
|
@@ -46,11 +53,11 @@ class Beamformer:
|
|
|
46
53
|
lateral_position_m : np.ndarray
|
|
47
54
|
Lateral positions (m) where the beamformed image will be computed.
|
|
48
55
|
axial_position_m : np.ndarray
|
|
49
|
-
Axial positions (m) where the
|
|
56
|
+
Axial positions (m) from the top of the grid where the image is computed.
|
|
50
57
|
num_elements : int
|
|
51
58
|
Number of transducer elements.
|
|
52
59
|
transducer_coordinates : np.ndarray
|
|
53
|
-
|
|
60
|
+
Receiver coordinates [n_elements, 2] in grid points (row, column).
|
|
54
61
|
f_number : float, optional
|
|
55
62
|
F-number for aperture calculation, by default 1.0.
|
|
56
63
|
|
|
@@ -83,6 +90,8 @@ class Beamformer:
|
|
|
83
90
|
if signals.ndim == 2:
|
|
84
91
|
signals = signals[:, :, np.newaxis]
|
|
85
92
|
n_transmit = signals.shape[2]
|
|
93
|
+
# idt_0 is the pulse arrival at the receivers, so time is measured from the array.
|
|
94
|
+
z_array = np.mean(self.transducer_coordinates[:, 0]) * self.dx
|
|
86
95
|
|
|
87
96
|
hilbert_signals = hilbert(signals, axis=1)
|
|
88
97
|
beamformed_image = np.zeros(
|
|
@@ -133,7 +142,7 @@ class Beamformer:
|
|
|
133
142
|
dd = np.round(dd / (self.dt / self.dx * self.c0)).astype(int)
|
|
134
143
|
dd = dd - dd.min()
|
|
135
144
|
|
|
136
|
-
idt = idt_0 + int(2 * axial / self.c0 / self.dt)
|
|
145
|
+
idt = idt_0 + int(2 * (axial - z_array) / self.c0 / self.dt)
|
|
137
146
|
|
|
138
147
|
# idp: 1D indices into signals flattened array
|
|
139
148
|
# we convert 2D indices to 1D indices
|
fullwave/transducer.py
CHANGED
|
@@ -352,6 +352,9 @@ class TransducerGeometry:
|
|
|
352
352
|
) -> tuple[NDArray[np.int64], NDArray[np.int64], NDArray[np.int64], NDArray[np.int64]]:
|
|
353
353
|
"""Build flat coordinate arrays for source and sensor pixels.
|
|
354
354
|
|
|
355
|
+
For a flat array the sensor row is the first row in front of the element
|
|
356
|
+
(``position_px[0] + element_layer_px``), outside the source cells.
|
|
357
|
+
|
|
355
358
|
Returns
|
|
356
359
|
-------
|
|
357
360
|
source_coords : NDArray[np.int64], shape [N_src, ndim]
|
|
@@ -404,7 +407,7 @@ class TransducerGeometry:
|
|
|
404
407
|
source_ids_parts.append(np.full(len(src), element_index + 1))
|
|
405
408
|
|
|
406
409
|
# Single center pixel for sensor output
|
|
407
|
-
sx =
|
|
410
|
+
sx = self._flat_sensor_row(element_pos_x=element_pos_x)
|
|
408
411
|
sy = element_pos_y + self.element_width_px - 1
|
|
409
412
|
sz = element_pos_z + self.element_height_px // 2 - 1
|
|
410
413
|
sensor_coords_parts.append(np.array([[sx, sy, sz]]))
|
|
@@ -432,7 +435,7 @@ class TransducerGeometry:
|
|
|
432
435
|
source_coords_parts.append(src)
|
|
433
436
|
source_ids_parts.append(np.full(len(src), element_index + 1))
|
|
434
437
|
|
|
435
|
-
sx =
|
|
438
|
+
sx = self._flat_sensor_row(element_pos_x=element_pos_x)
|
|
436
439
|
if self.average_surface_signals:
|
|
437
440
|
out_ys = np.arange(element_pos_y, element_pos_y + self.element_width_px)
|
|
438
441
|
out_xs = np.full(len(out_ys), sx)
|
|
@@ -452,6 +455,39 @@ class TransducerGeometry:
|
|
|
452
455
|
sensor_ids = np.concatenate(sensor_ids_parts, axis=0).astype(np.int64)
|
|
453
456
|
return source_coords, source_ids, sensor_coords, sensor_ids
|
|
454
457
|
|
|
458
|
+
def _flat_sensor_row(self, *, element_pos_x: int) -> int:
|
|
459
|
+
"""Return the sensor row of a flat-array element.
|
|
460
|
+
|
|
461
|
+
The row lies in front of the source cells because a hard source keeps a
|
|
462
|
+
static pressure after the drive, which a receiver inside it would record.
|
|
463
|
+
|
|
464
|
+
Parameters
|
|
465
|
+
----------
|
|
466
|
+
element_pos_x : int
|
|
467
|
+
First (back) source row of the element.
|
|
468
|
+
|
|
469
|
+
Returns
|
|
470
|
+
-------
|
|
471
|
+
int
|
|
472
|
+
The sensor row, ``element_pos_x + element_layer_px``.
|
|
473
|
+
|
|
474
|
+
Raises
|
|
475
|
+
------
|
|
476
|
+
ValueError
|
|
477
|
+
If the sensor row is outside the grid.
|
|
478
|
+
|
|
479
|
+
"""
|
|
480
|
+
sensor_row = int(element_pos_x + self.element_layer_px)
|
|
481
|
+
if sensor_row >= self.stored_grid_size[0]:
|
|
482
|
+
error_msg = (
|
|
483
|
+
f"The sensor row {sensor_row} (one row in front of the element) is "
|
|
484
|
+
f"outside the grid, which has {self.stored_grid_size[0]} rows in x. "
|
|
485
|
+
"Move the transducer back or enlarge the grid."
|
|
486
|
+
)
|
|
487
|
+
logger.error(error_msg)
|
|
488
|
+
raise ValueError(error_msg)
|
|
489
|
+
return sensor_row
|
|
490
|
+
|
|
455
491
|
def _arc_element_coords(
|
|
456
492
|
self,
|
|
457
493
|
) -> tuple[NDArray[np.int64], NDArray[np.int64], NDArray[np.int64], NDArray[np.int64]]:
|
|
@@ -1728,6 +1764,18 @@ class Transducer:
|
|
|
1728
1764
|
out[eid] = np.rint(coords[sel].mean(axis=0)).astype(np.int64)
|
|
1729
1765
|
return out
|
|
1730
1766
|
|
|
1767
|
+
@property
|
|
1768
|
+
def element_id_to_receiver_center(self) -> dict[int, NDArray[np.int64]]:
|
|
1769
|
+
"""Return the mapping from element IDs to the mean receiver coordinate in grid points."""
|
|
1770
|
+
ndim = 3 if self.is_3d else 2
|
|
1771
|
+
out: dict[int, NDArray[np.int64]] = {}
|
|
1772
|
+
for eid, coords in self.element_id_to_element_surface.items():
|
|
1773
|
+
if len(coords) == 0:
|
|
1774
|
+
out[eid] = np.full(ndim, -1, dtype=np.int64)
|
|
1775
|
+
else:
|
|
1776
|
+
out[eid] = np.rint(coords.mean(axis=0)).astype(np.int64)
|
|
1777
|
+
return out
|
|
1778
|
+
|
|
1731
1779
|
@property
|
|
1732
1780
|
def sensor(self) -> fullwave.sensor.Sensor:
|
|
1733
1781
|
"""Return the Sensor object with active sensor coordinates.
|
|
@@ -35,6 +35,44 @@ def _on_the_host(array: NDArray) -> NDArray:
|
|
|
35
35
|
return _array_module(array).asnumpy(array)
|
|
36
36
|
|
|
37
37
|
|
|
38
|
+
# float32 stores 1.2 as 1.2000000477 (about 6e-8 relative); table nodes are 1e-2 or more apart.
|
|
39
|
+
_FLOAT32_NOISE_RTOL = 1e-6
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _snap_to_nearby_node(
|
|
43
|
+
*,
|
|
44
|
+
values: NDArray[np.float64],
|
|
45
|
+
nodes: NDArray[np.float64],
|
|
46
|
+
xp: object,
|
|
47
|
+
) -> NDArray[np.float64]:
|
|
48
|
+
"""Replace each value that is float32 noise away from a table node by that node.
|
|
49
|
+
|
|
50
|
+
Parameters
|
|
51
|
+
----------
|
|
52
|
+
values : NDArray[np.float64]
|
|
53
|
+
Requested values.
|
|
54
|
+
nodes : NDArray[np.float64]
|
|
55
|
+
Sorted table nodes.
|
|
56
|
+
xp : object
|
|
57
|
+
Array module of ``values``, numpy or cupy.
|
|
58
|
+
|
|
59
|
+
Returns
|
|
60
|
+
-------
|
|
61
|
+
NDArray[np.float64]
|
|
62
|
+
The values, with near-node values replaced by the node.
|
|
63
|
+
|
|
64
|
+
"""
|
|
65
|
+
if nodes.size < 2:
|
|
66
|
+
return values
|
|
67
|
+
values = xp.asarray(values)
|
|
68
|
+
upper_index = xp.clip(xp.searchsorted(nodes, values, side="left"), 1, nodes.size - 1)
|
|
69
|
+
lower_node = nodes[upper_index - 1]
|
|
70
|
+
upper_node = nodes[upper_index]
|
|
71
|
+
is_near_lower = xp.abs(values - lower_node) <= _FLOAT32_NOISE_RTOL * xp.abs(lower_node)
|
|
72
|
+
is_near_upper = xp.abs(values - upper_node) <= _FLOAT32_NOISE_RTOL * xp.abs(upper_node)
|
|
73
|
+
return xp.where(is_near_lower, lower_node, xp.where(is_near_upper, upper_node, values))
|
|
74
|
+
|
|
75
|
+
|
|
38
76
|
def scale_relaxation_attenuation(
|
|
39
77
|
relaxation_param_dict: dict[str, NDArray[np.float64]],
|
|
40
78
|
factor: NDArray[np.float64] | float,
|
|
@@ -938,6 +976,8 @@ class RelaxationParametersGenerator:
|
|
|
938
976
|
|
|
939
977
|
"""
|
|
940
978
|
alpha_coeff = band_scaled_alpha_coeff(alpha_coeff, alpha_power, band_scale)
|
|
979
|
+
alpha_coeff = self._snap_to_table(alpha_coeff, table_axis=self.alpha_list)
|
|
980
|
+
alpha_power = self._snap_to_table(alpha_power, table_axis=self.power_list)
|
|
941
981
|
self._warn_invalid(alpha_coeff, alpha_power)
|
|
942
982
|
|
|
943
983
|
use_gpu = not isinstance(alpha_coeff, np.ndarray)
|
|
@@ -991,6 +1031,7 @@ class RelaxationParametersGenerator:
|
|
|
991
1031
|
|
|
992
1032
|
"""
|
|
993
1033
|
xp = np if isinstance(alpha_coeff, np.ndarray) else _array_module(alpha_coeff)
|
|
1034
|
+
alpha_coeff = self._snap_to_table(alpha_coeff, table_axis=self.alpha_list)
|
|
994
1035
|
axis = xp.asarray(np.asarray(self.alpha_list).ravel().round(10))
|
|
995
1036
|
return (
|
|
996
1037
|
alpha_coeff / axis[self._axis_index(alpha_coeff, axis, self.alpha_min, self.alpha_max)]
|
|
@@ -1113,6 +1154,15 @@ class RelaxationParametersGenerator:
|
|
|
1113
1154
|
],
|
|
1114
1155
|
)
|
|
1115
1156
|
|
|
1157
|
+
@staticmethod
|
|
1158
|
+
def _snap_to_table(
|
|
1159
|
+
values: NDArray[np.float64], *, table_axis: NDArray[np.float64]
|
|
1160
|
+
) -> NDArray[np.float64]:
|
|
1161
|
+
"""Replace each value that is float32 noise away from a node of one table axis."""
|
|
1162
|
+
xp = np if isinstance(values, np.ndarray) else _array_module(values)
|
|
1163
|
+
nodes = xp.asarray(np.asarray(table_axis).ravel().round(10))
|
|
1164
|
+
return _snap_to_nearby_node(values=values, nodes=nodes, xp=xp)
|
|
1165
|
+
|
|
1116
1166
|
@staticmethod
|
|
1117
1167
|
def _axis_index(
|
|
1118
1168
|
value: NDArray[np.float64],
|
|
@@ -1122,6 +1172,7 @@ class RelaxationParametersGenerator:
|
|
|
1122
1172
|
) -> NDArray[np.int64]:
|
|
1123
1173
|
"""Return the axis index the lookup kernel selects, which clips at both ends."""
|
|
1124
1174
|
xp = np if isinstance(value, np.ndarray) else _array_module(value)
|
|
1175
|
+
value = _snap_to_nearby_node(values=value, nodes=axis, xp=xp)
|
|
1125
1176
|
clipped = xp.clip(value, lowest, highest)
|
|
1126
1177
|
return xp.minimum(xp.searchsorted(axis, clipped, side="left"), axis.size - 1)
|
|
1127
1178
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: fullwave25
|
|
3
|
-
Version: 1.4.2-
|
|
3
|
+
Version: 1.4.2-dev3
|
|
4
4
|
Summary: Fullwave 2.5: Ultrasound wave propagation simulation with heterogeneous power law attenuation modelling capabilities
|
|
5
5
|
Author: Masashi Sode, Gianmarco Pinton
|
|
6
6
|
Maintainer: Masashi Sode
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
fullwave/__init__.py,sha256=
|
|
1
|
+
fullwave/__init__.py,sha256=Lu4dG1mNq4CKaKD34LmhtJNlYn8m2gRXrw8qYqnAZKg,2382
|
|
2
2
|
fullwave/grid.py,sha256=YbxAAgdYKR8AXwobtTZzHm9C4KR4D5--51zgbs0aQGY,7435
|
|
3
3
|
fullwave/medium.py,sha256=qoAh-aYLVkC_ojt1K61hDNV0j9KfIeRkh35tZU8gSKM,68574
|
|
4
4
|
fullwave/sensor.py,sha256=McHyaSKxM11_Kfb_2hfnOtV9Z1UMBCLkeovncLT8_bA,9308
|
|
5
5
|
fullwave/source.py,sha256=5IHktJubOsk94qBWlLBbu-Vxo2UczVFL4EnEyKqEIUE,21321
|
|
6
|
-
fullwave/transducer.py,sha256=
|
|
6
|
+
fullwave/transducer.py,sha256=H67VF_T2KKF68mWtXG6DrVYKaaZieGc7p-IDtWxX1SQ,77623
|
|
7
7
|
fullwave/transmit.py,sha256=BAD_G-J7Q1pMppXw1yjLrQDUdkhPq-3Q4eNq4jaf7Ss,9693
|
|
8
8
|
fullwave/beamformer/__init__.py,sha256=tNPq_5lfLXfAdueyfQtdTxLTVEV6nEDkeSGAeTf7Q5s,88
|
|
9
|
-
fullwave/beamformer/beamformer.py,sha256=
|
|
9
|
+
fullwave/beamformer/beamformer.py,sha256=7dlQVArUGpUeUP6O-9QyMpR6asbbI5JBVJ_F1fpSowU,6175
|
|
10
10
|
fullwave/constants/__init__.py,sha256=WOmkhLsSe86D71gwKsa4Tz8QPeOdayLGgvBCh7UEOPk,104
|
|
11
11
|
fullwave/constants/material_properties.py,sha256=SouMKD4ONzYl0VU8ZsC2I_85y-ZLj9scRNwAArCD7bg,2729
|
|
12
12
|
fullwave/medium_builder/__init__.py,sha256=QVz0sA4niq51O94RnXUVlHutx7FYEIa1TRcR5fDc_Ps,201
|
|
@@ -64,11 +64,11 @@ fullwave/utils/memory_tempfile.py,sha256=RqoFX67VCMIHNIDYWwHLCaS4dwSPK6eKsvElXX4
|
|
|
64
64
|
fullwave/utils/numerical.py,sha256=khdi2KE3y_Ky9cEnmGXvVHjeYuKfb-8gQTV1a8nx9uA,3260
|
|
65
65
|
fullwave/utils/plot_utils.py,sha256=SoByd2fEr3VdvMM9D2kNq8VgaAQ-fh82bDGRFdeq2R8,39327
|
|
66
66
|
fullwave/utils/pulse.py,sha256=aqnCdBxivACouIGlot1aE2k4V9nctUxNCEPzdBpsupg,2811
|
|
67
|
-
fullwave/utils/relaxation_parameters.py,sha256=
|
|
67
|
+
fullwave/utils/relaxation_parameters.py,sha256=pNul3_8rrbBrjkcc0nKBJ6An9y3bI92DVxNu1Ww8cf4,48133
|
|
68
68
|
fullwave/utils/scatterer.py,sha256=CLqD085f9pqX4cF1YiT2IkzT5P0o1xp45Do7lf5BLnM,24049
|
|
69
69
|
fullwave/utils/signal_filter.py,sha256=_Tf42aBygGtIUrcSPybYqvZoAW2o_4dd-hHTEvZ8--E,5219
|
|
70
70
|
fullwave/utils/signal_process.py,sha256=MP38r37JSPHN4sT-lIReXS5BT2GT_tkRxD0Y1wQ-lNs,5153
|
|
71
|
-
fullwave25-1.4.2.
|
|
72
|
-
fullwave25-1.4.2.
|
|
73
|
-
fullwave25-1.4.2.
|
|
74
|
-
fullwave25-1.4.2.
|
|
71
|
+
fullwave25-1.4.2.dev3.dist-info/METADATA,sha256=q0L2mArY9AQfnOaJKDYCLIke8U4wNM0w5TOm-EPxMIU,28226
|
|
72
|
+
fullwave25-1.4.2.dev3.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
|
|
73
|
+
fullwave25-1.4.2.dev3.dist-info/licenses/LICENSE,sha256=46mU2C5kSwOnkqkw9XQAJlhBL2JAf1_uCD8lVcXyMRg,7652
|
|
74
|
+
fullwave25-1.4.2.dev3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|