Tutorial - embeding scipy + matplotlib with tkinter to work on images in a GUI framework

in #utopian-io7 years ago (edited)

Repository

https://github.com/scipy/scipy/

What Will I Learn?

  • How to make a gui using tkinter libraries
  • Display images using matplotlib on a tkinter frame
  • Image manipulation and processing using Numpy and Scipy

Requirements

  • Windows OS (for this tutorial I'm using windows)
  • Python 3.6 distribution
  • Scipy, matplotlib, numpy (You can Install by using Anaconda or pip or any other way you think might work better for you)
  • Basic understanding of python programming language

Difficulty

  • Intermediate

Tutorial Contents

Python has huge number of GUI frameworks you can find here. tkinter is traditionally bundled with python and here we're going to use it as the gui framework.

Make an empty frame with a button

First we make a simple gui to understand the basic definitions of tkinter gui class and methods. in the code below, made a simple frame which is a gui container with a single button inside which has no method binds to it yet.

# from tkinter import *  will import everything from tkinter.
# we can also use import tkinter as tk
# but that way for every attribute we must type tk.attrib-name
from tkinter import *

# this is the main class, ImageProc, inherited from Frame
class ImageProc(Frame):

    # Defines settings upon initialization.
    def __init__(self, master=None):

        # parameters we want to send through Frame class
        Frame.__init__(self, master)

        #reference to the master widget, which is the tk window
        self.master = master

        #initialization of imageProc frame
        self.init_imageProc()

     # here we create the initialization frame
    def init_imageProc(self):

        # set the title of master widget
        self.master.title("Image Processing")

        # allowing the widget to take the full space of the root window
        self.pack(fill=BOTH, expand=1)
        self.create_widgets()
        # creating a menu instance

    def create_widgets(self):
            self.browse = Button(self)
            self.browse["text"] = "Browse Image"
            self.browse["command"] = ""
            self.browse.grid(row=0, column=0)

# root window created. Here, that would be the only window, but
# you can later have windows within windows.
root = Tk()

root.geometry("1024x600")

# creation of an instance
app = ImageProc(root)

# mainloop
root.mainloop()

When you run the code you should see the result like the picture below:

pic1.png

Open Image File

Now that the GUI is ready we need a method to bind with the browse button to select an image to display, we call the method loadImage, there are multiple ways to load an image but since we want to browse and select an image, we're going to use filedialog.askopenfilename, which in this case from tkinter import filedialog is needed, so the first line gives the selected image file address to filename variable and by importing import matplotlib.pyplot as plt we can use plotting tools from matplotlib libraries, plt.imread(filename) will take the image and give it as an <numpy.ndarray> which is an array object representing a multidimensional, homogeneous array of fixed-size items, and is a good container for image data, we can also use misc.imread(filename) by first importing from scipy import misc this will also give us the image as a numpy.ndarray.
Now the reason I'm using global variables like self.image is because we need this variables in other methods that we're going to define next. in python global variables doesn't need to be predefined and we don't need setter/getter methods.
Next we define a figure and using plt.imshow(self.image) we can show the image on that figure but we want to show the figure inside our GUI frame, so matplotlib has a specified library to use plotting tools with tkinter GUI and it's called tkagg, to use this feature first import from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg and then using FigureCanvasTkAgg(fig, master=root) we will give the figure to our root frame in GUI. The next 2 line of code will draw the figure and gives it the location and size on the frame. I defined self.im and self.canvas as global because we need them in other methods.


from tkinter import filedialog
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg

    def loadImage(self):

        filename = filedialog.askopenfilename(filetypes = (("image files", "*.png;*.jpg")
                                                             ,("All files", "*.*") ))
        self.image = plt.imread(filename)
        fig = plt.figure(figsize=(5,5))
        self.im = plt.imshow(self.image) # for later use self.im.set_data(new_data)

        # DrawingArea
        self.canvas = FigureCanvasTkAgg(fig, master=root)
        self.canvas.draw()
        self.canvas.get_tk_widget().pack(side=TOP, fill=BOTH, expand=1)

After adding this method we should bind it with browse button simply by adding this line: self.browse["command"] = "self.loadImage"
like the code below:

    def create_widgets(self):
            self.browse = Button(self)
            self.browse["text"] = "Browse Image"
            self.browse["command"] = "self.loadImage"
            self.browse.grid(row=0, column=0)

If you run the code it should be like below:

result1.gif

It's working good but there is a problem, if you want to load another image it will make another canvas area without clearing the current figure:

2.png

To prevent this problem I made some changes on the code above, also if you want to remove the number labels on axes just perform these changes:

    canvas = ''
    def loadImage(self):
        filename = filedialog.askopenfilename(filetypes = (("image files", "*.png;*.jpg")
                                                             ,("All files", "*.*") ))
        self.image = plt.imread(filename)
        fig = plt.figure(figsize=(5,4))
        if self.canvas=='':
            self.im = plt.imshow(self.image) # later use a.set_data(new_data)
            axs = plt.gca()
            axs.set_xticklabels([])
            axs.set_yticklabels([])            

            # a tk.DrawingArea
            self.canvas = FigureCanvasTkAgg(fig, master=root)
            self.canvas.draw()
            self.canvas.get_tk_widget().pack(side=TOP, fill=BOTH, expand=1)
        else:
            self.im.set_data(self.image)
            self.canvas.draw()

axs = plt.gca() this will get the current axes and gives it to axs variable then using empty brackets [] inside axs.set_xticklabels([]) will remove number labels.
And to check if the canvas is not empty I defined global variable canvas, you can find another and maybe better way to do this.

Adding extra features

Our GUI is ready and we can load image into canvas, now let's add some extra methods to perform actions on the image and display it, Here I want to show rotation, uniform_filter, gaussian_filter, minimum value and variance.

Rotation methods

To make image rotation by 90 degrees I'm going to use scipy libraries so first lets import from scipy import ndimage, Because I need to maintain theta value, I defined it as global (you can do it your own way).
ndimage.rotate(self.image, self.theta) will rotate the image by theta degree and the next 2 line of codes will reset the image data and draw it again.

    theta = 0
    def rotate_left(self):
        self.theta += 90
        rotated = ndimage.rotate(self.image, self.theta)
        self.im.set_data(rotated)
        self.canvas.draw()

    def rotate_right(self):
            self.theta -= 90
            rotated = ndimage.rotate(self.image, self.theta)
            self.im.set_data(rotated)
            self.canvas.draw()

For these two methods we also need two buttons, which we should define them in create_widgets methods like this:
(by setting row=1 they will be placed under browse button)

    def create_widgets(self):
            self.browse = Button(self)
            self.browse["text"] = "Browse Image"
            self.browse["command"] = self.loadImage
            self.browse.grid(row=0, column=0)

            self.rotat_right = Button(self)
            self.rotat_right["text"]="Rotate Right"
            self.rotat_right["command"] = self.rotate_right
            self.rotat_right.grid(row=1, column=0)

            self.rotat_left = Button(self)
            self.rotat_left["text"]="Rotate Left"
            self.rotat_left["command"] = self.rotate_left
            self.rotat_left.grid(row=1, column=1)

And here is the result:

result2.gif

Adding image filters

In ndimage library we have some methods which we can use to perform filters on images, I chose gaussian and unifrom filters for this tutorial:

    def unifilter(self):
        self.image = ndimage.uniform_filter(self.image, size=30)
        self.im.set_data(self.image)
        self.canvas.draw()

    def gaufilter(self):
        self.image = ndimage.gaussian_filter(self.image, sigma=5)
        self.im.set_data(self.image)
        self.canvas.draw()

For uniform_filter's size and gaussian_filter's sigma values, you can give them directly in code, but I want to enter the value from keyboard so let's change the code, and create a textbox and two buttons for the our methods in create_widgets method like this:

    def create_widgets(self):
            self.browse = Button(self)
            self.browse["text"] = "Browse Image"
            self.browse["command"] = self.loadImage
            self.browse.grid(row=0, column=0)

            self.rotat_right = Button(self)
            self.rotat_right["text"]="Rotate Right"
            self.rotat_right["command"] = self.rotate_right
            self.rotat_right.grid(row=1, column=0)

            self.rotat_left = Button(self)
            self.rotat_left["text"]="Rotate Left"
            self.rotat_left["command"] = self.rotate_left
            self.rotat_left.grid(row=1, column=1)

            self.uni_filter = Button(self)
            self.uni_filter["text"] = "Uniform Filter"
            self.uni_filter["command"] = self.unifilter
            self.uni_filter.grid(row=0, column=1)

            self.gau_filter = Button(self)
            self.gau_filter["text"]="Gaussian Filter"
            self.gau_filter["command"] = self.gaufilter
            self.gau_filter.grid(row=0, column=3)

            self.text1=Entry(self)
            self.text1.grid()
            self.text1.grid(row=1, column=3)
            self.text1.focus()

And change the size and sigma values to int(self.text1.get()) since self.text1.get() is a str value we need to change it to int using int():

    def unifilter(self):
        self.image = ndimage.uniform_filter(self.image, size=int(self.text1.get()))
        self.im.set_data(self.image)
        self.canvas.draw()

    def gaufilter(self):
        self.image = ndimage.gaussian_filter(self.image, sigma=int(self.text1.get()))
        self.im.set_data(self.image)
        self.canvas.draw()

It should be working like this:

result3.gif

Adding minimum and variance values

Now let's calculate minimum and variance values of the image and display it in the textbox, like before first we need to add two buttons in create_widgets method:

            self.minm = Button(self)
            self.minm["text"]="Minimum"
            self.minm["command"] = self.min_val
            self.minm.grid(row=0, column=4)

            self.varianc = Button(self)
            self.varianc["text"]="Variance"
            self.varianc["command"] = self.var_val
            self.varianc.grid(row=0, column=5)

And add new methods to ImageProc class:

    def min_val(self):
        self.text1.delete(0,END)
        self.text1.insert(0,ndimage.minimum(self.image))

    def var_val(self):
        self.text1.delete(0,END)
        self.text1.insert(0,ndimage.variance(self.image))

This self.text1.delete(0,END) will clear textbox and this line self.text1.insert(0,ndimage.minimum(self.image)) will insert new value:

result4.gif

Conclusion

Many programmers which are working with python scientific libraries usually coming from mathematical world and are not much familiar with programming concepts and using GUIs, so this tutorial may be useful to start and get things done using a user interface, also for practice you can use other ndimage methods here.
Thanks for your time and special thanks to utopian moderators.

Curriculum

This is my first contribution about image processing using python scientific libraries.

Links:

Sort:  

Thank you for your contribution.
Overall I liked your work. I would advise you though to:

  • better review your post for English mistakes and mis-written sentences as those could lose the author.
  • include a github repository that contains the code that you made in this post here, and provide the link at the end of the post. This would be helpful for your readers to be able to simply try to run and re-use your work.
    Looking forward to your upcoming tutorials.

Need help? Write a ticket on https://support.utopian.io/.
Chat with us on Discord.
[utopian-moderator]

Thanks for your advise, I will correct these problems.

Hey @hadif66

Thanks for contributing via Utopian.
We're already looking forward to your next contribution!

Contributing on Utopian
Learn how to contribute on our website or by watching this tutorial on Youtube.

Want to chat? Join us on Discord https://discord.gg/h52nFrV.

Vote for Utopian Witness!

Congratulations @hadif66! You have received a personal award!

1 Year on Steemit
Click on the badge to view your Board of Honor.

Do not miss the last post from @steemitboard!


Participate in the SteemitBoard World Cup Contest!
Collect World Cup badges and win free SBD
Support the Gold Sponsors of the contest: @lukestokes


Do you like SteemitBoard's project? Then Vote for its witness and get one more award!

Congratulations @hadif66! You have completed the following achievement on the Steem blockchain and have been rewarded with new badge(s) :

Award for the number of upvotes

Click on the badge to view your Board of Honor.
If you no longer want to receive notifications, reply to this comment with the word STOP

Do not miss the last post from @steemitboard:

SteemitBoard - Witness Update
SteemFest³ - SteemitBoard support the Travel Reimbursement Fund.

Support SteemitBoard's project! Vote for its witness and get one more award!

Congratulations @hadif66! You received a personal award!

Happy Birthday! - You are on the Steem blockchain for 2 years!

You can view your badges on your Steem Board and compare to others on the Steem Ranking

Vote for @Steemitboard as a witness to get one more award and increased upvotes!