As I mentioned at the end of the last post, I wanted to spend some more time messing around with rotational transformations. I would like to look at various numbers of rotations per image. I would also like to look at what different base rotation points would do for the final image. Not sure what numbers I will use; expect some experimentation will sort that out. But first—

Simplify!

A slight aside. As I said in the last post, all that bumbling about and commenting out of various code attempts left a huge amount of junk in the file. Around 750 lines of useful code, useless code, useful comments and commented out code in the section dealing with rotations. Once I tidied everything up, I was left with 90 lines of code, give or take a line or two.

A lot of that commented out code could have been prevented if I was better about committing changes to my Git repository. But, I am conceptually relatively new to that kind of work behaviour. So, I comment stuff out to sort of keep track of what I am doing. Though unless I remember what I was doing, not much help in the end. So, guess I need to learn to make much smaller commits — as I have before suggested is the right thing to do.

Different Numbers of Rotations

For any given number of rotations, I will need to determine the rotational angle for each rotation. So, I figured I best write my self a function, even though I would only do the calculation once for each image generation iteration. It always seems tidier to use functions. I also need to determine the initial angle to use in generating the list. I decided I wanted that angle to always be less than or equal to 45°.

      def get_angles(nbr_rots):
        tr_ang = 360 // nbr_rots
        st_ang = tr_ang
        while st_ang > 45:
          st_ang = st_ang // 2
        all_ang = [st_ang + (tr_ang * i) for i in range(nbr_rots)]
        return all_ang

Now, before generating that list of angles, I also need to sort out how many rotations are going to be generated. I did think about picking a random number, but then I decided to use the command line interface to allow me to specify the number of rotations I want. I added two variables. One to determine whether or not to use a user specified number of rotations or go with the default of 4. And, the second to hold the user specified number. Then I check on all that before generating my list of angles.

      # sort number of quadrants
      do_qd = range(nbr_qds)
      if tr_qd and tq_r:
        do_qd = range(tq_r)
        nbr_qds = max(list(do_qd)) + 1

      lld_rot = get_angles(nbr_qds)

I also changed from using Affine2D().rotate() to Affine2D().rotate_deg() so I could save myself the minor inconvenience of converting the list of angles in degrees to one in radians. Seemed to make sense to me to use the function if available. For now I am setting the rotation point to (-(half the positive width), (half the positive height)).

      dx, dy = pxx26 / 2, pyx26 / 2
      tst_x = [-dx, -dx, dx, dx]
      tst_y = [dy, -dy, -dy, dy]

... ... ...

        for tq in do_qd:
          xc = tst_x[0]
          yc = tst_y[0]
          shadow_transform = transforms.Affine2D().translate(-xc, -yc).rotate_deg(lld_rot[tq]).translate(xc, yc) + ax.transData

... ... ...

Now hopefully I can find some nice examples.

Examples with Varying Numbers of Rotations

I used to slightly different base images for the two groups.

This group used a curve generated by 5 tetracuspids.

Base underlying image
gnarly spirograph images modified with varying numbers of rotational translations
The default 4 rotational transformations
gnarly spirograph images modified with varying numbers of rotational translations
3 rotational transformations
gnarly spirograph images modified with varying numbers of rotational translations

Decided to try a different underlying curve. This time, 5 ellipses.

Base underlying image
gnarly spirograph images modified with varying numbers of rotational translations
5 rotational transforms
gnarly spirograph images modified with varying numbers of rotational translations
15 rotational transforms
gnarly spirograph images modified with varying numbers of rotational translations
7 rotational transforms
gnarly spirograph images modified with varying numbers of rotational translations

That’s enough fooling around. Next experiment, different rotation points.

Different Rotation Points

Wasn’t too sure how to go about this. But I decided to determine the largest number (width or height). Randomly select one of 5 values between 37.5 and 87.5% of that value as the y-value. Then rotate the point (0, y) by the first angle in the list of angles for the current number of rotations. The result will be the rotation point used to calculate the transforms.

During developement I will start with the default of 4 rotations and see how things go/look. If happy, then I will see how it goes with differing numbers of rotations. And, I will once again add a new function to get the rotated point.

Seen the formula for this guy a few times now.

      def rotate_pt(px, py, angle=45, cx=0, cy=0):
        r_ang = math.radians(angle)
        a_cos = math.cos(r_ang)
        a_sin = math.sin(r_ang)
        xp_c = px - cx
        yp_c = py - cy
        x_rt = (xp_c * a_cos) - (yp_c * a_sin) + cx
        y_rt = (xp_c * a_sin) + (yp_c * a_cos) + cy
        return x_rt, y_rt

Then we select a y-value and generate the rotation point. I have continued to use some obsolete code because I didn’t feel like reworking everything just yet. I did look at 100%, but that seemed to produce too much white space in the centre of the image for my liking.

      mdy = max(pxx26, pyx26)
      if not t_sv:
        y_mlt = random.choice([.875, .75, .625, .5, .375])
      trdy = mdy * y_mlt
      dx, dy = rotate_pt(0, trdy, angle=lld_rot[0], cx=0, cy=0)
      # that bit of obsolete code, don't currently use the 4 points
      tst_x = [dx, dx, -dx, -dx]
      tst_y = [dy, -dy, -dy, dy]

The rest of the code is unchanged.

Some Examples

I have used the same underlying curve for all these examples. Though perhaps not the best choice — was getting lazy. It uses 7 circles. Sorry, didn’t save a view of the base image. The examples are in no particular order.

5 transformations, random multiplier: 0.5
gnarly spirograph images modified with varying numbers of rotational translations and partially random rotation point

Got the same colour scheme? Can just see the centre opening up a little.

5 transformations, random multiplier: 0.75
gnarly spirograph images modified with varying numbers of rotational translations and partially random rotation point

More transformations. And you can see the centre opening up even more.

10 transformations, random multiplier: 0.875
gnarly spirograph images modified with varying numbers of rotational translations and partially random rotation point
10 transformations, random multiplier: 0.625
gnarly spirograph images modified with varying numbers of rotational translations and partially random rotation point

Done m’thinks

Not sure that the somewhat random rotation point does much to the overall look. But, changing the number of rotational transforms certainly seems to do so.

For now, I think that’s enough talk about random rotations. And the trials and tribulations of coding things you don’t entirely understand.

Next time we will finally get to that other variation.

Until then, enjoy your time coding.