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.
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.