Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ref ARRUS-111: Extending the B-Mode display functionality #247

Merged
merged 15 commits into from
Nov 20, 2021
Merged
122 changes: 105 additions & 17 deletions api/matlab/arrus/DuplexDisplay.m
Original file line number Diff line number Diff line change
Expand Up @@ -12,26 +12,74 @@
showTimes
dynamicRange
powerThreshold

cineLoop
cineLoopLength
cineLoopIndex

persistence

bmodeTgc
bmodeAutoTgcResp
end

methods
function obj = DuplexDisplay(proc, dynamicRange, powerThreshold, subplotEnable)
function obj = DuplexDisplay(varargin)

if nargin < 4
subplotEnable = false;
end
% Input parser
dispParParser = inputParser;

addRequired(dispParParser, 'reconstructionObject', ...
@(x) assert(isscalar(x) && isa(x,'Reconstruction'), ...
"reconstructionObject is an obligatory scalar input of class Reconstruction."));

addParameter(dispParParser, 'dynamicRange', [0 80], ...
@(x) assert(isnumeric(x) && all(size(x) == [1 2]), ...
"dynamicRange must be a 2-element numerical vector: [min max]."));

addParameter(dispParParser, 'powerThreshold', -inf, ...
@(x) assert(isnumeric(x) && isscalar(x), ...
"powerThreshold must be a numerical scalar."));

addParameter(dispParParser, 'subplotEnable', false, ...
@(x) assert(islogical(x) && isscalar(x), ...
"subplotEnable must be a logical scalar."));

if nargin < 3
powerThreshold = -inf;
addParameter(dispParParser, 'cineLoopLength', 1, ...
@(x) assert(isnumeric(x) && isscalar(x) && mod(x,1)==0 && x>=1, ...
"cineLoopLength must be a positive integer scalar."));

addParameter(dispParParser, 'persistence', 1, ...
@(x) assert(isnumeric(x) && ((isvector(x) && numel(x)>1) || ...
(isscalar(x) && mod(x,1)==0 && x>=1)), ...
"persistence must be a positive integer scalar or numerical vector."));

addParameter(dispParParser, 'bmodeTgc', 0, ...
@(x) assert(isnumeric(x) && isscalar(x), ...
"bmodeTgc must be a numerical scalar."));

addParameter(dispParParser, 'bmodeAutoTgcResp', 0, ...
@(x) assert(isnumeric(x) && isscalar(x) && x>=0 && x<=1, ...
"bmodeAutoTgcResp must be a numerical scalar in <0,1> range."));

parse(dispParParser, varargin{:});

proc = dispParParser.Results.reconstructionObject;
dynamicRange = dispParParser.Results.dynamicRange;
powerThreshold = dispParParser.Results.powerThreshold;
subplotEnable = dispParParser.Results.subplotEnable;
cineLoopLength = dispParParser.Results.cineLoopLength;
persistence = dispParParser.Results.persistence;
bmodeTgc = dispParParser.Results.bmodeTgc;
bmodeAutoTgcResp = dispParParser.Results.bmodeAutoTgcResp;

if isscalar(persistence)
persistence = ones(1,persistence);
end

if nargin < 2
dynamicRange = [0 60];
else
if ~all(size(dynamicRange) == [1 2])
error("ARRUS:IllegalArgument", ...
"Invalid dimensions of dynamic range vector, should be: [min max]")
end
if numel(persistence)>cineLoopLength
warning("cineLoopLength increased to fit the persistence.");
cineLoopLength = numel(persistence);
end

obj.xGrid = proc.xGrid;
Expand All @@ -40,6 +88,15 @@
obj.vectorEnable = proc.vectorEnable;
obj.dynamicRange = dynamicRange;
obj.powerThreshold = powerThreshold;
obj.cineLoopLength = cineLoopLength;
obj.persistence = reshape(persistence,1,1,[]) / sum(persistence);
obj.bmodeTgc = bmodeTgc * obj.zGrid(:) / obj.zGrid(end);
obj.bmodeAutoTgcResp = bmodeAutoTgcResp;

% Prepare cineLoop
cineLoopLayersNumber = 1 + 2*double(obj.colorEnable && ~obj.vectorEnable) + 3*double(obj.vectorEnable);
obj.cineLoop = nan(numel(obj.zGrid), numel(obj.xGrid), obj.cineLoopLength, cineLoopLayersNumber);
obj.cineLoopIndex = 0;

% Create figure.
obj.hFig = figure();
Expand All @@ -54,7 +111,7 @@

for iAx=1:4
obj.hAx(iAx) = subplot(2,2,iAx);
obj.hImg(iAx) = image(obj.xGrid*1e3, obj.zGrid*1e3, []);
obj.hImg(iAx) = imagesc(obj.xGrid*1e3, obj.zGrid*1e3, []);
colormap(gca,subplotColorMaps(:,:,iAx));
colorbar;
xlabel('x [mm]');
Expand All @@ -66,7 +123,7 @@
end
else
obj.hAx = axes();
obj.hImg = image(obj.xGrid*1e3, obj.zGrid*1e3, []);
obj.hImg = imagesc(obj.xGrid*1e3, obj.zGrid*1e3, []);
colormap(gca,gray);
colorbar;
xlabel('x [mm]');
Expand All @@ -78,6 +135,7 @@
end

obj.hQvr = nan;

end

function state = isOpen(obj)
Expand All @@ -95,9 +153,29 @@ function updateImg(obj, data)
try
[nZPix,nXPix,~,~] = size(data);

bmode = data(:,:,:,1);
bmode(isnan(bmode)) = -inf;
% update cineLoop
obj.cineLoopIndex = mod(obj.cineLoopIndex, obj.cineLoopLength) + 1;
obj.cineLoop(:,:,obj.cineLoopIndex,:) = data;

% persistence
index = mod(obj.cineLoopIndex - (0:(numel(obj.persistence) - 1)) - 1, obj.cineLoopLength) + 1;
bmode = sum(obj.cineLoop(:,:,index,1) .* obj.persistence, 3, 'omitnan');

% time gain compensation
if obj.bmodeAutoTgcResp ~= 0
% linear regression of bmode average brightness profile
n = numel(obj.zGrid);
x = obj.zGrid(:);
y = mean(bmode,2,'omitnan');
a = (n*sum(x.*y) - sum(x)*sum(y)) / (n*sum(x.^2) - sum(x)^2); % [dB/m]

obj.bmodeTgc = obj.bmodeTgc * (1 - obj.bmodeAutoTgcResp) + ...
(-a * obj.zGrid(:)) * obj.bmodeAutoTgcResp;
end
bmode = bmode + obj.bmodeTgc;

% conversion to RGB
bmode(isnan(bmode)) = -inf;
bmodeRGB = (bmode - obj.dynamicRange(1)) / diff(obj.dynamicRange);
bmodeRGB = max(0,min(1,bmodeRGB));
bmodeRGB = bmodeRGB .* ones(1,1,3); % colormap = gray
Expand Down Expand Up @@ -171,6 +249,7 @@ function updateImg(obj, data)
% reaction to that close.
% That was an issue on MATLAB 2018b, testenv2.
pause(0.01);

catch ME
if(strcmp(ME.identifier, 'MATLAB:class:InvalidHandle'))
disp('Display was closed.');
Expand All @@ -179,6 +258,15 @@ function updateImg(obj, data)
end
end
end

function cineLoop = getCineLoop(obj)
if obj.cineLoopLength > 0
cineLoop = circshift(obj.cineLoop, -obj.cineLoopIndex, 3);
else
cineLoop = [];
end
end

end
end

9 changes: 8 additions & 1 deletion api/matlab/examples/Us4R_duplex.m
Original file line number Diff line number Diff line change
Expand Up @@ -95,5 +95,12 @@
%% Run sequence and reconstruction
% [rf,img] = us.run;
%
display = DuplexDisplay(rec, [0 60], 10, true);
display = DuplexDisplay(rec, ...
'dynamicRange', [0 60], ...
'powerThreshold', 10, ...
'subplotEnable', true, ...
'cineLoopLength', 1, ...
'persistence', 1, ...
'bmodeTgc', 0, ...
'bmodeAutoTgcResp', 0);
us.runLoop(@display.isOpen, @display.updateImg);