2
votes

I have written a function to detect objects in arrays. In this case I use it to clear white objects, which touch the border of an image. But the problem is, that there is always a StackOverFlowError in vcat and it occurs at different lines (depends on image). Here is a minimum working example. It works fine with the second image, but not with the first: Test.png Test2.png

using Images


function getImChars(I::Array)
  charAr=copy(I);
  indexMax=find(x->(x==1),charAr);
  (h,w)=size(charAr);
  # check border values
  (r,c)=ind2sub(size(charAr),indexMax);
  indexBorderR1=find(x->(x==1),r);
  indexBorderR2=find(x->(x==h),r);
  indexBorderR=[indexBorderR1;indexBorderR2];
  indexBorderC1=find(x->(x==1),c);
  indexBorderC2=find(x->(x==w),c);
  indexBorderC=[indexBorderC1;indexBorderC2];
  borderPixels=[999;999]; # initialize def value FIX
  for bRc=1:length(indexBorderR)
    borderPixels=[borderPixels [r[indexBorderR[bRc]];c[indexBorderR[bRc]]]];
  end
  for bCc=1:length(indexBorderC)
     borderPixels=[borderPixels [r[indexBorderC[bCc]];c[indexBorderC[bCc]]]];
  end
 borderPixels=borderPixels[:,2:end];
  (rbP,cbP)=size(borderPixels);
  fcharAr=[];
  for k=1:cbP
  bP=[borderPixels[:,k];false;false;false;false];
  locObj1=[];
  locObj2=[];
  locObj3=[];
  locObj4=[];
  locObj5=[];
  locObj6=[];
  if(charAr[bP[1],bP[2]]==0)
   continue;
  else
   charAr[bP[1],bP[2]]=0;
  end
  recGetCharClearBorders(true,false,h,w,bP,locObj1,locObj2,locObj3,locObj4,locObj5,locObj6,charAr);
  end
return charAr;
end



function recGetCharClearBorders(firstFlag::Bool,doNotAdd::Bool,h::Int,w::Int,hP::Array,locObj1::Array,locObj2::Array,locObj3::Array,locObj4::Array,locObj5::Array,locObj6::Array,imAr::Array)
  leftPoint=[hP[1];hP[2]-1;0;0;0;0];
  rightPoint=[hP[1];hP[2]+1;0;0;0;0];
  topPoint=[hP[1]-1;hP[2];0;0;0;0];
  bottomPoint=[hP[1]+1;hP[2];0;0;0;0];
 # check if it is not out of bounds and relative directions
  if(topPoint[1]!=0)
    if(imAr[topPoint[1],topPoint[2]]==1)
     hP[4]=1;
    end
  end
  if(bottomPoint[1]!=(h+1))
   if(imAr[bottomPoint[1],bottomPoint[2]]==1)
     hP[6]=1;
  end
  end
  if(leftPoint[2]!=0)
   if(imAr[leftPoint[1],leftPoint[2]]==1)
    hP[3]=1;
   end
  end
   if(rightPoint[2]!=(w+1))
  if(imAr[rightPoint[1],rightPoint[2]]==1)
      hP[5]=1;
  end
  end
  # add first elements
  if(firstFlag)
  locObj1=collect(hP[1]);
    locObj2=collect(hP[2]);
    locObj3=collect(hP[3]);
    locObj4=collect(hP[4]);
    locObj5=collect(hP[5]);
    locObj6=collect(hP[6]);
  firstFlag=false;
  else
    # if first element of locObj was deleted actual point should not get pushed to array
  if(!doNotAdd)
  push!(locObj1,hP[1]);
  push!(locObj2,hP[2]);
  push!(locObj3,hP[3]);
  push!(locObj4,hP[4]);
  push!(locObj5,hP[5]);
  push!(locObj6,hP[6]);
  imAr[hP[1],hP[2]]=0;
  end
  end

  goL=false;
  goT=false;
  goR=false;
  goB=false;
  doNotAdd=false;


  if(length(locObj1)!=0)
    # always take and check first elements of locObj
   hPfInLoc=[locObj1[1],locObj2[1],locObj3[1],locObj4[1],locObj5[1],locObj6[1]];
    hPl=[hPfInLoc[1];hPfInLoc[2]-1;0;0;0;0];
    hPt=[hPfInLoc[1]-1;hPfInLoc[2];0;0;0;0];
    hPr=[hPfInLoc[1];hPfInLoc[2]+1;0;0;0;0];
    hPb=[hPfInLoc[1]+1;hPfInLoc[2];0;0;0;0];

    compL=false;
    compT=false;
    compR=false;
    compB=false;
    # check bounds and if array values have changed
    if(hPt[1]!=0)
      if(imAr[hPt[1],hPt[2]]!=0)
        compT=true;
      end
  end
  if(hPb[1]!=(h+1))
    if(imAr[hPb[1],hPb[2]]!=0)
        compB=true;
      end
  end
  if(hPl[2]!=0)
    if(imAr[hPl[1],hPl[2]]!=0)
        compL=true;
      end
  end
   if(hPr[2]!=(w+1))
   if(imAr[hPr[1],hPr[2]]!=0)
        compR=true;
      end
  end
  # define directions and set defined direction false in locObj
  if((locObj3[1]==1)& compL)
      locObj3[1]=0;
      goL=true;
  elseif((locObj4[1]==1)& compT)
      locObj4[1]=0;
      goT=true;
  elseif((locObj5[1]==1)& compR)
      locObj5[1]=0;
      goR=true;
  elseif((locObj6[1]==1)& compB)
      locObj6[1]=0;
      goB=true;
  else

    if (length(locObj1)==1)
    locObj=[];
    else # if everything is zero delete first rows of arrays
    deleteat!(locObj1,1);
    deleteat!(locObj2,1);
    deleteat!(locObj3,1);
    deleteat!(locObj4,1);
    deleteat!(locObj5,1);
    deleteat!(locObj6,1);
    doNotAdd=true;
    return recGetCharClearBorders(firstFlag,doNotAdd,h,w,hP,locObj1,locObj2,locObj3,locObj4,locObj5,locObj6,imAr);
    end
    end
  end

  #execute choosen direction
  if(goL)
    return recGetCharClearBorders(firstFlag,doNotAdd,h,w,hPl,locObj1,locObj2,locObj3,locObj4,locObj5,locObj6,imAr);
  end
  if(goT)
    return recGetCharClearBorders(firstFlag,doNotAdd,h,w,hPt,locObj1,locObj2,locObj3,locObj4,locObj5,locObj6,imAr);
  end
  if(goR)
    return recGetCharClearBorders(firstFlag,doNotAdd,h,w,hPr,locObj1,locObj2,locObj3,locObj4,locObj5,locObj6,imAr);
  end
  if(goB)
    return recGetCharClearBorders(firstFlag,doNotAdd,h,w,hPb,locObj1,locObj2,locObj3,locObj4,locObj5,locObj6,imAr);
  end
end

# execute test procedure
Im=Images.load("Test.png");
 Im=data(Im);
imAr=map(Float64,Im);
resIm=getImChars(imAr);
save("Imout.png",resIm);

You only have to change the name of the image in my code. Just tell me, if you need some more information. Thanks a lot.

Cheers, clax

2
Recursion depth has limits in Julia. A rewrite with loops instead of recursion, and lists (or Vectors) instead of the local variables might be the best workaround. - Dan Getz

2 Answers

4
votes

Unlike many functional programming language, Julia doesn't optimize for tail recursion (reusing the stack frame when a function ends by a call). So in your case it is very likely that your recursive function hits the max stack depths depending on your OS's thread stack size.

Unfortunately, the Julia core team decide not to prioritise Tail call optimization as they don't deem such feature a must. Indeed people can often refactor their code into loops.

Afaik, tail call optimization is pretty difficult and many other programming languages featuring functional programming paradigm also lack it.

2
votes

Took a few minutes needed to run and fixup the tail recursion. Is the following replacement for recGetCharClearBorders performing correctly:

    function recGetCharClearBorders(firstFlag::Bool,doNotAdd::Bool,h::Int,w::Int,hP::Array,locObj1::Array,locObj2::Array,locObj3::Array,locObj4::Array,locObj5::Array,locObj6::Array,imAr::Array,recdepth,recloc)
        while true
            leftPoint=[hP[1];hP[2]-1;0;0;0;0];
            rightPoint=[hP[1];hP[2]+1;0;0;0;0];
            topPoint=[hP[1]-1;hP[2];0;0;0;0];
            bottomPoint=[hP[1]+1;hP[2];0;0;0;0];
            # check if it is not out of bounds and relative directions
            if(topPoint[1]!=0)
                if(imAr[topPoint[1],topPoint[2]]==1)
                    hP[4]=1;
                end
            end
            if(bottomPoint[1]!=(h+1))
                if(imAr[bottomPoint[1],bottomPoint[2]]==1)
                    hP[6]=1;
                end
            end
            if(leftPoint[2]!=0)
                if(imAr[leftPoint[1],leftPoint[2]]==1)
                    hP[3]=1;
                end
            end
            if(rightPoint[2]!=(w+1))
                if(imAr[rightPoint[1],rightPoint[2]]==1)
                    hP[5]=1;
                end
            end
            # add first elements
            if(firstFlag)
                locObj1=collect(hP[1]);
                locObj2=collect(hP[2]);
                locObj3=collect(hP[3]);
                locObj4=collect(hP[4]);
                locObj5=collect(hP[5]);
                locObj6=collect(hP[6]);
                firstFlag=false;
            else
                # if first element of locObj was deleted actual point should not get pushed to array
                if(!doNotAdd)
                    push!(locObj1,hP[1]);
                    push!(locObj2,hP[2]);
                    push!(locObj3,hP[3]);
                    push!(locObj4,hP[4]);
                    push!(locObj5,hP[5]);
                    push!(locObj6,hP[6]);
                    imAr[hP[1],hP[2]]=0;
                end
            end

        goL=false;
        goT=false;
        goR=false;
        goB=false;
        doNotAdd=false;

        if(length(locObj1)!=0)
            # always take and check first elements of locObj
            hPfInLoc=[locObj1[1],locObj2[1],locObj3[1],locObj4[1],locObj5[1],locObj6[1]];
            hPl=[hPfInLoc[1];hPfInLoc[2]-1;0;0;0;0];
            hPt=[hPfInLoc[1]-1;hPfInLoc[2];0;0;0;0];
            hPr=[hPfInLoc[1];hPfInLoc[2]+1;0;0;0;0];
            hPb=[hPfInLoc[1]+1;hPfInLoc[2];0;0;0;0];

            compL=false;
            compT=false;
            compR=false;
            compB=false;
            # check bounds and if array values have changed
            if(hPt[1]!=0)
                if(imAr[hPt[1],hPt[2]]!=0)
                    compT=true;
                end
            end
            if(hPb[1]!=(h+1))
                if(imAr[hPb[1],hPb[2]]!=0)
                    compB=true;
                end
            end
            if(hPl[2]!=0)
                if(imAr[hPl[1],hPl[2]]!=0)
                    compL=true;
                end
            end
            if(hPr[2]!=(w+1))
                if(imAr[hPr[1],hPr[2]]!=0)
                    compR=true;
                end
            end
            # define directions and set defined direction false in locObj
            if((locObj3[1]==1)& compL)
                locObj3[1]=0;
                goL=true;
            elseif((locObj4[1]==1)& compT)
                locObj4[1]=0;
                goT=true;
            elseif((locObj5[1]==1)& compR)
                locObj5[1]=0;
                goR=true;
            elseif((locObj6[1]==1)& compB)
                locObj6[1]=0;
                goB=true;
            else

                if (length(locObj1)==1)
                    locObj=[];
                else # if everything is zero delete first rows of arrays
                deleteat!(locObj1,1);
                deleteat!(locObj2,1);
                deleteat!(locObj3,1);
                deleteat!(locObj4,1);
                deleteat!(locObj5,1);
                deleteat!(locObj6,1);
                doNotAdd=true;
                continue
            end
        end
    end

    #execute choosen direction
    if(goL)
        hP = hPl
        continue
    end
    if(goT)
        hP = hPt
        continue
    end
    if(goR)
        hP = hPr
        continue
    end
    if(goB)
        hP = hPb
        continue
    end
    break
end
end

Somehow the output image turned on its side in my run.