Skip to content

Commit 15a771a

Browse files
authored
#33 updated adjoint and TR examples (#34)
* updated adjoint and TR examples (thank you Felix Lucka) * Fixed bug in generateRegressionData.m due to change in adjoint example * Fixed cast to single bug in unit test for diffusion model * Fixed tempdir folder bug in pstdElastic movie saving unit test
1 parent 7c89ad5 commit 15a771a

6 files changed

Lines changed: 99 additions & 81 deletions

k-Wave/examples/example_pr_2D_TR_iterative.m

Lines changed: 53 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
% example builds on the 2D Time Reversal Reconstruction For A Line Sensor
66
% Example.
77
%
8-
% author: Ben Cox and Bradley Treeby
8+
% author: Ben Cox, Bradley Treeby and Felix Lucka
99
% date: 22nd January 2012
10-
% last update: 29th July 2019
10+
% last update: 22nd October 2021
1111
%
1212
% This function is part of the k-Wave Toolbox (http://www.k-wave.org)
1313
% Copyright (C) 2012-2019 Ben Cox and Bradley Treeby
@@ -32,7 +32,7 @@
3232
% =========================================================================
3333

3434
% define literals
35-
NUMBER_OF_ITERATIONS = 3; % number of iterations
35+
NUMBER_OF_ITERATIONS = 5; % number of iterations
3636
PML_SIZE = 20; % size of the perfectly matched layer in grid points
3737

3838
% create the computational grid
@@ -68,59 +68,33 @@
6868
% set the input arguments: force the PML to be outside the computational
6969
% grid, switch off p0 smoothing within kspaceFirstOrder2D
7070
input_args = {'PMLInside', false, 'PMLSize', PML_SIZE, 'Smooth', false, ...
71-
'PlotPML', false, 'PlotSim', true};
71+
'PlotPML', false, 'PlotSim', false};
7272

7373
% run the simulation
7474
sensor_data = kspaceFirstOrder2D(kgrid, medium, source, sensor, input_args{:});
7575

7676
% =========================================================================
77-
% RECONSTRUCT AN IMAGE USING TIME REVERSAL
77+
% RECONSTRUCT AN IMAGE USING ITERATIVE TIME REVERSAL
7878
% =========================================================================
7979

80-
% remove the initial pressure field used in the simulation
81-
source = rmfield(source, 'p0');
80+
% set the initial reconstructed image to be zeros
81+
p0_estimate = zeros(Nx, Ny);
8282

83-
% use the sensor points as sources in time reversal
84-
source.p_mask = sensor.mask;
83+
% set the initial model times series to be zero
84+
modelled_time_series = zeros(size(sensor_data));
8585

86-
% time reverse and assign the data
87-
source.p = fliplr(sensor_data);
86+
% calculate the difference between the measured and modelled data
87+
data_difference = sensor_data - modelled_time_series;
8888

89-
% enforce, rather than add, the time-reversed pressure values
90-
source.p_mode = 'dirichlet';
91-
92-
% set the simulation to record the final image (at t = 0)
93-
sensor.record = {'p_final'};
94-
95-
% run the time reversal reconstruction
96-
p0_estimate = kspaceFirstOrder2D(kgrid, medium, source, sensor, input_args{:});
97-
98-
% apply a positivity condition
99-
p0_estimate.p_final = p0_estimate.p_final .* (p0_estimate.p_final > 0);
100-
101-
% store the latest image estimate
102-
p0_1 = p0_estimate.p_final;
89+
% track goodness of fit and relative error during the iteration
90+
gof = ones(NUMBER_OF_ITERATIONS+1, 1);
91+
rel_err = ones(NUMBER_OF_ITERATIONS+1, 1);
10392

10493
% =========================================================================
10594
% ITERATE TO IMPROVE THE IMAGE
10695
% =========================================================================
10796

108-
for loop = 2:NUMBER_OF_ITERATIONS
109-
110-
% remove the source used in the previous time reversal
111-
source = rmfield(source, 'p');
112-
113-
% set the initial pressure to be the latest estimate of p0
114-
source.p0 = p0_estimate.p_final;
115-
116-
% set the simulation to record the time series
117-
sensor = rmfield(sensor, 'record');
118-
119-
% calculate the time series using the latest estimate of p0
120-
sensor_data2 = kspaceFirstOrder2D(kgrid, medium, source, sensor, input_args{:});
121-
122-
% calculate the error in the estimated time series
123-
data_difference = sensor_data - sensor_data2;
97+
for loop = 1:NUMBER_OF_ITERATIONS
12498

12599
% assign the data_difference as a time-reversal source
126100
source.p_mask = sensor.mask;
@@ -133,16 +107,36 @@
133107

134108
% run the time reversal reconstruction
135109
p0_update = kspaceFirstOrder2D(kgrid, medium, source, sensor, input_args{:});
136-
137-
% add the update to the latest image
138-
p0_estimate.p_final = p0_estimate.p_final + p0_update.p_final;
139-
110+
111+
% update the image
112+
p0_estimate = p0_estimate + p0_update.p_final;
113+
140114
% apply a positivity condition
141-
p0_estimate.p_final = p0_estimate.p_final .* (p0_estimate.p_final > 0);
115+
p0_estimate = p0_estimate .* (p0_estimate >= 0);
142116

143117
% store the latest image estimate
144-
eval(['p0_' num2str(loop) ' = p0_estimate.p_final;']);
118+
p0_iterates{loop} = p0_estimate;
119+
120+
121+
% remove the source used in the previous time reversal
122+
source = rmfield(source, 'p');
145123

124+
% set the initial pressure to be the latest estimate of p0
125+
source.p0 = p0_estimate;
126+
127+
% set the simulation to record the time series
128+
sensor = rmfield(sensor, 'record');
129+
130+
% calculate the time series using the latest estimate of p0
131+
modelled_time_series = kspaceFirstOrder2D(kgrid, medium, source, sensor, input_args{:});
132+
133+
% calculate the error in the estimated time series
134+
data_difference = sensor_data - modelled_time_series;
135+
136+
% measure goodness of fit and relative error
137+
gof(loop+1) = norm(data_difference(:))^2/norm(sensor_data(:))^2;
138+
rel_err(loop+1) = norm(p0_estimate(:) - p0(:))^2/norm(p0(:))^2;
139+
146140
end
147141

148142
% =========================================================================
@@ -168,7 +162,7 @@
168162

169163
% plot the first iteration
170164
figure;
171-
imagesc(p0_1, c_axis);
165+
imagesc(p0_iterates{1}, c_axis);
172166
axis image;
173167
set(gca, 'XTick', [], 'YTick', []);
174168
ylabel('x-position [mm]');
@@ -184,7 +178,7 @@
184178

185179
% plot the 2nd iteration
186180
figure;
187-
imagesc(p0_2, c_axis);
181+
imagesc(p0_iterates{2}, c_axis);
188182
axis image;
189183
set(gca, 'XTick', [], 'YTick', []);
190184
title('Time Reversal Reconstruction, 2 Iterations');
@@ -196,17 +190,23 @@
196190
plot([Ny, 1], [1, 1], 'k-');
197191
plot([1, 1], [1, Nx], 'k-');
198192

199-
% plot the 3rd iteration
193+
% plot the last iteration
200194
figure;
201-
imagesc(p0_3, c_axis);
195+
imagesc(p0_iterates{end}, c_axis);
202196
axis image;
203197
set(gca, 'XTick', [], 'YTick', []);
204-
title('Time Reversal Reconstruction, 3 Iterations');
198+
title(['Time Reversal Reconstruction, ' int2str(NUMBER_OF_ITERATIONS) ' Iterations']);
205199
colorbar;
206200
scaleFig(1, 0.65);
207201

208202
% add lines indicating the sensor mask and 'visible region'
209203
hold on;
210204
plot([Ny, 1], [1, 1], 'k-');
211205
plot([1, 1], [1, Nx], 'k-');
212-
plot([Ny, 1], [1, Nx], 'k--');
206+
plot([Ny, 1], [1, Nx], 'k--');
207+
208+
% plot gof and relative error
209+
figure();
210+
plot(0:NUMBER_OF_ITERATIONS, gof, 'DisplayName', 'goodness of fit'); hold on
211+
plot(0:NUMBER_OF_ITERATIONS, rel_err, 'DisplayName', 'relative error'); hold on
212+
legend(gca,'show');

k-Wave/examples/example_pr_2D_adjoint.m

Lines changed: 42 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@
1111
% For a more detailed discussion of this example and the underlying
1212
% techniques, see Arridge et al. Inverse Problems 32, 115012 (2016).
1313
%
14-
% author: Ben Cox and Bradley Treeby
14+
% author: Ben Cox, Bradley Treeby and Felix Lucka
1515
% date: 29th May 2017
16-
% last update: 29th July 2019
16+
% last update: 22nd October 2021
1717
%
1818
% This function is part of the k-Wave Toolbox (http://www.k-wave.org)
1919
% Copyright (C) 2017-2019 Ben Cox and Bradley Treeby
@@ -38,7 +38,7 @@
3838
% =========================================================================
3939

4040
% define literals
41-
NUMBER_OF_ITERATIONS = 5; % number of iterations
41+
NUMBER_OF_ITERATIONS = 5; % number of iterations
4242
PML_SIZE = 20; % size of the perfectly matched layer in grid points
4343

4444
% create the computational grid
@@ -89,6 +89,13 @@
8989
% set the initial model times series to be zero
9090
modelled_time_series = zeros(size(sensor_data));
9191

92+
% calculate the difference between the measured and modelled data
93+
data_difference = modelled_time_series - sensor_data;
94+
95+
% track goodness of fit and relative error during the iteration
96+
gof = ones(NUMBER_OF_ITERATIONS+1, 1);
97+
rel_err = ones(NUMBER_OF_ITERATIONS+1, 1);
98+
9299
% reconstruct p0 image iteratively using the adjoint to estimate the
93100
% functional gradient
94101
for loop = 1:NUMBER_OF_ITERATIONS
@@ -106,40 +113,49 @@
106113
% set the source type to act as an adjoint source
107114
source.p_mode = 'additive';
108115

109-
% calculate the difference between the measured and modelled data
110-
difference = modelled_time_series - sensor_data;
111-
112116
% assign the difference time series as an adjoint source
113-
% (see Appendix B in Arridge et al. Inverse Problems 32, 115012 (2016))
114-
time_reversed_data = fliplr(difference);
115-
source.p = [time_reversed_data(:, 1), time_reversed_data(:, 1), time_reversed_data(:, 1:end-1)] + ...
116-
[zeros(size(time_reversed_data(:, 1))), time_reversed_data(:, 1:end-1), 2 * time_reversed_data(:, end)];
117+
% (see Appendix B, Eqn B.2 in Arridge et al. Inverse Problems 32, 115012 (2016))
118+
p_adj = [data_difference(:, end:-1:1), zeros(size(data_difference, 1), 1)] ...
119+
+ [zeros(size(data_difference, 1), 1), data_difference(:, end:-1:1)];
120+
p_adj(:, end-1) = p_adj(:, end-1) + p_adj(:, end);
121+
p_adj = p_adj(:, 1:end-1);
122+
source.p = p_adj;
117123

118124
% send difference through adjoint model
119125
image_update = kspaceFirstOrder2D(kgrid, medium, source, sensor, input_args{:});
120126

127+
% add smoothing (see Appendix B in Arridge et al. Inverse Problems 32, 115012 (2016))
128+
image_update = smooth(image_update.p_final, false);
129+
121130
% set the step length (this could be chosen by doing a line search)
122-
nu = 0.25;
131+
nu = 0.5;
123132

124133
% update the image
125-
p0_estimate = p0_estimate - nu * image_update.p_final;
134+
p0_estimate = p0_estimate - nu * image_update;
126135

127136
% apply a positivity condition
128137
p0_estimate = p0_estimate .* (p0_estimate >= 0);
129138

130139
% store the latest image estimate
131-
eval(['p0_' num2str(loop) ' = p0_estimate;']);
140+
p0_iterates{loop} = p0_estimate;
132141

133142
% set the latest image to be the initial pressure in the forward model
134143
source = rmfield(source, 'p');
135-
source.p0 = p0_estimate;
144+
source.p0 = smooth(p0_estimate, true);
136145

137146
% set the sensor to record time series (by default)
138147
sensor = rmfield(sensor, 'record');
139148

140149
% calculate the time series at the sensors using the latest estimate of p0
141150
modelled_time_series = kspaceFirstOrder2D(kgrid, medium, source, sensor, input_args{:});
142151

152+
% calculate the difference between the measured and modelled data
153+
data_difference = modelled_time_series - sensor_data;
154+
155+
% measure goodness of fit and relative error
156+
gof(loop+1) = norm(data_difference(:))^2/norm(sensor_data(:))^2;
157+
rel_err(loop+1) = norm(p0_estimate(:) - p0(:))^2/norm(p0(:))^2;
158+
143159
end
144160

145161
% =========================================================================
@@ -165,7 +181,7 @@
165181

166182
% plot the first iteration
167183
figure;
168-
imagesc(p0_1, c_axis);
184+
imagesc(p0_iterates{1}, c_axis);
169185
axis image;
170186
set(gca, 'XTick', [], 'YTick', []);
171187
title('Gradient Descent, 1 Iteration');
@@ -179,7 +195,7 @@
179195

180196
% plot the 2nd iteration
181197
figure;
182-
imagesc(p0_2, c_axis);
198+
imagesc(p0_iterates{2}, c_axis);
183199
axis image;
184200
set(gca, 'XTick', [], 'YTick', []);
185201
title('Gradient Descent, 2 Iterations');
@@ -191,17 +207,23 @@
191207
plot([Ny, 1], [1, 1], 'k-');
192208
plot([1, 1], [1, Nx], 'k-');
193209

194-
% plot the 5th iteration
210+
% plot the last iteration
195211
figure;
196-
imagesc(p0_5, c_axis);
212+
imagesc(p0_iterates{end}, c_axis);
197213
axis image;
198214
set(gca, 'XTick', [], 'YTick', []);
199-
title('Gradient Descent, 5 Iterations');
215+
title(['Gradient Descent, ' int2str(NUMBER_OF_ITERATIONS) ' Iterations']);
200216
colorbar;
201217
scaleFig(1, 0.65);
202218

203219
% add lines indicating the sensor mask and 'visible region'
204220
hold on;
205221
plot([Ny, 1], [1, 1], 'k-');
206222
plot([1, 1], [1, Nx], 'k-');
207-
plot([Ny, 1], [1, Nx], 'k--');
223+
plot([Ny, 1], [1, Nx], 'k--');
224+
225+
% plot gof and relative error
226+
figure();
227+
plot(0:NUMBER_OF_ITERATIONS, gof, 'DisplayName', 'goodness of fit'); hold on
228+
plot(0:NUMBER_OF_ITERATIONS, rel_err, 'DisplayName', 'relative error'); hold on
229+
legend(gca,'show');

k-Wave/testing/regression/generateRegressionData.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@
243243

244244
index = index + 1;
245245
test_examples{index}.name = 'example_pr_2D_adjoint';
246-
test_examples{index}.outputs = {'sensor_data', 'p0', 'p0_1', 'p0_2', 'p0_3', 'p0_4', 'p0_5'};
246+
test_examples{index}.outputs = {'sensor_data', 'p0_estimate'};
247247
test_examples{index}.precision = 'double';
248248

249249
% =========================================================================

k-Wave/testing/unit/kWaveDiffusion_compare_1D_2D_3D_plane_waves.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@
108108
comparison_thresh = COMPARISON_THRESH_DOUBLE;
109109
case 2
110110
comparison_thresh = COMPARISON_THRESH_SINGLE;
111-
input_args = [input_args, {'DataCast', 'gpuArray-single'}]; %#ok<AGROW>
111+
input_args = [input_args, {'DataCast', 'single'}]; %#ok<AGROW>
112112
end
113113

114114
% loop through tests

k-Wave/testing/unit/kWaveDiffusion_compare_3D_heterog_perfusion_with_exact.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@
105105
case 1
106106
input_args = {'PlotSim', plot_simulations, 'PlotScale', [37, 38]};
107107
case 2
108-
input_args = {'PlotSim', plot_simulations, 'PlotScale', [37, 38], 'DataCast', 'gpuArray-single'};
108+
input_args = {'PlotSim', plot_simulations, 'PlotScale', [37, 38], 'DataCast', 'single'};
109109
end
110110

111111
% create kWaveDiffusion object and take time steps

k-Wave/testing/unit/pstdElastic2D_save_movie.m

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,7 @@
2727
test_pass = true;
2828

2929
% folder to store temporary movies in
30-
if nargin == 0
31-
folder = '';
32-
else
33-
folder = tempdir;
34-
end
30+
folder = tempdir;
3531

3632
% movie names
3733
movie_name_1 = 'test-movie-elastic-2D-avi';

0 commit comments

Comments
 (0)