fullwave25 1.2.5.dev0__tar.gz → 1.2.6.dev0__tar.gz
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.
- {fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/.bumpversion.toml +1 -1
- {fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/PKG-INFO +1 -1
- {fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/fullwave/__init__.py +1 -1
- {fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/fullwave/solver/binary_manager.py +1 -1
- {fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/fullwave/solver/input_file_writer.py +17 -0
- {fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/fullwave/solver/pml_builder.py +43 -1
- {fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/fullwave/source.py +147 -12
- {fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/pyproject.toml +1 -1
- {fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/uv.lock +1 -1
- {fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/.gitignore +0 -0
- {fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/.pre-commit-config.yaml +0 -0
- {fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/.python-version +0 -0
- {fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/20260120_0844.log +0 -0
- {fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/CITATION.cff +0 -0
- {fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/CLAUDE.md +0 -0
- {fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/CONTRIBUTING.md +0 -0
- {fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/LICENSE +0 -0
- {fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/Makefile +0 -0
- {fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/README.md +0 -0
- {fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/beamformer.m +0 -0
- {fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/debug_solver_bin/fullwave2_2d_2_relax_multi_gpu_cuda124 +0 -0
- {fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/debug_solver_bin/fullwave2_2d_exponential_attenuation_multi_gpu +0 -0
- {fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/experiments/__init__.py +0 -0
- {fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/experiments/exp_scatterer_diameter.py +0 -0
- {fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/fullwave/beamformer/__init__.py +0 -0
- {fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/fullwave/beamformer/beamformer.py +0 -0
- {fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/fullwave/constants/__init__.py +0 -0
- {fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/fullwave/constants/material_properties.py +0 -0
- {fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/fullwave/grid.py +0 -0
- {fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/fullwave/medium.py +0 -0
- {fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/fullwave/medium_builder/__init__.py +0 -0
- {fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/fullwave/medium_builder/domain.py +0 -0
- {fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/fullwave/medium_builder/medium_builder.py +0 -0
- {fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/fullwave/medium_builder/presets/__init__.py +0 -0
- {fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/fullwave/medium_builder/presets/data/.keep +0 -0
- {fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/fullwave/medium_builder/presets/data/abdominal_wall/i2365f_etfw1.mat +0 -0
- {fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/fullwave/medium_builder/presets/domain_abdominal_wall.py +0 -0
- {fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/fullwave/medium_builder/presets/domain_background.py +0 -0
- {fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/fullwave/medium_builder/presets/domain_scatterer.py +0 -0
- {fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/fullwave/medium_builder/presets/domain_simple.py +0 -0
- {fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/fullwave/medium_builder/presets/domain_water_gel.py +0 -0
- {fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/fullwave/sensor.py +0 -0
- {fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/fullwave/solver/__init__.py +0 -0
- {fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/fullwave/solver/bins/_gpu/2d/num_relax=2/fullwave2_2d_2_relax_multi_gpu_cuda118 +0 -0
- {fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/fullwave/solver/bins/_gpu/2d/num_relax=2/fullwave2_2d_2_relax_multi_gpu_cuda124 +0 -0
- {fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/fullwave/solver/bins/_gpu/2d/num_relax=2/fullwave2_2d_2_relax_multi_gpu_cuda129 +0 -0
- {fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/fullwave/solver/bins/_gpu/2d/num_relax=2/fullwave2_2d_2_relax_multi_gpu_cuda130 +0 -0
- {fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/fullwave/solver/bins/_gpu/3d/num_relax=2/fullwave2_3d_2_relax_multi_gpu_cuda118 +0 -0
- {fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/fullwave/solver/bins/_gpu/3d/num_relax=2/fullwave2_3d_2_relax_multi_gpu_cuda124 +0 -0
- {fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/fullwave/solver/bins/_gpu/3d/num_relax=2/fullwave2_3d_2_relax_multi_gpu_cuda129 +0 -0
- {fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/fullwave/solver/bins/_gpu/3d/num_relax=2/fullwave2_3d_2_relax_multi_gpu_cuda130 +0 -0
- {fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/fullwave/solver/bins/database/relaxation_params_database_num_relax=2_20260113_0957.mat +0 -0
- {fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/fullwave/solver/cuda_utils.py +0 -0
- {fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/fullwave/solver/launcher.py +0 -0
- {fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/fullwave/solver/solver.py +0 -0
- {fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/fullwave/solver/utils.py +0 -0
- {fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/fullwave/transducer.py +0 -0
- {fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/fullwave/utils/__init__.py +0 -0
- {fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/fullwave/utils/check_functions.py +0 -0
- {fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/fullwave/utils/coordinates.py +0 -0
- {fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/fullwave/utils/memory_tempfile.py +0 -0
- {fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/fullwave/utils/numerical.py +0 -0
- {fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/fullwave/utils/plot_utils.py +0 -0
- {fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/fullwave/utils/pulse.py +0 -0
- {fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/fullwave/utils/relaxation_parameters.py +0 -0
- {fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/fullwave/utils/scatterer.py +0 -0
- {fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/fullwave/utils/signal_process.py +0 -0
- {fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/ruff.toml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fullwave25
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.6.dev0
|
|
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
|
|
@@ -60,7 +60,7 @@ try:
|
|
|
60
60
|
__version__ = version("fullwave")
|
|
61
61
|
except PackageNotFoundError:
|
|
62
62
|
# Update via bump-my-version, not manually
|
|
63
|
-
__version__ = "1.2.
|
|
63
|
+
__version__ = "1.2.6-dev0"
|
|
64
64
|
|
|
65
65
|
VERSION = __version__ # for convenience
|
|
66
66
|
logger.info("Fullwave version: %s", __version__)
|
|
@@ -28,7 +28,7 @@ CACHE_DIR = Path.home() / ".cache" / "fullwave25" / "bins"
|
|
|
28
28
|
|
|
29
29
|
# Pinned release tag for the solver binaries.
|
|
30
30
|
# Update this only when new binaries are uploaded to a GitHub release.
|
|
31
|
-
BINARY_RELEASE_TAG = "fullwave_bin_v1.
|
|
31
|
+
BINARY_RELEASE_TAG = "fullwave_bin_v1.2"
|
|
32
32
|
|
|
33
33
|
|
|
34
34
|
def _download_url(filename: str, tag: str) -> str:
|
|
@@ -169,6 +169,19 @@ class InputFileWriter:
|
|
|
169
169
|
)
|
|
170
170
|
if incoords_add is not None:
|
|
171
171
|
self._queue_coords_write(simulation_dir / "icc_add.dat", incoords_add)
|
|
172
|
+
for _vel_suffix, _vel_attr in (("u", "u0"), ("v", "v0"), ("w", "w0")):
|
|
173
|
+
_signal_vel = getattr(self.source, _vel_attr, None)
|
|
174
|
+
_incoords_vel = getattr(self.source, f"incoords_{_vel_suffix}", None)
|
|
175
|
+
if _signal_vel is not None:
|
|
176
|
+
self._queue_ic_write(
|
|
177
|
+
simulation_dir / f"icmat_{_vel_suffix}.dat",
|
|
178
|
+
np.transpose(_signal_vel),
|
|
179
|
+
)
|
|
180
|
+
if _incoords_vel is not None:
|
|
181
|
+
self._queue_coords_write(
|
|
182
|
+
simulation_dir / f"icc_{_vel_suffix}.dat",
|
|
183
|
+
_incoords_vel,
|
|
184
|
+
)
|
|
172
185
|
self._queue_coords_write(simulation_dir / "icc.dat", self.source.incoords)
|
|
173
186
|
self._copy_simulation_bin_file(simulation_dir)
|
|
174
187
|
|
|
@@ -1047,6 +1060,10 @@ class InputFileWriter:
|
|
|
1047
1060
|
n_sources_add = getattr(self.source, "n_sources_add", 0)
|
|
1048
1061
|
if n_sources_add > 0:
|
|
1049
1062
|
var_list.append(("ncoords_add", n_sources_add))
|
|
1063
|
+
for _vel_suffix in ("u", "v", "w"):
|
|
1064
|
+
_n_vel = getattr(self.source, f"n_sources_{_vel_suffix}", 0)
|
|
1065
|
+
if _n_vel > 0:
|
|
1066
|
+
var_list.append((f"ncoords_{_vel_suffix}", _n_vel))
|
|
1050
1067
|
if self.is_3d:
|
|
1051
1068
|
var_list.extend(
|
|
1052
1069
|
[
|
|
@@ -322,12 +322,33 @@ class PMLBuilder:
|
|
|
322
322
|
if getattr(self.source_org, "incoords_add", None) is not None
|
|
323
323
|
else None
|
|
324
324
|
)
|
|
325
|
+
incoords_u_ext = (
|
|
326
|
+
self.source_org.incoords_u + self.num_boundary_points
|
|
327
|
+
if getattr(self.source_org, "incoords_u", None) is not None
|
|
328
|
+
else None
|
|
329
|
+
)
|
|
330
|
+
incoords_v_ext = (
|
|
331
|
+
self.source_org.incoords_v + self.num_boundary_points
|
|
332
|
+
if getattr(self.source_org, "incoords_v", None) is not None
|
|
333
|
+
else None
|
|
334
|
+
)
|
|
335
|
+
incoords_w_ext = (
|
|
336
|
+
self.source_org.incoords_w + self.num_boundary_points
|
|
337
|
+
if getattr(self.source_org, "incoords_w", None) is not None
|
|
338
|
+
else None
|
|
339
|
+
)
|
|
325
340
|
self.extended_source = fullwave.Source(
|
|
326
341
|
p0=self.source_org.p0,
|
|
327
342
|
coords=self.source_org.incoords + self.num_boundary_points,
|
|
328
343
|
grid_shape=extended_grid_shape,
|
|
329
344
|
p0_additive=self.source_org.p0_additive,
|
|
330
345
|
coords_additive=incoords_add_ext,
|
|
346
|
+
u0=getattr(self.source_org, "u0", None),
|
|
347
|
+
coords_u=incoords_u_ext,
|
|
348
|
+
v0=getattr(self.source_org, "v0", None),
|
|
349
|
+
coords_v=incoords_v_ext,
|
|
350
|
+
w0=getattr(self.source_org, "w0", None),
|
|
351
|
+
coords_w=incoords_w_ext,
|
|
331
352
|
)
|
|
332
353
|
logger.debug("building extended source for pml...done")
|
|
333
354
|
|
|
@@ -1434,7 +1455,7 @@ class PMLBuilder:
|
|
|
1434
1455
|
class PMLBuilderExponentialAttenuation(PMLBuilder):
|
|
1435
1456
|
"""A class to set up PML for exponential attenuation media."""
|
|
1436
1457
|
|
|
1437
|
-
def __init__(
|
|
1458
|
+
def __init__( # noqa: PLR0915
|
|
1438
1459
|
self,
|
|
1439
1460
|
grid: fullwave.Grid,
|
|
1440
1461
|
medium: fullwave.Medium,
|
|
@@ -1594,12 +1615,33 @@ class PMLBuilderExponentialAttenuation(PMLBuilder):
|
|
|
1594
1615
|
if getattr(self.source_org, "incoords_add", None) is not None
|
|
1595
1616
|
else None
|
|
1596
1617
|
)
|
|
1618
|
+
incoords_u_ext = (
|
|
1619
|
+
self.source_org.incoords_u + self.num_boundary_points
|
|
1620
|
+
if getattr(self.source_org, "incoords_u", None) is not None
|
|
1621
|
+
else None
|
|
1622
|
+
)
|
|
1623
|
+
incoords_v_ext = (
|
|
1624
|
+
self.source_org.incoords_v + self.num_boundary_points
|
|
1625
|
+
if getattr(self.source_org, "incoords_v", None) is not None
|
|
1626
|
+
else None
|
|
1627
|
+
)
|
|
1628
|
+
incoords_w_ext = (
|
|
1629
|
+
self.source_org.incoords_w + self.num_boundary_points
|
|
1630
|
+
if getattr(self.source_org, "incoords_w", None) is not None
|
|
1631
|
+
else None
|
|
1632
|
+
)
|
|
1597
1633
|
self.extended_source = fullwave.Source(
|
|
1598
1634
|
p0=self.source_org.p0,
|
|
1599
1635
|
coords=self.source_org.incoords + self.num_boundary_points,
|
|
1600
1636
|
grid_shape=extended_grid_shape,
|
|
1601
1637
|
p0_additive=self.source_org.p0_additive,
|
|
1602
1638
|
coords_additive=incoords_add_ext,
|
|
1639
|
+
u0=getattr(self.source_org, "u0", None),
|
|
1640
|
+
coords_u=incoords_u_ext,
|
|
1641
|
+
v0=getattr(self.source_org, "v0", None),
|
|
1642
|
+
coords_v=incoords_v_ext,
|
|
1643
|
+
w0=getattr(self.source_org, "w0", None),
|
|
1644
|
+
coords_w=incoords_w_ext,
|
|
1603
1645
|
)
|
|
1604
1646
|
extended_sensor_grid_shape = tuple(
|
|
1605
1647
|
s + 2 * self.num_boundary_points for s in self.sensor_org.grid_shape
|
|
@@ -23,6 +23,12 @@ class Source:
|
|
|
23
23
|
grid_shape: tuple[int, ...]
|
|
24
24
|
p0_additive: NDArray[np.float64] | None = None
|
|
25
25
|
incoords_add: NDArray[np.int64] | None = None
|
|
26
|
+
u0: NDArray[np.float64] | None = None
|
|
27
|
+
incoords_u: NDArray[np.int64] | None = None
|
|
28
|
+
v0: NDArray[np.float64] | None = None
|
|
29
|
+
incoords_v: NDArray[np.int64] | None = None
|
|
30
|
+
w0: NDArray[np.float64] | None = None
|
|
31
|
+
incoords_w: NDArray[np.int64] | None = None
|
|
26
32
|
|
|
27
33
|
def __init__( # noqa: C901 PLR0912 PLR0915
|
|
28
34
|
self,
|
|
@@ -37,6 +43,16 @@ class Source:
|
|
|
37
43
|
coords_additive: NDArray[np.int64] | None = None,
|
|
38
44
|
# ---
|
|
39
45
|
grid_shape: tuple[int, ...] | None = None,
|
|
46
|
+
# --- velocity source components ---
|
|
47
|
+
u0: NDArray[np.float64] | None = None,
|
|
48
|
+
coords_u: NDArray[np.int64] | None = None,
|
|
49
|
+
mask_u: NDArray[np.bool] | None = None,
|
|
50
|
+
v0: NDArray[np.float64] | None = None,
|
|
51
|
+
coords_v: NDArray[np.int64] | None = None,
|
|
52
|
+
mask_v: NDArray[np.bool] | None = None,
|
|
53
|
+
w0: NDArray[np.float64] | None = None,
|
|
54
|
+
coords_w: NDArray[np.int64] | None = None,
|
|
55
|
+
mask_w: NDArray[np.bool] | None = None,
|
|
40
56
|
) -> None:
|
|
41
57
|
"""Source class for Fullwave.
|
|
42
58
|
|
|
@@ -44,8 +60,9 @@ class Source:
|
|
|
44
60
|
----------
|
|
45
61
|
p0 : NDArray[np.float64] | None
|
|
46
62
|
Time-varying pressure at each source position; shape [n_sources, nt].
|
|
47
|
-
If None, p0_additive
|
|
48
|
-
|
|
63
|
+
If None, either p0_additive or a velocity component (u0/v0/w0) must be provided.
|
|
64
|
+
When omitted with a velocity-only source, the pressure node list is empty
|
|
65
|
+
and icmat is written as a zero-row matrix.
|
|
49
66
|
mask : NDArray[np.bool] | None
|
|
50
67
|
binary matrix specifying the positions of the time varying pressure source distribution
|
|
51
68
|
shape: [nx, ny] for 2D, [nx, ny, nz] for 3D.
|
|
@@ -54,7 +71,7 @@ class Source:
|
|
|
54
71
|
Coordinate array of source positions (hard source); shape [n_sources, ndim].
|
|
55
72
|
Must be provided together with grid_shape.
|
|
56
73
|
grid_shape : tuple[int, ...] | None
|
|
57
|
-
Shape of the computational grid. Required when using coords input.
|
|
74
|
+
Shape of the computational grid. Required when using coords or velocity-only input.
|
|
58
75
|
p0_additive : NDArray[np.float64] | None
|
|
59
76
|
Optional additive (soft) source term; shape [n_sources_add, nt].
|
|
60
77
|
When provided, positions come from coords_additive or mask_additive,
|
|
@@ -66,15 +83,36 @@ class Source:
|
|
|
66
83
|
Coordinates for the additive source; shape [n_sources_add, ndim].
|
|
67
84
|
If None and p0_additive is provided, defaults to primary incoords.
|
|
68
85
|
Mutually exclusive with mask_additive.
|
|
86
|
+
u0 : NDArray[np.float64] | None
|
|
87
|
+
Time-varying velocity in the depth (x) direction; shape [n_sources_u, nt].
|
|
88
|
+
coords_u : NDArray[np.int64] | None
|
|
89
|
+
Coordinates for the u-velocity source; shape [n_sources_u, ndim].
|
|
90
|
+
Mutually exclusive with mask_u.
|
|
91
|
+
mask_u : NDArray[np.bool] | None
|
|
92
|
+
Boolean mask for u-velocity source positions. Mutually exclusive with coords_u.
|
|
93
|
+
v0 : NDArray[np.float64] | None
|
|
94
|
+
Time-varying velocity in the lateral (y) direction; shape [n_sources_v, nt].
|
|
95
|
+
coords_v : NDArray[np.int64] | None
|
|
96
|
+
Coordinates for the v-velocity source; shape [n_sources_v, ndim].
|
|
97
|
+
Mutually exclusive with mask_v.
|
|
98
|
+
mask_v : NDArray[np.bool] | None
|
|
99
|
+
Boolean mask for v-velocity source positions. Mutually exclusive with coords_v.
|
|
100
|
+
w0 : NDArray[np.float64] | None
|
|
101
|
+
Time-varying velocity in the elevational (z) direction; shape [n_sources_w, nt].
|
|
102
|
+
coords_w : NDArray[np.int64] | None
|
|
103
|
+
Coordinates for the w-velocity source; shape [n_sources_w, ndim].
|
|
104
|
+
Mutually exclusive with mask_w.
|
|
105
|
+
mask_w : NDArray[np.bool] | None
|
|
106
|
+
Boolean mask for w-velocity source positions. Mutually exclusive with coords_w.
|
|
69
107
|
|
|
70
108
|
Raises
|
|
71
109
|
------
|
|
72
110
|
ValueError
|
|
73
|
-
If grid_shape is missing when using coords or
|
|
111
|
+
If grid_shape is missing when using coords, additive-only, or velocity-only.
|
|
74
112
|
If both mask and coords, or both coords_additive and mask_additive, are provided.
|
|
75
|
-
If primary source positions cannot be resolved (need coords+grid_shape,
|
|
76
|
-
|
|
77
|
-
If
|
|
113
|
+
If primary source positions cannot be resolved (need coords+grid_shape, mask,
|
|
114
|
+
additive-only args, or at least one velocity component with grid_shape).
|
|
115
|
+
If all of p0, p0_additive, u0, v0, and w0 are None.
|
|
78
116
|
If p0 / p0_additive row counts do not match their coordinate arrays.
|
|
79
117
|
|
|
80
118
|
"""
|
|
@@ -110,17 +148,28 @@ class Source:
|
|
|
110
148
|
mask_add = np.atleast_2d(mask_additive)
|
|
111
149
|
self.grid_shape = mask_add.shape
|
|
112
150
|
self.incoords = map_to_coords(mask_add)
|
|
151
|
+
elif u0 is not None or v0 is not None or w0 is not None:
|
|
152
|
+
# Velocity-only: no hard pressure nodes; pressure arrays will be empty
|
|
153
|
+
if grid_shape is None:
|
|
154
|
+
error_msg = "grid_shape is required for velocity-only source"
|
|
155
|
+
raise ValueError(error_msg)
|
|
156
|
+
self.grid_shape = tuple(grid_shape)
|
|
157
|
+
self.incoords = np.empty((0, len(self.grid_shape)), dtype=np.int64)
|
|
113
158
|
else:
|
|
114
159
|
error_msg = (
|
|
115
160
|
"Provide (coords + grid_shape), or mask, or"
|
|
116
|
-
" (p0_additive + (coords_additive or mask_additive))"
|
|
161
|
+
" (p0_additive + (coords_additive or mask_additive)), or"
|
|
162
|
+
" a velocity component (u0/v0/w0) with grid_shape"
|
|
117
163
|
)
|
|
118
164
|
raise ValueError(
|
|
119
165
|
error_msg,
|
|
120
166
|
)
|
|
121
167
|
|
|
122
|
-
if p0 is None and p0_additive is None:
|
|
123
|
-
error_msg =
|
|
168
|
+
if p0 is None and p0_additive is None and u0 is None and v0 is None and w0 is None:
|
|
169
|
+
error_msg = (
|
|
170
|
+
"At least one of p0, p0_additive, or a velocity component"
|
|
171
|
+
" (u0/v0/w0) must be provided"
|
|
172
|
+
)
|
|
124
173
|
raise ValueError(error_msg)
|
|
125
174
|
|
|
126
175
|
# --- Resolve additive coords (for later use) ---
|
|
@@ -148,7 +197,7 @@ class Source:
|
|
|
148
197
|
)
|
|
149
198
|
raise ValueError(error_msg)
|
|
150
199
|
self.p0_additive = np.atleast_2d(p0_additive) if p0_additive is not None else None
|
|
151
|
-
|
|
200
|
+
elif p0_additive is not None:
|
|
152
201
|
# Additive-only: icmat written as zeros
|
|
153
202
|
p0_add = np.atleast_2d(p0_additive)
|
|
154
203
|
n_add = _coords_add.shape[0] if _coords_add is not None else self.incoords.shape[0]
|
|
@@ -166,6 +215,12 @@ class Source:
|
|
|
166
215
|
dtype=np.float64,
|
|
167
216
|
)
|
|
168
217
|
self.p0_additive = p0_add
|
|
218
|
+
else:
|
|
219
|
+
# Velocity-only: no pressure nodes; derive nt from the first velocity signal
|
|
220
|
+
_first_vel = next(s for s in (u0, v0, w0) if s is not None)
|
|
221
|
+
nt_vel = np.atleast_2d(_first_vel).shape[1]
|
|
222
|
+
self.p0 = np.zeros((0, nt_vel), dtype=np.float64)
|
|
223
|
+
self.p0_additive = None
|
|
169
224
|
|
|
170
225
|
# --- Set incoords_add for writer/binary ---
|
|
171
226
|
if self.p0_additive is not None:
|
|
@@ -173,6 +228,11 @@ class Source:
|
|
|
173
228
|
else:
|
|
174
229
|
self.incoords_add = None
|
|
175
230
|
|
|
231
|
+
# --- Resolve velocity source components ---
|
|
232
|
+
self.incoords_u, self.u0 = self._resolve_velocity_component("u", u0, coords_u, mask_u)
|
|
233
|
+
self.incoords_v, self.v0 = self._resolve_velocity_component("v", v0, coords_v, mask_v)
|
|
234
|
+
self.incoords_w, self.w0 = self._resolve_velocity_component("w", w0, coords_w, mask_w)
|
|
235
|
+
|
|
176
236
|
self.is_3d = len(self.grid_shape) == 3
|
|
177
237
|
super().__init__()
|
|
178
238
|
self.__post_init__()
|
|
@@ -202,11 +262,64 @@ class Source:
|
|
|
202
262
|
)
|
|
203
263
|
raise ValueError(error_msg)
|
|
204
264
|
|
|
265
|
+
@staticmethod
|
|
266
|
+
def _resolve_velocity_component(
|
|
267
|
+
name: str,
|
|
268
|
+
signal: NDArray | None,
|
|
269
|
+
coords: NDArray | None,
|
|
270
|
+
mask: NDArray | None,
|
|
271
|
+
) -> tuple[NDArray | None, NDArray | None]:
|
|
272
|
+
"""Resolve a velocity source component to (incoords, signal_arr).
|
|
273
|
+
|
|
274
|
+
Returns
|
|
275
|
+
-------
|
|
276
|
+
tuple[NDArray | None, NDArray | None]
|
|
277
|
+
(incoords, signal_arr) if the component is provided, else (None, None).
|
|
278
|
+
|
|
279
|
+
Raises
|
|
280
|
+
------
|
|
281
|
+
ValueError
|
|
282
|
+
If coords and mask are both provided.
|
|
283
|
+
If signal is provided but neither coords nor mask are given.
|
|
284
|
+
If signal is None but coords/mask are provided.
|
|
285
|
+
If signal row count does not match the number of coordinate points.
|
|
286
|
+
|
|
287
|
+
"""
|
|
288
|
+
if signal is None:
|
|
289
|
+
if coords is not None or mask is not None:
|
|
290
|
+
error_msg = f"coords_{name}/mask_{name} provided without {name}0 signal"
|
|
291
|
+
raise ValueError(error_msg)
|
|
292
|
+
return None, None
|
|
293
|
+
if coords is not None and mask is not None:
|
|
294
|
+
error_msg = f"coords_{name} and mask_{name} are mutually exclusive"
|
|
295
|
+
raise ValueError(error_msg)
|
|
296
|
+
if coords is None and mask is None:
|
|
297
|
+
error_msg = f"{name}0 signal provided but neither coords_{name} nor mask_{name} given"
|
|
298
|
+
raise ValueError(error_msg)
|
|
299
|
+
if coords is not None:
|
|
300
|
+
incoords = np.atleast_2d(coords).astype(np.int64, copy=False)
|
|
301
|
+
else:
|
|
302
|
+
incoords = map_to_coords(np.atleast_2d(mask))
|
|
303
|
+
signal_arr = np.atleast_2d(signal)
|
|
304
|
+
if signal_arr.shape[0] != incoords.shape[0]:
|
|
305
|
+
error_msg = (
|
|
306
|
+
f"{name}0 has {signal_arr.shape[0]} rows but "
|
|
307
|
+
f"coords_{name}/mask_{name} has {incoords.shape[0]} (must match)"
|
|
308
|
+
)
|
|
309
|
+
raise ValueError(error_msg)
|
|
310
|
+
return incoords, signal_arr
|
|
311
|
+
|
|
205
312
|
def validate(self, grid_shape: NDArray[np.int64] | tuple) -> None:
|
|
206
313
|
"""Check if the source coordinates are consistent with the grid shape."""
|
|
207
314
|
grid_shape = tuple(grid_shape) if isinstance(grid_shape, np.ndarray) else grid_shape
|
|
208
315
|
assert self.grid_shape == grid_shape, f"{self.grid_shape} != {grid_shape}"
|
|
209
|
-
assert
|
|
316
|
+
assert (
|
|
317
|
+
self.n_sources > 0
|
|
318
|
+
or self.n_sources_add > 0
|
|
319
|
+
or self.n_sources_u > 0
|
|
320
|
+
or self.n_sources_v > 0
|
|
321
|
+
or self.n_sources_w > 0
|
|
322
|
+
), "No active source found."
|
|
210
323
|
logger.debug("Source validated against grid shape.")
|
|
211
324
|
|
|
212
325
|
@property
|
|
@@ -246,6 +359,21 @@ class Source:
|
|
|
246
359
|
return 0
|
|
247
360
|
return self.incoords_add.shape[0]
|
|
248
361
|
|
|
362
|
+
@property
|
|
363
|
+
def n_sources_u(self) -> int:
|
|
364
|
+
"""Return the number of velocity-u (depth) source positions."""
|
|
365
|
+
return self.incoords_u.shape[0] if self.incoords_u is not None else 0
|
|
366
|
+
|
|
367
|
+
@property
|
|
368
|
+
def n_sources_v(self) -> int:
|
|
369
|
+
"""Return the number of velocity-v (lateral) source positions."""
|
|
370
|
+
return self.incoords_v.shape[0] if self.incoords_v is not None else 0
|
|
371
|
+
|
|
372
|
+
@property
|
|
373
|
+
def n_sources_w(self) -> int:
|
|
374
|
+
"""Return the number of velocity-w (elevational) source positions."""
|
|
375
|
+
return self.incoords_w.shape[0] if self.incoords_w is not None else 0
|
|
376
|
+
|
|
249
377
|
def plot(
|
|
250
378
|
self,
|
|
251
379
|
export_path: Path | str | None = Path("./temp/temp.png"),
|
|
@@ -312,6 +440,13 @@ class Source:
|
|
|
312
440
|
lines.append(f" p0_additive shape: {self.p0_additive.shape}")
|
|
313
441
|
if self.incoords_add is not None:
|
|
314
442
|
lines.append(f" incoords_add: {self.incoords_add.shape[0]} points")
|
|
443
|
+
for comp, sig, n in (
|
|
444
|
+
("u", self.u0, self.n_sources_u),
|
|
445
|
+
("v", self.v0, self.n_sources_v),
|
|
446
|
+
("w", self.w0, self.n_sources_w),
|
|
447
|
+
):
|
|
448
|
+
if sig is not None:
|
|
449
|
+
lines.append(f" {comp}0 shape: {sig.shape} ({n} points)")
|
|
315
450
|
return "\n".join(lines) + "\n"
|
|
316
451
|
|
|
317
452
|
def __repr__(self) -> str:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "fullwave25"
|
|
3
|
-
version = "1.2.
|
|
3
|
+
version = "1.2.6-dev0" # Update via bump-my-version, not manually
|
|
4
4
|
description = "Fullwave 2.5: Ultrasound wave propagation simulation with heterogeneous power law attenuation modelling capabilities"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/fullwave/medium_builder/presets/domain_background.py
RENAMED
|
File without changes
|
{fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/fullwave/medium_builder/presets/domain_scatterer.py
RENAMED
|
File without changes
|
{fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/fullwave/medium_builder/presets/domain_simple.py
RENAMED
|
File without changes
|
{fullwave25-1.2.5.dev0 → fullwave25-1.2.6.dev0}/fullwave/medium_builder/presets/domain_water_gel.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|