Struggling to resize an image within Tkinter - python

I have developed a console-based adventure game for my Sixth Form Computing class, and now want to migrate it into Tkinter. The main reason for this is that I can make use of pictures, mainly ones from game-icons.net.
So far so good, but the images are such high quality that they appear huge when I display them. Here is an example:
The code works by using a for loop to iterate through a list of items that are in the current area (that the player is in). Here is the code:
if len(itemKeys) > 0:
l = Label(lookWindow, text="Looking around, you see the following items....\n").pack()
for x in range(0, len(itemKeys)):
icon = PhotoImage(file=("icons\\" + itemKeys[x] + ".png"))
l = Label(lookWindow, image=icon)
l.photo = icon
l.pack()
l = Label(lookWindow, text=("" + itemKeys[x].title())).pack()
l = Label(lookWindow, text=(" " + locations[position][2][itemKeys[x]][0] + "\n")).pack()
else:
l = Label(lookWindow, text="There's nothing at this location....").pack()
The part saying ("icons\\" + itemKeys[x] + ".png") simply goes into the icons folder in the game directory and strings together a file name, which in this case would result in "key.png" because the item we're currently looking at is a key.
Now, however, I want to resize the image. I've tried using PIL (which people say is deprecated but I managed to install just fine?) but so far no luck.
Any help appreciated.
Jake
EDIT:
The question has been marked as a duplicate, but I've already tried to use it, but the person who answered seems to open a file, save it as a ".ppm"(?) file and then display it, but when I try I get a huge error that says that I couldn't display a "PIL.Image.Image".
EDIT 2:
Changed it to this:
im_temp = PILImage.open(("icons\\" + itemKeys[x] + ".png")).resize((250,250), PILImage.ANTIALIAS)
photo = PhotoImage(file=im_temp)
label = Label(lookWindow, image=photo)
label.photo = photo
label.pack()
and now get this:

For python 2 you can do something like this, it should work for python 3 as well after small changes of import
from tkinter import Tk, Label
from PIL import Image, ImageTk
root = Tk()
file = 'plant001.png'
image = Image.open(file)
zoom = 0.5
#multiple image zise by zoom
pixels_x, pixels_y = tuple([int(zoom * x) for x in image.size])
img = ImageTk.PhotoImage(image.resize((pixels_x, pixels_y))) # the one-liner I used in my app
label = Label(root, image=img)
label.image = img # this feels redundant but the image didn't show up without it in my app
label.pack()
root.mainloop()

Instead of resizing those huge images on-the-fly, you could preprocess them before bunding them with your application. I took the 'key' and 'locked chest' images and placed them in a 'icons' subdirectory, then ran this code:
from PIL import Image
import glob
for infn in glob.glob("icons/*.png"):
if "-small" in infn: continue
outfn = infn.replace(".png", "-small.png")
im = Image.open(infn)
im.thumbnail((50, 50))
im.save(outfn)
It created a 'key-small.png' and 'locked-chest-small.png', which you can use in your application instead of the original images.

Related

Trying to run a program in Pycharm

I have to test out a code in Pycharm (the bottom of the question), but I cannot figure out how to run it in Pycharm without getting this error:
usage: color.py [-h] -i IMAGE
color.py: error: the following arguments are required: -i/--image
I know that if I was using Idle I would have written this code:
/Users/syedrishad/Downloads/python-project-color-detection/color_detection.py -i
Users/syedrishad/Downloads/python-project-color-detection/colorpic.jpg
But I don't know how to run it on Pycharm
I use a mac and for some reason i always have to put the full path name or it doesn't work.(If that makes a difference)
This program makes it so if I double click on a part of the image, it shows the exact color name. All the color names are stored in this file:
/Users/syedrishad/Downloads/python-project-color-detection/colors.csv
The actual code is here:
import cv2
import numpy as np
import pandas as pd
import argparse
#Creating argument parser to take image path from command line
ap = argparse.ArgumentParser()
ap.add_argument('-i', '--image', required=True, help="Image Path")
args = vars(ap.parse_args())
img_path = args['image']
#Reading the image with opencv
img = cv2.imread(img_path)
#declaring global variables (are used later on)
clicked = False
r = g = b = xpos = ypos = 0
#Reading csv file with pandas and giving names to each column
index=["color","color_name","hex","R","G","B"]
csv = pd.read_csv('colors.csv', names=index, header=None)
#function to calculate minimum distance from all colors and get the most matching color
def getColorName(R,G,B):
minimum = 10000
for i in range(len(csv)):
d = abs(R- int(csv.loc[i,"R"])) + abs(G- int(csv.loc[i,"G"]))+ abs(B- int(csv.loc[i,"B"]))
if(d<=minimum):
minimum = d
cname = csv.loc[i,"color_name"]
return cname
#function to get x,y coordinates of mouse double click
def draw_function(event, x,y,flags,param):
if event == cv2.EVENT_LBUTTONDBLCLK:
global b,g,r,xpos,ypos, clicked
clicked = True
xpos = x
ypos = y
b,g,r = img[y,x]
b = int(b)
g = int(g)
r = int(r)
cv2.namedWindow('image')
cv2.setMouseCallback('image',draw_function)
while(1):
cv2.imshow("image",img)
if (clicked):
#cv2.rectangle(image, startpoint, endpoint, color, thickness)-1 fills entire rectangle
cv2.rectangle(img,(20,20), (750,60), (b,g,r), -1)
#Creating text string to display( Color name and RGB values )
text = getColorName(r,g,b) + ' R='+ str(r) + ' G='+ str(g) + ' B='+ str(b)
#cv2.putText(img,text,start,font(0-7),fontScale,color,thickness,lineType )
cv2.putText(img, text,(50,50),2,0.8,(255,255,255),2,cv2.LINE_AA)
#For very light colours we will display text in black colour
if(r+g+b>=600):
cv2.putText(img, text,(50,50),2,0.8,(0,0,0),2,cv2.LINE_AA)
clicked=False
#Break the loop when user hits 'esc' key
if cv2.waitKey(20) & 0xFF ==27:
break
cv2.destroyAllWindows()
If you know how to run, can you please tell me. I have been searching for an answer but come up empty-handed.
As #Yves Daoust mentioned you basically have two options:
A) Either change the code you are testing to provide the required argument or
B) Use Run->Run...->Edit Configurations... to provide the argument as you would from the command line.
Let's examine in more details the options you have:
A) The easiest way would be to provide the path to the image you want to open like this:
# replace img_path = args['image'] with
img_path = 'path/to/the/image'
which has the advantage of being extremely easy to get but it breaks the argparser functionality.
A more versatile solution would be to provide a default parameter and edit this one each time you want to open an image.
import argparse
# Add a global variable here. It will provide the argument if no other is given (via `Run->Run...->Edit Configurations...`)
IMAGE_PATH = 'path/to/image'
#Creating argument parser to take image path from command line
ap = argparse.ArgumentParser()
ap.add_argument('-i', '--image', required=True, help="Image Path", default=IMAGE_PATH)
which has the advantage of keeping the arg parse functionality intact if you are interested in this one.
B) This option means you provide the parameters yourself just like you would do in the console. The parameters are put in the Parameters: field.
For example you could pass:
--image="path/to/image"
PyCharm provides the option to Apply (button) the changes you inserted (which in this case would mean to keep the parameters stored for this script as long as the script will be in the memory).
Hope this clarify things a bit.
You have to go to Run > Run and then select the program you want to run. If it still gives an error, check the file path, otherwise, you have an error in your code. I hope that answers your question.

How to iterate over and download each image in an image collection from the Google Earth Engine python api

I am new to google earth engine and was trying to understand how to use the Google Earth Engine python api. I can create an image collection, but apparently the getdownloadurl() method operates only on individual images. So I am trying to understand how to iterate over and download all of the images in the collection.
Here is my basic code. I broke it out in great detail for some other work I am doing.
import ee
ee.Initialize()
col = ee.ImageCollection('LANDSAT/LC08/C01/T1')
col.filterDate('1/1/2015', '4/30/2015')
pt = ee.Geometry.Point([-2.40986111110000012, 26.76033333330000019])
buff = pt.buffer(300)
region = ee.Feature.bounds(buff)
col.filterBounds(region)
So I pulled the Landsat collection, filtered by date and a buffer geometry. So I should have something like 7-8 images in the collection (with all bands).
However, I could not seem to get iteration to work over the collection.
for example:
for i in col:
print(i)
The error indicates TypeError: 'ImageCollection' object is not iterable
So if the collection is not iterable, how can I access the individual images?
Once I have an image, I should be able to use the usual
path = col[i].getDownloadUrl({
'scale': 30,
'crs': 'EPSG:4326',
'region': region
})
It's a good idea to use ee.batch.Export for this. Also, it's good practice to avoid mixing client and server functions (reference). For that reason, a for-loop can be used, since Export is a client function. Here's a simple example to get you started:
import ee
ee.Initialize()
rectangle = ee.Geometry.Rectangle([-1, -1, 1, 1])
sillyCollection = ee.ImageCollection([ee.Image(1), ee.Image(2), ee.Image(3)])
# This is OK for small collections
collectionList = sillyCollection.toList(sillyCollection.size())
collectionSize = collectionList.size().getInfo()
for i in xrange(collectionSize):
ee.batch.Export.image.toDrive(
image = ee.Image(collectionList.get(i)).clip(rectangle),
fileNamePrefix = 'foo' + str(i + 1),
dimensions = '128x128').start()
Note that converting a collection to a list in this manner is also dangerous for large collections (reference). However, this is probably the most scalable method if you really need to download.
Here is my solution:
import ee
ee.Initialize()
pt = ee.Geometry.Point([-2.40986111110000012, 26.76033333330000019])
region = pt.buffer(10)
col = ee.ImageCollection('LANDSAT/LC08/C01/T1')\
.filterDate('2015-01-01','2015-04-30')\
.filterBounds(region)
bands = ['B4','B5'] #Change it!
def accumulate(image,img):
name_image = image.get('system:index')
image = image.select([0],[name_image])
cumm = ee.Image(img).addBands(image)
return cumm
for band in bands:
col_band = col.map(lambda img: img.select(band)\
.set('system:time_start', img.get('system:time_start'))\
.set('system:index', img.get('system:index')))
# ImageCollection to List
col_list = col_band.toList(col_band.size())
# Define the initial value for iterate.
base = ee.Image(col_list.get(0))
base_name = base.get('system:index')
base = base.select([0], [base_name])
# Eliminate the image 'base'.
new_col = ee.ImageCollection(col_list.splice(0,1))
img_cummulative = ee.Image(new_col.iterate(accumulate,base))
task = ee.batch.Export.image.toDrive(
image = img_cummulative.clip(region),
folder = 'landsat',
fileNamePrefix = band,
scale = 30).start()
print('Export Image '+ band+ ' was submitted, please wait ...')
img_cummulative.bandNames().getInfo()
A reproducible example can you found it here: https://colab.research.google.com/drive/1Nv8-l20l82nIQ946WR1iOkr-4b_QhISu
You could possibly use ee.ImageCollection.iterate() with a function that gets the image and adds it to a list.
import ee
def accumluate_images(image, images):
images.append(image)
return images
for img in col.iterate(accumulate_images, []):
url = img.getDownloadURL(dict(scale=30, crs='EPSG:4326', region=region))
Unfortunately I am not able to test this code as I do not have access to the API, but it might help you arrive at a solution.
I have a similar problem and was not able o solve with presented solutions. Then I have elaborated a sample code for this purpose. It iterates over an image collection in client side, then it is not affected by limitations (server side only) of .map() or .iterate().
It is possible to download the code and see its explanation here
It basically transform the ImageCollection into a list (ic.toList()). Then it performs a standard loop, and for each individual image it is possible to convert it back to ee.Image(list.get(i)), and then process one by one taking all images in the collection.
In your particular case, to download each image, the function to be called within the loop could be: getDOwnloadURL() or getThumbURL():
var url = imgNew.getDownloadURL({
region: geometry,
});
var thumbURL = imgNew.getThumbURL({region: geometry,dimensions: 512, format: 'png'});

Why os.remove() or shutil.move() can only move part of files

I want to randomly choose 10 images from the training dataset to be the test data. If I only copy the selected data to the destination path, it works. But if I want to remove the source data, it can only remove some of them. I tried both os.remove() and shutil.move() function, but the issue remains. The below is my script:
for label in labels:
training_data_path_ch1 = os.path.join(training_data_folder, label, 'ch1')
test_data_path_ch1 = os.path.join(test_data_folder, label, 'ch1')
training_data_path_ch5 = os.path.join(training_data_folder, label, 'ch5')
test_data_path_ch5 = os.path.join(test_data_folder, label, 'ch5')
ch1_imgs = listdir(training_data_path_ch1)
# Randomly select 10 images
ch1_mask = np.random.choice(len(ch1_imgs), 10)
ch1_selected_imgs = [ch1_imgs[i] for i in ch1_mask]
for selected_img in ch1_selected_imgs:
ch1_img_path = os.path.join(training_data_path_ch1, selected_img)
shutil.copy2(ch1_img_path, test_data_path_ch1)
os.remove(ch1_img_path)
print('Successfully move ' + label + ' ch1 images')
And I add an image to show the running status.
You can see, the program indeed can copy the images and remove some of the images, but why it cannot remove all images?
Any ideas? I appreciate any helps!
In:
ch1_mask = np.random.choice(len(ch1_imgs), 10)
You're potentially getting the same index returned more than once which means you're then trying to copy a file you've already copied and deleted (so you can't copy it again as it's removed), instead pass replace=False, eg:
ch1_mask = np.random.choice(len(ch1_imgs), 10, replace=False)

Comparing two images/pictures, and mark the difference

I am learning to compare two images/pictures. I found the post Compare two images the python/linux way is very useful and I have some questions regarding the technique.
Question 1:
The post shows ways to compare 2 pictures/images. Probably the easiest way is:
from PIL import Image
from PIL import ImageChops
im1 = Image.open("file1.jpg")
im2 = Image.open("file2.jpg")
diff = ImageChops.difference(im2, im1).getbbox()
print diff
when I have 2 look alike pictures and run above, it give result:
(389, 415, 394, 420)
It’s the position on the picture where the difference in 2 pictures lies. So my question is, would it be possible to mark the difference on the picture (for example, draw a circle)?
Question 2:
import math, operator
from PIL import Image
def compare(file1, file2):
image1 = Image.open(file1)
image2 = Image.open(file2)
h1 = Image.open("image1").histogram()
h2 = Image.open("image2").histogram()
rms = math.sqrt(reduce(operator.add, map(lambda a,b: (a-b)**2, h1, h2))/len(h1))
if __name__=='__main__':
import sys
file1 = ('c:\\a.jpg') # added line
file2 = ('c:\\b.jpg') # added line
file1, file2 = sys.argv[1:]
print compare(file1, file2)
When I run above, it gives an error “ValueError: need more than 0 values to unpack”, and the problem lies in this line:
file1, file2 = sys.argv[1:]
How can I have it corrected? and I tried below it neither works.
print compare('c:\\a.jpg', 'c:\\b.jpg')
Update
Added question following Matt's help.
It can draw a rectangle to mark the difference on the two images/pictures. When the two images/pictures looked general the same but there are small spots differences spread. It draws a big rectangle marking the big area include all the spots differences. Is there a way to identically mark the differences individually?
Regarding your first question:
import ImageDraw
draw = ImageDraw.Draw(im2)
draw.rectangle(diff)
im2.show()
Regarding your second question:
The error states, that sys.argv does not contain enough values to be assigned to file1 and file2. You need to pass the names of the two files you want to compare to you python script (the variable sys.arv contains the name of your script and all the command line parameters):
python name_of_your_script.py file1.jpg file2.jpg
Question 1: ImageDraw
image = Image.open("x.png")
draw = ImageDraw.Draw(image)
draw.ellipse((x-r, y-r, x+r, y+r))

Raising Image with Tkinter and Python

I have several images that I want to layer in a different order, than the order in which they were created. I am using Python with Tkinter and was wondering if someone could help me with this. The order that I create the images is:
#Using Tkinter
image1 = PhotoImage(file = "imageA.gif")
image2 = PhotoImage(file = "imageB.gif")
image3 = PhotoImage(file = "imageC.gif")
A = canvas.create_image(X,Y,image=image1)
B = canvas.create_image(X,Y,image=image2)
C = canvas.create_image(X,Y,image=image3)
The order in which I create the images cannot be changed, so as of right now C is on top of B which is on top of A.
Is there a way to change the order - without changing the order that I create them - so that B is on top of C, and both on top of A? Perhaps there is some sort of attribute like B.Ontopof(C) ? Thanks for your help in advance.
canvas.tag_raise(item)
at least I think ...

Categories