0

I have functioning code but there are a few things which I would like to change about it but don't know how to so thought i'd ask here. My code is as follows:

import numpy as np
from PIL import Image
import matplotlib.pyplot as plt
import tkinter as tk
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg


#Define the target, source and output arrays. Source has to be completely white otherwise it kills everything
def initialize(x,y):
    xarr = np.zeros(x)
    yarr = np.zeros(y)
    target = np.meshgrid(xarr,yarr)
    target = target[0]
    source = np.meshgrid(xarr,yarr)
    source = source[0]
    output = np.meshgrid(xarr,yarr)
    output = output[0]
    for i in range(x):
        for n in range(y):
            source[n][i] = 1
    return target, source, output

# creates trap between XTrapMin-XTrapMax and YTrapMin-YTrapMax on Array
def trap(xtmi,xtma,xs,ytmi,ytma,ys,array):
    for i in range(xs):
        if xtmi < i < xtma:
            for n in range(ys):
                if ytmi < n < ytma:
                    array[n][i] = 255
    return

#Returns the amplitude of a complex number
def Amplitude(x):
    if isinstance(x, complex):
        return np.sqrt(x.real**2+x.imag**2)
    else:
        return np.abs(x)

#Returns the phase of a complex number
def Phase(z):
        return np.angle(z)

#Main GS algorithm implementation using numpy FFT package
#performs the GS algorithm to obtain a phase distribution for the plane, Source
#such that its Fourier transform would have the amplitude distribution of the plane, Target.
def GS(target,source):
    A = np.fft.ifft2(target)
    for i in range(5):
        B = Amplitude(source) * np.exp(1j * Phase(A))
        C = np.fft.fft2(B)
        D = Amplitude(target) * np.exp(1j * Phase(C))
        A = np.fft.ifft2(D)
    output = Phase(A)
    return output

#Make array into PIL Image
def mkPIL(array):
    im = Image.fromarray(np.uint8(array))
    return im

def up():
    global ytmi
    global ytma
    ytmi -= 10
    ytma -= 10
    return 

def down():
    global ytmi
    global ytma
    ytmi += 10
    ytma += 10
    return

def right():
    global xtmi
    global xtma
    xtmi += 10
    xtma += 10
    return

def left():
    global xtmi
    global xtma
    xtmi -= 10
    xtma -= 10
    return

xtmi = 125
xtma = 130
xs = 1024
ytmi = 0
ytma = 5
ys = 768


root = tk.Tk()
root.attributes('-fullscreen', True)
def main():
    app = Lower(root)
    root.mainloop()

class Lower:
    def __init__(self, master):
        self.master = master
        self.frame = tk.Frame(self.master).pack()
        self.displayimg = tk.Button(self.frame, text = 'Display', width = 25, command = self.plot)
        self.displayimg.pack()
        self.makewidg()
    def makewidg(self):
        self.fig = plt.figure(figsize=(100,100), frameon=False)  #changing figsize doesnt cange the size of the plot display
        self.fig.subplots_adjust(left=0, right=1, top=1, bottom=0)
        self.fig.tight_layout()
        self.ax = self.fig.add_subplot(111)
        self.ax.set_yticklabels([])                        
        self.ax.set_xticklabels([])
        self.canvas = FigureCanvasTkAgg(self.fig, master=self.master)
        self.canvas.get_tk_widget().pack(expand=True)
        self.canvas.figure.tight_layout()
        self.canvas.draw()
        self.new_window()
    def new_window(self):
        self.newWindow = tk.Toplevel()
        self.app = Display(self.newWindow)
    def plot(self): 
        global xtmi, xtma, xs, ytmi, ytma, ys, i
        target,source,output=initialize(xs,ys)
        trap(xtmi,xtma,xs,ytmi,ytma,ys,target)
        output = GS(target,source)
        self.ax.imshow(output, cmap='gray')
        self.ax.set_yticklabels([])                        
        self.ax.set_xticklabels([])
        self.canvas.draw()
        self.ax.clear()

    def kill(self): 
        root.destroy()

class Display:

    def __init__(self, master):
        self.master = master
        self.frame = tk.Frame(self.master)
        self.frame.pack()
        self.up = tk.Button(self.frame, text = 'Up', width = 25, command = up)
        self.up.pack()
        self.down = tk.Button(self.frame, text = 'Down', width = 25, command = down)
        self.down.pack()
        self.right = tk.Button(self.frame, text =  'Right', width = 25, command = right)
        self.right.pack()
        self.left = tk.Button(self.frame, text = 'Left', width = 25, command = left)
        self.left.pack()
        self.kill = tk.Button(self.frame, text = 'Kill', width = 25, command = self.kill)
        self.kill.pack()
    def kill(self): 
        root.destroy()
main()

Currently the button displayimg from the class Lower is displayed above the image, is there a way in which I can have the display button on the Display class and still have it manipulate the image on the Lower screen? Also, I intend to display the window opened by Lower on a separate monitor, but can't drag it seeing as it is fullscreen, is there a way around that I can get it on my second monitor?


I try that as such:

self.displayimg = tk.Button(self.top, text = 'Display', width = 25, command = Lower.plot(Lower)) 
self.displayimg.pack() 

But this causes a misreference I think as I get an error code

AttributeError: type object 'Lower' has no attribute 'ax'

stovfl
  • 14,998
  • 7
  • 24
  • 51
Paul
  • 19
  • 6
  • It's not the job of `tkinter` to deal with multiple screens. Your Window Manager have to deal with it. – stovfl Feb 17 '20 at 23:30
  • Thank you for your answer. Do you have an idea of how I can transfer the display button to the second window? – Paul Feb 18 '20 at 11:35
  • 1
    ***" button displayed above the image"***: Open another `Toplevel` window, place the `Button` there and move this window to the other screeen. – stovfl Feb 18 '20 at 11:40
  • My main problem is that I try that as such self.displayimg = tk.Button(self.top, text = 'Display', width = 25, command = Lower.plot(Lower)) self.displayimg.pack() But this causes a misreference I think as I get an error code AttributeError: type object 'Lower' has no attribute 'ax' – Paul Feb 18 '20 at 12:38
  • 1
    ***"AttributeError: type object 'Lower'"***: There a **two** errors in `command = Lower.plot(Lower)`. **1.** Read [Why is Button parameter “command” executed when declared?](https://stackoverflow.com/questions/5767228/why-is-button-parameter-command-executed-when-declared) **2.** You are using `Lower.` which is a `class defenition` instead of `.` which ist a `class object`. – stovfl Feb 18 '20 at 13:59
  • But from my limited understanding of OOP I'm not initiating an instance of `Lower` though? How would i reference the instance of `Lower` Currently I have `command = lambda: Lower.plot()` but I am obviously missing the parameter. I don't understand enough of how the classes are initialized to see where the reference to the `Lower` window is stored. – Paul Feb 18 '20 at 14:11
  • 1
    ***"I'm not initiating an instance of Lower though? "***: Here: `app = Lower(root)`, `class Lower` get instantiated and `app` holds the reference to the `Lower` instance. – stovfl Feb 18 '20 at 14:24
  • Thank you so much I'm sorry for asking so many questions. – Paul Feb 18 '20 at 14:27
  • Is your issue solved? Does the `Button` show up on the second Screen? – stovfl Feb 18 '20 at 14:29

1 Answers1

0

Call to Lower.plot

You are using Lower.plot as your button command. It needs one argument, self which must be an instance of Lower - so Lower.plot(Lower) is passing a class where an instance is expected. Instead you need to use the app instance you've made, and call app.plot(). The arguement self is automatically the instance itself, this is fundamental to OOP in python. Calling the method on an instance passes self as the first arg, so it's missing from the call. Calling Lower.plot(...) is calling the method on the class Lower, so there is no instance, and you have to supply your own. I'd avoid calling methods without an instance like this in this situation, and use your app instance.

Command for the display button

Your button creation becomes something like: self.displayimg = tk.Button(self.top, text = 'Display', width = 25, command = app.plot) If you need to pass additional args to plot, you need to delay the function call so it happens on click, not on creation of the button. You can use lambda : app.plot("red", 10, whatever) to make a nameless function, taking no arguments, that when called will go on to call app.plot with the given args.

Positioning the window

You can control the position of the app window using wm_geometry:

app.wm_geometry("200x200+100+500")

Will cause the app window to be 200px by 200px, positioned 100px left and 500px down from the origin, and on a windows machine, this is the top left corner of your primary monitor. You can keep the width and height the same and just move the window with eg

app.wm_geometry("+100+500")

You can use more scripting to build the string +{xpos}+{ypos} with whichever values you like to match your desktop layout.

RFairey
  • 736
  • 3
  • 9