This post may never see the light of day. But, if not, it will hopefully serve to document my attempts to use my existing Flask web app code to generate repeated images. I.E. redraw, preferably exactly, an image I liked and for which I saved the pertinent details. Those details would be the information provided at the bottom of the page on which the image was displayed. Don’t know if that will be enough info, but going to see. Nor do I know how muddled this is going to get as it could be any of the image types I currently generate. And they all have slightly different bits of data passed to the different drawing functions.

I may eventually add an input page to facilitate the redraw. But for development I am going to start with a data dictionary for one of the datasets I saved.

New Function

I am going to assume I can get most of this too work within a new route, /spirograph/repeat in the main module. But, don’t expect it will be the only function added to one module or another. This route’s function will be called sp_repeat(). I am going to create a dictionary, d_rpt, for the repeated curve’s data in the global variable module, g_vars.py. Will also add a variable do_rpt to that module to track whether or not a repeat attempt is in progress.

Generate Curve Data

I will start by generating the spirograph’s plot data. That is likely the easiest part of this whole exercise. But then, I have been wrong before. The functions I wrote to generate the plot data worked in very specific modules and situations. They may not work in the current context.

You will likely recall that for the data to be plotted, I generate a multidimensional array. It has one row for each wheel used to generate the spirograph. And one column for each of the number of plotting points per row. So five wheels at 1024 plotting points, results in a 5 x 1024 array. In the case of gnarly style spirographs one or more of those rows may be dropped from the actual plot.

Within the Flask app, all spirographs are generated on a form submission. I wasn’t going to initially use a form submission. So, I will likely have to create a dictionary to imitate what the various forms would submit. Then make a call to proc_curve_form() in the app library module. Seems like a lot of unnecessary work, but for initial development setting up a form may not be any easier.

Here’s the data from the display page for the curve I am trying to repeat.

Curve Parameters
    Wheels: 3 wheels (shape(s): tetracuspid (t)), mosaic
    Symmetry: k_fold = 2, congruency = 1
    Frequencies: [1, 7, 7]
    Widths: [1, 0.554072220504108j, 0.5360955718153062]
    Heights: [1, 0.7143622794074377, 0.46450502181680864]
    Data points: 1024
Image Type Parameters
    Colouring: between adjacent datarows with overlap
    Sections: 16 colour sections per datarow pairing
Drawing Parameters
    Colour map: PuRdhttps://geology.com/world/rwanda-satellite-image.shtml
    BG colour map: RdPu
    Line width (if used): 3
    Image size: 8
    Image DPI: 72

And, here’s the dictionary in the global variables module, I will be passing to the form processing function and using its values wherever needed. Oh yeah, and that do_rpt global. The latter will be set at the start of the route, and unset just before the image is displayed. Note, I printed the form data I was passing around to the screen so I could sort the following.

do_rpt = False
d_rpt = {
  'i_typ': 'mosaic',
  'shape': 't',
  'wheels': '3',
  'k_f': 2,
  'cgv': 1,
  'freqs': [1, 7, 7],
  'wds': [1, 0.554072220504108j, 0.5360955718153062],
  'hts': [1, 0.7143622794074377, 0.46450502181680864],
  'ln_sz': '3',
  'cmap': 'PuRd',
  'c_nbr': '16',
  'do_bg': 'true',
  'c_bg': 'RdPu',
  'npts': 1024,
  'sz': 8,
  'dpi': 72,
  'how': 'between adjacent datarows with overlap',
  'clw_pct': 'false',
  'clwc': '8',
  'clwpp': '12',
  'ntr': 'r',
  'shp_mlt': 'false',
  'u_agn': 'false',
  'torb': 'top',
  'drows': '1',
  'r_mlt': 'false',
  'do_drpt': 'false',
  'ani_step': 8,
}

Route Function

I will start development with the GET request. I don’t expect to use the POST method as I am not currently planning on using a form to obtain the data for repeat images. The code will pretty much duplicate the route code for the POST method of other routes. But will use the data in the global variables module shown above.

In my actual code, I copied the sp_mosaic to this route. Copied the POST request code into the GET request block, then modified accordingly. As such, I actually just left the POST request code unchanged.

@app.route('/spirograph/repeat', methods=['GET', 'POST'])
def sp_repeat():
  pg_ttl = f"Repeat: {g.d_rpt['i_typ']}"
  # set repeat global
  g.do_rpt = True
  # intiialize and generate curve data, don't want any of this randomly generated
  # 'k' fold symmetry
  g.k_f = g.d_rpt['k_f']
  # congruency vs k_f
  g.cgv = g.d_rpt['cgv']
  # widths of none circular shapes or diameter of circle
  g.wds = g.d_rpt['wds']
  # heights of none circular shapes
  g.hts = g.d_rpt['hts']
  # get rid of the imaginary unit if present
  g.r_wds = [max(np.real(rd), np.imag(rd)) for rd in g.wds]
  g.r_hts = [max(np.real(rd), np.imag(rd)) for rd in g.hts]
  # get the actually frequencies and congruency
  g.freqs = g.d_rpt['freqs']

  splt.set_spiro(g.freqs, g.wds, g.hts, nbr_t=g.t_pts)

  sal.cnt_btw = 0

  sal.proc_curve_form(g.d_rpt, g.d_rpt['i_typ'])
  t_xs, t_ys, t_shp = sal.init_curve()

  r_xs, r_ys, m_xs, m_ys, m2_xs, m2_ys = sal.get_gnarly_dset(t_xs, t_ys, g.r_skp, g.drp_f, g.t_sy)

  ax.clear()
  ax.set_xlim(-0.1, 0.1)
  ax.set_ylim(-0.1, 0.1)
  ax.autoscale()
  if g.u_fgsz and g.fig_sz > 8:
    ax.margins(g.sz2mrg[g.u_fgsz])
  # ax.patch.set_alpha(0.01)
  g.rcm, g.cycle = sal.set_clr_map(ax, g.mx_s, g.rcm)

  if g.u_again:
    sal.cnt_btw = 1
  else:
    sal.cnt_btw = 0

  if g.DEBUG:
    print(f"repeat:\n\tt_shp: {t_shp}\n\tcalling sal.btw_n_apart")

  sal.btw_n_apart(ax, r_xs, r_ys, dx=g.bw_dx, ol=g.bw_o, r=g.bw_r, fix=None, mlt=g.bw_m, sect=g.bw_s)
  ax.text(0.5, 0.01, "© koach (barkncats) 2022", ha="center", transform=ax.transAxes,
          fontsize=10, alpha=.3, c=g.cycle[len(g.cycle)//2])
  ax.autoscale()

  bax2, _ = sal.set_bg(ax)

  data = sal.fig_2_base64(fig)

  c_data = sal.get_curve_dtl()
  i_data = sal.get_image_dtl(pg_ttl)
  p_data = {'bdx': g.bw_dx, 'bol': g.bw_o, 'bcs': g.bw_s,
            # 'bmlt': g.bw_m, 'mlts': sal.c_mlts
            'bmlt': g.u_rmlt != 'false', 'mlts': sal.c_mlts
            }

  g.do_rpt = False

  return render_template('disp_image.html', sp_img=data, type=g.d_rpt['i_typ'], c_data=c_data, i_data = i_data, p_data=p_data)

elif request.method == 'POST':
  # don't currently expect need to process POST requests
  ...

Well I got a curve. But I was not getting the repeated curve. It was the basic mosaic style curve. A different one each time I reloaded the route.

Refactor App Library Functions

For the use again situation I had an if block that prevented generating a random image in init_curve. So I added do_rpt to that block. I changed if not g.u_agn: to if not (g.u_agn or g.do_rpt):

After that I was getting the correct curve. But there were two issues. The colouring of the curve changed from reload to reload. It used the correct colour map, but the individual blocks of the mosaic changed colour each time.

And, I was also getting a random background colour everytime I reloaded the page. And, that actually is what I should have expected. The app, as currently written, does not get any setting for the background colour in the form data. It always makes a random selection from the set of background colour maps allowed for the curve’s colour map. That is unless use again has been selected/specified.

Let’s start with the background colour. I needed to add another if block at the appropriate place in proc_curve_form(). I replaced if g.use_bb2 and g.use_rand_bg: with the following:

  if g.do_rpt:
    g.bg_cmnm = g.d_rpt['c_bg']
    g.bg_cmap = bgcm_mpl[g.bg_cmnm]
  elif g.use_bb2 and g.use_rand_bg:

And, now the image is repeated, the colour maps for the curve and background are correct. Though the pattern of colours for both change everytime the curve is regenerated.

No Fix for Changing Patterns

The colour cycle for the curve is set in the route function in the line g.rcm, g.cycle = sal.set_clr_map(ax, g.mx_s, g.rcm). I do not currently save the cycle, g.cycle. So no way to exactly repeat it. Similarly for the background pattern.

That pattern is set during the call bax2, _ = sal.set_bg(ax). bax2 is randomly generated on each call. I do not currently save that array either.

For now I am going to ignore this pattern issue. I may decide to rework the code in order to show/save that information as well.

Do It Again

Now I earlier said I wasn’t expecting to use the POST request section of the repeat route. But, when I clicked the “Draw another one!” button, I got a random mosaic style image. Which, of course, given I hadn’t modified that section of the route code, I really should have expected. I had forgotten that the button submitted a form to the route. So, I decided to update the code for a POST request. At first I duplicated pretty much everything. Then I decided to move the code that set the curve parameters to its own function. Which is currently still in the main module.

def set_rpt_data():
  # set repeat global
  g.do_rpt = True
  # intiialize and generate curve data
  # 'k' fold symmetry
  g.k_f = g.d_rpt['k_f']
  # congruency vs k_f
  g.cgv = g.d_rpt['cgv']
  # widths of none circular shapes or diameter of circle
  g.wds = g.d_rpt['wds']
  # heights of none circular shapes
  g.hts = g.d_rpt['hts']
  # get rid of the imaginary unit if present
  g.r_wds = [max(np.real(rd), np.imag(rd)) for rd in g.wds]
  g.r_hts = [max(np.real(rd), np.imag(rd)) for rd in g.hts]
  # get the actually frequencies and congruency
  g.freqs = g.d_rpt['freqs']

  if g.DEBUG:
    print(f"\tinit_curve(): data points = {g.t_pts}")

  splt.set_spiro(g.freqs, g.wds, g.hts, nbr_t=g.t_pts)

Then I showed a moment of unusual thoughtfulness. Realized I didn’t really need to duplicate the code in a separate code block. And the modified code for the route now looks like the following.

@app.route('/spirograph/repeat', methods=['GET', 'POST'])
def sp_repeat():
  pg_ttl = f"Repeat: {g.d_rpt['i_typ']}"

  if request.method == 'GET' or request.method == 'POST':
    set_rpt_data()
    sal.cnt_btw = 0

    sal.proc_curve_form(g.d_rpt, g.d_rpt['i_typ'])
    t_xs, t_ys, t_shp = sal.init_curve()

    r_xs, r_ys, m_xs, m_ys, m2_xs, m2_ys = sal.get_gnarly_dset(t_xs, t_ys, g.r_skp, g.drp_f, g.t_sy)

    ax.clear()
    ax.set_xlim(-0.1, 0.1)
    ax.set_ylim(-0.1, 0.1)
    ax.autoscale()
    if g.u_fgsz and g.fig_sz > 8:
      ax.margins(g.sz2mrg[g.u_fgsz])
    g.rcm, g.cycle = sal.set_clr_map(ax, g.mx_s, g.rcm)

    if g.u_again:
      sal.cnt_btw = 1
    else:
      sal.cnt_btw = 0

    sal.btw_n_apart(ax, r_xs, r_ys, dx=g.bw_dx, ol=g.bw_o, r=g.bw_r, fix=None, mlt=g.bw_m, sect=g.bw_s)
    ax.text(0.5, 0.01, "© koach (barkncats) 2022", ha="center", transform=ax.transAxes,
            fontsize=10, alpha=.3, c=g.cycle[len(g.cycle)//2])
    ax.autoscale()

    bax2, _ = sal.set_bg(ax)

    data = sal.fig_2_base64(fig)

    c_data = sal.get_curve_dtl()
    i_data = sal.get_image_dtl(pg_ttl)
    p_data = {'bdx': g.bw_dx, 'bol': g.bw_o, 'bcs': g.bw_s,
              # 'bmlt': g.bw_m, 'mlts': sal.c_mlts
              'bmlt': g.u_rmlt != 'false', 'mlts': sal.c_mlts
             }

    g.do_rpt = False

    return render_template('disp_image.html', sp_img=data, type=g.d_rpt['i_typ'], c_data=c_data, i_data = i_data, p_data=p_data)

Example Image

The images are a bit bigger than usual. But I wanted true colour so that you could see the background pattern changes.

using repeat route to reproduce a mosaic style spirograpn
A "repeated" image.
using repeat route to reproduce a mosaic style spirograpn
A second "repeated" image.

The curve and colour maps stay the same. The exact pattern of the colours not so much. At this point not too sure how important that is. But I expect for some spirograph variations it will be very important.

Done

I believe that is enough for this post. I will play around with the few data sets I saved and see how much the changing pattern affects why I wanted to save a particular image. If sufficiently motivated I may look at saving the colour pattern data for future images.

Until then, may your development go better than mine generally does.