Up until now I have been more or less following the same path as my earlier development. At this point on that path, I added background colours to the images. But, for now, I think I will go elsewhere. In fact close to the end of my earlier development history.

Mosaic like images. I attempted this variation based on a locker room conversation with my friend Gary. He was probably the only person to have read more than one or two of my blog posts. And, was very supportive in the early stages of my spirograph project. During that convesation he mentioned he had been at a local art show over the weekend and saw some interesting wood mosaics. So, I thought, any chance that can be done with spirograph curves? The answer is a definite maybe.

Mosaic Like Images

I found a matplotlib method that I thought might prove useful, fill_between(). Of course, nothing is ever quite that simple. I wanted variations on the basic colour between image. But let’s start with the simple version. Colouring between adjacent rows in the dataset, with and without overlap. Where overlap means the second data row for each colour between pair is used for the first of the next pair.

We will need a new path, /spirograph/mosaic. And a new function or two. And, a new form field to control overlap of colour between pairs. I’ll start with the path and use a function from my previous code. I have copied the function into the app library module. And added a number of new global variables related to basic mosaic style images and potential future variations.

New Path

Here’s the code so far. Do note, as I know where I am going, there’s a few extras in there that have not yet actually been implemented in this app. We will get there. For this situation I use the data generated by get_gnarly_dset(), but I don’t drop any rows when generating the data. I have however found that plotting between the first and second datarows can often mess things up. So, I do not use the 1st datarow in the colour between function. Saves me remembering to do it elsewhere. E.G. in the call from the route function.

Because I am using that function from my earlier work, there are a number of parameters we do not currently really need or know about. The one being used below is one of two basic colour between functions I developed earlier. This one colours between adjacent datarows, 1 or more steps apart. The other colours between a fixed datarow and all other datarows. But as I have said, small steps.

@app.route('/spirograph/mosaic', methods=['GET', 'POST'])
def sp_mosaic():
  pg_ttl = "Mosaic Like Spirograph Image"
  if request.method == 'GET':
    f_data = {'pttl': pg_ttl, 'shapes': splt.shp_nm, 'cmaps': g.clr_opt}
    return render_template('get_curve.html', type='mosaic', f_data=f_data)
  elif request.method == 'POST':
    sal.proc_curve_form(request.form, 'mosaic')
    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, 0, g.drp_f, g.t_sy)

    ax.clear()
    g.rcm, g.cycle = sal.set_clr_map(ax, g.lc_frq, g.rcm)

    # if u_again, make sure things generated by colour between function don't change
    if g.u_again:
      sal.cnt_btw = 1
    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=1)
  
    # Save it to a temporary buffer.
    buf = BytesIO()
    fig.savefig(buf, format="png", bbox_inches="tight")
    # Embed the result in the html output.
    data = base64.b64encode(buf.getbuffer()).decode("ascii")

    p_data = {'pttl': pg_ttl, 'n_whl': g.n_whl, 'ln_sz': g.ln_w, 'shape': t_shp,
              'cmap': g.rcm, 'agn': g.u_again, 'bdx': g.bw_dx, 'bol': g.bw_o
             }

    return render_template('disp_image.html', sp_img=data, type='mosaic', p_data=p_data)

This works, but it is not really, in my mind, mosaic like. As I have said before, more logo like. Especially when using a small number of wheels. Here’s an example using 5 circle shaped wheels. Because I don’t use the first data row, that only leaves us with 4 datarows. Which in turn only generates 3 colour between areas/sections.

screen shot of mosaic like spirograph image

I suppose interesting enough (though not always). But, in my earlier development, I decided to use multiple colour sections for each individual pair of datarows. So, I am going to add that to the app and see how things look.

Increase Number of Colour Sections

I will eventually add a form field to allow specifying the number of colour sections for each colour between pairing. But for now, I am just going to go with 48, and have a look. I believe the function, btw_n_apart(), already handles this option via the sect= parameter. But, I need to modify the call to set_clr_map(). Here’s an example.

screen shot of mosaic like spirograph image with 48 colour sections per datarow pairing

Now, that’s more like it.

Let’s add a suitable field to the form in get_curve.html. And, refactor proc_curve_form() to process the value accordingly and our route code to use it. Already have a global variable for this curve parameter, bw_s. But, did add mx_s = 64 so that I can change that as needed in a single location.

The form field is pretty simple.

    {% if type == 'mosaic' %}
    <div>
      <p style="margin-left:1rem;margin-top:0;font-size:110%;"><b>Mosaic Type Image</b></p>
      <div style="display:none;">
        <label for="r_btw">Number of rows between pairs: </label><br>
        <input type="text" name="r_btw" placeholder="'r' for random, or a number less than half the number of wheels"/>
      </div>
    </div>
    {% endif %}

The form processing function pretty much as simple. Only showing the relevant section.

    if i_typ == 'mosaic':
      if f_data['c_nbr'].isnumeric():
        n_tmp = int(f_data['c_nbr'])
        if n_tmp >= 1 and n_tmp <= g.mx_s:
          g.bw_s = n_tmp
        else:
          g.bw_s = 32
      elif f_data['c_nbr'].lower() == 'r':
        g.bw_s = rng.integers(1, g.mx_s + 1)
      # else: leave as is

I also modified disp_image.html to show the number of colour sections being used.

A few minor changes to the routing function.

@app.route('/spirograph/mosaic', methods=['GET', 'POST'])
def sp_mosaic():
  pg_ttl = "Mosaic Like Spirograph Image"
  if request.method == 'GET':
    f_data = {'pttl': pg_ttl, 'shapes': splt.shp_nm, 'cmaps': g.clr_opt}
    return render_template('get_curve.html', type='mosaic', f_data=f_data)
  elif request.method == 'POST':
    sal.proc_curve_form(request.form, 'mosaic')
    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()
## !! use global mx_s to set the number of colours in the colour cycle assigned to the plot
    g.rcm, g.cycle = sal.set_clr_map(ax, g.mx_s, g.rcm)

    if g.u_again:
      sal.cnt_btw = 1
## !! pass global bw_s to sect= paramter
    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)
  
    # Save it to a temporary buffer.
    buf = BytesIO()
    fig.savefig(buf, format="png", bbox_inches="tight")
    # Embed the result in the html output.
    data = base64.b64encode(buf.getbuffer()).decode("ascii")

## !! add some more info regarding the number of colour sections to display page
    p_data = {'pttl': pg_ttl, 'n_whl': g.n_whl, 'ln_sz': g.ln_w, 'shape': t_shp,
              'cmap': g.rcm, 'agn': g.u_again, 'bdx': g.bw_dx, 'bol': g.bw_o,
              'bcs': g.bw_s
             }

    return render_template('disp_image.html', sp_img=data, type='mosaic', p_data=p_data)

And here’s a few examples.

screen shot of mosaic like spirograph image with multiple colour sections per datarow pairing screen shot of mosaic like spirograph image with multiple colour sections per datarow pairing screen shot of mosaic like spirograph image with multiple colour sections per datarow pairing

Before calling it a day, I decided to add a copyright message to the on-line images. I also modified the call to savefig to use 72 dpi. Just ‘cuz.

After the images are plotted in the route code, I added the following immediately afterward. See the appropriate resource for the details.

    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])

Occasionally interferes with the image, but tried to keep that as minimal as possible.

Done

I think that will be it for this post. Next time I want to look at modifying the generated image by applying a multiplier to one of the data rows. An attempt to generate larger colour areas. I may also look at adding a field to modify the distance between data rows. Which depending on how it get’s implemented may reduce the number of datarow pairings drawn on the image.

And, I did deploy the updates to gcloud. Not exactly a quick process. Likely because I am using a free account (well for 90 days anyway).

Until then hope your coding time is as much fun as mine.

Resouces