├── maps.png ├── transmit.png ├── excitation.png ├── .gitignore ├── @fwObj ├── get_delays.m ├── make_incoords_row.m ├── focus_transmit.m ├── preview_sim.m ├── make_acq_params.m ├── add_points.m ├── add_wall.m ├── add_speckle.m ├── make_xdc.m ├── do_sim.m ├── add_fii_phantom.m ├── focus_xdc.m └── fwObj.m ├── extract_struct.m ├── demo.m ├── README.md └── setup_example.m /maps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jameslong12/ooFullwave/HEAD/maps.png -------------------------------------------------------------------------------- /transmit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jameslong12/ooFullwave/HEAD/transmit.png -------------------------------------------------------------------------------- /excitation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jameslong12/ooFullwave/HEAD/excitation.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.asv 2 | *.dat 3 | demo* 4 | slurm* 5 | Beamforming 6 | fullwave2D 7 | *.mat 8 | *.png 9 | -------------------------------------------------------------------------------- /@fwObj/get_delays.m: -------------------------------------------------------------------------------- 1 | function delays = get_delays(obj,focus) 2 | 3 | % Retrieve time delay profile for elements 4 | % 5 | % James Long 12/06/2018 6 | 7 | rx_pos = obj.xdc.out(obj.xdc.on_elements,:); 8 | if(any(isinf(focus))) 9 | delays=zeros(1,size(rx_pos,1)); 10 | else 11 | delays=sqrt((focus(1)-rx_pos(:,1)).^2+(focus(2)-rx_pos(:,3)).^2); 12 | if(focus(2)>0) 13 | delays = -delays; 14 | end 15 | delays=delays-min(delays); 16 | end 17 | delays = delays/obj.input_vars.c0; 18 | 19 | end 20 | 21 | 22 | -------------------------------------------------------------------------------- /@fwObj/make_incoords_row.m: -------------------------------------------------------------------------------- 1 | function incoords = make_incoords_row(obj,inmap) 2 | 3 | % Replacement for mapToCoords to make definition curvilinear arrays easier 4 | % 5 | % James Long 04/15/2020 6 | 7 | inmapsum = sum(inmap,2); 8 | assert(~any(inmapsum~=1),'inmap must be defined across lateral span') 9 | assert(size(inmap,1)==obj.grid_vars.nY,'inmap must match dimensions of grid.') 10 | assert(size(inmap,2)==obj.grid_vars.nZ,'inmap must match dimensions of grid.') 11 | 12 | for i = 1:size(inmap,1) 13 | idy(i) = find(logical(inmap(i,:))); 14 | idx(i) = i; 15 | end 16 | incoords = [idx(:) idy(:)]-1; 17 | 18 | end -------------------------------------------------------------------------------- /extract_struct.m: -------------------------------------------------------------------------------- 1 | function assignments = extract_struct(struct) 2 | 3 | % Extract structure fields as individual variables 4 | % 5 | % Calling: assignments = extract_struct(struct) 6 | % for i = 1:length(assignments) 7 | % eval(assignments{i}); 8 | % end 9 | % 10 | % Parameters: struct - Structure with fields 11 | % 12 | % Return: assignments - Nx1 cell array, where N is the number of 13 | % fields in struct 14 | % 15 | % James Long 03/06/2018 16 | 17 | names = fieldnames(struct); 18 | s = inputname(1); 19 | assignments= cellfun(@(f) [f ' = ' s '.' f '; '],names,'uniformoutput',0); 20 | 21 | end -------------------------------------------------------------------------------- /@fwObj/focus_transmit.m: -------------------------------------------------------------------------------- 1 | function icmat = focus_transmit(obj,idy,idz,icvec,incoords) 2 | 3 | % Function to calculate transmit focusing 4 | % 5 | % Parameters: 6 | % obj - Fullwave simulation object 7 | % idy, idz - y, z indices of focus point 8 | % icvec - Precalculated initial condition vector 9 | % incoords - Input coordinates for initial condition 10 | % 11 | % Return: 12 | % icmat - Initial condition matrix 13 | % 14 | % James Long, 12/06/2018 15 | 16 | cfl = obj.input_vars.cfl; 17 | 18 | dd = sqrt((incoords(:,1)-idy).^2+(incoords(:,2)-idz).^2); 19 | dd = -round(dd/cfl); if idz<0; dd = -dd; end 20 | dd = dd-min(dd); 21 | 22 | icmat = zeros(size(incoords,1),length(icvec)); 23 | for i=1:size(incoords,1) 24 | icmat(i,dd(i)+1:end) = icvec(1:end-dd(i)); 25 | end 26 | 27 | end 28 | -------------------------------------------------------------------------------- /@fwObj/preview_sim.m: -------------------------------------------------------------------------------- 1 | function obj = preview_sim(obj) 2 | 3 | % Method to preview simulation. Shows acoustic map with transducer, 4 | % initial condition matrix, transmit delays, transducer impulse response, 5 | % and transmitted pulse. 6 | % 7 | % Calling: 8 | % obj.preview_sim() 9 | % 10 | % James Long 04/16/2020 11 | 12 | close all 13 | figure('pos',[100 100 1400 600]) 14 | subplot(131) 15 | imagesc(obj.grid_vars.y_axis*1e3,obj.grid_vars.z_axis*1e3,obj.field_maps.cmap'); axis image 16 | hold on 17 | plot(obj.xdc.out(:,1)*1e3,obj.xdc.out(:,3)*1e3,'-k','linewidth',2); 18 | xlabel('Lateral (mm)'); ylabel('Axial (mm)'); title('Acoustic map'); 19 | 20 | subplot(232) 21 | imagesc(obj.grid_vars.y_axis*1e3,obj.grid_vars.t_axis*1e6,obj.xdc.icmat(1:obj.grid_vars.nY,:)'); 22 | xlabel('Lateral (mm)'); ylabel('Time (us)'); title('Focused transmit'); ylim([0 20]) 23 | 24 | subplot(235) 25 | scatter(obj.xdc.on_elements,obj.xdc.delays*1e6,100,'.b'); axis tight 26 | xlabel('Element number'); ylabel('Time (us)'); title('Focused delays') 27 | 28 | subplot(233) 29 | plot(obj.xdc.impulse_t*1e6,obj.xdc.impulse) 30 | xlabel('Time (us)'); ylabel('Amplitude (a.u.)'); title('Impulse response') 31 | axis tight 32 | 33 | subplot(236) 34 | plot(obj.xdc.pulse_t*1e6,obj.xdc.pulse) 35 | xlabel('Time (us)'); ylabel('Amplitude (a.u.)'); title('Transmitted pulse') 36 | axis tight 37 | 38 | end -------------------------------------------------------------------------------- /@fwObj/make_acq_params.m: -------------------------------------------------------------------------------- 1 | function acq_params = make_acq_params(obj) 2 | 3 | % Function to calculate acquisition parameters to match beamforming used 4 | % by nbb5 and wjl11. 5 | % 6 | % Calling: 7 | % acq_params = obj.make_acq_params() 8 | % 9 | % Returns: 10 | % acq_params - Acquisition parameters for 11 | % beamforming 12 | % 13 | % Nick Bottenus, 11/27/2018 14 | 15 | acq_params = []; 16 | acq_params.fs = 1/obj.grid_vars.dT; 17 | acq_params.f0 = obj.input_vars.f0; 18 | acq_params.c = obj.input_vars.c0; 19 | acq_params.t0 = -length(obj.xdc.pulse)/2; 20 | if obj.input_vars.v == 2 21 | acq_params.samples=length(obj.grid_vars.t_axis)-1; 22 | else 23 | acq_params.samples=length(obj.grid_vars.t_axis); 24 | end 25 | acq_params.rx_pos = obj.xdc.out; 26 | acq_params.tx_apod = false(obj.xdc.n,1); 27 | acq_params.tx_apod(obj.xdc.on_elements) = true; 28 | acq_params.focus = [obj.xdc.focus(1) 0 obj.xdc.focus(2)]; 29 | 30 | if strcmp(obj.xdc.type,'curvilinear') 31 | acq_params.apex = -obj.xdc.r; 32 | acq_params.theta = atand(obj.xdc.focus(1)/(obj.xdc.focus(2)+obj.xdc.r)); 33 | acq_params.tx_pos = [obj.xdc.r*sind(acq_params.theta) 0 ... 34 | obj.xdc.r*cosd(acq_params.theta)-obj.xdc.r]; 35 | else 36 | acq_params.apex=0; 37 | acq_params.theta = atand(obj.xdc.focus(1)/obj.xdc.focus(2)); 38 | rx_pos = acq_params.rx_pos(obj.xdc.on_elements,:); 39 | acq_params.tx_pos = mean(rx_pos); 40 | acq_params.theta = 0; 41 | end 42 | acq_params.steer = [sind(acq_params.theta) 0 cosd(acq_params.theta)]; 43 | 44 | ind1=find(obj.xdc.delays==0,1,'first'); 45 | [~,ind2] = max(obj.xdc.delays); 46 | foc_pm=sign(obj.xdc.focus(2)); 47 | rx_pos = acq_params.rx_pos(obj.xdc.on_elements,:); 48 | acq_params.t0_var=foc_pm*(sqrt(sum((acq_params.focus-rx_pos(ind1,:)).^2))-... 49 | sqrt(sum((acq_params.focus-rx_pos(ind2,:)).^2)))/obj.input_vars.c0/obj.grid_vars.dT; 50 | end -------------------------------------------------------------------------------- /@fwObj/add_points.m: -------------------------------------------------------------------------------- 1 | function obj = add_points(obj, varargin) 2 | 3 | % Function to add point targets to map 4 | % 5 | % Calling: 6 | % obj.add_points('pos',[0.01 0.02],'zd',0.5,'method','adapt') 7 | % 8 | % Optional parameters: 9 | % pos: Positions of point targets in [y z], each row 10 | % designating a new point 11 | % zd: Target impedance as fraction 12 | % method: Method to calculate impedance. 'fixed' to use 13 | % obj.input_vars.c0, 'adapt' to use index at 14 | % obj.field_maps.cmap. 15 | % 16 | % James Long, 04/16/2020 17 | 18 | %%% Use inputParser to set optional parameters %%%%%%%%%%%%%%%%%%%%%%%%%%%% 19 | p = inputParser; 20 | addOptional(p,'pos',[0 obj.input_vars.wZ/2]) % Laterally centered, half depth 21 | addOptional(p,'zd',0.5) % 50% acoustic impedance 22 | addOptional(p,'method','fixed') % Use set c0 23 | 24 | p.parse(varargin{:}) 25 | var_struct = p.Results; 26 | assignments = extract_struct(var_struct); 27 | for i = 1:length(assignments) 28 | eval(assignments{i}) 29 | end 30 | 31 | if length(zd) == 1; zd = zd*ones(size(pos,1),1); end 32 | if length(zd) ~= size(pos,1); error('Size of "zd" and "pos" must match.'); end 33 | 34 | %%% Place points in map %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 35 | for idx = 1:size(pos,1) 36 | if abs(pos(idx,1)) > obj.input_vars.wY/2; error('Specified lateral position outside field range.'); end 37 | if pos(idx,2) > obj.input_vars.wZ || pos(idx,2) < 0; error('Specified axial position outside field range.'); end 38 | [~,iy] = min(abs(obj.grid_vars.y_axis-pos(idx,1))); 39 | [~,iz] = min(abs(obj.grid_vars.z_axis-pos(idx,2))); 40 | if strcmp(method,'fixed') 41 | obj.field_maps.cmap(iy-1:iy+1,iz-1:iz+1) = zd(idx)*obj.input_vars.c0; 42 | elseif strcmp(method,'adapt') 43 | obj.field_maps.cmap(iy-1:iy+1,iz-1:iz+1) = zd(idx)*obj.field_maps.cmap(iy,iz); 44 | else 45 | error('Unrecognized method. Use "fixed" or "adapt".') 46 | end 47 | end 48 | 49 | -------------------------------------------------------------------------------- /@fwObj/add_wall.m: -------------------------------------------------------------------------------- 1 | function obj = add_wall(obj, wall_name, offset, filt_size) 2 | 3 | % Function to add abdominal wall. 4 | % 5 | % Calling: 6 | % obj.add_wall('r75hi') 7 | % 8 | % Parameters: 9 | % wall_name - String of wall name 10 | % offset - Lateral offset from center (m) 11 | % filt_size - Factor for Gaussian blurring (default: 12) 12 | % 13 | % James Long, 04/16/2020 14 | 15 | if ~exist('offset','var'); offset=0; end 16 | 17 | dY = obj.grid_vars.dY; 18 | dZ = obj.grid_vars.dZ; 19 | if(obj.input_vars.v==1) 20 | [cwall, rhowall, attenwall, Bwall] = img2fieldFlatten(wall_name,dY,dZ,obj.input_vars.c0,obj.input_vars.rho); 21 | Bwall=-Bwall*obj.input_vars.rho*obj.input_vars.c0.^4; 22 | else 23 | [cwall, rhowall, attenwall, Bwall] = img2fieldFlatten2(wall_name,dY,dZ); 24 | end 25 | 26 | if size(cwall,1) < obj.grid_vars.nY; error('Simulation width exceeds wall width.'); end 27 | if size(cwall,2) > obj.grid_vars.nZ; error('Wall depth exceeds simulation depth.'); end 28 | 29 | % Lateral offset %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 30 | nY = obj.grid_vars.nY; 31 | nW = size(cwall,1); 32 | pad = round((nW-nY)/2); 33 | offset = round(offset/obj.grid_vars.dY); 34 | wall_select = (pad+1:pad+nY)+offset; 35 | 36 | if any(wall_select < 1) || any(wall_select > nW); error('Offset exceeds wall width.'); end 37 | cwall = cwall(wall_select,:); 38 | rhowall = rhowall(wall_select,:); 39 | attenwall = attenwall(wall_select,:); 40 | Bwall = Bwall(wall_select,:); 41 | 42 | % Apply Gaussian blur %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 43 | 44 | if ~exist('filt_size','var'); filt_size = 12; end 45 | if filt_size ~= 0 46 | cwall = imgaussfilt(cwall,obj.input_vars.ppw/filt_size); 47 | rhowall = imgaussfilt(rhowall,obj.input_vars.ppw/filt_size); 48 | attenwall = imgaussfilt(attenwall,obj.input_vars.ppw/filt_size); 49 | Bwall = imgaussfilt(Bwall,obj.input_vars.ppw/filt_size); 50 | end 51 | 52 | % Add to field_maps structure %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 53 | ind = 1:size(cwall,2); 54 | ax = 2*obj.input_vars.ppw; 55 | for i = 1:size(obj.xdc.inmap,1) 56 | int = find(obj.xdc.inmap(i,:)==1,1,'last'); 57 | obj.field_maps.cmap(i,ind+ax+int) = cwall(i,:); 58 | obj.field_maps.rhomap(i,ind+ax+int) = rhowall(i,:); 59 | obj.field_maps.attenmap(i,ind+ax+int) = attenwall(i,:); 60 | if(obj.input_vars.v==1) 61 | obj.field_maps.boveramap(i,ind+ax+int) = (Bwall(i,:)-1)*2; 62 | elseif(obj.input_vars.v==2) 63 | obj.field_maps.Bmap(i,ind+ax+int) = Bwall(i,:); 64 | end 65 | end 66 | 67 | end -------------------------------------------------------------------------------- /demo.m: -------------------------------------------------------------------------------- 1 | % ooFullwave, v2.4.0 2 | % 3 | % Demo using fwObj to set up and run a Fullwave simulation for a focused 4 | % transmit through an abdominal wall. 5 | % 6 | % James Long 04/16/2020 7 | % ***Fullwave written by Gianmarco Pinton*** 8 | 9 | %% 1. Create fwObj %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 10 | clear 11 | c0 = 1540; % Homogeneous speed of sound 12 | f0 = 3e6; % Transmit center frequency (Hz) 13 | wZ = 6e-2; % Axial extent (m) 14 | wY = 5e-2; % Lateral extent (m) 15 | td =(wZ+1e-2)/c0; % Time duration of simulation (s) 16 | ppw = 12; 17 | sim = fwObj('c0',c0,'f0',f0,'wY',wY,'wZ',wZ,'td',td,'ppw',ppw); 18 | 19 | %% 2. Specify transducer and transmit parameters %%%%%%%%%%%%%%%%%%%%%%%%%% 20 | sim.xdc.type = 'curvilinear'; % Curvilinear or linear 21 | sim.xdc.pitch = 0.000412; % Center-to-center element spacing 22 | sim.xdc.n = 64; % Number of elements 23 | sim.xdc.r = 0.04; % Convex radius 24 | sim.make_xdc(); % Call make_xdc to set up transducer 25 | 26 | %% 3. Focus transmit at 4 cm %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 27 | focus = [0 0.04]; % Focal point in [y z] (m) 28 | sim.focus_xdc(focus); % Call focus_xdc to calculate icmat 29 | 30 | %% 4. Add abdominal wall and speckle %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 31 | sim.add_wall('r75hi'); % Mast abdominal wall name 32 | sim.add_speckle('nscat',25); % Add 25 scat/res cell 33 | 34 | %% 5. Preview simulation %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 35 | close all 36 | sim.preview_sim(); 37 | 38 | %% 6. Collect channel data %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 39 | t = tic; 40 | rf_data = double(sim.do_sim()); 41 | fprintf(' Channel data generated in %1.2f seconds \n',toc(t)) 42 | 43 | %% 7. Collect full field pressure data %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 44 | t = tic; 45 | p = double(sim.do_sim(1)); 46 | fprintf(' Field pressure data generated in %1.2f seconds \n',toc(t)) 47 | 48 | %% 8. Visualize %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 49 | close 50 | maxp = max(p(:).^2); 51 | for i = 1:5:size(p,1) 52 | imagesc(sim.grid_vars.y_axis*1e2,sim.grid_vars.z_axis*1e2,squeeze(p(i,:,:))'.^2/maxp,[0 1]) 53 | title(sprintf('t = %1.2f us',sim.grid_vars.t_axis(i)*1e6)) 54 | colormap jet 55 | axis image 56 | xlabel('Lateral (cm)') 57 | ylabel('Axial (cm)') 58 | drawnow 59 | end 60 | 61 | close 62 | maxp = squeeze(max(p.^2,[],1))'; 63 | imagesc(sim.grid_vars.y_axis*1e2,sim.grid_vars.z_axis*1e2,maxp) 64 | colormap jet 65 | axis image 66 | xlabel('Lateral (cm)') 67 | ylabel('Axial (cm)') 68 | title('Maximum intensity') 69 | -------------------------------------------------------------------------------- /@fwObj/add_speckle.m: -------------------------------------------------------------------------------- 1 | function obj = add_speckle(obj, varargin) 2 | 3 | % Function to add scatterers and optional cyst(s) to map 4 | % 5 | % Calling: 6 | % obj.add_speckle('nscat',15,'csr',0.05,'nC',0) 7 | % 8 | % Optional parameters: 9 | % nscat: Scatterers per resolution cell (15) 10 | % csr: Scatterer impedance contrast (0.05) 11 | % nC: Number of cysts (0) 12 | % rC: Vector of cyst radii (m), length equal to nC 13 | % cC: Matrix of cyst center locations in [y,z] (m), length equal to nC 14 | % zC: Vector of cyst impedance contrast, length equal to nC 15 | % 16 | % James Long, 04/16/2020 (Code partially from Nick Bottenus) 17 | 18 | %%% Use inputParser to set optional parameters %%%%%%%%%%%%%%%%%%%%%%%%%%%% 19 | p = inputParser; 20 | addOptional(p,'nC',0) 21 | addOptional(p,'csr',0.05) 22 | addOptional(p,'nscat',25) 23 | addParameter(p,'rC',[]) 24 | addParameter(p,'cC',[]) 25 | addParameter(p,'zC',[]) 26 | 27 | p.parse(varargin{:}) 28 | var_struct = p.Results; 29 | assignments = extract_struct(var_struct); 30 | for i = 1:length(assignments) 31 | eval(assignments{i}) 32 | end 33 | 34 | %%% Calculate resolution cell using input variables %%%%%%%%%%%%%%%%%%%%%%% 35 | res_cell = rescell2d(obj.input_vars.c0, obj.input_vars.omega0, obj.input_vars.wZ/2,... 36 | obj.input_vars.wY, obj.input_vars.ncycles, obj.grid_vars.dY,... 37 | obj.grid_vars.dZ); 38 | 39 | %%% Randomly place scatterers according to density %%%%%%%%%%%%%%%%%%%%%%%% 40 | scat_density = nscat/res_cell; 41 | cscatmap = rand(obj.grid_vars.nY, obj.grid_vars.nZ)-0.5; 42 | cscatmap(abs(cscatmap) > scat_density) = 0; 43 | cscatmap = cscatmap/scat_density; 44 | 45 | %%% Create lesions %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 46 | if nC 47 | if size(cC,1) ~= nC || length(rC) ~= nC || length(zC) ~= nC 48 | error('cC, rC, and zC must match nC.') 49 | end 50 | for idx = 1:nC 51 | [z_int, y_int] = meshgrid(obj.grid_vars.z_axis, obj.grid_vars.y_axis); 52 | lesion_mask = (y_int-cC(idx,1)).^2+(z_int-cC(idx,2)).^2 < rC(idx).^2; 53 | cscatmap(lesion_mask) = zC(idx)*cscatmap(lesion_mask); 54 | end 55 | end 56 | 57 | %%% Superimpose original cmap and cscatmap %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 58 | obj.field_maps.cmap = obj.field_maps.cmap+cscatmap*csr.*obj.field_maps.cmap; 59 | 60 | %%% Axial offset %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 61 | ax = 2*obj.input_vars.ppw; 62 | for i = 1:size(obj.xdc.inmap,1) 63 | int = find(obj.xdc.inmap(i,:)==1,1,'last'); 64 | obj.field_maps.cmap(i,1:ax+int) = obj.input_vars.c0; 65 | obj.field_maps.rhomap(i,1:ax+int) = obj.input_vars.rho; 66 | obj.field_maps.attenmap(i,1:ax+int) = obj.input_vars.atten; 67 | if(obj.input_vars.v==1) 68 | obj.field_maps.boveramap(i,1:ax+int) = obj.input_vars.bovera; 69 | elseif(obj.input_vars.v==2) 70 | obj.field_maps.Bmap(i,1:ax+int) = obj.input_vars.B; 71 | end 72 | end 73 | 74 | end -------------------------------------------------------------------------------- /@fwObj/make_xdc.m: -------------------------------------------------------------------------------- 1 | function obj = make_xdc(obj) 2 | 3 | % Function to create transducer related fields 4 | % 5 | % Calling: 6 | % obj.make_xdc() 7 | % 8 | % Parameters: 9 | % obj.xdc.type - Type of transducer (only 'linear' 10 | % currently supported) 11 | % obj.xdc.pitch - Center-to-center element spacing (m) 12 | % obj.xdc.n - Number of elements 13 | % obj.xdc.on_elements - (Optional) Elements selected for transmit 14 | % (default = 1:obj.xdc.n) 15 | % obj.xdc.tx_apod - (Optional) Vector of transmit apodization 16 | % applied to elements (default = 17 | % ones(length(obj.xdc.on_elements),1)) 18 | % obj.xdc.p_size - (Optional) Downsampling factors for 19 | % [t,y,z] (pressure field only) 20 | % (default = [1,1,1]) 21 | % 22 | % Returns: 23 | % obj.xdc.e_ind - Lateral element indices on grid 24 | % obj.xdc.out - Element positions in [x y z] for beamforming 25 | % 26 | % James Long 04/16/2020 27 | 28 | if obj.grid_vars.dY > obj.xdc.pitch, error('Grid spacing is too large.'); end 29 | assert(isfield(obj.xdc,'pitch'),'Unidentified element pitch in obj.xdc.') 30 | assert(isfield(obj.xdc,'n'),'Unidentified number of elements (n) in obj.xdc.') 31 | if ~isfield(obj.xdc,'on_elements'); obj.xdc.on_elements = 1:obj.xdc.n; end 32 | if ~isfield(obj.xdc,'tx_apod'); obj.xdc.tx_apod = ones(length(obj.xdc.on_elements),1); end 33 | if ~isfield(obj.xdc,'p_size'); obj.xdc.p_size = [1,1,1]; end 34 | assert(length(obj.xdc.tx_apod)==length(obj.xdc.on_elements),'Transmit apodization must match on elements.'); 35 | 36 | if strcmp(obj.xdc.type, 'curvilinear') 37 | assert(isfield(obj.xdc,'r'),'Unidentified convex radius (r) in obj.xdc.') 38 | sector = obj.xdc.pitch*obj.xdc.n; theta_xdc = sector/obj.xdc.r; 39 | theta = linspace(-theta_xdc/2,theta_xdc/2,obj.xdc.n); 40 | yp = sin(theta)*obj.xdc.r; 41 | zp = cos(theta)*obj.xdc.r; 42 | obj.xdc.out = zeros(obj.xdc.n,3); 43 | obj.xdc.out(:,1) = yp; obj.xdc.out(:,3) = zp-max(zp); 44 | wy = obj.xdc.pitch*cos(theta); 45 | iy = [yp(:)-wy(:)/2 yp(:)+wy(:)/2]; 46 | [~,e_ind(:,1)] = min(abs(iy(:,1)'-obj.grid_vars.y_axis')); 47 | [~,e_ind(:,2)] = min(abs(iy(:,2)'-obj.grid_vars.y_axis')); 48 | for i = 1:size(e_ind,1)-1 49 | if e_ind(i,2) == e_ind(i+1,1) 50 | e_ind(i+1,1) = e_ind(i+1,1)+1; 51 | end 52 | end 53 | assert(~any(e_ind(:)<1),'Aperture too large for grid size.') 54 | assert(~any(e_ind(:)>obj.grid_vars.nY),'Aperture too large for grid size.') 55 | obj.xdc.e_ind = e_ind; 56 | 57 | elseif strcmp(obj.xdc.type, 'linear') 58 | 59 | obj.xdc.out = zeros(obj.xdc.n,3); 60 | yp = (1:obj.xdc.n)*obj.xdc.pitch; yp = yp-mean(yp); 61 | obj.xdc.out(:,1) = yp; 62 | iy = [yp(:)-obj.xdc.pitch/2 yp(:)+obj.xdc.pitch/2]; 63 | [~,e_ind(:,1)] = min(abs(iy(:,1)'-obj.grid_vars.y_axis')); 64 | [~,e_ind(:,2)] = min(abs(iy(:,2)'-obj.grid_vars.y_axis')); 65 | for i = 1:size(e_ind,1)-1 66 | if e_ind(i,2) == e_ind(i+1,1) 67 | e_ind(i+1,1) = e_ind(i+1,1)+1; 68 | end 69 | end 70 | assert(~any(e_ind(:)<1),'Aperture too large for grid size.') 71 | assert(~any(e_ind(:)>obj.grid_vars.nY),'Aperture too large for grid size.') 72 | obj.xdc.e_ind = e_ind; 73 | 74 | else 75 | error('Unsupported transducer type.') 76 | end 77 | 78 | end -------------------------------------------------------------------------------- /@fwObj/do_sim.m: -------------------------------------------------------------------------------- 1 | function out = do_sim(obj,field_flag) 2 | 3 | % Method to call Fullwave executable and perform simulation 4 | % 5 | % Calling: 6 | % out = obj.do_sim(field_flag) 7 | % 8 | % Parameters: 9 | % field_flag - Flag to indicate entire field output. 1 10 | % for field pressure data output, 0 for 11 | % channel data (default = 0). 12 | % 13 | % Returns: 14 | % out - Simulation result data 15 | % 16 | % James Long 01/16/2019 17 | 18 | %%% Use field flag to get pressure across entire map %%%%%%%%%%%%%%%%%%%%%% 19 | if ~exist('field_flag','var'), field_flag = 0; end 20 | if field_flag 21 | % redefine outmap and outcoords 22 | dY=1:obj.xdc.p_size(2):obj.grid_vars.nY; 23 | dZ=1:obj.xdc.p_size(3):obj.grid_vars.nZ; 24 | obj.xdc.outmap(dY,dZ) = 1; 25 | obj.xdc.outcoords = mapToCoords(obj.xdc.outmap); 26 | end 27 | 28 | %%% Launch FullWave executable %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 29 | if obj.input_vars.v == 2 30 | fprintf(' Launching Fullwave version 2\n') 31 | if isunix 32 | if obj.input_vars.gpu 33 | warning('GPU version only available for version 1') 34 | end 35 | launch_fullwave2_try6_nln_relaxing4(obj.input_vars.c0,... 36 | obj.input_vars.omega0, obj.input_vars.wY, obj.input_vars.wZ,... 37 | obj.input_vars.td, obj.input_vars.p0, obj.input_vars.ppw,... 38 | obj.input_vars.cfl, obj.field_maps.cmap, obj.field_maps.rhomap,... 39 | obj.field_maps.attenmap, obj.field_maps.Bmap, obj.xdc.incoords,... 40 | obj.xdc.outcoords, obj.xdc.icmat); 41 | !./fullwave2_try6_nln_relaxing 42 | else 43 | error('Fullwave2 is not supported on your operating system.') 44 | end 45 | else 46 | fprintf(' Launching Fullwave version 1\n') 47 | if ispc 48 | if obj.input_vars.gpu 49 | warning('GPU version only available for Linux') 50 | end 51 | launchTotalFullWaveRebuild2(obj.input_vars.c0, obj.input_vars.omega0,... 52 | obj.input_vars.wY, obj.input_vars.wZ, obj.input_vars.td,... 53 | obj.input_vars.p0, obj.input_vars.ppw, obj.input_vars.cfl,... 54 | obj.field_maps.cmap, obj.field_maps.rhomap, obj.field_maps.attenmap,... 55 | obj.field_maps.boveramap, obj.xdc.incoords, obj.xdc.outcoords,... 56 | obj.xdc.icmat); 57 | elseif isunix 58 | launchTotalFullWave2(obj.input_vars.c0, obj.input_vars.omega0,... 59 | obj.input_vars.wY, obj.input_vars.wZ, obj.input_vars.td,... 60 | obj.input_vars.p0, obj.input_vars.ppw, obj.input_vars.cfl,... 61 | obj.field_maps.cmap', obj.field_maps.rhomap', obj.field_maps.attenmap',... 62 | obj.field_maps.boveramap', obj.xdc.incoords, obj.xdc.outcoords,... 63 | obj.xdc.icmat); 64 | if obj.input_vars.gpu 65 | fprintf(' Using GPU version\n') 66 | !./FullWave2D_PGI_19.10_CUDA_9.1_20200512.gpu_volta 67 | else 68 | !./try6_nomex 69 | end 70 | else 71 | error('Fullwave is not supported on your operating system.') 72 | end 73 | end 74 | 75 | %%% Reshape output data %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 76 | ncoordsout=size(obj.xdc.outcoords,1); 77 | nRun=sizeOfFile('genout.dat')/4/ncoordsout; 78 | genout = readGenoutSlice(['genout.dat'],0:nRun-1,size(obj.xdc.outcoords,1)); 79 | 80 | fprintf(' Size genout:\n') 81 | size(genout) 82 | if field_flag 83 | out = reshape(genout,size(genout,1),length(dY),length(dZ)); 84 | out = out(1:obj.xdc.p_size(1):end,:,:); 85 | else 86 | for idx = 1:size(obj.xdc.outcoords) 87 | % genout_re(:,obj.xdc.outcoords(idx,1)-min(obj.xdc.outcoords(:,1))+1) = genout(:,idx); 88 | genout_re(:,obj.xdc.outcoords(idx,1)+1) = genout(:,idx); 89 | end 90 | fprintf(' Size genout:\n') 91 | size(genout_re) 92 | %%% Average across output to reconstruct element traces %%%%%%%%%%%%%%% 93 | for idx = 1:obj.xdc.n 94 | out(:,idx) = mean(genout_re(:,obj.xdc.e_ind(idx,1):obj.xdc.e_ind(idx,2)),2); 95 | end 96 | fprintf(' Size out:\n') 97 | size(out) 98 | end 99 | 100 | end 101 | -------------------------------------------------------------------------------- /@fwObj/add_fii_phantom.m: -------------------------------------------------------------------------------- 1 | function obj = add_fii_phantom(obj, phtm_file, symmetry, el_lim, csr, fnum) 2 | 3 | % Method to add scatterers to maps from phantom files in format used in 4 | % Field II. Adds scatterers using impedance flow method. Written for use 5 | % with Mark Palmeri's ultratrack tools. 6 | % 7 | % Calling: 8 | % obj.add_fii_phantom('phtm_file.mat', 1.5-3, 0.05, 2) 9 | % 10 | % Parameters: 11 | % phtm_file: Path to phantom file. File must be .mat 12 | % containing "phantom" structure with fields 13 | % "position" and "amplitude" (standard output 14 | % from Mark Palmeri's ultratrack tools) 15 | % symmetry: Symmetry of phantom ('q','h','none') 16 | % el_lim: Elevational limit [m] (default: 1.5e-3) 17 | % csr: Standard deviation of scatterer impedance 18 | % contrast (default: 0.05) 19 | % fnum: (Optional) Transmit F-number used to calculate 20 | % scatterers per resolution cell 21 | % 22 | % James Long, 12/17/2018 23 | 24 | load(phtm_file) 25 | if ~exist('phantom','var'); error('Variable "phantom" not found in file.'); end 26 | if size(phantom.position,2) ~= 3; error('Could not find 3 dimensions to positions.'); end 27 | 28 | if nargin < 5; csr = 0.05; end 29 | if nargin < 4; el_lim = 1.5e-3; warning('Using default elevation limit. Check for adequate scatterer density.'); end 30 | 31 | switch symmetry 32 | case 'q' 33 | %%% Restrict positions in all dimensions %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 34 | ind = abs(phantom.position(:,2)) < el_lim; % elevation 35 | py = phantom.position(:,1); py = py(ind); 36 | pz = phantom.position(:,3); pz = pz(ind); 37 | amp = phantom.amplitude; amp = amp(ind); 38 | 39 | ind = abs(phantom.position(:,1)) < obj.input_vars.wY/2; % lateral 40 | py = phantom.position(:,1); py = py(ind); 41 | pz = phantom.position(:,3); pz = pz(ind); 42 | amp = phantom.amplitude; amp = amp(ind); 43 | 44 | ind = phantom.position(:,3) < obj.input_vars.wZ; % axial 45 | py = phantom.position(:,1); py = py(ind); 46 | pz = phantom.position(:,3); pz = pz(ind); 47 | amp = phantom.amplitude; amp = amp(ind); 48 | 49 | pz = [pz; pz]; 50 | py = [py; -py]; 51 | amp = [amp; amp]; 52 | 53 | %%% If F-number known, calculate scatterers per resolution cell %%%%%%%%%%% 54 | if exist('fnum','var') 55 | scat_density = length(py)/(abs(max(py)*max(pz))); 56 | ry = obj.input_vars.lambda*fnum; rz = obj.input_vars.ncycles*obj.input_vars.lambda/2; 57 | rescell = ry*rz; nscat_cell = scat_density*rescell; 58 | fprintf(' Scatterers per resolution cell = %1.1f',nscat_cell) 59 | 60 | %%% Optional code to visualize %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 61 | %scatter(py,pz,0.5,'.r'); axis image; title(sprintf('n_{scat per cell} = %1.1f',nscat_cell)) 62 | end 63 | case 'none' 64 | %%% Restrict positions in all dimensions %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 65 | ind = abs(phantom.position(:,2)) < el_lim; % elevation 66 | py = phantom.position(:,1); py = py(ind); 67 | pz = phantom.position(:,3); pz = pz(ind); 68 | amp = phantom.amplitude; amp = amp(ind); 69 | 70 | ind = abs(py) < 0.99*obj.input_vars.wY/2; % lateral 71 | py = py(ind); 72 | pz = pz(ind); 73 | amp = amp(ind); 74 | 75 | ind = pz < 0.99*obj.input_vars.wZ; % axial 76 | py = py(ind); 77 | pz = pz(ind); 78 | amp = amp(ind); 79 | 80 | %%% If F-number known, calculate scatterers per resolution cell %%%%%%%%%%% 81 | if exist('fnum','var') 82 | scat_density = length(py)/(abs(range(py)*range(pz))); 83 | ry = obj.input_vars.lambda*fnum; rz = obj.input_vars.ncycles*obj.input_vars.lambda/2; 84 | rescell = ry*rz; nscat_cell = scat_density*rescell; 85 | fprintf(' Scatterers per resolution cell = %1.1f\n',nscat_cell) 86 | 87 | %%% Optional code to visualize %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 88 | %scatter(py,pz,0.5,'.r'); axis image; title(sprintf('n_{scat per cell} = %1.1f',nscat_cell)) 89 | end 90 | end 91 | 92 | %%% Add scatterers using impedance flow %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 93 | scat_mask = zeros(size(obj.field_maps.cmap)); 94 | iy = interp1(obj.grid_vars.y_axis,1:length(obj.grid_vars.y_axis),py); 95 | iz = interp1(obj.grid_vars.z_axis,1:length(obj.grid_vars.z_axis),pz); 96 | for i = 1:length(iy) 97 | iy1 = floor(iy(i)); iy2 = ceil(iy(i)); 98 | iz1 = floor(iz(i)); iz2 = ceil(iz(i)); 99 | 100 | vy1 = abs(iy2-iy(i)); vy2 = abs(iy1-iy(i)); 101 | vz1 = abs(iz2-iz(i)); vz2 = abs(iz1-iz(i)); 102 | 103 | scat_mask(iy1,iz1) = amp(i)*csr*vy1*vz1; 104 | scat_mask(iy1,iz2) = amp(i)*csr*vy1*vz2; 105 | scat_mask(iy2,iz1) = amp(i)*csr*vy2*vz1; 106 | scat_mask(iy2,iz2) = amp(i)*csr*vy2*vz2; 107 | end 108 | obj.field_maps.cmap = obj.field_maps.cmap-scat_mask.*obj.input_vars.c0; -------------------------------------------------------------------------------- /@fwObj/focus_xdc.m: -------------------------------------------------------------------------------- 1 | function obj = focus_xdc(obj, focus, fc, fbw, excitation, bwr, tpe) 2 | 3 | % Function to create transmit related fields. Must call obj.make_xdc() 4 | % prior to use. 5 | % 6 | % Calling: 7 | % obj.focus_xdc(focus) 8 | % 9 | % Parameters: 10 | % focus - Focal point in [y z] (m) 11 | % fc - Center frequency of transducer (Hz) [obj.input_vars.f0] 12 | % fbw - Fractional bandwidth of transducer [0.8] 13 | % excitation - Vector of excitation in time 14 | % bwr - Fractional bandwidth reference level [-6 dB] 15 | % tpe - Cutoff for trailing pulse envelope [-40 dB] 16 | % 17 | % Returns: 18 | % obj.xdc.inmap - Input map for initial conditions 19 | % obj.xdc.incoords - Paired coordinates of inmap 20 | % obj.xdc.outmap - Output map for data collection 21 | % obj.xdc.outcoords - Paired coordinates of outmap 22 | % obj.xdc.icmat - Initial condition matrix for transmit 23 | % obj.xdc.delays - Time delays on elements in transmit 24 | % obj.xdc.t0 - Time of first time index (s) for beamforming 25 | % 26 | % James Long 04/16/2020 27 | 28 | if ~exist('fc','var')||isempty(fc), fc = obj.input_vars.f0; end 29 | if ~exist('fbw','var')||isempty(fbw), fbw = 0.8; end 30 | if ~exist('bwr','var')||isempty(bwr), bwr=-6; end 31 | if ~exist('tpe','var')||isempty(tpe), tpe=-40; end 32 | assert(length(obj.xdc.tx_apod)==length(obj.xdc.on_elements),'Transmit apodization must match on elements.'); 33 | 34 | %%% Define in/out parameters, shift z_axis %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 35 | if strcmp(obj.xdc.type, 'curvilinear') 36 | layers = 5; 37 | sector = obj.xdc.pitch*obj.xdc.n; theta_xdc = sector/obj.xdc.r; 38 | theta = linspace(-theta_xdc/2,theta_xdc/2,obj.xdc.n); 39 | zp = cos(theta)*obj.xdc.r; zp = zp-min(zp); 40 | inmap0 = zeros(size(obj.field_maps.cmap)); inmap0(:,1) = 1; 41 | [zg,yg] = meshgrid(obj.grid_vars.z_axis,obj.grid_vars.y_axis); 42 | inmap0(yg.^2+(zg+obj.xdc.r-max(zp)).^2=obj.xdc.e_ind(:,1)&i<=obj.xdc.e_ind(:,2)); 123 | icmat(i,:) = icmat_sub(ct,:)*obj.xdc.tx_apod(obj.xdc.on_elements == idx); 124 | end 125 | for i = 1:layers-1 126 | tnew=t-(obj.grid_vars.dZ/obj.input_vars.c0); 127 | icvec = interp1(t,icvec,tnew,[],0); 128 | icmat_sub = focus_transmit(obj,fy,fz,icvec,[ey(:) ez(:)]); 129 | icmat_add = zeros(obj.grid_vars.nY,obj.grid_vars.nT); 130 | ct = 0; 131 | for i = ey1:ey2 132 | ct = ct+1; 133 | idx = find(i>=obj.xdc.e_ind(:,1)&i<=obj.xdc.e_ind(:,2)); 134 | icmat_add(i,:) = icmat_sub(ct,:)*obj.xdc.tx_apod(obj.xdc.on_elements == idx); 135 | end 136 | icmat = [icmat; icmat_add]; 137 | end 138 | 139 | [~,n] = max(icmat~=0,[],2); 140 | n = min(n(n~=1)); 141 | icmat = circshift(icmat,-n,2); 142 | 143 | obj.xdc.focus = focus; 144 | obj.xdc.icmat = icmat; 145 | obj.xdc.delays = get_delays(obj,focus); 146 | 147 | end -------------------------------------------------------------------------------- /@fwObj/fwObj.m: -------------------------------------------------------------------------------- 1 | classdef fwObj < handle 2 | 3 | % Create simulation object of class fwObj to perform Fullwave simulations 4 | % (created by Gianmarco Pinton). Assumes path to Fullwave tools have 5 | % already been added. 6 | % 7 | % Initialization: 8 | % obj = fwObj(varargin) 9 | % 10 | % Optional parameters (default): 11 | % f0 - Center frequency in Hz (1e6) 12 | % ncycles - Excitation cycles (2) 13 | % c0 - Speed of sound in m/s (1540) 14 | % td - Time duration of simulation in s (40e-6) 15 | % p0 - Pressure amplitude of transmit in Pa (1e5) 16 | % ppw - Spatial points per wavelength (8) 17 | % cfl - Courant-Friedrichs-Levi number (0.4) 18 | % wY - Lateral span of simulation in m (5e-2) 19 | % wZ - Depth of simulation in m (5e-2) 20 | % rho - Density in kg/m^3 (1000) 21 | % atten - Attenuation in dB/MHz/cm (0) 22 | % B - Non-linearity parameter (0) 23 | % v - Version of Fullwave (1) 24 | % gpu - GPU flag, version 1 only (0) 25 | % 26 | % Return: 27 | % obj - Simulation object with properties: 28 | % input_vars: Input variables 29 | % grid_vars: Grid variables 30 | % field_maps: Field maps (cmap, rhomap, 31 | % attenmap, Bmap) 32 | % 33 | % James Long, 04/16/2020 34 | 35 | properties 36 | input_vars 37 | grid_vars 38 | field_maps 39 | xdc 40 | end 41 | 42 | methods 43 | 44 | %%% Initialization %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 45 | function obj = fwObj(varargin) 46 | 47 | p = inputParser; 48 | %%% Add defaults for optional requirements %%%%%%%%%%%%%%%%%%%% 49 | addOptional(p,'c0',1540) 50 | addOptional(p,'td',100e-6) 51 | addOptional(p,'p0',1e5) 52 | addOptional(p,'ppw',8) 53 | addOptional(p,'cfl',0.4) 54 | addOptional(p,'wY',10e-2) 55 | addOptional(p,'wZ',6e-2) 56 | addOptional(p,'rho',1000) 57 | addOptional(p,'atten',0) 58 | addOptional(p,'B',0) 59 | addOptional(p,'bovera',-2) 60 | addOptional(p,'f0',1e6) 61 | addOptional(p,'ncycles',2) 62 | addOptional(p,'v',1) 63 | addOptional(p,'gpu',0) 64 | 65 | %%% Parse inputs and extract variables from p %%%%%%%%%%%%%%%%% 66 | p.parse(varargin{:}) 67 | var_struct = p.Results; 68 | assignments = extract_struct(var_struct); 69 | for i = 1:length(assignments) 70 | eval(assignments{i}) 71 | end 72 | 73 | %%% Grid size calculations %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 74 | omega0 = 2*pi*f0; 75 | lambda = c0/omega0*2*pi; 76 | nY = round(wY/lambda*ppw); 77 | nZ = round(wZ/lambda*ppw); 78 | nT = round(td*c0/lambda*ppw/cfl); 79 | dY = c0/omega0*2*pi/ppw; 80 | dZ = c0/omega0*2*pi/ppw; 81 | dT = dY/c0*cfl; 82 | t_axis = 0:dT:(nT-1)*dT; 83 | z_axis = 0:dZ:(nZ-1)*dZ; 84 | y_axis = 0:dY:(nY-1)*dY; y_axis = y_axis - mean(y_axis); 85 | 86 | %%% Generate field maps %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 87 | cmap = ones(nY,nZ)*c0; % speed of sound map (m/s) 88 | rhomap = ones(nY,nZ)*rho; % density map (kg/m^3) 89 | attenmap = ones(nY,nZ)*atten; % attenuation map (dB/MHz/cm) 90 | Bmap = ones(nY,nZ)*B; % nonlinearity map 91 | boveramap = ones(nY,nZ)*bovera; 92 | 93 | %%% Package into structures %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 94 | obj.input_vars = struct('c0',c0,... 95 | 'td',td,... 96 | 'p0',p0,... 97 | 'ncycles',ncycles,... 98 | 'ppw',ppw,... 99 | 'cfl',cfl,... 100 | 'wY',wY,... 101 | 'wZ',wZ,... 102 | 'rho',rho,... 103 | 'atten',atten,... 104 | 'B',B,... 105 | 'bovera',bovera,... 106 | 'f0',f0,... 107 | 'omega0',omega0,... 108 | 'lambda',lambda,... 109 | 'v',v,... 110 | 'gpu',gpu); 111 | 112 | obj.grid_vars = struct('nY',nY,... 113 | 'nZ',nZ,... 114 | 'nT',nT,... 115 | 'dY',dY,... 116 | 'dZ',dZ,... 117 | 'dT',dT,... 118 | 'y_axis',y_axis,... 119 | 'z_axis',z_axis,... 120 | 't_axis',t_axis); 121 | 122 | obj.field_maps = struct('cmap',cmap,... 123 | 'rhomap',rhomap,... 124 | 'attenmap',attenmap); 125 | if v==2 126 | obj.field_maps.Bmap = Bmap; 127 | elseif v==1 128 | obj.field_maps.boveramap = boveramap; 129 | end 130 | 131 | end 132 | 133 | % Self-referential methods 134 | obj = make_xdc(obj); 135 | obj = focus_xdc(obj, focus, fc, fbw, excitation, bwr, tpe); 136 | obj = add_wall(obj, wall_name, offset, filt_size); 137 | obj = add_speckle(obj, varargin); 138 | obj = add_points(obj, varargin) 139 | obj = add_fii_phantom(obj, phtm_file, symmetry, el_lim, csr, fnum) 140 | obj = preview_sim(obj); 141 | 142 | % Output methods 143 | rf = do_sim(obj, field_flag); 144 | acq_params = make_acq_params(obj); 145 | 146 | % Latent methods 147 | incoords = make_incoords_row(obj,inmap) 148 | delays = get_delays(focus,rx_pos,c); 149 | end 150 | 151 | end 152 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Object oriented tools for Fullwave simulations 2 | 3 | This repository contains an object-oriented wrapper for Fullwave and a demo script for usage. Fullwave is written by Gianmarco Pinton. 4 | 5 | ### Setup 6 | * [fwObj()](fwObj.m) - Initialize simulation object by setting parameters keywords-style 7 | 8 | ### Methods 9 | * [make_xdc()](make_xdc.m) - Initialize transducer properties 10 | * [focus_xdc()](focus_xdc.m) - Calculate transmit focal delays and sets up initial conditions 11 | * [add_wall()](add_wall.m) - Add abdominal wall in nearfield 12 | * [add_speckle()](add_speckle.m) - Add scatterers to field, with option to include cysts of tunable size and scattering properties 13 | * [add_points()](add_points.m) - Add point targets to field with varying impedance contrast 14 | * [add_fii_phantom()](add_fii_phantom.m) - Add Field II phantom using phantom output from [ultratrack](https://github.com/mlp6/ultratrack) 15 | * [preview_sim()](preview_sim.m) - Preview acoustic map, focusing delays, and transmitted pulse 16 | * [do_sim()](do_sim.m) - Perform simulation, with option to save channel data or field pressure distribution 17 | * [make_acq_params()](make_acq_params.m) - Convert simulation parameters to `acq_params`, necessary for use with beamforming tools 18 | 19 | ### Helper scripts 20 | * [extract_struct.m](extract_struct.m) - Extracts structure fields as individual variables in workspace 21 | * [focus_transmit.m](focus_transmit.m) - Calculate transmit focal delays pixel-by-pixel 22 | * [make_incoords_row.m](make_incoords_row.m) - Convert input map to input coordinates 23 | * [get_delays.m](get_delays.m) - Retrieve element-by-element delays in transmit 24 | 25 | ### Workflow 26 | * Call `fwObj()` to initialize the simulation object-oriented 27 | * Set transducer properties and call `make_xdc()` 28 | * Define the focus and call `focus_linear()` 29 | * If desired, change the field maps via `add_wall()`, `make_speckle()`, `make_points()`, or `add_fii_phantom()` 30 | * Run the simulation with `do_sim()` 31 | 32 | ### Example set up 33 | * [setup_example.m](setup_example.m) - Example usage with varying focal configurations and simple field map changes 34 | 35 | ``` 36 | %%% Create fwObj %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 37 | c0 = 1540; % Homogeneous speed of sound 38 | f0 = 2e6; % Transmit center frequency (Hz) 39 | wZ = 8e-2; % Axial extent (m) 40 | wY = 6e-2; % Lateral extent (m) 41 | td =(wZ+1e-2)/c0; % Time duration of simulation (s) 42 | sim = fwObj('c0',c0,'f0',f0,'wY',wY,'wZ',wZ,'td',td); 43 | ``` 44 | First, we will call `fwObj()` to initialize the simulation object. In this example, we have specified the homogeneous speed of sound, transmit center frequency, axial and lateral extents of the field, and the time duration of simulation. 45 | 46 | ``` 47 | %%% Specify transducer and transmit parameters %%%%%%%%%%%%%%%%%%%%%%%%%%%% 48 | sim.xdc.type = 'linear'; % Curvilinear or linear 49 | sim.xdc.pitch = 0.000412; % Center-to-center element spacing 50 | sim.xdc.n = 64; % Number of elements 51 | sim.make_xdc(); % Call make_xdc to set up transducer 52 | ``` 53 | Next, we will set transducer properties and call `make_xdc()` to configure the remaining properties. The transducer type, pitch, kerf, and number of elements is required. 54 | 55 | ``` 56 | %%% Focused transmit %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 57 | focus = [0 0.03]; % Focal point in [y z] (m) 58 | sim.focus_xdc(focus); % Call focus_linear to calculate icmat 59 | 60 | %%% Plane transmit %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 61 | th = 15; % Angle for plane wave transmit (degrees) 62 | focus = 10*[sind(th) cosd(th)]; % Very far focus to flatten delays 63 | sim.focus_xdc(focus); % Call focus_linear to calculate icmat 64 | 65 | %%% Diverging transmit %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 66 | focus = [0 -0.03]; % Negative focus for diverging in [y z] (m) 67 | sim.focus_xdc(focus); % Call focus_linear to calculate icmat 68 | ``` 69 | `focus_xdc()` is called to calculate focal delays in transmit and set up the initial condition matrix (`icmat`). Plane wave transmit is achieved with a large focal distance relative to the field extent; diverging wave transmit is achieved with a negative focal depth. 70 | The corresponding `icmat` and element-wise focal delays can be seen here: 71 | ![alt text](transmit.png) 72 | 73 | ``` 74 | %%% 2-cycle pulse %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 75 | focus = [0 0.03]; % Focal point in [y z] (m) 76 | sim.focus_xdc(focus); % Call focus_linear to calculate icmat 77 | 78 | %%% 10-cycle pulse %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 79 | sim.input_vars.ncycles = 10; 80 | sim.make_xdc(); % Call make_xdc to set up transducer 81 | focus = [0 0.03]; % Focal point in [y z] (m) 82 | sim.focus_xdc(focus); % Call focus_linear to calculate icmat 83 | 84 | %%% Chirp %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 85 | flow = 1e6; fhigh = 3e6; 86 | t = 0:sim.grid_vars.dT:6e-6; 87 | k = (pi/max(t))*(fhigh-flow); 88 | phi = k*t.^2; 89 | excitation = sin(2*pi*flow*t+phi); 90 | sim.focus_xdc(focus,[],[],excitation); 91 | ``` 92 | The excitation may be altered by changing the `ncycles` input variable or manually defining an excitation sequence prior to calling `focus_linear()`. Here, the default 2-cycle pulse is shown with a 10-cycle and chirp variant: 93 | ![alt text](excitation.png) 94 | 95 | ``` 96 | %%% Add abdominal wall with lateral offset %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 97 | wall_name = 'r75hi'; % Mast abdominal wall name 98 | offset = 0.01; % Lateral offset from center (m) 99 | sim.add_wall(wall_name,offset); 100 | ``` 101 | A Mast abdominal wall can be added in the nearfield using `add_wall()`. Here, we add one with a lateral offset of 1 cm. 102 | 103 | ``` 104 | %%% Add speckle and cysts of varying impedance contrasts %%%%%%%%%%%%%%%%%% 105 | cC = 1e-3*[-15 50; -5 50; 5 50; 15 50]; % Locations of cyst centers in [y z] (m) 106 | rC = 0.004*ones(size(cC,1),1); % Radii of cysts (m) 107 | zC = [0 0.025 0.075 0.1]'; % Cyst relative impedance contrast 108 | sim.add_speckle('nscat',50,'csr',0.05,'nC',length(rC),'cC',cC,'rC',rC,'zC',zC); 109 | ``` 110 | Scattering and cyst targets can be added by calling `make_speckle()`. The impedance mismatch, scattering density, and cyst size and location can be adjusted. Combining the scatterers with the abdominal wall, we arrive at the following `cmap`: 111 | ![alt text](maps.png) 112 | 113 | ``` 114 | %%% Collect single transmit channel data %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 115 | t = tic; 116 | rf_data = double(sim.do_sim()); 117 | fprintf(' Channel data generated in %1.2f seconds \n',toc(t)) 118 | ``` 119 | Finally, the simulation is run by calling `do_sim()`. Here, we collect single transmit channel data. 120 | 121 | ### Demo 122 | A simple demo for a 1 MHz pulse focused at 4 cm through an abdominal wall can be found in [demo.m](demo.m). 123 | 124 | ### References 125 | * Fullwave: [Pinton, G. F., Dahl, J., Rosenzweig, S., & Trahey, G. E. (2009). A heterogeneous nonlinear attenuating full-wave model of ultrasound. IEEE transactions on ultrasonics, ferroelectrics, and frequency control, 56(3).](https://ieeexplore.ieee.org/abstract/document/4816057) 126 | * Impedance flow for Fullwave: [Pinton, G. F. (2017). Subresolution Displacements in Finite Difference Simulations of Ultrasound Propagation and Imaging. IEEE transactions on ultrasonics, ferroelectrics, and frequency control, 64(3), 537-543.](https://ieeexplore.ieee.org/abstract/document/7781628) 127 | * Abdominal wall models: [Mast, T. D., Hinkelman, L. M., Orr, M. J., Sparrow, V. W., & Waag, R. C. (1997). Simulation of ultrasonic pulse propagation through the abdominal wall. The Journal of the Acoustical Society of America, 102(2), 1177-1190.](https://asa.scitation.org/doi/abs/10.1121/1.421015) 128 | 129 | -------------------------------------------------------------------------------- /setup_example.m: -------------------------------------------------------------------------------- 1 | % ooFullwave, v2.4.0 2 | % 3 | % Examples using fwObj to setup and run Fullwave simulations for focused, 4 | % plane wave, and diverging transmits on linear arrays. Includes examples 5 | % with abdominal walls and adding scatterers. 6 | % 7 | % James Long 04/16/2020 8 | % ***Fullwave written by Gianmarco Pinton*** 9 | 10 | %% Setup fwObj for varying transmit cases %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 11 | clear 12 | close; figure('pos',[100 100 1200 600],'color','w') 13 | msz = 100; 14 | 15 | %%% Create fwObj %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 16 | c0 = 1540; % Homogeneous speed of sound 17 | f0 = 2e6; % Transmit center frequency (Hz) 18 | wZ = 8e-2; % Axial extent (m) 19 | wY = 6e-2; % Lateral extent (m) 20 | td =(wZ+1e-2)/c0; % Time duration of simulation (s) 21 | sim = fwObj('c0',c0,'f0',f0,'wY',wY,'wZ',wZ,'td',td); 22 | 23 | %%% Specify transducer and transmit parameters %%%%%%%%%%%%%%%%%%%%%%%%%%%% 24 | sim.xdc.type = 'linear'; % Curvilinear or linear 25 | sim.xdc.pitch = 0.000412; % Center-to-center element spacing 26 | sim.xdc.n = 64; % Number of elements 27 | sim.make_xdc(); % Call make_xdc to set up transducer 28 | 29 | %%% Focused transmit %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 30 | focus = [0 0.03]; % Focal point in [y z] (m) 31 | sim.focus_xdc(focus); % Call focus_linear to calculate icmat 32 | 33 | subplot(231) 34 | imagesc(sim.grid_vars.y_axis*1e3,sim.grid_vars.t_axis*1e6,sim.xdc.icmat(1:sim.grid_vars.nY,:)'); 35 | xlabel('Lateral (mm)'); ylabel('Time (us)'); title('Focused transmit'); ylim([0 20]) 36 | subplot(234) 37 | scatter(sim.xdc.on_elements,sim.xdc.delays*1e6,msz,'.b'); axis tight 38 | xlabel('Element number'); ylabel('Time (us)'); title('Focused delays') 39 | 40 | %%% Plane transmit %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 41 | th = 15; % Angle for plane wave transmit (degrees) 42 | focus = 10*[sind(th) cosd(th)]; % Very far focus to flatten delays 43 | sim.focus_xdc(focus); % Call focus_linear to calculate icmat 44 | 45 | subplot(232) 46 | imagesc(sim.grid_vars.y_axis*1e3,sim.grid_vars.t_axis*1e6,sim.xdc.icmat(1:sim.grid_vars.nY,:)'); 47 | xlabel('Lateral (mm)'); ylabel('Time (us)'); title('Plane transmit'); ylim([0 20]) 48 | subplot(235) 49 | scatter(sim.xdc.on_elements,sim.xdc.delays*1e6,msz,'.b'); axis tight 50 | xlabel('Element number'); ylabel('Time (us)'); title('Plane delays') 51 | 52 | %%% Diverging transmit %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 53 | focus = [0 -0.03]; % Negative focus for diverging in [y z] (m) 54 | sim.focus_xdc(focus); % Call focus_linear to calculate icmat 55 | 56 | subplot(233) 57 | imagesc(sim.grid_vars.y_axis*1e3,sim.grid_vars.t_axis*1e6,sim.xdc.icmat(1:sim.grid_vars.nY,:)'); 58 | xlabel('Lateral (mm)'); ylabel('Time (us)'); title('Diverging transmit'); ylim([0 20]) 59 | subplot(236) 60 | scatter(sim.xdc.on_elements,sim.xdc.delays*1e6,msz,'.b'); axis tight 61 | xlabel('Element number'); ylabel('Time (us)'); title('Diverging delays') 62 | 63 | %% Add arbitrary waveform %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 64 | clear 65 | close; figure('pos',[100 100 1200 600],'color','w') 66 | msz = 100; 67 | 68 | %%% Create fwObj %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 69 | c0 = 1540; % Homogeneous speed of sound 70 | f0 = 2e6; % Transmit center frequency (Hz) 71 | wZ = 8e-2; % Axial extent (m) 72 | wY = 6e-2; % Lateral extent (m) 73 | td =(wZ+1e-2)/c0; % Time duration of simulation (s) 74 | sim = fwObj('c0',c0,'f0',f0,'wY',wY,'wZ',wZ,'td',td); 75 | 76 | %%% Specify transducer and transmit parameters %%%%%%%%%%%%%%%%%%%%%%%%%%%% 77 | sim.xdc.type = 'linear'; % Curvilinear or linear 78 | sim.xdc.pitch = 0.000412; % Center-to-center element spacing 79 | sim.xdc.n = 64; % Number of elements 80 | sim.make_xdc(); % Call make_xdc to set up transducer 81 | 82 | %%% 2-cycle pulse %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 83 | focus = [0 0.03]; % Focal point in [y z] (m) 84 | sim.focus_xdc(focus); % Call focus_linear to calculate icmat 85 | subplot(231) 86 | linewidth = 2; 87 | plot(sim.xdc.excitation_t*1e6,sim.xdc.excitation,'linewidth',linewidth) 88 | xlabel('Time (us)'); title('2-cycle excitation'); axis tight 89 | 90 | subplot(234) 91 | plot(sim.xdc.pulse_t*1e6,sim.xdc.pulse,'linewidth',linewidth) 92 | xlabel('Time (us)'); title('2-cycle pulse'); axis tight 93 | 94 | %%% 10-cycle pulse %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 95 | sim.input_vars.ncycles = 10; 96 | sim.make_xdc(); % Call make_xdc to set up transducer 97 | focus = [0 0.03]; % Focal point in [y z] (m) 98 | sim.focus_xdc(focus); % Call focus_linear to calculate icmat 99 | subplot(232) 100 | linewidth = 2; 101 | plot(sim.xdc.excitation_t*1e6,sim.xdc.excitation,'linewidth',linewidth) 102 | xlabel('Time (us)'); title('10-cycle excitation'); axis tight 103 | 104 | subplot(235) 105 | plot(sim.xdc.pulse_t*1e6,sim.xdc.pulse,'linewidth',linewidth) 106 | xlabel('Time (us)'); title('10-cycle pulse'); axis tight 107 | 108 | %%% Chirp %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 109 | flow = 1e6; fhigh = 3e6; 110 | t = 0:sim.grid_vars.dT:6e-6; 111 | k = (pi/max(t))*(fhigh-flow); 112 | phi = k*t.^2; 113 | excitation = sin(2*pi*flow*t+phi); 114 | sim.focus_xdc(focus,[],[],excitation); 115 | subplot(233) 116 | linewidth = 2; 117 | plot(sim.xdc.excitation_t*1e6,sim.xdc.excitation,'linewidth',linewidth) 118 | xlabel('Time (us)'); title('Chirp excitation'); axis tight 119 | 120 | subplot(236) 121 | plot(sim.xdc.pulse_t*1e6,sim.xdc.pulse,'linewidth',linewidth) 122 | xlabel('Time (us)'); title('Chirp pulse'); axis tight 123 | 124 | %% Setup fwObj to make changes to maps %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 125 | clear 126 | close; figure('pos',[100 100 1400 500],'color','w') 127 | cax = [1440 1640]; 128 | 129 | %%% Create fwObj %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 130 | c0 = 1540; % Homogeneous speed of sound 131 | f0 = 2e6; % Transmit center frequency (Hz) 132 | wZ = 8e-2; % Axial extent (m) 133 | wY = 6e-2; % Lateral extent (m) 134 | td =(wZ+1e-2)/c0; % Time duration of simulation (s) 135 | sim = fwObj('c0',c0,'f0',f0,'wY',wY,'wZ',wZ,'td',td); 136 | 137 | subplot(131) 138 | imagesc(sim.grid_vars.y_axis*1e3,sim.grid_vars.z_axis*1e3,sim.field_maps.cmap',cax); 139 | xlabel('Lateral (mm)'); ylabel('Axial (mm)'); axis image 140 | title('Initial') 141 | 142 | %%% Add abdominal wall with lateral offset %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 143 | wall_name = 'r75hi'; % Mast abdominal wall name 144 | offset = 0.01; % Lateral offset from center (m) 145 | sim.add_wall(wall_name,offset); 146 | 147 | subplot(132) 148 | imagesc(sim.grid_vars.y_axis*1e3,sim.grid_vars.z_axis*1e3,sim.field_maps.cmap',cax); 149 | xlabel('Lateral (mm)'); ylabel('Axial (mm)'); axis image 150 | title('Add abdominal wall') 151 | 152 | %%% Add speckle and cysts of varying impedance contrasts %%%%%%%%%%%%%%%%%% 153 | cC = 1e-3*[-10 50; 0 50; 10 50]; % Locations of cyst centers in [y z] (m) 154 | rC = 0.004*ones(size(cC,1),1); % Radii of cysts (m) 155 | zC = [0 0.5 2]'; % Cyst relative impedance contrast 156 | sim.add_speckle('nscat',50,'csr',0.05,'nC',length(rC),'cC',cC,'rC',rC,'zC',zC); 157 | 158 | subplot(133) 159 | imagesc(sim.grid_vars.y_axis*1e3,sim.grid_vars.z_axis*1e3,sim.field_maps.cmap',cax); 160 | xlabel('Lateral (mm)'); ylabel('Axial (mm)'); axis image 161 | title('Add speckle with cysts') 162 | 163 | %% Collect single transmit channel data %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 164 | t = tic; 165 | rf_data = double(sim.do_sim()); 166 | fprintf(' Channel data generated in %1.2f seconds \n',toc(t)) 167 | --------------------------------------------------------------------------------