Message Boards Message Boards

[GIF] A to Z (Image morph)

Image morph

A to Z

I've mentioned before that there's a construction which gives a correspondence between points on the Grassmann manifold $G_2(\mathbb{R}^n)$ of 2-dimensional linear subspaces of $\mathbb{R}^n$ and planar $n$-gons; details at 1, 2, 3, 4, 5, 6.

In particular, this gives a way of morphing between any two (discrete) shapes you like; this animation shows the shortest path from A to Z.

Here's the code; notice that I use the smootheststep function:

ProjectionBasis[{A_, B_}, {C_, D_}] := 
  Normalize[#] & /@ 
   Eigenvectors[
    Transpose[Transpose[{A, B}].{A, B}.Transpose[{C, D}].{C, D}], 2];

PlaneGeo[{A_, B_}, {C_, D_}, t_] := 
  Module[{a, b, c, d, cPerp, dPerp, dist1, dist2},
   {a, b} = ProjectionBasis[{C, D}, {A, B}];
   {c, d} = ProjectionBasis[{A, B}, {C, D}];
   {cPerp, dPerp} = {Normalize[c - (c.a)*a], Normalize[d - (d.b)*b]};
   dist1 = ArcCos[a.c];
   dist2 = ArcCos[b.d];
   {Cos[t*dist1]*a + Sin[t*dist1]*cPerp, 
    Cos[t*dist2]*b + Sin[t*dist2]*dPerp}
   ];

MakePoly[data_] := 
  Normalize[#] & /@ 
   Transpose[{Re[#], Im[#]} & /@ 
     Sqrt[Table[#[[n]][[1]] + I*#[[n]][[2]], {n, 1, Length[#]}] &[
       RotateLeft[#] - # &[data]]]];

RawAData = {{-3., .9}, {1.07, 8.18}, {1.85, 8.2}, {5.96, 0.86}, {4.89,
     0.86}, {1.77, 6.6}, {1.18, 6.6}, {.12, 4.58}, {2.87, 4.6}, {3.35,
     3.76}, {-.18, 3.76}, {-1.72, .94}};

RawZData = {{0.82, 7.26}, {7.61, 7.32}, {7.65, 
    6.04}, {1.92, -.06}, {7.76, -.02}, {7.8, -1.16}, {.71, -1.12}, \
{.71, .02}, {6.36, 6.12}, {.85, 6.12}, {.82, 6.12}, {.82, 7.26}};

ToPol[frame_] := 
  Accumulate[ReIm[(Complex @@ #)^2] & /@ Transpose[frame]];

smootheststep[t_] := -20 t^7 + 70 t^6 - 84 t^5 + 35 t^4;

DynamicModule[{cols = RGBColor /@ {"#f77e5e", "#3dbd5d", "#303030"}, 
  centeredpoints, s},
 Manipulate[
  s = smootheststep[t];
  centeredpoints = # - ConstantArray[Mean[#], Length[#]] &[
    RotationTransform[(26 - 44 s) Degree][
     Prepend[ToPol[
       PlaneGeo[MakePoly[RawAData], MakePoly[RawZData], s]], {0, 0}]]];
  Graphics[{Thickness[.008], JoinForm["Round"], 
    Blend[cols[[;; 2]], s], Line[centeredpoints]}, 
   ImageSize -> {540, 540}, 
   PlotRange -> {{-.26 + .02 s, .29 + .02 s}, {-.28 - .065 s, .32 - .065 s}}, Background -> cols[[-1]]], {t, 0, 1}]
 ]
13 Replies

Agree! I'm not even sure I grasp a tiny bit of it to be honest! But it is very interesting and potentially useful!

POSTED BY: Sander Huisman

Yes, there often is some interesting math hiding behind my animations, and you're right that I should probably do a better job pointing it out when it's there. Often when I finish an animation (which can sometimes take a long time), the last thing I want to do is spend another hour explaining the ideas behind it, but I will try to be more conscientious about that.

Yep this works perfectly. Thanks.

POSTED BY: Sam Carrettie
POSTED BY: Marina Shchitova

By the way, I really like your image. Here's my version:

Module[{cols = RGBColor /@ {"#f77e5e", "#3dbd5d", "#303030"}, 
   centeredpoints, s}, tmp = Table[s = smootheststep[t];
    centeredpoints = # + ConstantArray[{.6 s, .36 s}, Length[#]] &[
      RotationTransform[(26 - 44 s) Degree][
       Prepend[ToPol[
         PlaneGeo[MakePoly[RawAData], MakePoly[RawZData], s]], {0, 
         0}]]];
    Graphics[{Thickness[.008], Opacity[.5], JoinForm["Round"], 
      Blend[cols[[;; 2]], s], Line[centeredpoints]}, 
     Background -> cols[[-1]]], {t, 0, 1, .005}]];

Show[tmp, PlotRange -> {{-.05, .95}, {-.2, .55}}, 
 ImageSize -> {1000, 700}]

A to Z

Ah, okay, I'm on a different version, so my guess is that this is some internal change in how eigenvectors are computed. How about this (which completely obscures what's going on, but eliminates the eigenvector computations)?

ToPol[frame_] := 
  Accumulate[ReIm[(Complex @@ #)^2] & /@ Transpose[frame]];

smootheststep[t_] := -20 t^7 + 70 t^6 - 84 t^5 + 35 t^4;

DynamicModule[{cols = RGBColor /@ {"#f77e5e", "#3dbd5d", "#303030"}, 
  centeredpoints, s, AZPath},
 AZPath[s_] := {{-0.6183 Cos[0.6237 s] - 
     0.05281 Sin[0.6237 s], -0.1936 Cos[0.6237 s] - 
     0.05875 Sin[0.6237 s], -0.4726 Cos[0.6237 s] + 
     0.1089 Sin[0.6237 s], -0.05233 Cos[0.6237 s] - 
     0.7733 Sin[0.6237 s], -0.3966 Cos[0.6237 s] + 
     0.2414 Sin[0.6237 s], -0.03886 Cos[0.6237 s] + 
     0.1973 Sin[0.6237 s], -0.1054 Cos[0.6237 s] - 
     0.07660 Sin[0.6237 s], -0.3627 Cos[0.6237 s] - 
     0.3582 Sin[0.6237 s], -0.1609 Cos[0.6237 s] + 
     0.3523 Sin[0.6237 s], -0.09504 Cos[0.6237 s] + 
     0.1416 Sin[0.6237 s], -0.1219 Cos[0.6237 s] - 
     0.05366 Sin[0.6237 s], 
    0.05338 Cos[0.6237 s] - 
     0.07418 Sin[0.6237 s]}, {-0.1933 Cos[1.476 s] - 
     0.06772 Sin[1.476 s], 0.04221 Cos[1.476 s] + 0.1317 Sin[1.476 s],
     0.4471 Cos[1.476 s] + 
     0.4660 Sin[1.476 s], -0.2261 Cos[1.476 s] - 
     0.05784 Sin[1.476 s], -0.4140 Cos[1.476 s] + 
     0.1673 Sin[1.476 s], -0.1679 Cos[1.476 s] - 0.5314 Sin[1.476 s], 
    0.3220 Cos[1.476 s] - 0.2102 Sin[1.476 s], 
    0.08257 Cos[1.476 s] - 0.3301 Sin[1.476 s], 
    0.1509 Cos[1.476 s] - 0.4968 Sin[1.476 s], -0.4106 Cos[1.476 s] + 
     0.003600 Sin[1.476 s], 0.3832 Cos[1.476 s] - 0.2160 Sin[1.476 s],
     0.2482 Cos[1.476 s] - 0.02369 Sin[1.476 s]}};
 Manipulate[s = smootheststep[t];
  centeredpoints = # - ConstantArray[Mean[#], Length[#]] &[
    RotationTransform[(26 - 44 s) Degree][
     Prepend[ToPol[AZPath[s]], {0, 0}]]];
  Graphics[{Thickness[.008], JoinForm["Round"], 
    Blend[cols[[;; 2]], s], Line[centeredpoints]}, 
   ImageSize -> {540, 540}, 
   PlotRange -> {{-.26 + .02 s, .29 + .02 s}, {-.28 - .065 s, .32 - .065 s}}, Background -> cols[[-1]]], {t, 0, 1}]]

Sorry, the Markdown parser let me down; I've edited to put in the links to more detailed descriptions of the connection between polygons and Grassmannians.

In any case, the upshot is that one can view $n$-gons as points in the Grassmannian $G_2(\mathbb{R}^n)$, so two different polygons correspond to two different points. The special orthogonal group $SO(n)$ naturally acts on the Grassmannian, and the Grassmannian has a unique (up to scale) left-invariant Riemannian metric. When I say "shortest path", I really mean the shortest path between the two points in the Grassmannian with respect to this Riemannian metric.

Now, what this means at the level of polygons is a little unclear. In the usual way, we can think of geodesics in the Grassmannian as critical points of some energy functional. We can interpret the energy as a functional on the space of polygons, and the path I showed is a critical point of this functional. Unfortunately, I do not (yet) know of a nice polygonal interpretation of this functional: I would absolutely love to prove that it's something like the area the boundary traces (though it presumably can't actually be that because the Grassmannian interpretation of polygons is orientation-independent, whereas the area surely depends on how you register the images to begin). If you have any ideas of how to interpret it, I would certainly be very interested.

This is a complete mystery. Below the Manipulates for both codes you provided on

$Version
"11.0.1 for Mac OS X x86 (64-bit) (September 21, 2016)"

1st

enter image description here

2nd

enter image description here

POSTED BY: Sam Carrettie

The Manipulate gives a straight A on my computer, so I'm not sure what the problem is. Maybe this will work?

ProjectionBasis[{A_, B_}, {C_, D_}] := 
  Normalize[#] & /@ 
   Eigenvectors[
    Transpose[Transpose[{A, B}].{A, B}.Transpose[{C, D}].{C, D}], 2];

PlaneGeo[{A_, B_}, {C_, D_}, t_] := 
  Module[{a, b, c, d, cPerp, dPerp, dist1, dist2}, {a, b} = 
    ProjectionBasis[{C, D}, {A, B}];
   {c, d} = ProjectionBasis[{A, B}, {C, D}];
   {cPerp, dPerp} = {Normalize[c - (c.a)*a], Normalize[d - (d.b)*b]};
   dist1 = ArcCos[a.c];
   dist2 = ArcCos[b.d];
   {Cos[t*dist1]*a + Sin[t*dist1]*cPerp, 
    Cos[t*dist2]*b + Sin[t*dist2]*dPerp}];

AFrame = {{0.5587691672676915`, 0.1981237081903733`, 
    0.561282439075137`, 0.`, 0.29303258430728735`, 0.`, 
    0.17528178862450003`, 0.37198264830477284`, 0.1908285679285995`, 
    0.`, 0.205163066218776`, 
    0.00396483829652537`}, {0.3277750387491631`, 
    0.00253963018178132`, -0.3289966965729807`, 0.23203064786359323`, 
    0.49280250767258005`, 0.17229769584575513`, -0.28992889821878054`,
     0.0013526462896582667`, -0.11074211838066708`, 
    0.421444913379544`, -0.34580148914295383`, -0.25381158645873014`}};

ZFrame = {{0.5395712230915538`, 0.16826135725211128`, 
    0.2378635055662534`, 0.500401524060885`, 0.15909799999674124`, 
    0.0015553022620756472`, 0.1563317110647911`, 0.547153066305452`, 
    0.`, 0.`, 0.15633171106479107`, 
    0.`}, {0.0023839205766322895`, -0.16308532865529982`, -0.5497830309003788`, 0.0017136837511977539`, -0.15361352050274618`, 
    0.5513590391833545`, 0.1563317110647911`, 0.23900682840695045`, 
    0.4860550423730885`, 0.035864954221169684`, 0.15633171106479107`, 
    0.`}};

ToPol[frame_] := 
  Accumulate[ReIm[(Complex @@ #)^2] & /@ Transpose[frame]];

smootheststep[t_] := -20 t^7 + 70 t^6 - 84 t^5 + 35 t^4;

DynamicModule[{cols = RGBColor /@ {"#f77e5e", "#3dbd5d", "#303030"}, 
  centeredpoints, s}, Manipulate[s = smootheststep[t];
  centeredpoints = # - ConstantArray[Mean[#], Length[#]] &[
    RotationTransform[(26 - 44 s) Degree][
     Prepend[ToPol[PlaneGeo[AFrame, ZFrame, s]], {0, 0}]]];
  Graphics[{Thickness[.008], JoinForm["Round"], 
    Blend[cols[[;; 2]], s], Line[centeredpoints]}, 
   ImageSize -> {540, 540}, 
   PlotRange -> {{-.26 + .02 s, .29 + .02 s}, {-.28 - .065 s, .32 - .065 s}}, Background -> cols[[-1]]], {t, 0, 1}]]

Neat image!

POSTED BY: Sander Huisman

Very cool. But your GIF has letter "A" straight. And you Manipulate does not. Do you have the code that makes Z - > A with both letters straight, as on GIF?

POSTED BY: Sam Carrettie

This is interesting:

this animation shows the shortest path from A to Z

But "shortest" in what sense, what is the measure? Perhaps the area that boundary traces? This is an attempt to visualize the "integral change". If we modify just the part of code starting from DynamicModule, we get (note .SVG gives nice vector look):

Module[{cols = RGBColor /@ {"#f77e5e", "#3dbd5d", "#303030"}, centeredpoints, s},
  tmp = Table[s = smootheststep[t];
    centeredpoints = # - ConstantArray[Mean[#], Length[#]] &[
      RotationTransform[(26 - 44 s) Degree][Prepend[ToPol[
         PlaneGeo[MakePoly[RawAData], MakePoly[RawZData], s]], {0, 0}]]];
    Graphics[{Thickness[.008], Opacity[.5], JoinForm["Round"], 
      Blend[cols[[;; 2]], s], Line[centeredpoints]}, 
     ImageSize -> {540, 540}, 
     PlotRange -> {{-.26 + .02 s, .29 + .02 s}, {-.28 - .065 s, .32 - .065 s}}, 
Background -> cols[[-1]]], {t, 0, 1, .005}]];

Show[tmp, PlotRange -> All]

enter image description here

POSTED BY: Marina Shchitova

enter image description here - another post of yours has been selected for the Staff Picks group, congratulations! We are happy to see you at the top of the "Featured Contributor" board. Thank you for your wonderful contributions, and please keep them coming!

POSTED BY: Moderation Team
Reply to this discussion
Community posts can be styled and formatted using the Markdown syntax.
Reply Preview
Attachments
Remove
or Discard

Group Abstract Group Abstract