We were thinking about getting some images printed and framed to put in the bathroom off the family room. And Barbara thought it might be better if the image was wider rather than square. She thought that way a single image could nicely fill the available space. Or perhaps two rectangular images of a smaller size side by side or slightly staggered one above the other. Having had a look, it does make some sense.

The problem is going to be finding the proper ratio for the width and height of any such image. We are currently thinking of a finished (i.e. framed) image width of 24 inches.

So I am going to refactor the app to allow for rectangular images and, possibly, user selectable widths and/or heights. I will start by using a few fixed image sizes, with a ratio likely based on the golden ratio to start. But any such image is probably going to be too tall for what we are envisioning.

Global Variables, Form Processing and Such

I am not at the moment going to provide user selectable dimensions. But, I will add new global variables for image width and height. Then make sure they are looked after in any form processing (including for repeats). For now I will randomly select between square images and rectangular images. Rectangular images will for now default to a width of 12, 15 or 18 inches. That is the actual image size, not the framed size. And for the height, I will randomly select from some routinely used ratios.

Will also need to update the data displayed at the bottom of the image page to show image width and height if appropriate.

Perhaps too many code changes for a single refactor, but…

I added the new variables just after the fig_sz variable in the global variables module. I also modified the sz_opt and related margins variables to include the two new widths. Though I am not sure I use the latter in any current code. These values may change over time.

fig_sz = 8
fig_wd = None
fig_ht = None

... ...
# these are image sizes finished sizes (i.e. framed) would be significantly bigger
sz_opt = ['8', '10', '12', '14', '15', '18']
sz2mrg = {'10': 0.12, '12': 0.1, '14': 0.0857, '15': 0.08, '18': 0.076}

In the sp_app_data module I modified the set_rpt_data() function to allow for the possibility of image width and height data. Including the line above and below for context.

  g.fig_sz = r.d_rpt[r.do_nbr]['sz']
  # wide and high could be None
  if 'wide' in r.d_rpt[r.do_nbr]:
    g.fig_wd = r.d_rpt[r.do_nbr]['wide']
    g.fig_ht = r.d_rpt[r.do_nbr]['high']
  g.i_dpi = r.d_rpt[r.do_nbr]['dpi']

And in the sp_app_util module, I modified reset_globals() to reset the two new variables.

  g.fig_sz = 8
  g.fig_wd = None
  g.fig_ht = None

Now in the main module I currently instantiate the figure object, a square, using:

fig = Figure(figsize=(g.fig_sz, g.fig_sz), frameon=False, dpi=72)

This will need to change. And since I am going to be making a reasonable number of random selections I am going to add a new function to the sp_app_util module. Tentatively called rnd_image_dims. For now, I will make the choice between rectangular or square roughly 50/50. Then for rectangular images, I will give the selected image ratios equal opportunity billing.

Not sure this is the best implementation, but for now it should work.

def rnd_image_dims():
  # for now, generate the widths we are most interested in more frequently
  avl_widths = [8, 8, 8, 8, 12, 15, 18, 18, 18, 18, 18, 18]
  avl_ratios = [(1.618, 1), (16, 9), (5, 4), (14, 11), (36, 11)]
  is_sq = (rng.choice([0, 1]) == 0)
  if not is_sq:
    fig_wd = rng.choice(avl_widths)
    r_wd, r_ht = rng.choice(avl_ratios)
    fig_ht = int(fig_wd * r_ht / r_wd)
    # following for debug only
    print(f"\trectangular image: {fig_wd} by {fig_ht}, ratio: {r_wd} x {r_ht}")
  else:
    fig_wd = None
    fig_wd = None
  return is_sq, fig_wd, fig_ht

Another problem, I only ever instantiate the figure object once at the start of the main module. That clearly is not going to produce a randomly size image every time I generate a new image. Now that may require a serious refactoring.

Set Image Size

Well the solution was simpler than I expected. In every image route the function setup_image(fig, ax) (in the sp_app_plot module) is called. So, I am going to use the Figure method set_size_inches(fig_wd, fig_ht) in that function to adjust the size of the figure accordingly on each call. Here’s the relevant modification.

def setup_image(fig, ax):
  ax.cla()
  # randomly determine shape and width and height if appropriate
  is_sq, fig_wd, fig_ht = sau.rnd_image_dims()
  if is_sq:
    fig.set_size_inches(g.fig_sz, g.fig_sz)
    g.fig_wd, g.fig_ht = None, None
  else:
    g.fig_wd, g.fig_ht = fig_wd, fig_ht
    fig.set_size_inches(fig_wd, fig_ht)
  fig.set_dpi(g.i_dpi)
... ...

Output on Display Page

Okay let’s get rid of that debug print statement in rnd_image_dims and output the image size info on the display page. So can use for repeats if desired. Again simpler than I expected. A bit of refactoring of the utility function get_image_dtl() was all it took. Didn’t need to change anything in the display page template.

def get_image_dtl(pg_ttl):
  if g.abg is not None and not g.do_rpt:
    list_abg = g.abg.tolist()
  else:
    list_abg = g.abg
  # deal with possible rectangular image sizes
  if g.fig_wd is None:
    fig_sz = g.fig_sz
  else:
    fig_sz = f"{g.fig_wd} by {g.fig_ht}"
  c_d = {
   'pttl': pg_ttl, 'ln_sz': g.ln_w, 'cmap': g.rcm, 'c_lph': g.alph, 'c_ndx': g.c_ndx,
    # 'pttl': pg_ttl, 'ln_sz': g.ln_w, 'cmap': g.rcm, 'c_lph': g.tmp_alph, 'c_ndx': g.c_ndx,
    'dobg': g.use_bb2, 'bgcm': g.bg_cmnm, 'bg_lph': g.bg_lpha,
    'fig_sz': fig_sz, 'fig_wd': g.fig_wd, 'fig_ht': g.fig_ht, 'dpi': g.i_dpi,
    'b_mrgn': g.b_mrgn, 'c_cyc': g.cycle, 'abg': list_abg,
  }

  return c_d

Example Image

This is a rectangular mosaic style spirograph image. The generated image is ~18 x 10 inches in size (16:9 aspect ratio). The image displayed below has been resized for display in this post. But you can clearly see the rectangular aspect ratio.

example of a rectangular mosaic style spirograph
Example of rectangular spirograph image (mosaic style)

Repeating Images

And the above refactoring works. Generating either square images or rectangular images of varying sizes and aspect ratios. But, if I tried to generate a repeat image as a .png image at a higher resolution for printing, I would not currently get a repeated image. That is because the setup_image() function doesn’t account for that situation. It arbitrarily changes the image size on each call. Let’s see if I can fix that.

Though I am uncertain where to make that change: setup_image() or rnd_image_dims(). Or maybe even somewhere else. The setup_image function is already fairly lengthy. And it just needs the dimensions from the rnd_image_dims() call. The latter is also a bit lengthy, so I am going to add a new function, get_image_dims(), which will be called by setup_image(). This new function will sort out whether we are generating a repeat and act accordingly. Calling rnd_image_dims() if a repeat is not being generated. It will return the same values as rnd_image_dims(). For now I am putting the new function in the sp_app_plot module (along with setup_image).

Note: I am ignoring the use again case. As that is about repeatedly using the same curve data, not the same plotting parameters. I.E. colour maps, image size, line width, etc.

I started with a limited function definition, refactored setup_image to call it and tested.

def get_image_dims():
  is_sq, fig_wd, fig_ht = True, None, None
  if g.do_rpt:
    ...
  else:
    is_sq, fig_wd, fig_ht = sau.rnd_image_dims()
  
  return is_sq, fig_wd, fig_ht
def setup_image(fig, ax):
  ax.cla()
  # randomly determine shape and width and height if appropriate
  is_sq, fig_wd, fig_ht = get_image_dims()
  if is_sq:
... ...

No problems for the no repeat case. Now to code the if block to cover the repeat image case.

import repeats as r

... ...

def get_image_dims():
  is_sq, fig_wd, fig_ht = True, None, None
  if g.do_rpt:
    is_rect = 'wide' in r.d_rpt[r.do_nbr] and r.d_rpt[r.do_nbr]['wide'] is not None
    if is_rect:
      is_sq = False
      fig_wd = r.d_rpt[r.do_nbr]['wide']
      fig_ht = r.d_rpt[r.do_nbr]['high']
  else:
    is_sq, fig_wd, fig_ht = sau.rnd_image_dims()
  
  return is_sq, fig_wd, fig_ht

The code still works for the no repeat cases of image generation. Now to try to repeat the image above. Won’t bother with showing you the repeat data dictionary. Had some failures. Initially because I set the key shp_mlt to the list shown in the image data at the bottom of the display page: ['r', 't', 's', 's']. Needed a string, so I used rtss. In both the preceding cases I kept getting randomly selected shapes for the 4 wheels. Finally used 'shp_mlt': 'r,t,s,s' and we were off to the races. Won’t bother showing the image, but it is a faithful representation of the original.

Done

I think that’s it for this one. Will need to eventually sort the random cases being generated as they are currently rather biased. But until we sort the image for the wall in question I will leave things as they are.

Resources