diff --git a/warp.m b/warp.m index 8d0b3edd29af56d17aab949514755ff98765364c..145cd8f1aca8492649e583f066c0e1c28559409f 100644 --- a/warp.m +++ b/warp.m @@ -1,213 +1,200 @@ -function [Value,Mask,Pos,DispX,DispY] = warp(Value,Mask,Pos,DispX,DispY,varargin) +function [Value,Mask,Pos,dX,dY] = warp(Value,Mask,Pos,dX,dY,varargin) %WARP Summary of this function goes here % Detailed explanation goes here %% Set parameter values according to given arguments interps = {'nearest','linear','cubic','spline'}; -boundaryTypes = {'current','any','all'}; directions = {'forward','backward'}; p = inputParser; p.KeepUnmatched = true; p.StructExpand = true; -p.addParameter('interpMethod', 'nearest' , @(x) any(validatestring(x,interps))); -p.addParameter('SRBoundary' , 'current' , @(x) any(validatestring(x,boundaryTypes))); -p.addParameter('LFBoundary' , false , @islogical); -p.addParameter('direction' , 'backward', @(x) any(validatestring(x,directions))); -p.addParameter('relative' , true , @islogical); +p.addParameter('interpMethod', 'cubic' , @(x) any(validatestring(x,interps ))); +p.addParameter('direction' , 'forward', @(x) any(validatestring(x,directions))); +p.addParameter('backproject' , true , @islogical); p.parse(varargin{:}); -interpMethod = p.Results.interpMethod; % interpolation method of Gridded Interpolant -SRBoundary = p.Results.SRBoundary; % Query values to retain -LFBoundary = p.Results.LFBoundary; % Retain values inside original boundaries -direction = p.Results.direction; % Forward or backward projection -relative = p.Results.relative; % Scale disparity relative to reference view +interpMethod = p.Results.interpMethod; +direction = p.Results.direction; +backproject = p.Results.backproject; -%% +%% Compute reference grid +% Reference size Size = size(Value); -ValMask = ~isnan(Value); -% Lightfield grid vectors representing super-ray bounding box -ugv = Pos(1)+reshape(1:Size(1),[],1); -vgv = Pos(2)+reshape(1:Size(2),1,[]); -xgv = Pos(3)+reshape(1:Size(3),1,1,[]); -ygv = Pos(4)+reshape(1:Size(4),1,1,1,[]); +% Mask bounding box +gv = subarrayIdx(Mask); -gv = {ugv,vgv,xgv,ygv}; +% Mask grid coordinates +[~,~,X,Y] = ndgrid(gv{:}); -% Lightfield coordinates (4D) -[U,V,X,Y] = ndgrid(ugv,vgv,xgv,ygv); +% Mask grid vectors +[ugv,vgv,~,~] = deal(gv{:}); + +ugv = reshape(ugv,[],1); +vgv = reshape(vgv,1,[]); % Reference view -uref = median(ugv); -vref = median(vgv); +u_ref = floor(numel(ugv)/2)+1; +v_ref = floor(numel(vgv)/2)+1; + +% Disparity +DispX = dX*(ugv-u_ref); +DispY = dY*(vgv-v_ref); + +if backproject + % Temporary mask + tempMask = Mask(gv{:}); + + % Backproject coordinates + SRX = X + DispX; + SRY = Y + DispY; + + % Backprojected boundaries + minSRX = floor(min(SRX(tempMask))); maxSRX = ceil(max(SRX(tempMask))); + minSRY = floor(min(SRY(tempMask))); maxSRY = ceil(max(SRY(tempMask))); + + minSRX = max(minSRX,1); maxSRX = min(maxSRX,Size(3)); + minSRY = max(minSRY,1); maxSRY = min(maxSRY,Size(4)); + + % Reference grid vectors + xgv = minSRX:maxSRX; + ygv = minSRY:maxSRY; + + % Reference bounding box + gv = {ugv,vgv,xgv,ygv}; +end -% Fill NaN values to allow cubic interpolation (way faster than fillmissing) -seAng = strel('arbitrary',[0,1,0]|[0,1,0]'); -seSpa = strel('arbitrary',reshape([0,1,0]|[0,1,0]',1,1,3,3)); +% Reference grid +[~,~,X,Y] = ndgrid(gv{:}); -if strcmp(interpMethod,'cubic')&&any(isnan(Value(:))) - Value = extend(Value,seSpa,6); - Value = extend(Value,seAng,6); -end -if strcmp(interpMethod,'cubic')&&any(isnan(DispX(:))) - DispX = extend(DispX,seSpa,6); - DispX = extend(DispX,seAng,6); -end -if strcmp(interpMethod,'cubic')&&any(isnan(DispY(:))) - DispY = extend(DispY,seSpa,6); - DispY = extend(DispY,seAng,6); -end +%% Crop reference +Value = Value(gv{:}); +Mask = Mask (gv{:}); +Pos_ = cellfun(@(x) x(1),gv)-1; +End_ = cellfun(@(x) x(end),gv); -% If view-wise disparity, scale accordingly -if relative - DispX_ = (ugv-uref).*DispX; - DispY_ = (vgv-vref).*DispY; -else - DispX_ = DispX; - DispY_ = DispY; -end +%% Compute query grid +% Project coordinates +SRX = X - DispX; +SRY = Y - DispY; -% Check size of interpolated data (for GriddedInterpolant) -if numel(Value)==1, szVal=1; else, szVal = size(Value); end -singValue = szVal==1; -if numel(DispX)==1, szDispX=1; else, szDispX = size(DispX); end -singDispX = szDispX==1; -if numel(DispY)==1, szDispY=1; else, szDispY = size(DispX); end -singDispY = szDispY==1; - -% expand any singleton dimension (need at least 2) -if any(singValue),Value = repmat(Value,2*singValue); end -szVal = size(Value); - -singDispX(end+1:numel(szVal)) = 1; -szDispX(end+1:numel(szVal)) = 1; -singDispY(end+1:numel(szVal)) = 1; -szDispY(end+1:numel(szVal)) = 1; -if any(singDispX),DispX = repmat(DispX,szVal./szDispX); end -if any(singDispY),DispY = repmat(DispY,szVal./szDispY); end +% Projected boundaries +minPosX = floor(min(SRX(Mask))); maxPosX = ceil(max(SRX(Mask))); +minPosY = floor(min(SRY(Mask))); maxPosY = ceil(max(SRY(Mask))); -% Initialize interpolants -ValueInt = griddedInterpolant(gv,Value,interpMethod,'nearest'); % Interpolant on pixel values -DispXInt = griddedInterpolant(gv,DispX,interpMethod,'nearest'); % Interpolant on pixel values -DispYInt = griddedInterpolant(gv,DispY,interpMethod,'nearest'); % Interpolant on pixel values -MaskInt = griddedInterpolant(gv,double(Mask),'nearest','nearest'); % Interpolant on masked pixels +% Query grid vectors +uqgv = ugv; +vqgv = vgv; +xqgv = minPosX:maxPosX; +yqgv = minPosY:maxPosY; -disp(['Value: "' ValueInt.Method '" interp., "' ValueInt.ExtrapolationMethod '" extrap.']); -disp(['Mask: "' MaskInt.Method '" interp., "' MaskInt.ExtrapolationMethod '" extrap.']); +% Query bounding box +qgv = {uqgv,vqgv,xqgv,yqgv}; -% Projected values -projX = X-DispX_; -projY = Y-DispY_; +% Query grid +[Uq,Vq,Xq,Yq] = ndgrid(qgv{:}); -% Rounded projected values +Xq = Xq + DispX; +Yq = Yq + DispY; + +% Use invertible grid coordinates to compute mask switch direction case 'forward' - roundProjX = floor(projX+0.5); - roundProjY = floor(projY+0.5); + Xq_Mask = floor(Xq+0.5); + Yq_Mask = floor(Yq+0.5); case 'backward' - roundProjX = ceil(projX-0.5); - roundProjY = ceil(projY-0.5); + Xq_Mask = ceil(Xq-0.5); + Yq_Mask = ceil(Yq-0.5); end -% Query coordinates -Uq = U; -Vq = V; -Xq = X+roundProjX-projX; -Yq = Y+roundProjY-projY; +%% Interpolate +% Extend signal to allow cubic interpolation (way faster than fillmissing) +seAng = strel('arbitrary',[0,1,0]|[0,1,0]'); +seSpa = strel('arbitrary',reshape([0,1,0]|[0,1,0]',1,1,3,3)); -% Interpolate at query coordinates -Value = ValueInt(Uq,Vq,Xq,Yq); -DispX = DispXInt(Uq,Vq,Xq,Yq); -DispY = DispYInt(Uq,Vq,Xq,Yq); -Mask = MaskInt(Uq,Vq,Xq,Yq); - -Mask(isnan(Mask))=0; -Mask = logical(Mask); - -% Create mask corresponding to query points -switch SRBoundary - case 'current' - % inside boundary in current view - % Mask = Mask; - case 'any' - % inside boundary in at least one view - Mask = repmat(any(any(Mask,1),2),size(Mask,1),size(Mask,2),1,1); - case 'all' - % keep all query points - Mask = true(Size); +if strcmp(interpMethod,'cubic')&&any(isnan(Value(:))) + Value = extend(Value,seSpa,6); + Value = extend(Value,seAng,6); end -if LFBoundary - % Mask corresponding to query points inside lightfield boundary - Mask = Mask & ... - Xq-Pos(3)>=0.5 & Xq-Pos(3)<=Size(3)+0.5 & ... % <==> round(Xq)>=1 & round(Xq)<=LFSize(3) - Yq-Pos(4)>=0.5 & Yq-Pos(4)<=Size(4)+0.5; % & round(Yq)>=1 & round(Xq)<=LFSize(4) +% Expand any singleton dimension for interpolation (at least 3 for cubic) +szVal = size(Value); szVal(end+1:4)=1; + +singDims = double(szVal==1); +if any(singDims) + Value = repmat(Value,2*double(singDims)+1); + for it = find(singDims) + gv{it} = gv{it}-1:gv{it}+1; + end end -% Min and Max x and y coordinates of the pixels of the super-ray -minProjX = floor(min(projX(:))); maxProjX = ceil(max(projX(:))); -minProjY = floor(min(projY(:))); maxProjY = ceil(max(projY(:))); +dualDims = double(szVal==2); +if any(dualDims) + Value = repmat(Value,double(dualDims)+1); + for it = find(dualDims) + gv{it} = [gv{it},gv{it}(end)+1]; + end + +end -% Query grid vectors -projxgv = reshape(minProjX:maxProjX,1,1,[]); -projygv = reshape(minProjY:maxProjY,1,1,1,[]); -% New position -projPos = Pos; -projPos(3) = minProjX-1; -projPos(4) = minProjY-1; - -% New size -projSize = Size; -projSize(3) = numel(projxgv); -projSize(4) = numel(projygv); - -% Determine indices corresponding to original and warped pixels -ind = sub2ind_(Size,Pos,U(ValMask),V(ValMask),X(ValMask),Y(ValMask)); -roundInd = sub2ind_(projSize,projPos,U(ValMask),V(ValMask),roundProjX(ValMask),roundProjY(ValMask)); -roundMaskInd = sub2ind_(projSize,projPos,U(Mask),V(Mask),roundProjX(Mask),roundProjY(Mask)); - -% Performing warping -projValue = nan(projSize); -projMask = false(projSize); -projDispX = nan(projSize); -projDispY = nan(projSize); - -projValue(roundInd) = Value(ind); -projMask (roundMaskInd) = true; -projDispX(roundInd) = DispX(ind); -projDispY(roundInd) = DispY(ind); - -Value = projValue; -Mask = projMask; -DispX = -projDispX; -DispY = -projDispY; -Pos = projPos; -end +% Cast mask as double for interpolation +Mask = double(Mask); + +% Initialize interpolants +ValueInt = griddedInterpolant(gv,Value,interpMethod,'nearest'); +MaskInt = griddedInterpolant(gv,Mask ,'nearest' ,'nearest'); -function ind = sub2ind_(Size,Pos,varargin) -subs = varargin; -Pos = num2cell(Pos); +% Interpolation +Value = ValueInt(Uq,Vq,Xq,Yq); +Mask = MaskInt(Uq,Vq,Xq_Mask,Yq_Mask); Mask = logical(Mask); -subs = cellfun(@minus,subs,Pos,'UniformOutput',false); -subs = cellfun(@(x) x(:),subs,'UniformOutput',false); -ind = sub2ind(Size,subs{:}); -end +% Mask of query coordinates inside reference boundary +refMask = Xq_Mask>Pos_(3) & Xq_Mask<End_(3)+1 & Yq_Mask>Pos_(4) & Yq_Mask<End_(4)+1; -function varargout = ind2sub_(Size,Pos,ind) -subs = cell(1,numel(Size)); -[subs{:}] = ind2sub(Size,ind); -Pos = num2cell(Pos); +% New mask +Mask = Mask & refMask; + +% New position +Pos = Pos + cellfun(@(x) x(1),qgv)-1; -varargout = cellfun(@plus,subs,Pos,'UniformOutput',false); +% New disparities +dX = -dX; +dY = -dY; +end + +%% Auxiliary functions +% Compute bounding box of mask +function gv = subarrayIdx(Mask) +s = regionprops(Mask,'SubarrayIdx'); +if numel(s)==1 + gv = s.SubarrayIdx; +else + gv = {s.SubarrayIdx}; + [ugv,vgv,xgv,ygv] = cellfun(@(x)deal(x{:}),gv,'UniformOutput',false); + + for it = 1:numel(s)-1 + ugv = [union(ugv{1},ugv{2}),ugv(3:end)]; + vgv = [union(vgv{1},vgv{2}),vgv(3:end)]; + xgv = [union(xgv{1},xgv{2}),xgv(3:end)]; + ygv = [union(ygv{1},ygv{2}),ygv(3:end)]; + end + + ugv = min(ugv{:}):max(ugv{:}); + vgv = min(vgv{:}):max(vgv{:}); + xgv = min(xgv{:}):max(xgv{:}); + ygv = min(ygv{:}):max(ygv{:}); + gv = {ugv,vgv,xgv,ygv}; +end end +% Extend signal function Value = extend(Value,se,numIter) for i=1:numIter - Mask = ~isnan(Value); - Temp = Value; - Temp(~Mask)=-Inf; - Temp = imdilate(Temp,se); - Temp(isinf(Temp))=NaN; - Value(~Mask) = Temp(~Mask); + Mask = isnan(Value); + Temp = Value; + Temp(Mask)=-Inf; + Temp = imdilate(Temp,se); + Temp(isinf(Temp))=NaN; + Value(Mask) = Temp(Mask); end end \ No newline at end of file diff --git a/warpSet.m b/warpSet.m index 4f57cbe28e257809455e2263017fb3a67786ba61..d67a1221c73ce87d11520388a7c5de3cc3ca4f12 100644 --- a/warpSet.m +++ b/warpSet.m @@ -4,19 +4,17 @@ function SRSet = warpSet(SRSet,varargin) [Val,Lab,Pos,DispX,DispY] = SR.SetToFields(SRSet); -Mask = Lab; - for it = 1:numel(Lab) - Mask{it} = Lab{it}==it; + Lab{it} = Lab{it}==it; end for it = 1:numel(Val) - [Val{it},Mask{it},Pos{it},DispX{it},DispY{it}] = SR.warp(... - Val{it},Mask{it},Pos{it},DispX{it},DispY{it},varargin{:}); + [Val{it},Lab{it},Pos{it},DispX{it},DispY{it}] = SR.warp(... + Val{it},Lab{it},Pos{it},DispX{it},DispY{it},varargin{:}); end for it = 1:numel(Lab) - Lab{it} = it*double(Mask{it}); + Lab{it} = it*double(Lab{it}); end SRSet = SR.FieldsToSet(Val,Lab,Pos,DispX,DispY);