According to Python documentation, only recv() blocks but not send(). I wrote the following code trying to make a GUI sudoku game. I made it in such a way that I can update the game board even if the tkinter is executing its mainloop. However, during a test run, I found that if I close the window while the game is updating, the pipe.send() starts to block (I found that out using CPython profiler.) Can anyone please tell me why and, if possible, how to fix this issue?
To produce the issue: close the poped up window while the script is updating. That is, close the window while some numbers are printed to console.
My system: macOS Sierra 10.12.5
import multiprocessing as mp
import threading
import random
import time
try:
import tkinter as tk # Python3
except ImportError:
import Tkinter as tk # Python2
class VisualizedBoard:
def __init__(self,input_string,pipe):
'''input_string: a string has a length of at least 81 that represent the board from top-left to bottom right.
empty cell is 0'''
self.update_scheduled=False
self.pipe=pipe
# create board
self.root = tk.Tk()
self.canvas = tk.Canvas(self.root, width=500, height=500)
self.canvas.create_rectangle(50, 50, 500, 500, width=2)
for i in range(1, 10):
self.canvas.create_text(25 + 50 * i, 30, text=str(i))
self.canvas.create_text(30, 25 + 50 * i, text=str(i))
self.canvas.create_line(50 + 50 * i, 50, 50 + 50 * i, 500, width=2 if i % 3 == 0 else 1)
self.canvas.create_line(50, 50 + 50 * i, 500, 50 + 50 * i, width=2 if i % 3 == 0 else 1)
for i in range(81):
if input_string[i] != '0':
self.canvas.create_text(75 + 50 * (i // 9), 75 + 50 * (i % 9), tags=str((i//9+1,i%9+1)).replace(' ',''),text=input_string[i], fill='black')
self.canvas.pack()
self.root.attributes('-topmost', True)
self.root.geometry('550x550+%d+%d' % ((self.root.winfo_screenwidth() - 550) // 2, (self.root.winfo_screenheight() - 550) // 2))
self.root.wm_protocol('WM_DELETE_WINDOW',lambda :(self.root.destroy()))
threading.Thread(target=self.listen, args=()).start()
self.root.mainloop()
def update(self,coordinate,value,color='magenta'):
"""
:parameter coordinate: a tuple (x,y)
:parameter value: single digit
:returns: None
"""
try:
assert isinstance(coordinate,tuple)
except AssertionError:
print('Update Failed. Coordinate should be a tuple (x,y)')
coordinate_tag=str(coordinate).replace(' ','')
self.canvas.delete(coordinate_tag)
if value != 0 and value != '0':
self.canvas.create_text(25+50*coordinate[0],25+50*coordinate[1],tags=coordinate_tag,text=str(value),fill=color)
self.postponed_update()
#self.canvas.update()
def postponed_update(self):
if not self.update_scheduled:
self.canvas.after(50,self.scheduled_update)
self.update_scheduled=True
def scheduled_update(self):
self.canvas.update()
self.update_scheduled=False
def new_board(self,input_string):
self.root.destroy()
return VisualizedBoard(input_string,self.pipe)
def listen(self):
try:
while True:
msg=self.pipe.recv()
self.update(*msg)
except EOFError:
self.pipe.close()
tk.Label(self.root,text='Connection to the main script has been closed.\nIt is safe to close this window now.').pack()
except Exception as m:
self.pipe.close()
print('Error during listing:',m)
class BoardConnection:
def __init__(self,input_string):
ctx = mp.get_context('spawn')
self.receive,self.pipe=ctx.Pipe(False)
self.process=ctx.Process(target=VisualizedBoard,args=(input_string,self.receive))
self.process.start()
def update(self,coordinate,value,color='magenta'):
"""
:parameter coordinate: a tuple (x,y)
:parameter value: single digit
:returns: None
"""
self.pipe.send((coordinate,value,color))
def close(self):
self.pipe.close()
self.process.terminate()
if __name__ == "__main__":
b=BoardConnection('000000000302540000050301070000000004409006005023054790000000050700810000080060009')
start = time.time()
for i in range(5000): #test updating using random numbers
b.update((random.randint(1, 9), random.randint(1, 9)), random.randrange(10))
print(i)
print(time.time() - start)
Python Pipe is an abstractions on top of OS nameless pipes.
An OS pipe is generally implemented as a memory buffer of a certain size within the kernel. By default, if the buffer fills up the next call to send/write will block.
If you want to be able to continue publishing data even if no consumer is consuming it, you should either use a multiprocessing.Queue or asyncio facilities.
The multiprocessing.Queue employs a "limitless" buffer and a thread to push the data into the OS pipe. If the pipe gets full the caller will continue running as the published data will be piled up in the Queue buffer.
IIRC, asyncio sets the pipe O_NONBLOCK flag and waits for the pipe to be consumed. Additional messages are stored within a "limitless" buffer as for the multiprocessing.Queue.
Related
So, in an effort to refactor my code into a more modular project, i decided to export some of my most used functions, method and classes to separate py modules, so that i can simply "from x import y" and then use method y instead of copy-pasting the code (yes, i know that's horrible, but when you get "implement this yesterday" requests there is only so much you can do".
As an example, i often have to scan COM ports in my system and pick one or more, so i created this module:
from serial.tools import list_ports
from serial import Serial, SerialException
import tkinter as tk
from functools import partial
def COMScanner():
allports = []
ports = list_ports.comports()
for port, desc, hwid in sorted(ports):
hwid = hwid.split(" SER")[0]
# print("{}: {} [{}]".format(port, desc, hwid)) #DBG
try:
s = Serial(port)
s.close()
state = "FREE"
except (OSError, SerialException):
state = "BUSY"
pass
allports.append([port, hwid, state])
return allports
def targetSelector(COMS):
global sel_com
sel_com = None
# Once all COM ports are detected, if there are more than one, the user must pick one
# to connect to the testing board, the user must click the button with the name of
# the desired COM port and dismiss the dialog
if COMS and len(COMS) > 1:
COMSelector = tk.Tk()
baseWidth = 8 * len("".join(max(COMS, key=len)))
baseHeight = 35 * len(COMS)
finalWidth = baseWidth + 10
finalHeight = baseHeight # * len(COMS)
screen_width = COMSelector.winfo_screenwidth()
screen_height = COMSelector.winfo_screenheight()
x_coordinate = int((screen_width / 2) - (finalWidth / 2))
y_coordinate = int((screen_height / 2) - (finalHeight / 2))
COMSelector.geometry("{}x{}+{}+{}".format(finalWidth, finalHeight, x_coordinate, y_coordinate))
COMSelector.update_idletasks()
COMSelector.title("Select Target")
def save_action(selection):
global sel_com, user_pass
sel_com = str(selection)
COMSelector.destroy()
for id, COM in enumerate(COMS):
COM_HANDLE = str(COM[0])
COM_PID = str(COM[1])
COM_STATE = str(COM[2])
action = partial(save_action, COM_HANDLE)
COMLabel = tk.Label(master=COMSelector, text=COM_PID, borderwidth=0.5, relief="solid")
COMButton = tk.Button(master=COMSelector,
text=("[" + COM_STATE + "] " + COM_HANDLE),
command=action
)
if COM_STATE == "FREE":
COMButton.config(state="active")
else:
COMButton.config(state="disabled")
COMButton.grid(row=id, column=0, pady=5, padx=5, sticky="WE")
COMLabel.grid(row=id, column=1, pady=5, padx=5, sticky="WE")
COMSelector.mainloop()
else:
sel_com = COMS[0]
try:
return sel_com
except:
return "No target selected."
A typical usage might be, use COMScanner() to get the relevant port info and then targetSelector() to display the UI and let the user choose.
By defining the functions inside my program, both functions execute instantly (as expected)
By instead creating a module, in a folder I.E. "Snippets\COMSelector.py" and then using something like
from Snippets import COMSelector
[...]
start_time datetime.now()
available_COMS = COMSelector.COMScanner()
target = COMSelector.targetSelector(available_COMS)
print(datetime.now() - start_time)
with only one COM interface connected to the system, to eliminate the human clicking speed factor
i get 0:00:15.245072
Since i'm a beginner, i might be missing something crucial, please help!
There apparently was an issue with my USB controller, rebooting didn't fix it, had to disable and re-enable the driver in my device manager, somehow it took a really long time executing the
"from serial.tools import list_ports" and
"from serial import Serial, SerialException" lines
about 7.4 - 7.5s each, not only that, each time i tried importing anything from the pyserial module it took that long.
i have no idea what caused this but, it's fixed now, marking as solved.
I have designed an marine engine simulator - it's a program that sends some serial DATA using a certain protocol called "BlueVision". The data is encoded based on a header, a block number, a block type, actual data (indexed), a checksum and a footer. Due to the fact that if you want to change a value in the actual data - then the checksum changes - I designed an recalculation of the block with the correct checksum when the data is changed. I made a GUI that allows me to change the value of 2 data points in order to be able to test live. The problem is that I don't know how to use threads or subprocesses correctly and the window keeps on freezing. The application works - but works badly and I kept on searching for a similar issue - and did find some suggestions but I haven't been able to implement it. Can someone help? PS: the code is awkward at best - please don't judge - I know I could of made it way better - but this is the best I could do with the time I had. The issue might not be clear to you if you don't have a COM port where the program writes to.
# Blue Vision is a MTU serial communication protocol - based on HEX values grouped in BLOCKS
import tkinter as tk
from tkinter import ttk
import time
import serial
def split_by_n(seq, n):
while seq:
yield seq[:n]
seq= seq[n:]
def after(self, ms, func=None, *args):
"""Call function once after given time.
MS specifies the time in milliseconds. FUNC gives the
function which shall be called. Additional parameters
are given as parameters to the function call. Return
identifier to cancel scheduling with after_cancel."""
win = tk.Tk()
win.title("GUI - TEST VERSION")
win.geometry("750x250")
label = tk.Label(win, text = "Main Engine Overspeed")
label.pack()
v = tk.StringVar()
v.set('00')
c = tk.StringVar()
c.set("00")
def setText(word):
v.set(word)
a = ttk.Button(win, text ="01", command =lambda:setText("01"))
a.pack()
b = ttk.Button(win, text="00", command = lambda:setText("00"))
b.pack()
label1 = tk.Label(win, text ="Main Engine Speed")
label1.pack()
name_entry = tk.Entry(win, textvariable=c)
name_entry.pack()
def task():
MyVar = v.get()
priEngSp =c.get()
if len(priEngSp) == 0:
priEngSp = '00'
block_3 = 'FAF500030002000000290100000001000100000001000001000000000000000000000000000222AF5F'
block_4 = 'FAF500040003000001A000004650000047E00000000000000000000000000007EF4000083D6000000000000000000000000000000000000000000000012C000006D600000000000000000000278D00000000000000007FFFFFFF000000000001991500000000000000000016E36000000000000923D8000971F8000001F40000059F000026AC00002774000005800000251C00000580000027740000283C0000056200001D4C00001F400000061800000000000060FB00004650000036B000007D0000008CA0000006180000251C0000000000000000000000000000284800192D500017A6B00000051B0000251CFFFFFFA8000002580000044C000000FA0000000000000000000006770000CB200000D6D8000006770000CB200000D6D80000060600005DC000000000000027100000000000000000000000000000000000000000000003C2000061A8000000000000000000000000000000000000000000000000000000000000000000000000000000000000363300000EA6000249F0FFFFFB1E000F42400000000000000000000000000000000000000000000032D9AF5F'
block_5 = 'FAF5000500020000005600000000000000000000000000000000000000000000007F7F0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034AAF5F'
block_6 = 'FAF5000600020000003D00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000234AF5F'
block_7 = 'FAF5000700030000008C000006280000064A0000064A0000068D0000066B0000068D0000068D000006AE0000000000000000000006AE000006070000060700000607000005E5000005A2000006070000064A00000000000000000000062A000006AE000005A20000350EFFFFF07C00003CDEFFFFE8AC00000000000000000000000000000000000012DEAF5F'
block_8 = 'FAF50008000300000070000000000000112900000000000000000000059C000027740000283C000047E000000000000000000000000000000000000000000000000000000000000000007FFFFFFF7FFFFFFF0000055100002CEC0000000000000000000000000000000000000DD1AF5F'
block_9 = 'FAF50009000200000020000000000000000000000000000000000000021AAF5F'
block_10 = 'FAF5000A0002000000260000000000000000000000000000000000000000000000000221AF5F'
block_11 = 'FAF5000B0003000000EC00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002E9AF5F'
block_12 = 'FAF5000C000200000045000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000242AF5F'
block_2 = 'FAF50002000200000074010001000000000000000000000000000000000000000000007F0000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002E9AF5F'
checksum = hex(0x00)
block_2split = list(split_by_n(block_2,2))
block_2split[10] = MyVar.upper() #Overspeed Alarm Position
for i in range(len(block_2split)-6):
checksum = hex(int(checksum, 16) + int(block_2split[i], 16))
checksum_string = str(checksum)
checksum_actvalue = checksum_string[2:]
checksum_long = checksum_actvalue.rjust(8,'0').upper()
checksum_split = list(split_by_n(checksum_long,2))
block_2split[len(block_2split)-3] = checksum_split[3]
block_2split[len(block_2split)-4] = checksum_split[2]
block_2split[len(block_2split)-5] = checksum_split[1]
block_2split[len(block_2split)-6] = checksum_split[0]
Block_2Output = ''.join(str(item) for item in block_2split)
iEngineSpeed = int(priEngSp,10)
hEngineSpeed = hex(iEngineSpeed * 10)
sEngineSpeed = str(hEngineSpeed)[2:].upper()
while (len(sEngineSpeed)<8):
sEngineSpeed = '0' + sEngineSpeed
block_4split = list(split_by_n(block_4,4))
sEngineSpeed_split = list(split_by_n(sEngineSpeed,4))
block_4split[5] = sEngineSpeed_split[0]
block_4split[6] = sEngineSpeed_split[1]
Block_4joint = ''.join(str(item) for item in block_4split)
Block_4joint_sp = list(split_by_n(Block_4joint,2))
checksumb4 = hex(0x00)
for i in range(len(Block_4joint_sp)-6):
checksumb4 = hex(int(checksumb4, 16) + int(Block_4joint_sp[i], 16))
checksumb4_string = str(checksumb4)
checksumb4_actvalue = checksumb4_string[2:]
checksumb4_long = checksumb4_actvalue.rjust(8,'0').upper()
checksumb4_split = list(split_by_n(checksumb4_long,2))
Block_4joint_sp[len(Block_4joint_sp)-3] = checksumb4_split[3]
Block_4joint_sp[len(Block_4joint_sp)-4] = checksumb4_split[2]
Block_4joint_sp[len(Block_4joint_sp)-5] = checksumb4_split[1]
Block_4joint_sp[len(Block_4joint_sp)-6] = checksumb4_split[0]
Block_4Output = ''.join(str(item) for item in Block_4joint_sp)
blocks = [Block_2Output, block_3, Block_4Output, block_5, block_6, block_7, block_8, block_9, block_10, block_11, block_12]
with serial.Serial('COM5', '9600') as ser: #you might wanna comment this part out
for block in blocks:
print(block)
ser.write(bytes.fromhex(block.strip())) #you might wanna comment this part out
time.sleep(1)
win.after(200, task)
win.after(200, task)
win.mainloop()
To avoid freezing, one of the way is to use thread:
from threading import Thread
...
def task():
while True:
MyVar = v.get()
...
blocks = [Block_2Output, block_3, Block_4Output, block_5, block_6, block_7, block_8, block_9, block_10, block_11, block_12]
with serial.Serial('COM5', '9600') as ser: #you might wanna comment this part out
for block in blocks:
print(block)
ser.write(bytes.fromhex(block.strip())) #you might wanna comment this part out
time.sleep(1)
time.sleep(0.2)
# run task() in a thread
Thread(target=task, daemon=1).start()
win.mainloop()
I'm trying to learn how to use threading and specifically concurrent.futures.ThreadPoolExecutor this is because I need to return a numpy.array from a function I want to run concurrently.
The end goal is to have one process running a video loop of an application, while another process does object detection and GUI interactions. The result() keyword from the concurrent.futures library allows me to do this.
The issue is my code runs once, and then seems to lock up. I'm actually unsure what happens as when I step through it in the debugger it runs once, then the debugger goes blank and I literally cannot step through and no error is thrown.
The code appears to lock up on the line: notepadWindow = pygetwindow.getWindowsWithTitle('Notepad')[0]
I get exactly one loop, the print statement prints once the loop restarts and then it halts at pygetwindow
I don't know much about the GIL but I have tried using the max_workers=1 argument on ThreadPoolExecutor() which doesn't make a difference either way and I was under the impression concurrent.futures allows me to bypass the lock.
How do I run videoLoop as a single thread making sure to return DetectionWindow every iteration?
import cv2 as cv
import numpy as np
import concurrent.futures
from PIL import ImageGrab
import pygetwindow
def videoLoop():
notepadWindow = pygetwindow.getWindowsWithTitle('Notepad')[0]
x1 = notepadWindow.left
y1 = notepadWindow.top
height = notepadWindow.height
width = notepadWindow.width
x2 = x1 + width
y2 = y1 + height
haystack_img = ImageGrab.grab(bbox=(x1, y1, x2, y2))
haystack_img_np = np.array(haystack_img)
DetectionWindow= cv.cvtColor(haystack_img_np, cv.COLOR_BGR2GRAY)
return DetectionWindow
def f1():
with concurrent.futures.ThreadPoolExecutor() as executor:
f1 = executor.submit(videoLoop)
notepadWindow = f1.result()
cv.imshow("Video Loop", notepadWindow)
cv.waitKey(1)
print(f1.result())
while True:
f1()
A ThreadPoolExecutor won't help you an awful lot here, if you want a continuous stream of frames.
Here's a reworking of your code that uses a regular old threading.Thread and puts frames (and their capture timestamps, since this is asynchronous) in a queue.Queue you can then read in another (or the main) thread.
The thread has an otherwise infinite loop that can be stopped by setting the thread's exit_signal.
(I didn't test this, since I'm presently on a Mac, so there may be typos or other problems.)
import queue
import time
import cv2 as cv
import numpy as np
import threading
from PIL import ImageGrab
import pygetwindow
def do_capture():
notepadWindow = pygetwindow.getWindowsWithTitle("Notepad")[0]
x1 = notepadWindow.left
y1 = notepadWindow.top
height = notepadWindow.height
width = notepadWindow.width
x2 = x1 + width
y2 = y1 + height
haystack_img = ImageGrab.grab(bbox=(x1, y1, x2, y2))
return cv.cvtColor(np.array(haystack_img), cv.COLOR_BGR2GRAY)
class VideoCaptureThread(threading.Thread):
def __init__(self, result_queue: queue.Queue) -> None:
super().__init__()
self.exit_signal = threading.Event()
self.result_queue = result_queue
def run(self) -> None:
while not self.exit_signal.wait(0.05):
try:
result = do_capture()
self.result_queue.put((time.time(), result))
except Exception as exc:
print(f"Failed capture: {exc}")
def process_frames(result_queue: queue.Queue):
start_time = time.time()
while time.time() - start_time < 5: # Run for five seconds
frame = result_queue.get()
print(frame)
def main():
result_queue = queue.Queue()
thread = VideoCaptureThread(result_queue=result_queue)
thread.start()
process_frames(result_queue)
thread.exit_signal.set()
thread.join()
if __name__ == "__main__":
main()
I am currently working on a project of simple spectrum analyzer that cooperates with PlutoSDR, which is programmable radio.
I created a simple GUI with QTDesigner, then pyuic'ed it into python code (my version is 3.8)
Here's my code: (I've cut out some irrelevant parts)
# Form implementation generated from reading ui file 'signal_analyzer.ui'
#
# Created by: PyQt5 UI code generator 5.15.6
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
import pyqtgraph
from PyQt5 import QtCore, QtGui, QtWidgets
from pyqtgraph.Qt import QtCore, QtGui
from pyqtgraph.widgets import PlotWidget
import sys
import numpy as np
import argparse
import adi
import time
import threading
import pyqtgraph as pg
np.seterr(divide='ignore')
class FakePluto:
# this is the class that simulates the radio if it's not connected to my computer
"""
perform some operations
"""
# output vector of data
return samples[:self.rx_buffer_size]
class SpectrumAnalyzerThread(threading.Thread):
def __init__(self, sdr, *args, **kwargs):
super().__init__(*args, **kwargs)
"""
set some initial parmeters
"""
self.sdr = sdr
elf.start_freq = int(70e6)
self.end_freq = int(150e6)
self.settings_changed = True
self.result = None
#property
def steps_per_scan(self):
return (self.end_freq - self.start_freq) // self.step_size
def setStop(self):
self.stop = True
print("stop")
def setStart(self):
self.stop = False
print("start")
def setStopFreq(self, win):
self.stop_freq = int(win.lineEdit.text)
self.start_freq += (self.stop_freq - self.start_freq) % self.step_size
def setStartFreq(self, win):
self.start_freq = int(win.lineEdit_2.text)
self.stop_freq += (self.stop_freq - self.start_freq) % self.step_size
def reconfigure(self, start_freq, end_freq, step_size):
self.start_freq = int(start_freq)
self.end_freq = int(end_freq)
self.step_size = int(step_size)
if (self.end_freq - self.start_freq) % self.step_size != 0:
raise Exception('range is not a multiple of the step size')
self.settings_changed = True
def run(self):
while not self.stop:
print("2", end = "\r")
if self.settings_changed:
self._current_freq = self.start_freq
self.result = np.zeros(self.steps_per_scan * self.samples_per_step)
self.sdr.gain_control_mode_chan0 = 'manual'
self.sdr.rx_hardwaregain_chan0 = self.gain
self.sdr.sample_rate = self.step_size
self.sdr.rx_rf_bandwidth = self.step_size
self.sdr.rx_buffer_size = self.samples_per_step
self.settings_changed = False
else:
if self._current_freq + self.step_size >= self.end_freq:
self._current_freq = self.start_freq
else:
self._current_freq += self.step_size
self.sdr.rx_lo = self._current_freq + self.step_size//2
self.sdr.rx() # skip one sample
rx_samples = self.sdr.rx()
psd = np.abs(np.fft.fftshift(np.fft.fft(rx_samples))) ** 2
assert len(psd) == self.samples_per_step
start_idx = (self._current_freq - self.start_freq) // self.step_size * self.samples_per_step
self.result[start_idx:start_idx + self.samples_per_step] = psd
#print(self.result.tolist())
class Ui_MainWindow(QtWidgets.QMainWindow):
def setupUi(self, MainWindow, analyzer_thread):
"""
define UI elements - this part was generated from .UI file
"""
self.lineEdit = QtWidgets.QLineEdit(self.centralwidget)
self.lineEdit.setGeometry(QtCore.QRect(800, 140, 231, 25))
self.lineEdit.setObjectName("lineEdit")
self.runButton = QtWidgets.QPushButton(self.centralwidget)
self.runButton.setGeometry(QtCore.QRect(790, 310, 161, 51))
self.runButton.setObjectName("runButton")
self.stopButton = QtWidgets.QPushButton(self.centralwidget)
self.stopButton.setGeometry(QtCore.QRect(790, 380, 161, 51))
self.stopButton.setObjectName("stopButton")
self.retranslateUi(MainWindow)
# connect gui elements with functions
self.stopButton.clicked.connect(analyzer_thread.setStop) # type: ignore
self.runButton.clicked.connect(analyzer_thread.setStart) # type: ignore
self.lineEdit.textChanged[str].connect(analyzer_thread.setStartFreq)
#self.lineEdit_2.textChanged[str].connect(analyzer_thread.setStopFreq)
#self.sweepButton.clicked.connect(self.widget.singleSweep) # type: ignore
#self.lineEdit_3.modified.connect()
QtCore.QMetaObject.connectSlotsByName(self)
# function below is just for testing
def tomatoes(self, analyzer_thread):
print(analyzer_thread.start_freq)
def retranslateUi(self, MainWindow):
"""
UI retranslation
"""
def main():
# this part checks input script arguments, currently I'm using --simulate
parser = argparse.ArgumentParser(description='SDR Spectrum Analyzer for PlutoSDR')
parser.add_argument('pluto_uri', nargs='?', default=adi.Pluto._uri_auto,
help=f'URI of the PlutoSDR device (default: "{adi.Pluto._uri_auto}")')
parser.add_argument('--simulate', action='store_true',
help='Simulate by generating random noise instead of querying the Pluto device')
args = parser.parse_args()
if args.simulate:
sdr = FakePluto()
else:
sdr = adi.Pluto(args.pluto_uri)
# create and start the thread
analyzer_thread = SpectrumAnalyzerThread(sdr)
analyzer_thread.start()
app = QtGui.QApplication(sys.argv)
win = Ui_MainWindow()
win.show()
win.setupUi(win, analyzer_thread)
# this is the function that refreshes the plotWindow in GUI
def update():
if analyzer_thread.result is not None and not analyzer_thread.settings_changed:
print("1", end = "\r")
psd = analyzer_thread.result
num_bins = analyzer_thread.samples_per_step
if len(psd) % num_bins != 0:
raise Exception('num_bins is not a multiple of sample count')
binned = psd.reshape((-1, len(psd) // num_bins)).sum(axis=1)
binned_dB = 10 * np.log10(binned)
f = np.linspace(analyzer_thread.start_freq, analyzer_thread.end_freq, len(binned_dB))
#spectrum_trace.setData(f, binned_dB)
win.graphicsView.clear()
win.graphicsView.plot(f, binned_dB)
dispMax = str(np.amax(binned_dB))
win.currentMaxAmp.display(dispMax[0:5])
# update function is connected to the timer
timer = QtCore.QTimer()
timer.timeout.connect(update)
timer.start(1)
win.tomatoes(analyzer_thread)
app.exec()
analyzer_thread.stop = True
analyzer_thread.join()
if __name__ == '__main__':
main()
Now, I have two independent problems.
First
When I press "Stop" on the GUI, which sets analyzer_thread stop value to 'true', this works correctly (i. e. run function from analyzer_thread stops executing), though the update function is still running - the plot keeps refreshing but with the same values. Yet when I hit "start", the analyzer_thread doesn't start over. I have no idea what causes that.
Second
When I try to change stop frequency value, which should call analyzer_thread.setStopFreq I get this message:
Traceback (most recent call last):
File "main_window_ui.py", line 77, in setStopFreq
self.stop_freq = int(win.lineEdit.text)
AttributeError: 'str' object has no attribute 'lineEdit'
which I think is my mistake while connecting GUI objects to functions, but I couldn't figure out how to fix it. It seems there's a problem with function's arguments, yet when I call tomatoes function from main, it works despite having the same argument (analyzer_thread).
I know that my code is messy and sorry for that. The processing part was done by another person, my role is to make it work with GUI. I am attaching the main window view:
https://i.stack.imgur.com/vUZjj.png
I'll be thankful for any help :)
Your code wont resume running, because when you set self.stop to True once, whole ,,run'' loop terminates and self.run function ends its job, terminating thread. You should rather try something like:
while True:
if not self.stop:
#do your code
else:
time.sleep(0.001) #
And add other possibility to terminate thread completly.
Your functions should rather be named suspend/resume, not stop and start.
Suppose we want to drive an autonomous car by predicting image labels from a previous set of images and labels collected (A Machine Learning application). For this task, the car is connected via bluetooth serial (rfcomm) to the Host Computer (A PC with *NIX) and the images are streamed directly from an Android phone using IP Webcam, meanwhile, the PC is running a program that links this two functions, displaying the captured images in a drawing environment created by pygame, and sending the instructions back to the car using serial.
At the moment, I've tried to implement those processes using the multiprocessing module, the seemed to work, but when I execute the client, the drawing function (if __name__ == '__main__') works after the getKeyPress() function ends.
The question is: It is possible to parallelize or synchronize the drawing fuinction enclosed within the if __name__ == '__main__' with the process declared in getKyPress(), such that the program works in two independent processes?
Here's the implemented code so far:
import urllib
import time
import os
import sys
import serial
import signal
import multiprocessing
import numpy as np
import scipy
import scipy.io as sio
import matplotlib.image as mpimg
from pygame.locals import *
PORT = '/dev/rfcomm0'
SPEED = 115200
ser = serial.Serial(PORT)
status = False
move = None
targets = []
inputs = []
tic = False
def getKeyPress():
import pygame
pygame.init()
global targets
global status
while not status:
pygame.event.pump()
keys = pygame.key.get_pressed()
targets, status = processOutputs(targets, keys)
targets = np.array(targets)
targets = flattenMatrix(targets)
sio.savemat('targets.mat', {'targets':targets})
def rgb2gray(rgb):
r, g, b = np.rollaxis(rgb[...,:3], axis = -1)
return 0.299 * r + 0.587 * g + 0.114 * b
def processImages(inputX, inputs):
inputX = flattenMatrix(inputX)
if len(inputs) == 0:
inputs = inputX
elif inputs.shape[1] >= 1:
inputs = np.hstack((inputs, inputX))
return inputs
def flattenMatrix(mat):
mat = mat.flatten(1)
mat = mat.reshape((len(mat), 1))
return mat
def send_command(val):
connection = serial.Serial( PORT,
SPEED,
timeout=0,
stopbits=serial.STOPBITS_TWO
)
connection.write(val)
connection.close()
def processOutputs(targets, keys):
global move
global status
global tic
status = False
keypress = ['K_p', 'K_UP', 'K_LEFT', 'K_DOWN', 'K_RIGHT']
labels = [1, 2, 3, 4, 5]
commands = ['p', 'w', 'r', 'j', 's']
text = ['S', 'Up', 'Left', 'Down', 'Right']
if keys[K_q]:
status = True
return targets, status
else:
for i, j, k, g in zip(keypress, labels, commands, text):
cmd = compile('cond = keys['+i+']', '<string>', 'exec')
exec cmd
if cond:
move = g
targets.append(j)
send_command(k)
break
send_command('p')
return targets, status
targetProcess = multiprocessing.Process(target=getKeyPress)
targetProcess.daemon = True
targetProcess.start()
if __name__ == '__main__':
import pygame
pygame.init()
w = 288
h = 352
size=(w,h)
screen = pygame.display.set_mode(size)
c = pygame.time.Clock() # create a clock object for timing
pygame.display.set_caption('Driver')
ubuntu = pygame.font.match_font('Ubuntu')
font = pygame.font.Font(ubuntu, 13)
inputs = []
try:
while not status:
urllib.urlretrieve("http://192.168.0.10:8080/shot.jpg", "input.jpg")
try:
inputX = mpimg.imread('input.jpg')
except IOError:
status = True
inputX = rgb2gray(inputX)/255
out = inputX.copy()
out = scipy.misc.imresize(out, (352, 288), interp='bicubic', mode=None)
scipy.misc.imsave('input.png', out)
inputs = processImages(inputX, inputs)
print inputs.shape[1]
img=pygame.image.load('input.png')
screen.blit(img,(0,0))
pygame.display.flip()
c.tick(1)
if move != None:
text = font.render(move, False, (255, 128, 255), (0, 0, 0))
textRect = text.get_rect()
textRect.centerx = 20 #screen.get_rect().centerx
textRect.centery = 20 #screen.get_rect().centery
screen.blit(text, textRect)
pygame.display.update()
if status:
targetProcess.join()
sio.savemat('inputs.mat', {'inputs':inputs})
except KeyboardInterrupt:
targetProcess.join()
sio.savemat('inputs.mat', {'inputs':inputs})
targetProcess.join()
sio.savemat('inputs.mat', {'inputs':inputs})
Thanks in advance.
I would personally suggest writing this without using the multiprocessing module: it uses fork() which has unspecified effects with most complex libraries, like in this case pygame.
You should try to write this as two completely separate programs. It forces you to think about what data needs to go from one to the other, which is both a bad and a good thing (as it may clarify things). You can use some inter-process communication facility, like the stdin/stdout pipe; e.g. in one program (the "main" one) you start the other as a sub-process like this:
popen = subprocess.Popen([sys.executable, '-u', 'my_subproc.py'],
stdin=subprocess.PIPE, stdout=subprocess.PIPE)
(The -u is for unbuffered.)
Then read/write the data to popen.stdin/popen.stdout in the parent process, and to sys.stdin/sys.stdout in the subprocess. The simplest example would be if the two processes only need a synchronization signal, e.g. the parent process waits in a loop for the subprocess to say "next please". To do this the subprocess does print 'next please', and the parent process does popen.stdin.readline(). (The print goes to sys.stdin in the subprocess.)
Unrelated small note:
keypress = ['K_p', ...]
...
cmd = compile('cond = keys['+i+']', '<string>', 'exec')
exec cmd
if cond:
This looks like very heavy code to just do:
keypress = [K_p, ...] # not strings, directly the values
...
if keys[i]:
My suggestion is to use separate threads.
#At the beginning
import threading
#Instead of def getKeyPress()
class getKeyPress(threading.Thread):
def run(self):
import pygame
pygame.init()
global targets
global status
while not status:
pygame.event.pump()
keys = pygame.key.get_pressed()
targets, status = processOutputs(targets, keys)
targets = np.array(targets)
targets = flattenMatrix(targets)
sio.savemat('targets.mat', {'targets':targets})
#Instead of
#targetProcess = multiprocessing.Process(target=getKeyPress)
#targetProcess.daemon = True
#targetProcess.start()
gkp = getKeyPress()
gkp.start()
An alternative would be creating two different scripts and using sockets to handle the inter-process communication.