Skip to content

Commit

Permalink
Ref ARRUS-111: Extending the B-Mode display functionality (#247)
Browse files Browse the repository at this point in the history
* Implementation of Cine Loop

* Input parser

* cineLoop not optional

* Implementation of persistence

* Update the control script

* DuplexDisplay - bug fix

* Implementation of TGC (postprocessing)

* DuplexDisplay - minor changes

* DuplexDisplay - auto TGC bug fix

* DuplexDisplay - colorbar bug fix

The bug consisted in: color limits of the colorbar were unrelated to the color limits of the image.

* DuplexDisplay - cineloop size bug fix

* DuplexDisplay - spaces around "+" and "-"

* DuplexDisplay - validation moved to addParameter function

* Us4R_duplex - reconstructionObject is now obligatory input

* DuplexDisplay - break code lines
  • Loading branch information
PiotrKarwat authored Nov 20, 2021
1 parent 1d0e38d commit e8ea4ea
Show file tree
Hide file tree
Showing 2 changed files with 113 additions and 18 deletions.
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);

0 comments on commit e8ea4ea

Please sign in to comment.