Okay time to take the work done in the test module this last while and apply it to the app’s modules.

New and Modified Functions

I will copy the new functions into the sp_app_lib module. And replace the refactored functions in whatever module they are currently in. Modifying any documentation as appropriate.

I do plan to rework the modules, including adding some new ones. Putting the appropriate data and functions in each. But that is for another day.

Needed to remember to change namespaces as appropriate. That said fairly painless and the CLW (scatter) and gnarly image types are being generated as expected. For which you will need to take my word.

Use New Functions to Refactor CLW (between) Routes/Functions

Let’s start with the cycle frequency values. Similar concept but implemented entirely differently. Looks like for CLW (scatter) I was setting the values in the route. But, for CLW (between) I was setting the number of cycles per image in the form processing function. As well as a value for the number of datapoints per colour segment. Don’t use the latter for CLW (scatter).

So, I think I will, based on first thoughts, refactor the CLW (between) routes to use the new set_cycle_freq() function. Which I will also refactor to generate the datapoints per segment value. The values will be saved to new/old global variables as appropriate. I will have to think about what the range of possible choices should be as they are somewhat different for both types of images. Will also need to modify the existing calls to the function to deal with the new return value.

Then I will refactor the current code of the form processing function to only set the values if numeric values are returned in the form. Otherwise it will do nothing. I will then use g.u_lwcyc to determine whether or not to generate random values. It will have a numeric value (in string format) if the user specified the number of cycles to use. A bit convoluted but… Then, see what happens. I think this may help make repeating these types of images a touch easier as well.

def set_cycle_freq():
  c_div = rng.choice([4, 6, 8, 10, 12, 14, 16, 18, 20])
  g.lc_frq = g.t_pts // c_div
  g.hf_frq = g.lc_frq // 2
  # g.n_lwcyc = rng.integers(2, 16, endpoint=True)
  g.n_lwcyc = c_div
  g.dpp_clr = rng.integers(1, 32, endpoint=True)

  return c_div, g.lc_frq, g.hf_frq, g.dpp_clr

The relevant code in the CLW (between) route now looks like the following. Similar in the transform version. I also had to change the default value for u_lwcyc used by fini_curve_form(), i.e. dflts['clwc'], from 8 to 'r'.

  elif request.method == 'POST':
    t_xs, t_ys, *_ = get_curve_data('clw_btw', request.form, 2048)
    setup_image(ax)
    if not g.u_lwcyc.isnumeric():
      c_div, lc_frq, hf_frq, n_pp_clr = sal.set_cycle_freq()

    h_cyc, mn_mlt, mx_mlt, m_inc, n_max, pp_clr = sal.cycle_lw_btw(ax, t_xs[-1], t_ys[-1], u_pcnt=g.use_pct, n_cyc=g.n_lwcyc, dppc=g.dpp_clr)
... ...

And the lines of code removed from the form processing function are shown as comments in the code block below.

  if f_data['clwc'].isnumeric():
    cyc_tmp = int(f_data['clwc'])
    if cyc_tmp >= 2 and cyc_tmp <= 16:
      g.n_lwcyc = cyc_tmp
  # elif f_data['clwc'].lower() == 'r':
  #   g.n_lwcyc = rng.integers(2, 16, endpoint=True)
  if f_data['clwpp'].isnumeric():
    cyc_tmp = int(f_data['clwpp'])
    if cyc_tmp >= 1 and cyc_tmp <= 32:
      g.dpp_clr = cyc_tmp
  # elif f_data['clwpp'].lower() == 'r':
  #  g.dpp_clr = rng.integers(1, 32, endpoint=True)

And for the first time in a while, I am getting differing cycle frequencies in the CLW (between) routes! Without form submission it was always defaulting to 8 complete cycles for each curve. And, this change I definitely like.

Repeating Save CLW (scatter) Type Images

Let’s start with repeating a basic CLW (scatter). No transforms just yet.

A little more effort than I expected. Especially when I tried to change the image size. A fair number of more or less small changes. Well except perhaps the code to handle the image type in the repeat route function.

In the global variable module I ended up at some point changing b_mrgn = 0.25 to b_mrgn = None. In the get_cycle_params() function I replaced if not u_fst: with if not (u_fst or g.do_rpt):.

In the repeats module I added some additional values to the dictionary of parameters used to generate repeat images.

d_rpt.append(
  {
    ... ...
    'b_mrgn': None,
    'mrgn': None,
    'how': '',
    'c_div': ,
    'c_frq': ,
    'h_frq': ,
    'm1': '',
    'm2': '',
    'm_sz': ,
    'c_strt': 0,
    ... ...
  }
)

Where c_div is the number of cycles (width and/or colour cycles) per image. I don’t currently use the 'mrgn' item. But I may add its use in future to handle the situation where I’d like a custom margin to get the image I want for printing.

The bulk of the changes were in the main module. Starting with setup_image().

def setup_image(ax):
  ax.cla()
  if g.i_dpi == 72:
    sal.set_margins(ax, u_mrg=0.25)
    g.b_mrgn = .25
  ## added this elif to account for a custom base margin
  elif g.b_mrgn > .375:
    sal.set_margins(ax, u_mrg=g.b_mrgn)
  else:
    sal.set_margins(ax, u_mrg=0.375)
    g.b_mrgn = .375

In set_rpt_data() made the following changes (marked with ##).

  g.rds = np.linspace(0, 2*np.pi, g.t_pts)
  # deal with the new repeat data values
  ## added the following
  g.i_dpi = r.d_rpt[r.do_nbr]['dpi']
  if r.d_rpt[r.do_nbr]['b_mrgn']:
    g.b_mrgn = r.d_rpt[r.do_nbr]['b_mrgn']
  else:
    g.b_mrgn = 0.25

And in the repeat route code a number of changes (marked with ##).

... ...
    pg_ttl = f"Repeat: {r.d_rpt[r.do_nbr]['i_typ']} ({r.do_nbr} of {r.rpt_max - 1})"

    ## added the following
    if r.d_rpt[r.do_nbr]['sz'] != 8:
      fig.set_size_inches(r.d_rpt[r.do_nbr]['sz'], r.d_rpt[r.do_nbr]['sz'])
... ...
    elif r.d_rpt[r.do_nbr]['i_typ'] == 'clw_scatter':
      c_div, lc_frq, hf_frq = (r.d_rpt[r.do_nbr]['c_div'], r.d_rpt[r.do_nbr]['c_frq'], r.d_rpt[r.do_nbr]['h_frq'])
      g.lc_frq, g.hf_frq = lc_frq, hf_frq
      r_m1, r_m2, r_mx, r_fst = (r.d_rpt[r.do_nbr]['m1'], r.d_rpt[r.do_nbr]['m2'], r.d_rpt[r.do_nbr]['m_sz'], r.d_rpt[r.do_nbr]['c_strt'])
      r_stp = 1
      m1, m2, mx_sz, t_fst, p_step, ax_mrgn, cyc_m_sz = sal.cycle_lw(ax, r_xs[-1], r_ys[-1], u_m1=r_m1, u_m2=r_m2, u_mx=r_mx, u_fst=r_fst, u_stp=r_stp)
... ...
    elif r.d_rpt[r.do_nbr]['i_typ'] == 'clw_scatter':
      p_data = {'m1': r_m1, 'm2': r_m2, 'mx_sz': r_mx, 't_fst': r_fst, 'p_step': r_stp, 'c_nbr': c_div, 'hf_frq': hf_frq, 'ax_mrgn': ax_mrgn}
... ...

And I do believe that’s pretty much it. But I am skipping the time I spent sorting out bugs and modifying code accordingly. Sorry didn’t take notes. Though I guess if I really cared I could use VSCode’s timeline feature to see all the changes I made throughout the day. And look for the bug fixes.

The Big Test

Okay, here’s the image I originally saved. Followed by the image data dictionary entry in the repeats module. Including the new fields added to make this work—hopefully.

test CLW (scatter) image
Test image, previously saved from app
d_rpt.append(
  {
    'i_typ': 'clw_scatter',
    'shape': 't',
    'shp_mlt': 'false',
    'wheels': '6',
    'k_f': 6,
    'cgv': 4,
    'freqs': [-2, 22, -8, 4, 10, 22],
    'wds': [1, 0.6226134614881264, 0.43168400631614684, 0.35784577785318605, 0.25959979037491204j, 0.18405867491607839j],
    'hts': [1, 0.7463480089677866, 0.40140529477585113, 0.310964772739066j, 0.18885080910187982j, 0.1421869817098761],
    'ln_sz': '6',
    'cmap': 'nipy_spectral',
    'c_cyc': [(0.0, 0.0, 0.0, 1.0), (0.2928313725490196, 0.0, 0.33461960784313727, 1.0), (0.4836764705882353, 0.0, 0.5503019607843137, 1.0), (0.525464705882353, 0.0, 0.5921529411764705, 1.0), (0.261421568627451, 0.0, 0.6340039215686274, 1.0), (0.0, 0.0, 0.6941509803921568, 1.0), (0.0, 0.0, 0.8196411764705882, 1.0), (0.0, 0.18301960784313726, 0.8667, 1.0), (0.0, 0.46931372549019607, 0.8667, 1.0), (0.0, 0.5529529411764705, 0.8667, 1.0), (0.0, 0.6183098039215686, 0.8117980392156863, 1.0), (0.0, 0.6601607843137255, 0.6863078431372549, 1.0), (0.0, 0.6667, 0.5960764705882353, 1.0), (0.0, 0.6562372549019607, 0.4496450980392157, 1.0), (0.0, 0.6143862745098039, 0.11502549019607844, 1.0), (0.0, 0.6548882352941177, 0.0, 1.0), (0.0, 0.7385313725490196, 0.0, 1.0), (0.0, 0.8222333333333334, 0.0, 1.0), (0.0, 0.9059058823529412, 0.0, 1.0), (0.0, 0.9895450980392156, 0.0, 1.0), (0.4025960784313726, 1.0, 0.0, 1.0), (0.7685941176470588, 0.9882294117647059, 0.0, 1.0), (0.8940843137254902, 0.9463784313725491, 0.0, 1.0), (0.9620725490196078, 0.8757980392156863, 0.0, 1.0), (1.0, 0.7882352941176471, 0.0, 1.0), (1.0, 0.6627450980392157, 0.0, 1.0), (1.0, 0.4117647058823529, 0.0, 1.0), (1.0, 0.03529411764705881, 0.0, 1.0), (0.9242019607843137, 0.0, 0.0, 1.0), (0.853621568627451, 0.0, 0.0, 1.0), (0.8117705882352941, 0.0, 0.0, 1.0), (0.8, 0.36078431372549025, 0.36078431372549025, 1.0), (0.8, 0.8, 0.8, 1.0), (0.8065392156862745, 0.0, 0.0, 1.0), (0.8431588235294118, 0.0, 0.0, 1.0), (0.9032921568627451, 0.0, 0.0, 1.0), (0.9764764705882353, 0.0, 0.0, 1.0), (1.0, 0.2235294117647072, 0.0, 1.0), (1.0, 0.6, 0.0, 1.0), (1.0, 0.7098039215686275, 0.0, 1.0), (0.9934607843137255, 0.8130686274509804, 0.0, 1.0), (0.9516098039215687, 0.8967078431372549, 0.0, 1.0), (0.8783980392156863, 0.9516098039215687, 0.0, 1.0), (0.7685941176470588, 0.9882294117647059, 0.0, 1.0), (0.4025960784313726, 1.0, 0.0, 1.0), (0.0, 1.0, 0.0, 1.0), (0.0, 0.9268156862745098, 0.0, 1.0), (0.0, 0.8431588235294117, 0.0, 1.0), (0.0, 0.7699196078431372, 0.0, 1.0), (0.0, 0.6967078431372549, 0.0, 1.0), (0.0, 0.6235235294117647, 0.0, 1.0), (0.0, 0.6300803921568627, 0.24050784313725487, 1.0), (0.0, 0.6667, 0.5333, 1.0), (0.0, 0.6667, 0.6065392156862744, 1.0), (0.0, 0.6549294117647059, 0.7019941176470588, 1.0), (0.0, 0.6183098039215686, 0.8117980392156863, 1.0), (0.0, 0.5634078431372549, 0.8667, 1.0), (0.0, 0.47976862745098037, 0.8667, 1.0), (0.0, 0.2562274509803922, 0.8667, 1.0), (0.0, 0.0, 0.8667, 1.0), (0.0, 0.0, 0.7412098039215687, 1.0), (0.0941117647058824, 0.0, 0.6549294117647059, 1.0), (0.3869039215686274, 0.0, 0.6183098039215686, 1.0), (0.5097941176470588, 0.0, 0.5764588235294118, 1.0)],
    'c_lpha': 0.75,
    'c_ndx': 22,
    'do_bg': 'true',
    'c_bg': 'BuGn',
    'abg': [[0.9844057218599277, 0.9334231920594479, 0.7436422971770077, 0.25474344907931157], [0.7628323971482052, 0.8869372929430667, 0.5884237533555651, 0.8951801561049693], [0.4188393528973544, 0.21525313674528335, 0.5943794717438289, 0.6884426327346489], [0.8449247998997353, 0.769813011805585, 0.6564674659839006, 0.335420553231079]],
    'bg_lpha': 0.15,
    'npts': 2048,
    'sz': 10,
    'dpi': 300,
    'b_mrgn': 0.6,
    'mrgn': 0.10642581630073251,
    'how': '',
    'c_div': 4,
    'c_frq': 512,
    'h_frq': 256,
    'm1': 'D',
    'm2': 'p',
    'm_sz': 7500,
    'c_strt': 0,
    'clw_pct': 'false',
    'clwc': '4',
    'clwpp': '12',
    'm_inc': None,
    'u_agn': 'false',
    'torb': 'top',
    'drows': '0',
    'c_nbr': '16',
    'r_btw': '1',
    'r_mlt': 'false',
    'mlts': [],
    'ntr': '3',
    'lld_deg': [],
    'y_mlt': 0.5,
    'trdy': 1.061,
    'do_qd': [],
    'do_qd2': [],
    't2t_btw': [],
    'do_drpt': 'false',
    'ani_step': 8,
  }
)

Now, I wanted to test the ability to generate different sized images and the 300 dpi needed for the giclée printing. So you will notice above that I set 'sz': 10, 'dpi': 300 and 'b_mrgn': 0.6. The latter to ensure there would be some blank space for the matting to overlap on the image without covering any of the spirograph itself.

Also note that I have not been reducing the data in the dictionary to eliminate anything not used by this particular image style. Just laziness on my part. And, no apparent hit to memory use.

And, here’s the generated repeat image. Though I have reduced its size and dpi for use in the post. The generated image was indeed 3000 x 3000 pixels in size. Which at 300 dpi makes it 10 x 10 inches in size. So things appear to have worked as desired/expected.

generate repeat image for comparison with original above
The generated repeat image using the above data

Notice the extra space at the top as desired. But it is also added to the bottom and sides. Not so desireable. But, for now, I can live with that.

I am currently thinking of getting this one printed. Something about it quite appeals to me. Though I may play with the marker shapes to see what happens.

Repeating Save CLW (scatter) with Transforms Type Images

Okay, let’s see if we can quickly code repeating images with transforms. Here’s the code that changed or was added.

    elif 'clw_scatter' in r.d_rpt[r.do_nbr]['i_typ']:
      c_div, lc_frq, hf_frq = (r.d_rpt[r.do_nbr]['c_div'], r.d_rpt[r.do_nbr]['c_frq'], r.d_rpt[r.do_nbr]['h_frq'])
      g.lc_frq, g.hf_frq = lc_frq, hf_frq
      r_m1, r_m2, r_mx, r_fst = (r.d_rpt[r.do_nbr]['m1'], r.d_rpt[r.do_nbr]['m2'], r.d_rpt[r.do_nbr]['m_sz'], r.d_rpt[r.do_nbr]['c_strt'])
      r_stp = 1
      if r.d_rpt[r.do_nbr]['i_typ'] == "clw_scatter":
        m1, m2, mx_sz, t_fst, p_step, ax_mrgn, cyc_m_sz = sal.cycle_lw(ax, r_xs[-1], r_ys[-1], u_m1=r_m1, u_m2=r_m2, u_mx=r_mx, u_fst=r_fst, u_stp=r_stp)
... ...
      elif 'clw_scatter' in r.d_rpt[r.do_nbr]['i_typ']:
        all_cndx = []
        tmp_rot = 0
        # Generate  and plot transformed datasets
        c_cnt = 0

        for i in do_qd2:
          dstx1, dsty1 = sal.get_transform_data(r_xs[-1], r_ys[-1], lld_rot[i], lld_rot[i], trdy)
          print(f"\tloop {i}: {lld_rot[i]}, {trdy}, len(dsty1): {len(dsty1)}")
          if c_cnt == 0:
            m1, m2, mx_sz, t_fst, p_step, ax_mrgn, cyc_m_sz = sal.cycle_lw(ax, dstx1, dsty1, u_m1=r_m1, u_m2=r_m2, u_mx=r_mx, u_fst=r_fst, u_stp=r_stp)
            print(f"cycle marker sizes ({len(cyc_m_sz)}):\n{cyc_m_sz}")
          else:
            _, _, _, _, _, x_mrg, _ = sal.cycle_lw(ax, dstx1, dsty1, u_m1=m1, u_m2=m2, u_mx=mx_sz, u_fst=t_fst, u_stp=p_step, m_sz_cyc=cyc_m_sz)

          all_cndx.append(g.c_ndx)
          ax.autoscale()

          c_cnt += 1

        data = fini_image(fig, ax)

        p_data = {'m1': m1, 'm2': m2, 'mx_sz': mx_sz, 't_fst': t_fst, 'p_step': p_step, 'c_nbr': c_div, 'hf_frq': hf_frq, 'ax_mrgn': ax_mrgn,
                'ntr': g.nbr_tr, 'trang': lld_deg, 'trpt': f'(0, {round(trdy, 3)}) ({y_mlt})',
                'trord2': do_qd2
                }

Quick Test

Here are the before and after images. I did have to mess with the background opacity value. I believe it was incorrectly stated on the display page for the original spirograph image. (Will need to check that out.) I won’t bother posting the image data dictionary.

test CLW (scatter) with transforms image
Test image with transforms, previously saved from app
generate repeat image for comparison with original above
The generated repeat image

Look the same to my poor eyesight.

That’s It

I think this post is getting plenty long enough. And, between it and a previous one or two, pretty much covers refactoring the app to generate and/or repeat CLW (scatter) type spirograph images (with and without transforms).

I have found this recent refactoring quite satisfying. I hope you find your time coding just as satisfying.

Resources