Skip to content

Commit f9edadd

Browse files
committed
Adapted virtual sources to dasmtx 2d
1 parent 1b211f9 commit f9edadd

File tree

9 files changed

+222
-170
lines changed

9 files changed

+222
-170
lines changed

examples/pfield.ipynb

Lines changed: 6 additions & 13 deletions
Large diffs are not rendered by default.

examples/quickstart_demo.ipynb

Lines changed: 69 additions & 34 deletions
Large diffs are not rendered by default.

examples/rotatingDiskVelocitySynthetic.ipynb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -529,7 +529,7 @@
529529
],
530530
"metadata": {
531531
"kernelspec": {
532-
"display_name": "gbernardino",
532+
"display_name": "pymust",
533533
"language": "python",
534534
"name": "python3"
535535
},

examples/rotatingDiskVelocitySyntheticML.ipynb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1654,7 +1654,7 @@
16541654
],
16551655
"metadata": {
16561656
"kernelspec": {
1657-
"display_name": "gbernardino",
1657+
"display_name": "pymust",
16581658
"language": "python",
16591659
"name": "python3"
16601660
},

examples/rotatingDisk_real.ipynb

Lines changed: 13 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"outputs": [],
88
"source": [
99
"import pymust, pymust.utils\n",
10-
"import numpy as np, matplotlib.pyplot as plt, scipy, scipy.io\n"
10+
"import numpy as np, matplotlib.pyplot as plt, scipy, scipy.io"
1111
]
1212
},
1313
{
@@ -58,13 +58,6 @@
5858
"execution_count": 4,
5959
"metadata": {},
6060
"outputs": [],
61-
"source": []
62-
},
63-
{
64-
"cell_type": "code",
65-
"execution_count": 5,
66-
"metadata": {},
67-
"outputs": [],
6861
"source": [
6962
"#% Create a 2.5-cm-by-2.5-cm image grid.\n",
7063
"dx = 1e-4; # grid x-step (in m)\n",
@@ -74,70 +67,10 @@
7467
},
7568
{
7669
"cell_type": "code",
77-
"execution_count": 18,
78-
"metadata": {},
79-
"outputs": [
80-
{
81-
"data": {
82-
"text/plain": [
83-
"(334, 128, 32)"
84-
]
85-
},
86-
"execution_count": 18,
87-
"metadata": {},
88-
"output_type": "execute_result"
89-
}
90-
],
91-
"source": [
92-
"RF.shape"
93-
]
94-
},
95-
{
96-
"cell_type": "code",
97-
"execution_count": 20,
98-
"metadata": {},
99-
"outputs": [
100-
{
101-
"data": {
102-
"text/plain": [
103-
"dtype('complex128')"
104-
]
105-
},
106-
"execution_count": 20,
107-
"metadata": {},
108-
"output_type": "execute_result"
109-
}
110-
],
111-
"source": [
112-
"M.dtype"
113-
]
114-
},
115-
{
116-
"cell_type": "code",
117-
"execution_count": 26,
70+
"execution_count": null,
11871
"metadata": {},
119-
"outputs": [
120-
{
121-
"name": "stderr",
122-
"output_type": "stream",
123-
"text": [
124-
"/Users/gbernardino/pymust/src/pymust/iq2doppler.py:109: RuntimeWarning: overflow encountered in scalar multiply\n",
125-
" VN = c*PRF/4/fc/lag; #% Nyquist velocity\n",
126-
"/Users/gbernardino/pymust/src/pymust/iq2doppler.py:135: RuntimeWarning: overflow encountered in scalar multiply\n",
127-
" return param.c*PRF/4/param.fc/lag\n"
128-
]
129-
},
130-
{
131-
"name": "stdout",
132-
"output_type": "stream",
133-
"text": [
134-
"682 ms ± 29 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n"
135-
]
136-
}
137-
],
72+
"outputs": [],
13873
"source": [
139-
"%%timeit\n",
140-
"\n",
14174
"# Demodulate the RF signals with RF2IQ.\n",
14275
"IQ =pymust.rf2iq(RF,param); \n",
14376
"\n",
@@ -156,42 +89,19 @@
15689
},
15790
{
15891
"cell_type": "code",
159-
"execution_count": 25,
92+
"execution_count": 1,
16093
"metadata": {},
16194
"outputs": [
16295
{
163-
"data": {
164-
"text/plain": [
165-
"array([[ 1043.75942521 +393.28573606j, -110.59674674 -794.7907838j ,\n",
166-
" 155.97024474 +19.16469254j, ...,\n",
167-
" 640.05718968 +79.877806j , 47.97989278+1650.34056168j,\n",
168-
" -786.16390502+1323.92393539j],\n",
169-
" [ 2120.85562304+1052.11123787j, 449.89643897 +473.17923728j,\n",
170-
" -2608.15759155 -45.45656041j, ...,\n",
171-
" 269.26341772-1415.41186441j, -448.39690033-2219.91092253j,\n",
172-
" 268.33542192-2998.85409566j],\n",
173-
" [ -709.59873315 -475.67716058j, -279.17991451-2085.20116294j,\n",
174-
" 1562.51252753 +590.63287135j, ...,\n",
175-
" -1943.1324732 +197.70210888j, -459.72749632 +978.46788931j,\n",
176-
" -769.83180953+1284.49579019j],\n",
177-
" ...,\n",
178-
" [ 2038.61650079-1722.36609223j, 762.66729482-1193.9384699j ,\n",
179-
" -1016.10629388 +895.42186554j, ...,\n",
180-
" 2511.86589386+1001.69833543j, 1270.01473896 +886.98610086j,\n",
181-
" 1889.04382837+1185.07157389j],\n",
182-
" [-3954.04195746-2266.62318821j, -101.54895284+1101.5181209j ,\n",
183-
" 1827.17382357 -666.99963509j, ...,\n",
184-
" 1041.77512085-2596.96349652j, -911.84412261-2286.95862888j,\n",
185-
" -1901.66374869-1338.63133119j],\n",
186-
" [ 548.99674494+1990.93245908j, 530.02085241-1093.80990497j,\n",
187-
" -554.44475243 -914.16567814j, ...,\n",
188-
" -286.04014307-2006.75468774j, 1242.96603169 +272.22541231j,\n",
189-
" -993.40683334 +383.0611551j ]])"
190-
]
191-
},
192-
"execution_count": 25,
193-
"metadata": {},
194-
"output_type": "execute_result"
96+
"ename": "NameError",
97+
"evalue": "name 'M' is not defined",
98+
"output_type": "error",
99+
"traceback": [
100+
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
101+
"\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)",
102+
"Cell \u001b[0;32mIn[1], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mM\u001b[49m \u001b[38;5;241m@\u001b[39m IQ\u001b[38;5;241m.\u001b[39mreshape(\u001b[38;5;241m-\u001b[39m\u001b[38;5;241m1\u001b[39m, RF\u001b[38;5;241m.\u001b[39mshape[\u001b[38;5;241m2\u001b[39m])\n",
103+
"\u001b[0;31mNameError\u001b[0m: name 'M' is not defined"
104+
]
195105
}
196106
],
197107
"source": [

src/pymust/dasmtx.py

Lines changed: 113 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import numpy as np
2+
import scipy.optimize, itertools
3+
24
import scipy, scipy.interpolate
35
from . import utils
46

@@ -439,10 +441,56 @@ def dasmtx(SIG : np.ndarray, x: np.ndarray, z: np.ndarray, *varargin):
439441
#% For a given location [x(k),z(k)], one has:
440442
#% dTX(k) = min(delaysTXi*c + sqrt((xTi-x(k)).^2 + (zTi-z(k)).^2));
441443
#% In a compact matrix form, this yields:
442-
if not param.passive:
443-
dTX = np.min(delaysTXi*c + np.sqrt((xTi-x)**2 + (zTi-z)**2), 1).reshape((-1,1))
444-
else:
444+
if param.passive:
445445
dTX = np.zeros_like(x)
446+
else:
447+
# OLD version of computing the transmission delays
448+
#% For a given location [x(k),z(k)], one has:
449+
#% dTX(k) = min(delaysTXi*c + sqrt((xTi-x(k)).^2 + (zTi-z(k)).^2));
450+
#% In a compact matrix form, this yields:
451+
#dTX = np.min(delaysTXi*c + np.sqrt((xTi-x)**2 + (zTi-z)**2), 1).reshape((-1,1))
452+
453+
WasTransmitting = np.logical_not(np.isnan(delaysTX))
454+
assert np.sum(np.abs(np.diff(WasTransmitting)))<3, 'Multiple transmitting sub-apertures are not allowed during beamforming.'
455+
nTX = np.count_nonzero(WasTransmitting); # number of transmitting elements
456+
457+
458+
#-- TX distances
459+
if nTX==1:
460+
# Only one element was active
461+
dTX = np.hypot(xe[WasTransmitting][0]-x,ze[WasTransmitting][0]-z) + delaysTX[WasTransmitting][0]*c
462+
463+
elif nTX<3:
464+
raise ValueError('If not 1, the number of neighboring transmitting elements must be at least 3.')
465+
466+
else:
467+
#-- We create a virtual transducer to calculate the TX distances.
468+
xv,zv = vxdcr(xe[WasTransmitting],ze[WasTransmitting], delaysTX[WasTransmitting],c);
469+
dzv = diff2(xv,zv)
470+
471+
# Distances between the (x,z) points and the lines normal to the
472+
# virtual transducer at (xv,zv)
473+
Dn = np.abs(x-xv + dzv*(z-zv))/np.hypot(1,dzv);
474+
# idx contains the element numbers that give the smallest Dn_s
475+
idx = np.argmin(Dn,1)
476+
477+
InterpMethod = 'nearest';
478+
479+
if InterpMethod == 'nearest':
480+
dTX = np.hypot(xv[idx].reshape((-1,1)) - x, zv[idx].reshape((-1,1)) -z)
481+
elif InterpMethod == 'parabolic':
482+
N = x.size
483+
dTX = np.hypot(xv.reshape((1,-1))-x,zv.reshape((1,-1))-z);
484+
dTX[:,1:nc] = scipy.signal.convolve2d(dTX, np.array([1, 0, 1]).reshape((1,-1)),'valid')/2 - \
485+
scipy.signal.convolve2d(dTX,np.array([1, 0, -1]).reshape((1,-1)),'valid')* \
486+
scipy.signal.convolve2d(Dn,np.array([1, 0, -1]).reshape((1,-1)),'valid')/ \
487+
scipy.signal.convolve2d(Dn,np.array([2, -4, 2]).reshape((1,-1)),'valid')
488+
489+
dTX = dTX.flatten()
490+
dTX = dTX[np.arange(N) + idx *N ]
491+
else:
492+
raise ValueError("Incorrect interpolation method on dTX. Valid options: [nearest | parabolic]")
493+
446494

447495

448496
#%-- RX distances
@@ -588,4 +636,65 @@ def __apply__(self, SIG, axis ):
588636
return (self.M @ SIG.flatten(order = 'F')).reshape(self.shape, order = 'F')
589637

590638

591-
639+
# Function to calculate the first derivative of y with respect to x using a second-order difference method
640+
def diff2(x, y):
641+
m = len(y)
642+
dx = np.diff(x)
643+
dy = np.zeros_like(y)
644+
645+
# y'(x_1)
646+
dy[0] = (1 / dx[0] + 1 / dx[1]) * (y[1] - y[0]) + dx[0] / (dx[0] * dx[1] + dx[1]**2) * (y[0] - y[2])
647+
648+
# y'(x_m)
649+
dy[-1] = (1 / dx[-2] + 1 / dx[-1]) * (y[-1] - y[-2]) + dx[-1] / (dx[-1] * dx[-2] + dx[-2]**2) * (y[-3] - y[-1])
650+
651+
# y'(x_i) (i > 1 & i < m)
652+
dx1 = dx[:-1]
653+
dx2 = dx[1:]
654+
y1 = y[:-2]
655+
y2 = y[1:-1]
656+
y3 = y[2:]
657+
dy[1:-1] = 1 / (dx1 * dx2 * (dx1 + dx2)) * (-dx2**2 * y1 + (dx2**2 - dx1**2) * y2 + dx1**2 * y3)
658+
659+
return dy
660+
661+
def vxdcr(xe, ze, delaysTX, c):
662+
# Virtual transducer (XDCR)
663+
T2 = delaysTX**2
664+
dT2dxe = diff2(xe, T2)
665+
err = np.min(np.diff(xe)) * 1e-3
666+
dzedxe = diff2(xe, ze)
667+
668+
# Virtual transducer positions
669+
if np.all(ze == 0):
670+
# For a linear transducer
671+
xV = xe - 0.5 * c**2 * dT2dxe
672+
zV = -np.sqrt(np.abs(c**2 * T2 - (xV - xe)**2))
673+
else:
674+
# For a convex array
675+
xV = xe.copy()
676+
tmp = np.sqrt(np.abs(c**2 * T2 - (xV - xe)**2))
677+
678+
def myfun(xv, k):
679+
return (xe[k] - xv) + tmp[k] * dzedxe[k] - 0.5 * c**2 * dT2dxe[k]
680+
681+
for k in range(len(xV)):
682+
xV[k] = scipy.optimize.fsolve(myfun, xV[k], args=(k,), xtol=err)[0]
683+
zV = ze - tmp
684+
685+
# Validate the virtual transducer
686+
test = np.all(np.diff(xV) > 0) and np.all((c**2 * T2 - (xV - xe)**2) > -err)
687+
if not test:
688+
raise ValueError("The delays do not allow a virtual transducer to be calculated.")
689+
690+
# Check if the virtual transducer is convex or concave
691+
idx = np.array([list(i) for i in itertools.combinations(range(len(xV)), 3)])
692+
lambda_vals = (xV[idx[:, 2]] - xV[idx[:, 1]]) / (xV[idx[:, 0]] - xV[idx[:, 1]])
693+
alst1 = np.abs(lambda_vals) < 1
694+
tmp = zV[idx[alst1, 2]] - (lambda_vals[alst1] * zV[idx[alst1, 0]] + (1 - lambda_vals[alst1]) * zV[idx[alst1, 1]])
695+
test = max(np.count_nonzero(tmp > -np.finfo(float).eps),
696+
np.count_nonzero(tmp < np.finfo(float).eps)) / len(tmp) > 0.999
697+
if not test:
698+
print("Warning: The virtual transducer is not convex or concave.")
699+
700+
return xV, zV

src/pymust/pfield.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -863,7 +863,7 @@ def pfield(x : np.ndarray,y : np.ndarray, z: np.ndarray, delaysTX : np.ndarray,
863863

864864
#% RMS acoustic pressure (if we are in PFIELD only)
865865
if not (isSIMUS or isMKMOVIE):
866-
RP =np.sqrt(RP).reshape(siz0)
866+
RP =np.sqrt(RP).reshape(siz0, order = 'F')
867867
SPECT = np.swapaxes(SPECT, 0, 1)
868868
SPECT = SPECT.reshape([siz0[0], siz0[1], nSampling], order = 'F')
869869
return RP, SPECT, IDX

tutorials/P1_radiofrequency.ipynb

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,15 @@
7272
"If this does not work, you can copy the source code from the github repository, and install it locally."
7373
]
7474
},
75+
{
76+
"cell_type": "code",
77+
"execution_count": null,
78+
"metadata": {},
79+
"outputs": [],
80+
"source": [
81+
"!pip install pymust"
82+
]
83+
},
7584
{
7685
"cell_type": "markdown",
7786
"metadata": {},
@@ -652,11 +661,9 @@
652661
"xScatterers = np.array([0, 0,np.sqrt(2)*1e-2, - np.sqrt(2)*1e-2, 0])\n",
653662
"zScatterers = np.array([1e-2, 2e-2, np.sqrt(2) * 1e-2, np.sqrt(2) * 1e-2, 2.2e-2])\n",
654663
"RCScatterers = np.random.rand(5) # There are 5 scatterers\n",
655-
"#RCScatterers[1:] = 0\n",
656664
"activationDelaysTX = np.array([0]).reshape(1,1)\n",
657665
"\n",
658666
"RF, _ = pymust.simus(xScatterers, zScatterers, RCScatterers, activationDelaysTX, param_single_element)\n",
659-
"RF, c = pymust.tgc(RF)\n",
660667
"t = np.arange(RF.shape[0]) / param_single_element.fs # param_single_element.fs is the sampling frequency, needed to go from sample number to time\n"
661668
]
662669
},

tutorials/P2_several_elements_new.ipynb

Lines changed: 9 additions & 11 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)