Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Randomness of Python's random

Tags:

python

random

I'm using Python to generate images using dashed lines for stippling. The period of the dashing is constant, what changes is dash/space ratio. This produces something like this:

enter image description here

However in that image the dashing has a uniform origin and this creates unsightly vertical gutters. So I tried to randomize the origin to remove the gutters. This sort of works but there is an obvious pattern:

enter image description here

Wondering where this comes from I made a very simple test case with stacked dashed straight lines:

  • dash ratio: 50%
  • dash period 20px
  • origin shift from -10px to +10px using random.uniform(-10.,+10.)(*) (after an initial random.seed()

enter image description here

And with added randomness:

enter image description here

So there is still pattern. What I don't understand is that to get a visible gutter you need to have 6 or 7 consecutive values falling in the same range (says, half the total range), which should be a 1/64 probability but seems to happen a lot more often in the 200 lines generated.

Am I misunderstanding something? Is it just our human brain which is seeing patterns where there is none? Could there be a better way to generate something more "visually random" (python 2.7, and preferably without installing anything)?

(*) partial pixels are valid in that context

Annex: the code I use (this is a Gimp script):

#!/usr/bin/env python # -*- coding: iso-8859-15 -*-  # Python script for Gimp (requires Gimp 2.10) # Run on a 400x400 image to see something without having to wait too much # Menu entry is in "Test" submenu of image menubar  import random,traceback from gimpfu import *  def constant(minShift,maxShift):     return 0  def triangle(minShift,maxShift):     return random.triangular(minShift,maxShift)  def uniform(minShift,maxShift):     return random.uniform(minShift,maxShift)  def gauss(minShift,maxShift):     return random.gauss((minShift+maxShift)/2,(maxShift-minShift)/2)  variants=[('Constant',constant),('Triangle',triangle),('Uniform',uniform),('Gauss',gauss)]  def generate(image,name,generator):     random.seed()     layer=gimp.Layer(image, name, image.width, image.height, RGB_IMAGE,100, LAYER_MODE_NORMAL)     image.add_layer(layer,0)     layer.fill(FILL_WHITE)     path=pdb.gimp_vectors_new(image,name)      # Generate path, horizontal lines are 2px apart,      # Start on left has a random offset, end is on the right edge right edge     for i in range(1,image.height, 2):         shift=generator(-10.,10.)         points=[shift,i]*3+[image.width,i]*3         pdb.gimp_vectors_stroke_new_from_points(path,0, len(points),points,False)     pdb.gimp_image_add_vectors(image, path, 0)      # Stroke the path     pdb.gimp_context_set_foreground(gimpcolor.RGB(0, 0, 0, 255))     pdb.gimp_context_set_stroke_method(STROKE_LINE)     pdb.gimp_context_set_line_cap_style(0)     pdb.gimp_context_set_line_join_style(0)     pdb.gimp_context_set_line_miter_limit(0.)     pdb.gimp_context_set_line_width(2)     pdb.gimp_context_set_line_dash_pattern(2,[5,5])     pdb.gimp_drawable_edit_stroke_item(layer,path)  def randomTest(image):     image.undo_group_start()     gimp.context_push()      try:         for name,generator in variants:             generate(image,name,generator)     except Exception as e:         print e.args[0]         pdb.gimp_message(e.args[0])         traceback.print_exc()      gimp.context_pop()     image.undo_group_end()     return;  ### Registration desc="Python random test"  register(     "randomize-test",desc,'','','','',desc,"*",     [(PF_IMAGE, "image", "Input image", None),],[],     randomTest,menu="<Image>/Test", )  main() 
like image 425
xenoid Avatar asked May 01 '19 09:05

xenoid


People also ask

What is randomness in Python?

Python offers random module that can generate random numbers. These are pseudo-random number as the sequence of number generated depends on the seed. If the seeding value is same, the sequence will be the same. For example, if you use 2 as the seeding value, you will always see the following sequence.

How do you simulate randomness in Python?

Random integer values can be generated with the randint() function. This function takes two arguments: the start and the end of the range for the generated integer values. Random integers are generated within and including the start and end of range values, specifically in the interval [start, end].

How good are Python random numbers?

Python has nothing that allows you to generate "truly random" or "authentic random" numbers, in the sense that they are uniformly distributed and independent of everything else (especially the latter). In any case, the distinction between "pseudorandom" and "truly random" numbers is not what applications care about.


2 Answers

Think of it like this: a gutter is perceptible until it is obstructed (or almost so). This only happens when two successive lines are almost completely out of phase (with the black segments in the first line lying nearly above the white segments in the next). Such extreme situations only happens about one out of every 10 rows, hence the visible gutters which seem to extend around 10 rows before being obstructed.

Looked at another way -- if you print out the image, there really are longish white channels through which you can easily draw a line with a pen. Why should your mind not perceive them?

To get better visual randomness, find a way to make successive lines dependent rather than independent in such a way that the almost-out-of-phase behavior appears more often.

like image 156
John Coleman Avatar answered Oct 02 '22 21:10

John Coleman


There's at least one obvious reason why we see a pattern in the "random" picture : the 400x400 pixels are just the same 20x400 pixels repeated 20 times.

enter image description here

So every apparent movement is repeated 20 times in parallel, which really helps the brain analyzing the picture.

Actually, the same 10px wide pattern is repeated 40 times, alternating between black and white:

enter image description here

You could randomize the dash period separately for each line (e.g. between 12 and 28):

enter image description here

Here's the corresponding code :

import numpy as np import random  from matplotlib import pyplot as plt %matplotlib inline plt.rcParams['figure.figsize'] = [13, 13]  N = 400  def random_pixels(width, height):     return np.random.rand(height, width) < 0.5  def display(table):     plt.imshow(table, cmap='Greys', interpolation='none')     plt.show()  display(random_pixels(N, N))  def stripes(width, height, stripe_width):     table = np.zeros((height, width))     cycles = width // (stripe_width * 2) + 1     pattern = np.concatenate([np.zeros(stripe_width), np.ones(stripe_width)])     for i in range(height):         table[i] = np.tile(pattern, cycles)[:width]     return table  display(stripes(N, N, 10))  def shifted_stripes(width, height, stripe_width):     table = np.zeros((height, width))     period = stripe_width * 2     cycles = width // period + 1     pattern = np.concatenate([np.zeros(stripe_width), np.ones(stripe_width)])     for i in range(height):         table[i] = np.roll(np.tile(pattern, cycles), random.randrange(0, period))[:width]     return table  display(shifted_stripes(N, N, 10))  def flexible_stripes(width, height, average_width, delta):     table = np.zeros((height, width))     for i in range(height):         stripe_width = random.randint(average_width - delta, average_width + delta)         period = stripe_width * 2         cycles = width // period + 1         pattern = np.concatenate([np.zeros(stripe_width), np.ones(stripe_width)])         table[i] = np.roll(np.tile(pattern, cycles), random.randrange(0, period))[:width]     return table  display(flexible_stripes(N, N, 10, 4)) 
like image 36
Eric Duminil Avatar answered Oct 02 '22 21:10

Eric Duminil