Rectangular-Polar Transformation Sequel Thread

This is a sequel to this thread - Geometry Help - Rectangular Polar Transformation (From Cartesian or To Cartesian) [SOLVED OR NEARLY SOLVED]

Right now, I decided to rework on obtaining a faster version of rectangular-polar transformation, and after so many attempts, I failed to find a better versions that solves the drawbacks from the one in gmic-community/reptorian.gmic. Hence, the need to make a sequel thread to see if any one else can solve it. There are application such as perspective streak, and that is exactly why I invented this. The code can be found below:

Reptorian's New Rectangular-Polar Transformation Attempt
new_rep_recpoltrans:
skip ${1=0},${2=0},${3=0},${4=}
if $1<-1||$1>1 error "($1>=-1&&$1<=1)=0" fi
if $2<-1||$2>1 error "($2>=-1&&$2<=1)=0" fi

#$1 = X-Location of co-aligned vertices. The range is -1 to 1 where 0 is center of image. #
#$2 = Y-Location of co-aligned vertices. The range is -1 to 1 where 0 is center of image.#
#$3 = 0=From rectangular-polar to cartersian standard | 1=To rectangular-polar#
#$4 = Defines the orientation the before image was. This can only be used if $3==0#

repeat $! l[$>]
 ov=${-rep_2dcr}
 if $3
  maxlength={max(w,h)*1.5}
  perimeter={(w+h)*2}
  {$perimeter},{$maxlength},{d},{s},":begin(
    surface=expr('begin(const dpi=2*pi;);(x/w)*dpi;',w); #x-coordinates in rectangular-polar are defined as in 0-2*pi range#
    const dpi=pi*2;
    const ww=w#0-1; #Pixel coordinates limits are defined by 0-(width of first image minus 1)#
    const hh=h#0-1; #Pixel coordinates limits are defined by 0-(width of first image minus 1)#
    const ihh=h-1; #This is used to convert from cartesian to rectangular-polar y-axis to 0-1 range#
    const point_x=round((($1*-1)*.5+.5)*ww); #This takes into account of the fact that we're using pixel coordinates. Rounding is not needed in vector. Also used to find distance away.#
    const point_y=round(($2*.5+.5)*hh); #This takes into account of the fact that we're using pixel coordinates. Rounding is not needed in vector Also used to find distance away.#
    const inv_point_x=ww-point_x; #Find the distance away from the center#
    const inv_point_y=hh-point_y; #Find the distance away from the center#
    const cut_ang_s0=abs(atan2(inv_point_y,inv_point_x)); #Draw 4 triangles where the center contains co-aligned vertices. This defines the top triangle#
    const cut_ang_s1=pi-abs(atan2(inv_point_y,point_x)); #Draw 4 triangles where the center contains co-aligned vertices. This defines the right triangle#
    const cut_ang_s2=pi+abs(atan2(point_y,point_x)); #Draw 4 triangles where the center contains co-aligned vertices. This defines the bottom triangle#
    const cut_ang_s3=dpi-abs(atan2(point_y,inv_point_x)); #Draw 4 triangles where the center contains co-aligned vertices. This defines the left triangle#
    distanceaway=[ww-point_x,hh-point_y,point_x,point_y]; #Vector of distance away#
   );
   surface_angle=surface[x];
   surface_angle>cut_ang_s0&&surface_angle<=cut_ang_s1?side=1:
   surface_angle>cut_ang_s1&&surface_angle<=cut_ang_s2?side=2:
   surface_angle>cut_ang_s2&&surface_angle<=cut_ang_s3?side=3:
   side=0;
   mdist=abs(side%2?1/sin(surface_angle):1/cos(surface_angle));
   dix=(point_x+cos(surface_angle)*distanceaway[side]*mdist*y/ihh);
   diy=(point_y+sin(surface_angle)*distanceaway[side]*mdist*y/ihh);
   I(#0,round(ww-dix),round(diy),z);"
   
  k.
 else
 
  #The block below defines the dimension given orientation info#

  #Block Begin#
  $=val
  orientation=${val{$>+4}}
  perimeter={w}
  length_1={$perimeter-h*(4/3)}
  length_2={$perimeter-$length_1}
  if $orientation
   width={min($length_1,$length_2)}
   height={max($length_1,$length_2)}
  else
   width={max($length_1,$length_2)}
   height={min($length_1,$length_2)}
  fi
  
  #Block End#
  
  v + echo {w},{h} v -
  {$width},{$height},{d},{s},":begin(
   const ww=w#0-1;
   const hh=h#0-1;
   const nw=w-1;
   const nh=h-1;
   const sd=max(nw,nh)/min(nw,nh);
   const dpi=2*pi;
   const cx=.5+$1*.5;
   const cy=.5+$2*.5;
   const px=round(cx*nw);
   const py=round(cy*nh);
   const rpx=nw-px;
   const rpy=nh-py;
  );
  xpos=x-px;
  ypos=y-py;
  ax=1-(atan2(ypos,xpos)+pi)/dpi;
  ay_x=x>px?(rpx-(nw-x))/rpx:(px-x)/px;
  ay_y=y>py?(rpy-(nh-y))/rpy:(py-y)/py;
  ay=max(ay_x,ay_y);
  I(#0,ax*ww,ay*hh,z);
  "
  r2dx. 50%,6
  k.
 else error "$3|"$"3!=intnum[-3,2]"
 fi
endl done

For those who don’t know. Here’s how rectangular-polar coordinates looks like:

It’s hard to explain in words, but x-coordinate is defined by the position around the the rectangle, and the y-axis is defined by the distance from the center to the edge of rectangle. Well, something like that.

What does the mona lisa image looks like when you convert Cartesian coordinate to rectangular-polar coordinates?

Original:

$ sp monalisa

After converting coordinates:

$ sp monalisa rep_recpoltrans 0,0,1

Note: Please see above image to follow along.

The goal is to transform it back and have zero-loss of information. This is theoretically possible as you do have sufficient information. Achieving it is difficult which is why this thread exists.

With the new version which attempts to fix drawbacks (it didn’t and it’s even worse than the implementation in gmic-community/reptorian.gmic. Here’s the result.

Test result of new reptorian rectangular-polar:

$ sp monalisa +new_rep_recpoltrans 0,0,1 new_rep_recpoltrans. 0,0,0,1 +blend difference a x

If you look closely on the dark image, it’s clear that there’s information loss between the transformation. This thread should be about solving the information loss while still being useful for applying filters such as axis streak.

There is a max difference of ~171.5. That is not good at all. On the upside, it is 5 times faster than the implementation in gmic-community/reptorian.gmic. That’s about the only drawback solved.

I found a theory that may actually solve this thorny problem. I could split the triangles into separate images, and enable processing into multiple triangles image. That way, the loss of details may be more minimal. I will try this later though.

Also, in theory, this may introduce artifacts at edges of triangles. So, not completely solved.

You would have to remove the periodicity but then that would change the image even if you kept that info.

Yes, I would have to do that. That being said, I think that now with this code, it looks promising:

It’s even better than the old code overall (both in performance and output). However, I still have to solve the stupid microshifting problem near the edge.

New rep_recpoltrans
$  sp monalisa tic +new_rep_recpoltrans 0,0,1 toc new_rep_recpoltrans. 0,0,0,1 +blend difference a z
new_rep_recpoltrans:
skip ${1=0},${2=0},${3=0},${4=}
if $1<-1||$1>1 error "($1>=-1&&$1<=1)=0" fi
if $2<-1||$2>1 error "($2>=-1&&$2<=1)=0" fi

repeat $! l[$>]
 ov=${-rep_2dcr}
 if $3
  maxlength={max(w,h)*2}
  perimeter={(w+h)*4}
  {$perimeter},{$maxlength},{d},{s},":begin(
    surface=expr('begin(const dpi=2*pi;);(x/w)*dpi;',w);
    const dpi=pi*2;
    const ww=w#0-1;
    const hh=h#0-1;
    const ihh=h-1;
    const point_x=(($1*-1)*.5+.5)*ww;
    const point_y=($2*.5+.5)*hh;
    const inv_point_x=ww-point_x;
    const inv_point_y=hh-point_y;
    const cut_ang_s0=abs(atan2(inv_point_y,inv_point_x));
    const cut_ang_s1=pi-abs(atan2(inv_point_y,point_x));
    const cut_ang_s2=pi+abs(atan2(point_y,point_x));
    const cut_ang_s3=dpi-abs(atan2(point_y,inv_point_x));
    distanceaway=[ww-point_x,hh-point_y,point_x,point_y];
   );
   surface_angle=surface[x];
   surface_angle>cut_ang_s0&&surface_angle<=cut_ang_s1?side=1:
   surface_angle>cut_ang_s1&&surface_angle<=cut_ang_s2?side=2:
   surface_angle>cut_ang_s2&&surface_angle<=cut_ang_s3?side=3:
   side=0;
   mdist=abs(side%2?1/sin(surface_angle):1/cos(surface_angle));
   dix=(point_x+cos(surface_angle)*distanceaway[side]*mdist*y/ihh);
   diy=(point_y+sin(surface_angle)*distanceaway[side]*mdist*y/ihh);
   I(#0,round(ww-dix),round(diy),z);"
   
  k.
 else
 
  $=val
  orientation=${val{$>+4}}
  perimeter={w}
  length_1={$perimeter-h*2}
  length_2={$perimeter-$length_1}
  if $orientation
   width={min($length_1,$length_2)}
   height={max($length_1,$length_2)}
  else
   width={max($length_1,$length_2)}
   height={min($length_1,$length_2)}
  fi

  {$width/4},{$height/4},{d},{s},":begin(
   const ww=w#0-1;
   const hh=h#0-1;
   const nw=w-1;
   const nh=h-1;
   const sd=max(nw,nh)/min(nw,nh);
   const dpi=2*pi;
   const cx=.5+$1*.5;
   const cy=.5+$2*.5;
   const px=cx*nw;
   const py=cy*nh;
   const rpx=nw-px;
   const rpy=nh-py;
  );
  xpos=x-px;
  ypos=y-py;
  ax=1-(atan2(ypos,xpos)+pi)/dpi;
  ay_x=x>px?(rpx-(nw-x))/rpx:(px-x)/px;
  ay_y=y>py?(rpy-(nh-y))/rpy:(py-y)/py;
  ay=max(ay_x,ay_y);
  I(#0,ax*ww,ay*hh,z,2);
  "
  k.
 else error "$3|"$"3!=intnum[-3,2]"
 fi
endl done

EDIT: Using this gives me way better result near the edge. It’s a naive solution. Overall with these changes I made, I think it can be pushed as it’s way faster and solves issues better than the old code.

I(#0,ax*ww+(ax*ay),ay*hh,z,2,1);

Ok, with that code with the edit and a test. It is actually lossless in pixel position or nearly 100% lossless. The values on the other hand is not lossless. Any theoretical way of getting around that?

In a paper I had read, hermite interpolation was used for lossless polar transformation. I might use quadric approach.

A different problem but you may glean something from this paper.

Yes, it is a different problem and I see that there’s a unique solution associated with 360 video streaming pipeline.

I am about to release a new version of rectilinear transformation. I just need to finish up with a new axis streak to update the perspective streak.

Ok, I think I have this, and moving on to creating axis_streak specifically for perspective streak.

Though, still not lossless, it’s the closest I can do (Oh well though):

Reptorian Rectangular-Polar Transformation
$1 = x-coordinates
$2 = y-coordinates
$3 = ( 0=convert back to cartersian coordinates | 1=forward to rectangular-polar coordinates )
$4 = orientation. Only applicable for $3==0. If you converted a vertical image into rec-pol coordinates, use 1 to convert it back.


new_rep_recpoltrans:
skip ${1=0},${2=0},${3=1},${4=}

xpos={cut($1,-1,1)}
ypos={cut($2,-1,1)*-1}
corner_mode={abs($xpos)==1&&abs($ypos)==1}
corner_equality={$xpos==$ypos}

repeat $! l[$>]
 if $3>0
 
  if $corner_mode
  
   maxlength={max(w,h)*2}
   perimeter={(w+h)*2}
   
   if $xpos==1 x_coord=round(ww-dix)
   else x_coord=round(dix)
   fi
   
   if $ypos==1 y_coord=round(hh-diy)
   else y_coord=round(diy)
   fi
   
   {$perimeter},{$maxlength},{d},{s},":begin(
     if("$corner_equality"
      ,surface=expr('begin(const hpi=acos(0););hpi-x/w*hpi;',w);
      ,surface=expr('begin(const hpi=acos(0););(x/w)*hpi;',w);
     );
     const hpi=acos(0);
     const ww=w#0-1;
     const hh=h#0-1;
     const ihh=h-1;
     const cut_ang=atan2(hh,ww);
     distanceaway=[ww,hh];
    );
    surface_angle=surface[x];
    surface_angle<cut_ang?side=0:side=1;
    mdist=side%2?abs(1/sin(surface_angle)):abs(1/cos(surface_angle));
    dix=cos(surface_angle)*distanceaway[side]*mdist*y/ihh;
    diy=sin(surface_angle)*distanceaway[side]*mdist*y/ihh;
    I(#0,"$x_coord","$y_coord",z);"
    
  else
   
   maxlength={max(w,h)*2}
   perimeter={(w+h)*4}
   
   {$perimeter},{$maxlength},{d},{s},":begin(
     surface=expr('begin(const dpi=2*pi;);(x/w)*dpi;',w);
     const dpi=pi*2;
     const ww=w#0-1;
     const hh=h#0-1;
     const ihh=h-1;
     const point_x=(("$xpos"*-1)*.5+.5)*ww;
     const point_y=("$ypos"*.5+.5)*hh;
     const inv_point_x=ww-point_x;
     const inv_point_y=hh-point_y;
     const cut_ang_s0=abs(atan2(inv_point_y,inv_point_x));
     const cut_ang_s1=pi-abs(atan2(inv_point_y,point_x));
     const cut_ang_s2=pi+abs(atan2(point_y,point_x));
     const cut_ang_s3=dpi-abs(atan2(point_y,inv_point_x));
     distanceaway=[ww-point_x,hh-point_y,point_x,point_y];
    );
    surface_angle=surface[x];
    surface_angle>cut_ang_s0&&surface_angle<=cut_ang_s1?side=1:
    surface_angle>cut_ang_s1&&surface_angle<=cut_ang_s2?side=2:
    surface_angle>cut_ang_s2&&surface_angle<=cut_ang_s3?side=3:
    side=0;
    mdist=abs(side%2?1/sin(surface_angle):1/cos(surface_angle));
    dix=point_x+cos(surface_angle)*distanceaway[side]*mdist*y/ihh;
    diy=point_y+sin(surface_angle)*distanceaway[side]*mdist*y/ihh;
    I(#0,round(ww-dix),round(diy),z);"  
   
  fi

 else
 
  $=val
  orientation=${val{$>+4}}
  perimeter={w}
  
  if $corner_mode
   length_1={$perimeter-h}
   length_2={$perimeter-$length_1}
  else
   length_1={$perimeter-h*2}
   length_2={$perimeter-$length_1}
  fi
  
  if $orientation
   width={min($length_1,$length_2)}
   height={max($length_1,$length_2)}
  else
   width={max($length_1,$length_2)}
   height={min($length_1,$length_2)}
  fi

  if $corner_mode
  
   if $xpos==1 surface_x=x-nw
   else surface_x=x
   fi
   if $ypos==1 surface_y=y-nh
   else surface_y=y
   fi
  
   if [$xpos,$ypos]==[-1,-1] calc_ax=(atan2(-ypos,xpos)+hpi)/hpi
   elif [$xpos,$ypos]==[1,1] calc_ax=y!=nh?atan2(-ypos,xpos)/pi:1
   elif [$xpos,$ypos]==[-1,1] calc_ax=atan2(-ypos,xpos)/hpi
   elif [$xpos,$ypos]==[1,-1] calc_ax=ypos?(atan2(-ypos,xpos)+pi)/hpi
   else error invalid_inp("$"1,"$"2) fi
   
   {$width/2},{$height/2},{d},{s},":begin(
     const ww=w#0-1;
     const ww1=ww+1;
     const hh=h#0-1;
     const nw=w-1;
     const nh=h-1;
     const hpi=acos(0);
    );
    xpos="$surface_x";
    ypos="$surface_y";
    ay=max(abs(xpos/nw),abs(ypos/nh));
    ax="$calc_ax";
    outx=ax*ww+(ay*ax);
    outy=ay*hh+((1-ay)*ax);
    int_outx=int(outx);
    int_outy=int(outy);
    min_v=min(
     i(#0,int_outx-1,int_outy-1,z,c,0,1),
     i(#0,int_outx,int_outy-1,z,c,0,1),
     i(#0,int_outx+1,int_outy-1,z,c,0,1),   
     i(#0,int_outx-1,int_outy,z,c,0,1),
     i(#0,int_outx,int_outy,z,c,0,1),
     i(#0,int_outx+1,int_outy,z,c,0,1),
     i(#0,int_outx-1,int_outy+1,z,c,0,1),
     i(#0,int_outx,int_outy+1,z,c,0,1),
     i(#0,int_outx+1,int_outy+1,z,c,0,1)
    );
    max_v=max(
     i(#0,int_outx-1,int_outy-1,z,c,0,1),
     i(#0,int_outx,int_outy-1,z,c,0,1),
     i(#0,int_outx+1,int_outy-1,z,c,0,1),
     i(#0,int_outx-1,int_outy,z,c,0,1),
     i(#0,int_outx,int_outy,z,c,0,1),
     i(#0,int_outx+1,int_outy,z,c,0,1),
     i(#0,int_outx-1,int_outy+1,z,c,0,1),
     i(#0,int_outx,int_outy+1,z,c,0,1),
     i(#0,int_outx+1,int_outy+1,z,c,0,1)
    );
    avg(
     cut(i(#0,outx,outy,z,c,2,1),min_v,max_v)
     ,i(#0,round(outx),round(outy),z,c,0,1)
    );
    "
  else
   {$width/4},{$height/4},{d},{s},":begin(
     const ww=w#0-1;
     const hh=h#0-1;
     const nw=w-1;
     const nh=h-1;
     const dpi=2*pi;
     const cx=.5+"$xpos"*.5;
     const cy=.5+"$ypos"*.5;
     const px=cx*nw;
     const py=cy*nh;
     const rpx=nw-px;
     const rpy=nh-py;
    );
    xpos=x-px;
    ypos=y-py;
    ax=1-(atan2(ypos,xpos)+pi)/dpi;
    ay_x=x>px?(rpx-(nw-x))/rpx:(px-x)/px;
    ay_y=y>py?(rpy-(nh-y))/rpy:(py-y)/py;
    ay=max(ay_x,ay_y);
    outx=ax*ww+(ax*ay);
    outy=ay*hh;
    (I(#0,outx,outy,z,1,1)+I(#0,round(outx),round(outy),z,0,1))/2;
    "
   fi
 fi
 k.
endl done
2 Likes

Looks like a huge amount of effort into that one! I’m glad you’re back doing some g’mic stuff :sunglasses:
That code is much more readable than your early days too, it’s easy to get lazy with it (and I still do so myself sometimes).

This makes me think that it could be nice to write a specific thread to discuss about good practices for writing G’MIC commands, and possible future language evolutions. There is this old thread about it, but the language has evolved quite bit since.
Having information on how filter developers perceive the language would be very interesting.

Personally I must say I’m quite happy with the language right now, it covers a lot of things I want to do in my everyday work, and when I write new commands, it is actually rare I’m telling myself : “it would be nice to have such a feature in the language”. Plus, it allows to do really quick prototyping of image processing algorithms, which is cool.
But this is my personal impression.

I know that some people around find the language extremely complicated to grasp :slight_smile:

I think so too - it would be a pity to derail this one, but that’s definitely also a worthwhile topic. I’ll resist writing more in here :wink:

1 Like

I edited the released code, use that to test. I think the lossy problem is in theory solveable. The problem mostly resides within the diagonals. Observed in both corner case, and none-corner case. Now, I found that the problem is even worse with wall cases i.e only one coordinate is 1 or -1, but with the caveat that every other pixels are solved outside of a line area as in there’s only 0 offset literally.

$ 512,512,1,2,[x,y] +new_rep_recpoltrans 0,0,1 new_rep_recpoltrans. 0,0,0,1 f.. abs(i-i#-1) k..

image

Problem is magnitude worse within corner case:

image