0

I have a short program that generates images of 1, 2, 3, and 4 random coloured circles to train a neural network on. My issue is, just doing 4000 images takes about 20-30 mins, and I need about 50000 images. My current method is to create the image, screenshot it, and then delete the tkinter object and restart. The issue is, windows 11 has a little fade in/slide animation when a new window is created, only about 200 ms, but it adds up quite a bit, because I need to wait for the animation to finish to take the screenshot.

So my question is, Is there another way to save a tkinter canvas other than screenshot?

I want to point out that I am putting these images into a numpy array, so putting it directly into an array is an option, but I need some way to save that as a file, so I dont need to generate the images every time.

My current code (only showing how I make 4 circles)

from PIL import ImageGrab
from tkinter import *
from random import choice, randint

colors = ["red", "blue", "green", "yellow", "orange", "purple", "black"]

s = 1   #scale 1 = 50x50 px canvas, 20x20 px circles

def four(i):
    def ss():
        x, y = screen.winfo_rootx(), screen.winfo_rooty()
        w, h = screen.winfo_width(), screen.winfo_height()
        img = ImageGrab.grab((x, y, x + w, y + h))
        img.save(f"4MC{i}.jpg")

    def des():
        root.destroy()
    
    root = Tk()

    screen = Canvas(root, width = 50 * s, height = 50 * s, bg="white")
    screen.pack()

    colors = ["red", "blue", "green", "yellow", "orange", "purple", "black"]

    x = randint(1 * s, 19 * s)
    y = randint(1 * s, 19 * s)

    screen.create_oval(x, y, x + 10 * s, y + 10 * s, fill=choice(colors), outline="")
    screen.create_oval(x, y + 20 * s, x + 10 * s, y + 30 * s, fill=choice(colors), outline="")
    screen.create_oval(x + 20 * s, y, x + 30 * s, y + 10 * s, fill=choice(colors), outline="")
    screen.create_oval(x + 20 * s, y + 20 * s, x + 30 * s, y + 30 * s, fill=choice(colors), outline="")

    root.after(200, ss)
    root.after(300, des)

    root.mainloop()

for i in range(1000):
    four(i)
Finn E
  • 309
  • 2
  • 12
  • You are using `root.after(200, ss)`, is there no 200ms delay? I think you could also put destroy, right after saving the image, because IIRC `save` will block the thread and hence `destroy` wont get executed unless the saving is done, so you can get rid of the 300 ms delay and get more accurate approach. But the real question is, why would you destroy and create a new window 1000 times when you can just edit the contents in the existing window. – Delrius Euphoria Aug 25 '22 at 18:46

1 Answers1

1

I think your approach of destroying and creating a new window over each iteration is tedious way of doing it. Instead, you can clear the canvas each time and keep creating this random circles and then click a picture of it.

from PIL import ImageGrab
from tkinter import *
from random import choice, randint

root = Tk()
colors = ["red", "blue", "green", "yellow", "orange", "purple", "black"]
s = 1 #scale 1 = 50x50 px canvas, 20x20 px circles
i = 1 # Variable for creating numbers in file-name: 1,2,3,...
LIMIT = 10 # Variable to keep limit of how many iterations/image

def create(i):
    if i <= LIMIT:
        screen.delete('all')
        
        x = randint(1 * s, 19 * s)
        y = randint(1 * s, 19 * s)

        screen.create_oval(x         , y         , x + 10 * s, y + 10 * s, fill=choice(colors), outline="")
        screen.create_oval(x         , y + 20 * s, x + 10 * s, y + 30 * s, fill=choice(colors), outline="")
        screen.create_oval(x + 20 * s, y         , x + 30 * s, y + 10 * s, fill=choice(colors), outline="")
        screen.create_oval(x + 20 * s, y + 20 * s, x + 30 * s, y + 30 * s, fill=choice(colors), outline="")
        
        if i == 1: # If it is first iteration, then the event loop hasnt been entered, so give a delay
            root.after(200, capture, screen, f'4MC{i}')
        else:
            # Give a general delay of 100ms before capturing the image
            root.after(100, capture, screen, f'4MC{i}')

        i += 1
        root.after(300, create, i) # Give a delay of 300ms before creating the circle

def capture(wid, file_name='img',file_format='png'):
    """Take screenshot of the passed widget"""
    
    x0 = wid.winfo_rootx()
    y0 = wid.winfo_rooty()
    x1 = x0 + wid.winfo_width()
    y1 = y0 + wid.winfo_height()
    
    im = ImageGrab.grab(bbox=(x0, y0, x1, y1)) # bbox means boundingbox, which is shown in the image below
    im.save(f'{file_name}.{file_format}')  # Can also say im.show() to display it

screen = Canvas(root, width = 50 * s, height = 50 * s, bg="white")
screen.pack()

create(i)

root.mainloop()

I replaced your ss with my capture which is taken from another answer of mine, the logic is same but adds more flexibility, you can use your function with necessary changes. I suggest you first run this with a LIMIT = 10 and check if the delay are okay, if not you can adjust it and then move on to produce your 50000 image-set.

If you furthermore want to convert/load your image as a numpy array, then check the first reference link below.

Extra references:

Delrius Euphoria
  • 14,910
  • 3
  • 15
  • 46
  • For some reason it is taking a screenshot of the top left corner of the window? Like each screenshot looks like this: https://imgur.com/a/qcOs7xq – Finn E Aug 26 '22 at 15:52
  • It seems to be changing where it is taking the screenshot of though. This time I am getting a bit of the top left corner of the canvas (i coloured the entire canvas black just so I could see it for texting) https://imgur.com/a/NPcIKBD – Finn E Aug 26 '22 at 15:57
  • also is there any reason screenshots of a 50x50 canvas come out as 54x54? – Finn E Aug 26 '22 at 15:57
  • @FinnE Did you try using your `ss` function instead of my `capture` ? – Delrius Euphoria Aug 26 '22 at 15:58
  • i will try it now – Finn E Aug 26 '22 at 16:00
  • oh hold on i just remembered something. The code I had in my question had 4 separate functions, one for each number of circles, because when i put them all in one function (using a for loop up to 4), I had the same issue. I assumed it was somehow taking a screenshot of the location of the window before it (as each new window opened it would be down and to the right of the one before it). But when I had 4 separate functions and ss defined in each of them (having 4 functions and 1 definition of ss also didnt work), it worked fine. – Finn E Aug 26 '22 at 16:05
  • @FinnE Weird because the code works fine on my system – Delrius Euphoria Aug 26 '22 at 16:06
  • it clearly isnt taking a screenshot of the location of the window before it, becuase now there is only one window, so what would the issue be? – Finn E Aug 26 '22 at 16:06
  • I tried `x1 = x - w`, thinking that maybe its coordinates are weird, but that just gave me this error: `SystemError: tile cannot extend outside image` – Finn E Aug 26 '22 at 16:08
  • @FinnE The coordinate cannot get messed up because it is basically dynamic. The coordinate is based on the window position, as it changes the coordinates also change. Are you sure you are using my exact same code? – Delrius Euphoria Aug 26 '22 at 16:09
  • i meant like if for some reason they counted up instead of down or something. It just seemed like it was taking a screenshot of 54 pixels above and to the left of the top left corner of the canvas, instead of down and to the right. But since then i have gotten results with some of the canvas in it. yes I am using the same code. The only thing i changed was where it saves the pictures, and when I change it back exactly it still doesnt work. – Finn E Aug 26 '22 at 16:11
  • so my comment above means i cant even like add 20px for example to the coordinates, because it changes slightly every time what it is taking a picture of – Finn E Aug 26 '22 at 16:12
  • @FinnE Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/247588/discussion-between-delrius-euphoria-and-finn-e). – Delrius Euphoria Aug 26 '22 at 16:16