How to detect objects shape from images using python3? - python-3.x

i want to write a code in python3 that detects objects shapes from images.
I want to choose a pixel from an object in the given image and find the neighbours pixels.
If they have the same RGB value that means that they are part of the object.
When neighbour pixel changes the RGB value with an ajustable difference from original pixel the algorithm should stop searching for neighbours. I think that this will work unless the backgroud and object have the same color.
I have found a way to put the pixels with same color in an rectangle,but this will not help me. I want to save just the shape of the object and put it in a different image.
For example,
If i want to start my algorithm from the middle of an object, let's
say a black table with a white background,the algorithm will find
pixels with the same color in any direction. When the neighbour pixel
RGB values will change with more than 30 units in one direction,the
algorithm will stop going in that direction,and will start going in
another direction untill I have the shape of the table.
I found a code on another post that help me to determinate regions of pixels with a shared value using PIL
Thanks!
from collections import defaultdict
from PIL import Image, ImageDraw
def connected_components(edges):
"""
Given a graph represented by edges (i.e. pairs of nodes), generate its
connected components as sets of nodes.
Time complexity is linear with respect to the number of edges.
"""
neighbors = defaultdict(set)
for a, b in edges:
neighbors[a].add(b)
neighbors[b].add(a)
seen = set()
def component(node, neighbors=neighbors, seen=seen, see=seen.add):
unseen = set([node])
next_unseen = unseen.pop
while unseen:
node = next_unseen()
see(node)
unseen |= neighbors[node] - seen
yield node
return (set(component(node)) for node in neighbors if node not in seen)
def matching_pixels(image, test):
"""
Generate all pixel coordinates where pixel satisfies test.
"""
width, height = image.size
pixels = image.load()
for x in xrange(width):
for y in xrange(height):
if test(pixels[x, y]):
yield x, y
def make_edges(coordinates):
"""
Generate all pairs of neighboring pixel coordinates.
"""
coordinates = set(coordinates)
for x, y in coordinates:
if (x - 1, y - 1) in coordinates:
yield (x, y), (x - 1, y - 1)
if (x, y - 1) in coordinates:
yield (x, y), (x, y - 1)
if (x + 1, y - 1) in coordinates:
yield (x, y), (x + 1, y - 1)
if (x - 1, y) in coordinates:
yield (x, y), (x - 1, y)
yield (x, y), (x, y)
def boundingbox(coordinates):
"""
Return the bounding box of all coordinates.
"""
xs, ys = zip(*coordinates)
return min(xs), min(ys), max(xs), max(ys)
def disjoint_areas(image, test):
"""
Return the bounding boxes of all non-consecutive areas
who's pixels satisfy test.
"""
for each in connected_components(make_edges(matching_pixels(image, test))):
yield boundingbox(each)
def is_black_enough(pixel):
r, g, b = pixel
return r < 10 and g < 10 and b < 10
if __name__ == '__main__':
image = Image.open('some_image.jpg')
draw = ImageDraw.Draw(image)
for rect in disjoint_areas(image, is_black_enough):
draw.rectangle(rect, outline=(255, 0, 0))
image.show()

Try using opencv with Python.
With opencv you can make advanced image analysis and there are many tutorials to use it.
http://www.pyimagesearch.com/2014/04/21/building-pokedex-python-finding-game-boy-screen-step-4-6/

Related

Why NMSboxes is not eleminating multiple bounding boxes?

First of all here is my code :
image = cv2.imread(filePath)
height, width, channels = image.shape
# USing blob function of opencv to preprocess image
blob = cv2.dnn.blobFromImage(image, 1 / 255.0, (416, 416),
swapRB=True, crop=False)
#Detecting objects
net.setInput(blob)
outs = net.forward(output_layers)
# Showing informations on the screen
class_ids = []
confidences = []
boxes = []
for out in outs:
for detection in out:
scores = detection[5:]
class_id = np.argmax(scores)
confidence = scores[class_id]
if confidence > 0.7:
# Object detected
center_x = int(detection[0] * width)
center_y = int(detection[1] * height)
w = int(detection[2] * width)
h = int(detection[3] * height)
# Rectangle coordinates
x = int(center_x - w / 2)
y = int(center_y - h / 2)
boxes.append([x, y, w, h])
confidences.append(float(confidence))
class_ids.append(class_id)
indexes = cv2.dnn.NMSBoxes(boxes, confidences,score_threshold=0.4,nms_threshold=0.8,top_k=1)
font = cv2.FONT_HERSHEY_PLAIN
colors = np.random.uniform(0, 255, size=(len(classes), 3))
labels = ['bicycle','car','motorbike','bus','truck']
for i in range(len(boxes)):
if i in indexes:
label = str(classes[class_ids[i]])
if label in labels:
x, y, w, h = boxes[i]
color = colors[class_ids[i]]
cv2.rectangle(image, (x, y), (x + w, y + h), color, 2)
cv2.putText(image, label, (x, y + 30), font, 2, color, 3)
cv2.imshow(fileName,image)
My Question is : Isn't cv2.dnn.NMSBoxes is suppose to eliminate multiple bounding boxes? then why I still get output like sample below :
What I expected is something like below :
Did I do something wrong with my code? Is there any better alternative? Thank you very much for your help.
The process of NMS goes like this
Input - A list of Proposal boxes B, corresponding confidence scores S and overlap threshold N
Output - A list of filtered proposals D
Algorithm/steps
Select the proposal with highest confidence score, remove it from B and add it to the final proposal list D. (Initially D is empty)
Now compare this proposal with all the proposals — calculate the IOU (Intersection over Union) of this proposal with every other proposal. If the IOU is greater than the threshold N, remove that proposal from B
Again take the proposal with the highest confidence from the remaining proposals in B and remove it from B and add it to D
Once again calculate the IOU of this proposal with all the proposals in B and eliminate the boxes which have high IOU than threshold
This process is repeated until there are no more proposals left in B
The threshold that is being referred to here is nothing but the nms_threshold.
In the cv2.dnn.NMSBoxes function, nms_threshold is the IOU threshold used in non-maximum suppression.
So if you have a large value, you are enforcing two boxes to have a very high overlap (which is usually not the case) and the box will be removed only if it has an IOU more than 0.8 with another box. Since there's usually not this much overlap, the boxes won't be removed. Reducing this value will make it easier to remove redundant detections
Hope this makes sense
You can read more about Non-Maxima Suppresion here

Region growing implementation in python, without seeding

I am trying to implement the region growing segmentation algorithm in python, but I am not allowed to use seed points. My idea so far is this:
Start from the very first pixel, verify its neighbors for boundaries check (within width and height), then verify the neighbors so that they are within the threshold (I obtained this by using the euclidean distance between the current pixel and the neighbor). To be made sure that I don't visit the same pixel again, I created a matrix with the same dimensions as the image (width, height) and made all elements 0 in the beginning. I will start from currentArea number 1 and increment as it goes. In the end, after obaining all areas with their respective average colors, I will use a function that writes down the new Image based on the tuples I have obtained. (averagePixelColor, areaNumber)
This is my code:
from PIL import Image
from scipy.spatial import distance
import statistics
import numpy as np
import sys
sys.setrecursionlimit(10**9)
# SCRIPT: color palette reduction applier script
# SCRIPT: this is the second method. region growing segmentation
# list of red values of pixels
rList = []
# list of green values of pixels
gList = []
# list of blue values of pixels
bList = []
# this matrix will be initially 0, then every region growth
# will be updated to its number
overlayMatrix = []
# starting area number
currentArea = 1
def isValidPixel(x, y, width, height):
if x < 0 or x >= width:
return False
if y < 0 or y >= height:
return False
return True
def get_average_color():
global rList
global gList
global bList
# set values to none
r = None
g = None
b = None
# get average value for each chanel
if rList != []:
r = sum(rList)/len(rList)
g = sum(gList)/len(gList)
b = sum(bList)/len(bList)
# make values integers to be used as pixel
r = int(r)
g = int(g)
b = int(b)
# return values
return (r, g, b)
def add_pixel_to_lists(pixel):
global rList
global gList
global bList
rList.append(pixel[0])
gList.append(pixel[1])
bList.append(pixel[2])
def region_growing(x, y, pixel, img, currentArea):
global overlayMatrix
global rList
global gList
global bList
# get width, heihgt
width, height = img.size
# set this pixel to be visited on current area
overlayMatrix[x][y] = currentArea
# set a list for all possible neighbours
neighbouringPixels = [(x-1, y), (x-1, y-1), (x-1, y+1), (x, y-1), (x, y+1), (x+1, y), (x+1, y-1), (x+1, y+1)]
# filter to get only valid neighbours
validPixels = [x for x in neighbouringPixels if isValidPixel(x[0], x[1], width, height) == True]
# filter pixels to be not visited
notVisitedPixels = [x for x in validPixels if overlayMatrix[x[0]][x[1]] == 0]
# set a threshold value
threshold = 5
# filter to get only pixels in threshold
thresholdPixels = []
thresholdPixels = [x for x in notVisitedPixels if distance.euclidean(img.getpixel(x), pixel) < threshold]
# set the list for pixels to make recursive calls
toVisitRecursive = []
# for every pixel that is a valid neighbour, add it to the toVisit list in recursive call
# and add its rgb values to the lists so an average can be computed
for pixel in thresholdPixels:
toVisitRecursive.append(pixel)
add_pixel_to_lists(img.getpixel(pixel))
# compute average
averagePixel = get_average_color()
# if I still have neighoburs that haven't been visited
# and are within the threshold, get first from list
# remove it so we don't do the same again, then apply
# the algorithm for it
if toVisitRecursive != []:
pixel = toVisitRecursive[0]
toVisitRecursive.remove(pixel)
region_growing(pixel[0], pixel[1], averagePixel, img, currentArea)
# finally, return
return (averagePixel, currentArea+1)
def write_image():
pass
# this will write to the image based on the list of tuples
# average color to the area with number X
def palette_reduction_mt2(input_image):
# open original image
img = Image.open(input_image)
tuple_list = []
# get width, height
width, height = img.size
# make overlay matrix of 0, initial
global overlayMatrix
overlayMatrix = np.zeros((width, height), dtype=np.int32)
# create new image
newimg = Image.new("RGB", (width, height), "white")
currentArea = 1
# iterate through image pixels
for y in range(0, height-1):
for x in range(0, width-1):
global rList
global gList
global bList
# get pixel from edge image
p = img.getpixel((x, y))
# apply region growing
average, currentArea = region_growing(x, y, p, img, currentArea)
tuple_list.append((average, currentArea-1))
# reset color lists to compute new average
rList = []
gList = []
bList = []
print(tuple_list)
# Save image
newimg.save("images/reduced_mt1.jpg")
# return the name of the image
return "images/reduced_mt1.jpg"
For some reason, when I print the tuples list, I only get a single value: [(0,0,0), 1]
Can anyone please point me in the right direction?
Thank you!
Seems like you are not actually iterating over the toVisitRecursive list.
Maybe you should add a loop like:
for pixel in toVisitRecursive:
// region_growing...
or change this line if toVisitRecursive != []: for a while loop;

Drawing Scaled grid system using openCV and python

What I want to do is draw a grid on an image that has smaller squares at the top and larger ones near the bottom. I am lost on how to do this though. I figured out how to draw a grid on the image using open cv but don't know how to fine turn it to get the results I want.
What I will be using this for is to find where a bounding box point is for a detected object. I will need a smaller bounding box at the top and a larger one closer to the bottom of the image.
This is what I want to do:
But I cannot figure out how to get the curves in the code.
This is what I have so far:
And this the is code that I am using.
'''
#This method draws simple grid overthe image based on the passed step
#The pxstep controls the size of the grid
'''
def drawBasicGrid(image, pxstep, midX, midY):
x = pxstep
y = pxstep
#Draw all x lines
while x < img.shape[1]:
cv2.line(img, (x, 0), (x, img.shape[0]), color=(255, 0, 255), thickness=1)
x += pxstep
while y < img.shape[0]:
cv2.line(img, (0, y), (img.shape[1], y), color=(255, 0, 255),thickness=1)
y += pxstep
This draws the basic grid.
and this creates thebounding boxes that I use for detection of the bounding points of a detected object.
def makeBoundingBox(h,w, step):
#BBox is 100*100 square or step which is defied
y = 0
bbox = []
while y < h:
#print("Y value", y)
x=0
while x < w:
#print("X Value", x)
bbox.append([(x,y), (x+step, y+step)])
x += step
y += step
return bbox
And this is how I am using it.
#Prepare Images
img = cv2.imread(image)
#img = imutils.resize(img, width=300)
#gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
(H,W) = img.shape[:2]
#Create bounding boxes for the image
'''
#Creates the boxes for people detection
#It will look for which bounding boxes the person is in and then
#Draw a box around their feet
'''
#People Detection
blob = cv2.dnn.blobFromImage(cv2.resize(img, (300, 300)),0.007843,(300, 300), 127.5)
net.setInput(blob)
detections = net.forward()
'''
#Get the center point of the image
'''
midY = H//2
midX = W//2
print("Middle Y pixel", midY)
#Draw center line
cv2.line(img, (0, midY), (W, midY), color=green, thickness=2)
cv2.line(img, (midX, 0), (midX, H), color=green, thickness=2)
#Visual grid drawn
drawBasicGrid(img, step, midX,midY)
bbox = makeBoundingBox(H, W, step) #Used for finding where the endx and startx BBOX points are
Any help on this will be appreciated.

Splitting HSV mask into multiple rectangles

I created a HSV mask from the image. The result like following:
My goal is draw muliple rectangles that fit mask height or width, like following:
I encouter 2 problem.
I don't know how to locate the starting and ending point in mask for creating rectangle. If I use for loop to scan though mask row by row, it may split mask into 2 part.
Sometime, there also contain 2 different mask in one image. How can I draw rectangles?
Anyone can give me some suggestion?
You can search for your biggest contour (cross-like shape) with cv2.findContour(). It returns an array of coordinates of the contour. Then you can search your contour for point that has the highest X coordinate (that being your most right point), lowest X coordinat (most left point), highest Y coordinate (being most bottom point) and lowest Y coordinate (being your highest point). After you have all 4 values you can search the contour again for all points that have that values and append them in 4 different lists which you sort them later so you can get these points as drown on the bottom picture:
cv2.circle(img,(top_vertical[0]), 4, (0,0,255), -1)
cv2.circle(img,(top_vertical[-1]), 4, (0,0,255), -1)
cv2.circle(img,(bottom_vertical[0]), 4, (0,0,255), -1)
cv2.circle(img,(bottom_vertical[-1]), 4, (0,0,255), -1)
cv2.circle(img,(left_horizontal[0]), 4, (0,0,255), -1)
cv2.circle(img,(left_horizontal[-1]), 4, (0,0,255), -1)
cv2.circle(img,(right_horizontal[0]), 4, (0,0,255), -1)
cv2.circle(img,(right_horizontal[-1]), 4, (0,0,255), -1)
From this point forward I have transformed the lists into numpy arrays as it is easier for me. You can do it any other way.
Then it is just a matter of how many rectangles you want and how do you want to display them. In my example code you have to input how many same size rectangles you want and the last one is the size of what is left. I have first displayed rectangles on Y coordinate (green color) and then on X coordinate, which is divided on two segments (left and right) because they slightly vary in distance and I did not want to draw over the Y coordinate rectangles as they are not drawn on your example image. You can change the logic of writting the rectangles as you wish. Hope it helps a bit or give an idea on how to proceede. Cheers!
Example code:
import cv2
import numpy as np
# Read image and search for contours.
img = cv2.imread('cross.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_, threshold = cv2.threshold(gray, 100, 255, cv2.THRESH_BINARY)
_, contours, hierarchy = cv2.findContours(threshold,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)
# Select the biggest contour (if you wish to segmentize only the cross-like contour).
cnt = max(contours, key=cv2.contourArea)
# Create empty lists for appending key points.
top_vertical = []
bottom_vertical = []
left_horizontal = []
right_horizontal = []
# Setting the starter values for N, S, E, W.
top = 10000
bottom = 0
left = 10000
right = 0
# Loop to get highest key values of N, S, E, W.
for i in cnt:
y = int(i[:,1])
x = int(i[:, 0])
if x < left:
left = int(x)
if x > right:
right = int(x)
if y < top:
top = int(y)
if y > bottom:
bottom = int(y)
# Loop for appending all points containing key values of N, S, E, W.
for i in cnt:
if int(i[:,1]) == top:
up = (int(i[:,0]), int(i[:,1]))
top_vertical.append(up)
if int(i[:,1]) == bottom:
down = (int(i[:,0]), int(i[:,1]))
bottom_vertical.append(down)
if int(i[:,0]) == left:
l = (int(i[:,0]), int(i[:,1]))
left_horizontal.append(l)
if int(i[:,0]) == right:
r = (int(i[:,0]), int(i[:,1]))
right_horizontal.append(r)
# Sorting the lists.
top_vertical.sort(key=lambda tup: tup[0])
bottom_vertical.sort(key=lambda tup: tup[0])
left_horizontal.sort(key=lambda tup: tup[1])
right_horizontal.sort(key=lambda tup: tup[1])
# Optional drawing of key points.
'''cv2.circle(img,(top_vertical[0]), 4, (0,0,255), -1)
cv2.circle(img,(top_vertical[-1]), 4, (0,0,255), -1)
cv2.circle(img,(bottom_vertical[0]), 4, (0,0,255), -1)
cv2.circle(img,(bottom_vertical[-1]), 4, (0,0,255), -1)
cv2.circle(img,(left_horizontal[0]), 4, (0,0,255), -1)
cv2.circle(img,(left_horizontal[-1]), 4, (0,0,255), -1)
cv2.circle(img,(right_horizontal[0]), 4, (0,0,255), -1)
cv2.circle(img,(right_horizontal[-1]), 4, (0,0,255), -1)'''
# Transforming lists to arrays.
top_vertical = np.array(top_vertical)
bottom_vertical = np.array(bottom_vertical)
left_horizontal = np.array(left_horizontal)
right_horizontal = np.array(right_horizontal)
# Calculating height and weight of the contour.
distance_y = bottom - top
distance_x = right - left
# Inputs for the number of same size segments.
a = input('Input the number of same size segments in Y coordinate: ')
b = input('Input the number of same size segments in left X coordinate: ')
c = input('Input the number of same size segments in right X coordinate: ')
# Calculation of area per segment and limit for the lenght of combined segments (height and weight) .
segment_y = distance_y/int(a)
segment_x_reference = int(top_vertical[0,0]) - int(left_horizontal[0,0])
segment_x = segment_x_reference/int(b)
segment_x_right_reference = int(right_horizontal[0,0]) - int(top_vertical[-1,0])
segment_x_right = segment_x_right_reference/int(c)
# Drawing rectangles on the Y axis.
for i in range(1,20):
sq = int(segment_y)*i
if sq < distance_y:
cv2.rectangle(img,(top_vertical[0,0], top_vertical[0,1]),((top_vertical[-1,0]),top_vertical[0,1] + sq),(0,255,0),1)
else:
sq = distance_y
cv2.rectangle(img,(top_vertical[0,0], top_vertical[0,1]),((top_vertical[-1,0]),sq),(0,255,0),1)
break
# Drawing rectangles on the left side of X axis.
for i in range(1,20):
sq = int(segment_x)*i
if sq < segment_x_reference:
cv2.rectangle(img,(left_horizontal[0,0], left_horizontal[0,1]),((left_horizontal[0,0])+sq, left_horizontal[-1,1]),(255,0,0),1)
else:
sq = segment_x_reference
cv2.rectangle(img,(left_horizontal[0,0], left_horizontal[0,1]),((left_horizontal[0,0])+sq, left_horizontal[-1,1]),(255,0,0),1)
break
# Drawing rectangles on the right side of X axis.
for i in range(1,20):
sq = int(segment_x_right)*i
if sq < segment_x_right_reference:
cv2.rectangle(img,(right_horizontal[0,0], right_horizontal[0,1]),((right_horizontal[0,0])-sq, right_horizontal[-1,1]),(255,0,0),1)
else:
sq = segment_x_right_reference
cv2.rectangle(img,(right_horizontal[0,0], right_horizontal[0,1]),((right_horizontal[0,0])-sq, right_horizontal[-1,1]),(255,0,0),1)
break
# Displaying result.
cv2.imshow('img', img)
Result:
Input the number of same size segments in Y coordinate: 5
Input the number of same size segments in left X coordinate: 2
Input the number of same size segments in right X coordinate: 2

TkInter python - creating points on a canvas to obtain a Sierpinsky triangle

I want to make a program which plots a Sierpinsky triangle (of any modulo). In order to do it I've used TkInter. The program generates the fractal by moving a point randomly, always keeping it in the sides. After repeating the process many times, the fractal appears.
However, there's a problem. I don't know how to plot points on a canvas in TkInter. The rest of the program is OK, but I had to "cheat" in order to plot the points by drawing small lines instead of points. It works more or less, but it doesn't have as much resolution as it could have.
Is there a function to plot points on a canvas, or another tool to do it (using Python)? Ideas for improving the rest of the program are also welcome.
Thanks. Here's what I have:
from tkinter import *
import random
import math
def plotpoint(x, y):
global canvas
point = canvas.create_line(x-1, y-1, x+1, y+1, fill = "#000000")
x = 0 #Initial coordinates
y = 0
#x and y will always be in the interval [0, 1]
mod = int(input("What is the modulo of the Sierpinsky triangle that you want to generate? "))
points = int(input("How many points do you want the triangle to have? "))
tkengine = Tk() #Window in which the triangle will be generated
window = Frame(tkengine)
window.pack()
canvas = Canvas(window, height = 700, width = 808, bg = "#FFFFFF") #The dimensions of the canvas make the triangle look equilateral
canvas.pack()
for t in range(points):
#Procedure for placing the points
while True:
#First, randomly choose one of the mod(mod+1)/2 triangles of the first step. a and b are two vectors which point to the chosen triangle. a goes one triangle to the right and b one up-right. The algorithm gives the same probability to every triangle, although it's not efficient.
a = random.randint(0,mod-1)
b = random.randint(0,mod-1)
if a + b < mod:
break
#The previous point is dilated towards the origin of coordinates so that the big triangle of step 0 becomes the small one at the bottom-left of step one (divide by modulus). Then the vectors are added in order to move the point to the same place in another triangle.
x = x / mod + a / mod + b / 2 / mod
y = y / mod + b / mod
#Coordinates [0,1] converted to pixels, for plotting in the canvas.
X = math.floor(x * 808)
Y = math.floor((1-y) * 700)
plotpoint(X, Y)
tkengine.mainloop()
If you are wanting to plot pixels, a canvas is probably the wrong choice. You can create a PhotoImage and modify individual pixels. It's a little slow if you plot each individual pixel, but you can get dramatic speedups if you only call the put method once for each row of the image.
Here's a complete example:
from tkinter import *
import random
import math
def plotpoint(x, y):
global the_image
the_image.put(('#000000',), to=(x,y))
x = 0
y = 0
mod = 3
points = 100000
tkengine = Tk() #Window in which the triangle will be generated
window = Frame(tkengine)
window.pack()
the_image = PhotoImage(width=809, height=700)
label = Label(window, image=the_image, borderwidth=2, relief="raised")
label.pack(fill="both", expand=True)
for t in range(points):
while True:
a = random.randint(0,mod-1)
b = random.randint(0,mod-1)
if a + b < mod:
break
x = x / mod + a / mod + b / 2 / mod
y = y / mod + b / mod
X = math.floor(x * 808)
Y = math.floor((1-y) * 700)
plotpoint(X, Y)
tkengine.mainloop()
You can use canvas.create_oval with the same coordinates for the two corners of the bounding box:
from tkinter import *
import random
import math
def plotpoint(x, y):
global canvas
# point = canvas.create_line(x-1, y-1, x+1, y+1, fill = "#000000")
point = canvas.create_oval(x, y, x, y, fill="#000000", outline="#000000")
x = 0 #Initial coordinates
y = 0
#x and y will always be in the interval [0, 1]
mod = int(input("What is the modulo of the Sierpinsky triangle that you want to generate? "))
points = int(input("How many points do you want the triangle to have? "))
tkengine = Tk() #Window in which the triangle will be generated
window = Frame(tkengine)
window.pack()
canvas = Canvas(window, height = 700, width = 808, bg = "#FFFFFF") #The dimensions of the canvas make the triangle look equilateral
canvas.pack()
for t in range(points):
#Procedure for placing the points
while True:
#First, randomly choose one of the mod(mod+1)/2 triangles of the first step. a and b are two vectors which point to the chosen triangle. a goes one triangle to the right and b one up-right. The algorithm gives the same probability to every triangle, although it's not efficient.
a = random.randint(0,mod-1)
b = random.randint(0,mod-1)
if a + b < mod:
break
#The previous point is dilated towards the origin of coordinates so that the big triangle of step 0 becomes the small one at the bottom-left of step one (divide by modulus). Then the vectors are added in order to move the point to the same place in another triangle.
x = x / mod + a / mod + b / 2 / mod
y = y / mod + b / mod
#Coordinates [0,1] converted to pixels, for plotting in the canvas.
X = math.floor(x * 808)
Y = math.floor((1-y) * 700)
plotpoint(X, Y)
tkengine.mainloop()
with a depth of 3 and 100,000 points, this gives:
Finally found a solution: if a 1x1 point is to be placed in pixel (x,y), a command which does it exactly is:
point = canvas.create_line(x, y, x+1, y+1, fill = "colour")
The oval is a good idea for 2x2 points.
Something remarkable about the original program is that it uses a lot of RAM if every point is treated as a separate object.

Resources