Nope, just not ready to quit. So, I thought I’d look at using the plot between method between rows of different transformations. I would generate the data for a set of transformations, then colour between rows in different transformations. No idea what it will look like, or even if really viable, but as with all the previous image iterations, it’s at least worth a look in my opinion.

Initial Attempt

New image type, # 42. I am going to start by generating the usual four transformations (rotational at 45° with a linear translation into each image quadrant). Then, for each transformation, colour between all the rows in it and one of the other transformations. Expect there will be a lot of overcolouring of previous colours. But, not sure how to avoid that. No way to easily determine where data will create overlaps.

To start I will also only look at using one colour for each set of rows.

Wrote a function to do the work for each pair of transformations. I have left my debug print statements in the code below. I do a lot of that.

def btw_qds(axt, rxs, rys1, rys2, mlt=False, sect=1):
  # save stuff needed to generate true copy when saving to file
  global c_mlts, i_data, rys2_i, t_c_ndx

  print(f"\t\tbtw_qds(..., mlt={mlt}, sect={sect})")

  c_ndx = 0
  c_len = len(cycle)
  c_jmp = c_len // 20
  if c_jmp % 2 == 0:
    c_jmp += 1

  if not t_sv:
    if mlt:
      c_mlts, i_data = incr_data(rys2)
    else:
      c_mlts = [1.0 for _ in range(len(rys2))]
      i_data = rys2
    c_ndx = (c_ndx + c_jmp) % c_len
    t_c_ndx = c_ndx
    if cnt_42 == 0:
      rys2_i = list(range(len(rys2)))
      random.shuffle(rys2_i)
  else:
    c_ndx = t_c_ndx

  print(f"\t\tDEBUG: rys2_i: {rys2_i}; (cnt_42: {cnt_42})")
  if mlt:
    print(f"\t\tDEBUG: mlt={mlt}, c_mlts={c_mlts}")

  for i in rys2_i:
    if do_dbg:
      print(f"\t\tax.fill_between(rxs[{i}], rys1[{i}]*{c_mlts[i]}, rys2[{i}], alpha={bw_lph}, c={c_ndx})")
    # axt.fill_between(rxs[i], rys1[i], rys2[rys2_i[i]], alpha=bw_lph, color=cycle[c_ndx])
    axt.fill_between(rxs[i], rys1[i], rys2[i], alpha=bw_lph, color=cycle[c_ndx])
    c_ndx = (c_ndx + c_jmp) % c_len

  return rys2_i

And, here’s the relevant code from the image type block.

... ...

  # sort number of quadrants and colouring order
  if not t_sv:
    do_qd = range(nbr_qds)
    if tr_qd and tq_r:
      do_qd = range(tq_r)
      nbr_qds = max(list(do_qd)) + 1
    # randomize plotting order
    do_qd = list(do_qd)
    random.shuffle(do_qd)
    do_qd2 = list(range(nbr_qds))
    random.shuffle(do_qd2)

... ...

  ax.autoscale(True)
  qd_data = []
  for i in range(nbr_qds):
    a = np.array([[np.cos(lld_rot[i]), -np.sin(lld_rot[i])],
                  [np.sin(lld_rot[i]), np.cos(lld_rot[i])]])

    dstx, dsty = affine_transformation(src[0], src[1], a)
    dx, dy = rotate_pt(0, trdy, angle=lld_rot[i], cx=0, cy=0)
    dstx = dstx + dx
    dsty = dsty + dy
    print(f"\trot angle: {math.degrees(lld_rot[i])}, pt angle: {math.degrees(pt_rot[i])}, dx: {dx}, dy: {dy}")
    qd_data.append([dstx, dsty])

  print(f"\tdo_qd: {do_qd}, do_qd2: {do_qd2}, len(qd_data): {len(qd_data)}, len(qd_data[i]): {len(qd_data[i])}")

  for i in range(len(do_qd)):
    if do_qd[i] == do_qd2[i]:
      # don't colour between the same transformations
      continue
    bwx = qd_data[do_qd[i]][0]
    bwy1 = qd_data[do_qd[i]][1]
    bwy2 = qd_data[do_qd2[i]][1]
    print(f"\n\tbtw_qds(rxs[{do_qd[i]}][0][{src_st}:], rys[{do_qd[i]}][1][{src_st}:], rys[{do_qd2[i]}][1][{src_st}:], mlt={bw_m}, sect={bw_s}, alpha={bw_lph})")
    rys2i = btw_qds(ax, bwx, bwy1, bwy2, mlt=False, sect=1)              

    ax.autoscale(True)
    cnt_42 += 1

Got some images that seemed promising. E.G.

wheels: 9 (Tetracuspid)
do_qd: [3, 2, 1, 0], do_qd2: [0, 3, 1, 2]
image generated using plot between functionality and affine transforms

But all too many look like this.

wheels: 5 (Circle)
do_qd: [3, 1, 2, 0], do_qd2: [1, 2, 0, 3]
image generated using plot between functionality and affine transforms

And, in this case, the non-elliptical wheels seemed to generate better images.

Attempt #2

Let’s look at what happens if we don’t plot between the same rows in each quadrant. Another module variable so can create and track to separate sets of random data row numbers. And a few changes to the code.

def btw_qds(axt, rxs, rys1, rys2, mlt=False, sect=1):
  # save stuff needed to generate true copy when saving to file
  global c_mlts, i_data, rxs_i, rys2_i, t_c_ndx, rnd_rw

  print(f"\t\tbtw_qds(..., mlt={mlt}, sect={sect})")

  c_ndx = 0
  c_len = len(cycle)
  c_jmp = c_len // 20
  if c_jmp % 2 == 0:
    c_jmp += 1

  if not t_sv:
    if mlt:
      c_mlts, i_data = incr_data(rys2)
    else:
      c_mlts = [1.0 for _ in range(len(rys2))]
      i_data = rys2

    c_ndx = (c_ndx + c_jmp) % c_len
    t_c_ndx = c_ndx

    # rys2_i = random.shuffle(list(range(n_whl)))

    if cnt_42 == 0:
      rxs_i = list(range(len(rxs)))
      random.shuffle(rxs_i)
      rys2_i = list(range(len(rys2)))
      random.shuffle(rys2_i)
  else:
    c_ndx = t_c_ndx

  print(f"\t\tDEBUG: rxs_i: {rxs_i}; (cnt_42: {cnt_42})")
  print(f"\t\tDEBUG: rys2_i: {rys2_i}")
  if mlt:
    print(f"\t\tDEBUG: mlt={mlt}, c_mlts={c_mlts}")

  for i in range(len(rxs)):
    if rxs_i[i] == rys2_i[i]:
      # don't plot between same data row
      continue
    if do_dbg:
      print(f"\t\tax.fill_between(rxs[{rxs_i[i]}], rys1[{rxs_i[i]}]*{c_mlts[i]}, rys2[{rys2_i[i]}], alpha={bw_lph}, c={c_ndx})")
    # axt.fill_between(rxs[i], rys1[i], rys2[rys2_i[i]], alpha=bw_lph, color=cycle[c_ndx])
    axt.fill_between(rxs[rxs_i[i]], rys1[rxs_i[i]], rys2[rys2_i[i]], alpha=bw_lph, color=cycle[c_ndx])
    c_ndx = (c_ndx + c_jmp) % c_len

  return rys2_i

Not going to show you any images. No significant improvement.

Increase Colour Areas

Okay, let’s try increasing the number of colour sections. Here’s the modification to our plotting function.

... ...

  s_sz = len(rxs[0]) // sect

... ...

  for i in range(len(rxs)):
    if rxs_i[i] == rys2_i[i]:
      # don't plot between same data row
      continue

    if sect == 1:
      if do_dbg:
        print(f"\t\tax.fill_between(rxs[{rxs_i[i]}], rys1[{rxs_i[i]}]*{c_mlts[i]}, rys2[{rys2_i[i]}], alpha={bw_lph}, c={c_ndx})")
      axt.fill_between(rxs[rxs_i[i]], rys1[rxs_i[i]], rys2[rys2_i[i]], alpha=bw_lph, color=cycle[c_ndx])
      c_ndx = (c_ndx + c_jmp) % c_len
    else:
      for j in range(sect-1):
        s_st = s_sz * j
        s_nd = s_sz * (j+1)
        if do_dbg:
          print(f"\t\tax.fill_between(rxs[{rxs_i[i]}][{s_st}:{s_nd}], rys1[{rxs_i[i]}][{s_st}:{s_nd}]*{c_mlts[i]}, rys2[{rys2_i[i]}][{s_st}:{s_nd}], alpha={bw_lph}, c={c_ndx})")
        axt.fill_between(rxs[rxs_i[i]][s_st:s_nd], rys1[rxs_i[i]][s_st:s_nd], rys2[rys2_i[i]][s_st:s_nd], alpha=bw_lph, color=cycle[c_ndx])

        c_ndx = (c_ndx + c_jmp) % c_len

      if do_dbg:
        print(f"\t\tax.fill_between(rxs[{rxs_i[i]}][{s_nd}:], rys1[{rxs_i[i]}][{s_nd}:]*{c_mlts[i]}, rys2[{rys2_i[i]}][{s_nd}:], alpha={bw_lph}), c={c_ndx}")
      axt.fill_between(rxs[rxs_i[i]][s_nd:], rys1[rxs_i[i]][s_nd:], rys2[rys2_i[i]][s_nd:], alpha=bw_lph, color=cycle[c_ndx])

... ...

And things already look better and/or more interesting.

wheels: 6 (Ellipse)
do_qd: [2, 3, 0, 1], do_qd2: [3, 0, 1, 2]
image generated using plot between functionality and affine transforms

For the one above, the row order is:

DEBUG: rxs_i: [3, 2, 4, 1, 0]; (cnt_42: 4)
DEBUG: rys2_i: [4, 0, 1, 2, 3]
wheels: 11 (Ellipse)
do_qd: [1, 0, 3, 2], do_qd2: [3, 2, 1, 0]
image generated using plot between functionality and affine transforms

For the one above, the row order is:

DEBUG: rxs_i: [9, 8, 4, 7, 5, 3, 6, 1, 2, 0]; (cnt_42: 4)
DEBUG: rys2_i: [1, 2, 8, 5, 0, 6, 4, 7, 3, 9]

A Further Refactoring

I am thinking that plotting between all the rows is likely a great deal of overkill. So, I am going to look at randomly selecting a single data row from the first transformation and plotting between it and all the rows in the second transformation. Keeping the increased number of colour areas.

A new module variable (to ensure proper saving of images to file) and a bit of code change. I won’t bother showing the modified fill_between() statements. Simple change to data row indices. And, I am dropping the first data row from the data set. Thought it was adding to the clutter on some of the images.

  global c_mlts, i_data, rys2_i, t_c_ndx, rnd_rw

... ...

    if cnt_42 == 0:
      rys2_i = list(range(len(rys2)))
      random.shuffle(rys2_i)
      rnd_rw = random.choice(rys2_i)

Example Images

And, you know, I’m liking the results. Here’s a set of images for the same underlying curve (5 circles). But with my cataclysmic variables hard at work.

do_qd: [3, 0, 1, 2], do_qd2: [0, 1, 2, 3]
rys2_i: [0, 2, 3, 1]; rnd_rw: 2
image generated using plot between functionality and affine transforms
do_qd: [1, 0, 2, 3], do_qd2: [2, 0, 1, 3]
rys2_i: [3, 0, 1, 2]; rnd_rw: 0
image generated using plot between functionality and affine transforms
do_qd: [3, 1, 2, 0], do_qd2: [0, 1, 2, 3]
rys2_i: [1, 2, 3, 0]; rnd_rw: 1
image generated using plot between functionality and affine transforms

The following looks a lot like one of the earlier ones. But, there are very visible differences resulting from the changes in the random values.

do_qd: [0, 1, 2, 3], do_qd2: [1, 2, 3, 0]
rys2_i: [2, 1, 3, 0]; rnd_rw: 0
image generated using plot between functionality and affine transforms

Again very similar to the viridis coloured one above. Yet different.

do_qd: [2, 3, 1, 0], do_qd2: [2, 1, 3, 0]
rys2_i: [2, 1, 3, 0]; rnd_rw: 2
image generated using plot between functionality and affine transforms

And effecively a partial rotation of the one above. With additional changes.

do_qd: [0, 3, 1, 2], do_qd2: [2, 3, 1, 0]
rys2_i: [0, 1, 3, 2]; rnd_rw: 2
image generated using plot between functionality and affine transforms
do_qd: [3, 2, 0, 1], do_qd2: [3, 2, 0, 1]
rys2_i: [0, 3, 2, 1]; rnd_rw: 3
image generated using plot between functionality and affine transforms

The one above, such a complete change from the others. But there were hints in a number of the preceding images, that it was a possibility.

And, yet again similar, but ever so slightly different. Quite like the colour map in this one.

do_qd: [0, 1, 3, 2], do_qd2: [2, 1, 3, 0]
rys2_i: [1, 0, 2, 3]; rnd_rw: 0
image generated using plot between functionality and affine transforms

And finally, the curls where I wanted them. On top, like a fancy bow on a gift (which I consider these images, a gift of code and the mind).

do_qd: [3, 1, 0, 2], do_qd2: [1, 3, 0, 2]
rys2_i: [3, 1, 0, 2]; rnd_rw: 1
image generated using plot between functionality and affine transforms

This one just for the colour map.

do_qd: [3, 0, 2, 1], do_qd2: [0, 3, 2, 1]
rys2_i: [2, 3, 0, 1]; rnd_rw: 0
image generated using plot between functionality and affine transforms

Another with the ribbons on top. But marginally and visibly different.

do_qd: [3, 2, 1, 0], do_qd2: [3, 1, 2, 0]
rys2_i: [3, 0, 2, 1]; rnd_rw: 3
image generated using plot between functionality and affine transforms
do_qd: [0, 3, 2, 1], do_qd2: [1, 2, 3, 0]
rys2_i: [2, 0, 3, 1]; rnd_rw: 2
image generated using plot between functionality and affine transforms

And, one more using the jet colour map.

do_qd: [2, 3, 1, 0], do_qd2: [2, 0, 3, 1]
rys2_i: [3, 2, 1, 0]; rnd_rw: 1
image generated using plot between functionality and affine transforms

Done

Lots of images (perhaps too many for a single post). So, I think I will call this one done. But I am not yet quite done. I want to see what happens with larger numbers of transformations. And, perhaps with some chaos added. Hoping those will all work without too much code refactoring.

Until next time, enjoy your coding time and any cataclysmic variables with which you may be experimenting.

Resources