In silico correction of blocked array using RTB and REFoCUS
Creating the simulated results in the paper:
A.E. Vrålstad, S.-E. Måsøy, T.G. Bjåstad, A.R. Sørnes and O.M.H. Rindal, "Retrospective transmit correction of blocked arrays applied to cardiac ultrasound imaging" in XX. doi:YY.ZZZZ/XX.QQQQQQQQ.
This script creates Fig.3-4 in the paper.
This code uses the UltraSound ToolBox (USTB) and you'll need to download it first to successfully rund this code. To know more about the USTB visit http://www.ustb.no/
by Anders E. Vrålstad and Ole Marius Hoel Rindal
Contents
- Clear environment
- Dataset source
- Speckle chamber ROI — all simulated blockage levels (published HTML uses snapnow)
- Primary case — full pipeline (paper figures, gCNR, differences): half aperture blocked
- Run REFoCUS preprocess
- Create Sector scan
- RTB Beamforming
- Store the Original RTB weights for plotting later
- Change beam geometry for RTB processing
- Store the Compensated RTB weights for plotting later
- Demodulate REFoCUS RF-data before DAS
- REFoCUS Beamforming
- Save PNGs
- Comparison (multi-frame: RTB, RTB Compensated, REFoCUS)
- Measure contrast (gCNR)
- Make Difference Images: of RTBs
- Make Difference Images: RTB comp and REFoCUS
- Make Difference Images: RTB and REFoCUS
Clear environment
clear all; close all;
Dataset source
Read the data, potentially download it
url = tools.zenodo_dataset_files_base(); % if not found downloaded from here local_path = [ustb_path(),'/data/']; % location of example data addpath(local_path);
Speckle chamber ROI — all simulated blockage levels (published HTML uses snapnow)
Red rectangle matches gCNR `center_rectangle` = [-0.006, 0.07, 0.007, 0.04] m on the Cartesian scan-converted grid (shown here in mm): [-6, 70, 7, 40]. Lightweight RTB-only pass per dataset so publish stays tractable.
speckle_chamber_mm = [-6, 70, 7, 40];
dataset_roi = {
'speckle_sim_FI_P4_probe_apod_1_speckle_long_many_angles.uff', 'Full aperture (no element blockage)'
'speckle_sim_FI_P4_probe_apod_2_speckle_long_many_angles.uff', 'One-third of aperture blocked'
'speckle_sim_FI_P4_probe_apod_3_speckle_long_many_angles.uff', 'Half of aperture blocked'
};
for r = 1:size(dataset_roi, 1)
fn_roi = dataset_roi{r, 1};
roi_title = dataset_roi{r, 2};
tools.download(fn_roi, url, data_path());
cd_roi = uff.read_object([data_path(), filesep, fn_roi], '/channel_data');
cd_roi.data = cd_roi.data ./ max(cd_roi.data(:));
depth_roi = linspace(0e-3, 110e-3, 512).';
az_roi = zeros(cd_roi.N_waves, 1);
for n = 1:cd_roi.N_waves
az_roi(n) = cd_roi.sequence(n).source.azimuth;
end
scan_roi = uff.sector_scan('azimuth_axis', az_roi, 'depth_axis', depth_roi);
Fnr = cd_roi.sequence(1).source.distance / (max(cd_roi.probe.x) * 2);
mid_roi = midprocess.das();
mid_roi.channel_data = cd_roi;
mid_roi.dimension = dimension.both();
mid_roi.scan = scan_roi;
if isempty(which('das_c'))
mid_roi.code = code.matlab;
else
mid_roi.code = code.mex;
end
mid_roi.receive_apodization.window = uff.window.boxcar;
mid_roi.receive_apodization.f_number = 1.7;
mid_roi.transmit_apodization.window = uff.window.hamming;
mid_roi.transmit_apodization.f_number = Fnr;
mid_roi.transmit_apodization.minimum_aperture = 3e-3;
b_roi = mid_roi.go();
[RTB_sc_roi, Xr, Zr] = tools.scan_convert(b_roi.get_image(), b_roi.scan.azimuth_axis, b_roi.scan.depth_axis, 1024, 1024);
figure('Visible', 'off');
imagesc(Xr * 1e3, Zr * 1e3, RTB_sc_roi, [-60 0]);
colormap gray;
colorbar;
axis image;
xlabel('x [mm]');
ylabel('z [mm]');
title(roi_title, 'Interpreter', 'none');
xlim([-20 20]);
ylim([60 110]);
hold on;
rectangle(gca, 'Position', speckle_chamber_mm, 'EdgeColor', 'r', 'LineWidth', 2);
hold off;
set(findall(gcf, '-property', 'FontSize'), 'FontSize', 14);
drawnow;
snapnow;
close(gcf);
end
UFF: reading channel_data [uff.channel_data] UFF: reading sequence [uff.wave] [====================] 100% USTB MATLAB beamformer...Completed in 13.81 seconds.
UFF: reading channel_data [uff.channel_data] UFF: reading sequence [uff.wave] [====================] 100% USTB MATLAB beamformer...Completed in 12.26 seconds.
UFF: reading channel_data [uff.channel_data] UFF: reading sequence [uff.wave] [====================] 100% USTB MATLAB beamformer...Completed in 12.26 seconds.
Primary case — full pipeline (paper figures, gCNR, differences): half aperture blocked
filename = 'speckle_sim_FI_P4_probe_apod_3_speckle_long_many_angles.uff'; tag = 'half'; tools.download(filename, url, data_path()); channel_data = uff.read_object([data_path(), filesep, filename], '/channel_data'); channel_data.data = channel_data.data ./ max(channel_data.data(:)); storefolder = ['./Figures/simulated_gCNR_',tag, '/']; mkdir(storefolder);
UFF: reading channel_data [uff.channel_data] UFF: reading sequence [uff.wave] [====================] 100% Warning: Directory already exists.
Run REFoCUS preprocess
tic REFoCUS = preprocess.refocus(); REFoCUS.input = channel_data; REFoCUS.use_filter = 0; REFoCUS.filter_N = 10; REFoCUS.filter_Wn = [0.05,0.4]; REFoCUS.regularization = @Hinv_tikhonov; REFoCUS.decode_parameter = 0.01; REFoCUS.post_pad_samples = 0; channel_data_REFoCUS = REFoCUS.go(); toc()
This is citationware. If you use REFoCUS in your publication, please cite Ali, R., Herickhoff, C. D., Hyun, D., Dahl, J. J., & Bottenus, N. (2020). Extending RetrospectiveEncoding for Robust Recovery of the Multistatic Data Set. IEEE Transactions on Ultrasonics,Ferroelectrics, and Frequency Control, 67(5), 943–956. https://doi.org/10.1109/TUFFC.2019.2961875Original REFoCUS code from www.github.com/nbottenus/REFoCUS Starting to REFoCUS channel data...Completed in 18.2566 seconds. Elapsed time is 18.305353 seconds.
Create Sector scan
depth_axis=linspace(0e-3,110e-3,512).'; azimuth_axis=zeros(channel_data.N_waves,1); for n=1:channel_data.N_waves azimuth_axis(n) = channel_data.sequence(n).source.azimuth; end scan=uff.sector_scan('azimuth_axis',azimuth_axis,'depth_axis',depth_axis);
RTB Beamforming
Calculate F-number
Fnumber = channel_data.sequence(1).source.distance/(max(channel_data.probe.x)*2);
mid=midprocess.das();
mid.channel_data=channel_data;
mid.dimension = dimension.both();
mid.scan=scan;
mid.code = code.mex;
mid.receive_apodization.window=uff.window.boxcar;
mid.receive_apodization.f_number=1.7;
mid.transmit_apodization.window=uff.window.hamming;
mid.transmit_apodization.f_number=Fnumber;
mid.transmit_apodization.minimum_aperture = 3e-3;
b_data_RTB = mid.go();
b_data_RTB.frame_rate = 20;
b_data_RTB.plot([], 'RTB');
USTB MEX C beamformer...Completed in 2.23 seconds.
Store the Original RTB weights for plotting later
b_data_tx_apod = uff.beamformed_data(b_data_RTB); b_data_tx_apod.data = mid.transmit_apodization.data; b_data_tx_apod.plot([],['Tx Weights no shift'],[],'none'); colormap default;
Change beam geometry for RTB processing
switch tag case 'full' origin_x = 0; case 'third' origin_x = max(channel_data.probe.x)*(2*2/3-1); Fnumber = Fnumber*3/2; case 'half' origin_x = max(channel_data.probe.x)*0.5; Fnumber = Fnumber*2; end channel_data_shifted = uff.channel_data(channel_data); for seq = 1:channel_data_shifted.N_waves channel_data_shifted.sequence(seq).origin.x = origin_x; end mid.channel_data = channel_data_shifted; mid.transmit_apodization.f_number=Fnumber; b_data_RTB_comp = mid.go(); b_data_RTB_comp.frame_rate = 20; b_data_RTB_comp.plot([],'Proposed RTB');
USTB MEX C beamformer...Completed in 1.05 seconds.
Store the Compensated RTB weights for plotting later
b_data_tx_apod = uff.beamformed_data(b_data_RTB_comp); b_data_tx_apod.data = mid.transmit_apodization.data; b_data_tx_apod.plot([],['Tx Weights with shift'],[],'none'); colormap default;
Demodulate REFoCUS RF-data before DAS
demod = preprocess.fast_demodulation(); demod.modulation_frequency = 2.5*10^6; demod.input = channel_data_REFoCUS; demod.plot_on = true; channel_data_STAI_demod = demod.go();
Warning: The downsampling frequency is not specified. Using 2 * modulation_frequency. Low-pass filtering
REFoCUS Beamforming
mid_REFoCUS = midprocess.das(); mid_REFoCUS.channel_data=channel_data_STAI_demod; mid_REFoCUS.dimension = dimension.receive(); mid_REFoCUS.scan=scan; mid_REFoCUS.code = code.mex; mid_REFoCUS.transmit_apodization.window=uff.window.boxcar; mid_REFoCUS.receive_apodization.f_number=1.7; mid_REFoCUS.receive_apodization.window=uff.window.boxcar; mid_REFoCUS.transmit_apodization.f_number=1; b_data_delayed_REFoCUS = mid_REFoCUS.go(); b_data_delayed_REFoCUS.plot([],'REFoCUS'); cc = postprocess.coherent_compounding; cc.input = b_data_delayed_REFoCUS; b_data_REFoCUS = cc.go(); tools.publish_beamformed_snap(b_data_REFoCUS, 'REFoCUS');
USTB MEX C beamformer...Completed in 3.28 seconds.
Save PNGs
f = figure('Visible', tools.headless_publish_figure_visible()); b_data_RTB.plot(f, 'RTB'); delete(findall(f, 'Type', 'uicontrol')); clim([-60 0]); xlim([-20 20]); saveas(f,[storefolder,'RTB_', tag,'.png']); drawnow; snapnow; close(f); f = figure('Visible', tools.headless_publish_figure_visible()); b_data_RTB_comp.plot(f, 'RTB Compensated'); delete(findall(f, 'Type', 'uicontrol')); clim([-60 0]); xlim([-20 20]); saveas(f,[storefolder,'RTB_compensated_', tag,'.png']); drawnow; snapnow; close(f); f = figure('Visible', tools.headless_publish_figure_visible()); b_data_REFoCUS.plot(f, 'REFoCUS'); delete(findall(f, 'Type', 'uicontrol')); clim([-60 0]); xlim([-20 20]); saveas(f,[storefolder,'REFoCUS_', tag,'.png']); drawnow; snapnow; close(f);
Comparison (multi-frame: RTB, RTB Compensated, REFoCUS)
b_data_compare = uff.beamformed_data(b_data_RTB); b_data_compare.data(:,1,1,1) = b_data_RTB.data./max(b_data_RTB.data(:)); b_data_compare.data(:,1,1,2) = b_data_RTB_comp.data./max(b_data_RTB_comp.data(:)); b_data_compare.data(:,1,1,3) = b_data_REFoCUS.data./max(b_data_REFoCUS.data(:))/3; all_images = squeeze(b_data_compare.get_image()); b_data_compare.data(:,1,1,2) = b_data_compare.data(:,1,1,2) .* median(all_images(:,:,1)./all_images(:,:,2),'all','omitnan'); b_data_compare.data(:,1,1,3) = b_data_compare.data(:,1,1,3) .* median(all_images(:,:,1)./all_images(:,:,3),'all','omitnan'); b_data_compare.frame_rate = 1; tools.publish_beamformed_snap(b_data_compare); if usejava('desktop') b_data_compare.save_as_gif(['Figures/Comparison_',tag,'.gif']); end
Measure contrast (gCNR)
Scan-convert images for contrast analysis
[RTB_sc, Xs, Zs] = tools.scan_convert(b_data_RTB.get_image(), b_data_compare.scan.azimuth_axis, b_data_compare.scan.depth_axis, 1024, 1024);
[RTB_comp_sc, ~, ~] = tools.scan_convert(b_data_RTB_comp.get_image(), b_data_compare.scan.azimuth_axis, b_data_compare.scan.depth_axis, 1024, 1024);
[REFoCUS_sc, ~, ~] = tools.scan_convert(b_data_REFoCUS.get_image()-12, b_data_compare.scan.azimuth_axis, b_data_compare.scan.depth_axis, 1024, 1024);
img_cell = {RTB_sc, RTB_comp_sc, REFoCUS_sc};
name_cell = {'RTB', 'RTB Compensated', 'REFoCUS'};
% Define ROI rectangles [x, z, width, height] in metres (same as paper)
center_rectangle = [-0.006, 0.07, 0.007, 0.04];
v1_rect = center_rectangle - [0.008, 0, 0, 0];
v2_rect = center_rectangle + [0.008, 0, 0, 0];
c_rect = center_rectangle;
% Create binary masks from rectangle positions (no drawrectangle / IPT needed)
v1_binary = rect_to_mask(v1_rect, Xs, Zs);
v2_binary = rect_to_mask(v2_rect, Xs, Zs);
c_binary = rect_to_mask(c_rect, Xs, Zs);
% Plot ROI image with rectangles overlaid
f_roi = figure('Visible', tools.headless_publish_figure_visible());
imagesc(Xs*1e3, Zs*1e3, img_cell{3}, [-60 0]);
colormap gray; colorbar; axis image;
xlabel('x [mm]'); ylabel('z [mm]');
hold on;
rectangle('Position', v1_rect.*[1e3 1e3 1e3 1e3], 'EdgeColor', 'g', 'LineWidth', 2);
rectangle('Position', v2_rect.*[1e3 1e3 1e3 1e3], 'EdgeColor', 'g', 'LineWidth', 2);
rectangle('Position', c_rect.*[1e3 1e3 1e3 1e3], 'EdgeColor', 'r', 'LineWidth', 2);
hold off;
set(findall(gcf, '-property', 'FontSize'), 'FontSize', 14);
title('ROI regions for gCNR');
drawnow;
tools.publish_snap_now_figure(f_roi);
close(f_roi);
% Compute gCNR for each beamforming method
GCNR = cell(1, numel(img_cell));
f_hist = figure('Visible', tools.headless_publish_figure_visible());
bins = linspace(-120, 0, 256);
for ii = 1:numel(img_cell)
[p_noise, ~] = hist(img_cell{ii}(c_binary), bins);
[p_signal, ~] = hist(img_cell{ii}(v1_binary | v2_binary), bins);
subplot(numel(img_cell), 1, ii);
plot(bins, p_noise./sum(p_noise), 'Color', '#D95319', 'LineWidth', 2); hold on;
plot(bins, p_signal./sum(p_signal), 'Color', '#0072BD', 'LineWidth', 2); hold off;
xlim([-80, 0]);
legend('Speckle', 'Background', 'Location', 'eastoutside');
title(sprintf('%s', name_cell{ii}));
OVL = min(p_noise./sum(p_noise), p_signal./sum(p_signal));
GCNR{ii} = 1 - sum(OVL);
ylabel(sprintf('gCNR = %.3f', GCNR{ii}));
end
set(findall(gcf, '-property', 'FontSize'), 'FontSize', 12);
drawnow;
tools.publish_snap_now_figure(f_hist);
close(f_hist);
fprintf('gCNR: RTB=%.3f, RTB Comp=%.3f, REFoCUS=%.3f\n', GCNR{1}, GCNR{2}, GCNR{3});
gCNR: RTB=0.845, RTB Comp=0.878, REFoCUS=0.889
Make Difference Images: of RTBs
all_images = b_data_compare.get_image(); diff = all_images(:,:,2)-all_images(:,:,1)-1.6; [diff_sc, Xs,Zs] = tools.scan_convert(diff, scan.azimuth_axis, scan.depth_axis,1024,1024); f = figure('Visible', tools.headless_publish_figure_visible()); imagesc(Xs*1e3,Zs*1e3,diff_sc,[-30,30]);colormap(bluewhitered);colorbar xlabel('x[mm]'); ylabel('z[mm]'); axis image; xlim([-20,20]); ylim([60,110]); title('RTB - RTB Compensated'); set(findall(gcf,'-property','FontSize'),'FontSize',15) savefig(f,[storefolder,'RTBminusRTBcomp_',tag,'.fig']); saveas(f,[storefolder,'RTBminusRTBcomp_',tag,'.png']); drawnow; tools.publish_snap_now_figure(f); close(f);
Make Difference Images: RTB comp and REFoCUS
all_images = b_data_compare.get_image(); diff = all_images(:,:,3)-all_images(:,:,2)-7; [diff_sc, Xs,Zs] = tools.scan_convert(diff, scan.azimuth_axis, scan.depth_axis,1024,1024); f = figure('Visible', tools.headless_publish_figure_visible()); imagesc(Xs*1e3,Zs*1e3,diff_sc,[-30,30]);colormap(bluewhitered);colorbar xlabel('x[mm]'); ylabel('z[mm]'); axis image; xlim([-20,20]); ylim([60,110]); title('REFoCUS - RTB Compensated'); set(findall(gcf,'-property','FontSize'),'FontSize',15) savefig(f,[storefolder,'REFoCUSminusRTBcomp_',tag,'.fig']); saveas(f,[storefolder,'REFoCUSminusRTBcomp_',tag,'.png']); drawnow; tools.publish_snap_now_figure(f); close(f);
Make Difference Images: RTB and REFoCUS
all_images = b_data_compare.get_image(); diff = all_images(:,:,3)-all_images(:,:,1)-7; [diff_sc, Xs,Zs] = tools.scan_convert(diff, scan.azimuth_axis, scan.depth_axis,1024,1024); f = figure('Visible', tools.headless_publish_figure_visible()); imagesc(Xs*1e3,Zs*1e3,diff_sc,[-30,30]);colormap(bluewhitered);colorbar xlabel('x[mm]'); ylabel('z[mm]'); axis image; xlim([-20,20]); ylim([60,110]); title('REFoCUS - RTB'); set(findall(gcf,'-property','FontSize'),'FontSize',15) savefig(f,[storefolder,'REFoCUSminusRTB_',tag,'.fig']); saveas(f,[storefolder,'REFoCUSminusRTB_',tag,'.png']); drawnow; tools.publish_snap_now_figure(f); close(f);