How to make Python wait while QGIS renders a shapefile - python

I have written code for my QGIS 2.8.1 which can successfully take screenshot of one shape file, but unfortunately it doesn't work when I try with multiple shapefiles.
So basically if I replace allFiles = ["C:/Shapefiles/Map_00721.shp"] in the code below with allFiles = ["C:/Shapefiles/Map_00721.shp", "C:/Shapefiles/Map_00711.shp", "C:/Shapefiles/Map_00731.shp", "C:/Shapefiles/Map_00791.shp", "C:/Shapefiles/Map_00221.shp"], the loop iterates over the array without waiting for the rendering and snapshot process to happen.
I have tried using time.sleep in the code below, but it stops the rendering of shapefiles too and the result doesn't came as expected.
import ogr,os
from PyQt4.QtGui import *
from PyQt4.QtCore import *
from qgis.core import *
import qgis.utils
import glob
from time import sleep
import math
import processing
from processing.core.Processing import Processing
from PyQt4.QtCore import QTimer
Processing.initialize()
Processing.updateAlgsList()
OutputFileName = "ABC" # Temprory global placeholder for filename
canvas = iface.mapCanvas()
def startstuffs():
qgis.utils.iface.zoomToActiveLayer() # Zoom to Layer
scale=canvas.scale() # Get current Scale
scale = scale * 1.5
canvas.zoomScale(scale) # Zoomout a bit
QTimer.singleShot(2000,saveImg) # Jump to save img
def saveImg():
qgis.utils.iface.mapCanvas().saveAsImage(OutputFileName)
QgsMapLayerRegistry.instance().removeAllMapLayers()
# Add array of address below
allFiles = ["C:/Shapefiles/Map_00721.shp"]
filesLen = len(allFiles)
TexLayer = "C:/US_County_NAD27.shp"
for lop in range(filesLen):
currentShpFile = allFiles[lop]
currentShpFileName = currentShpFile.strip("C:/Shapefiles/")
OutputFileName = "C:/ImageOut/" + currentShpFileName + ".png"
wb = QgsVectorLayer(currentShpFile, currentShpFileName, 'ogr')
wbTex = QgsVectorLayer(TexLayer, 'CountyGrid', 'ogr')
QgsMapLayerRegistry.instance().addMapLayer(wb) # Add the shapefile
QgsMapLayerRegistry.instance().addMapLayer(wbTex) # Add the county shapefile
qgis.utils.iface.setActiveLayer(wb) # Makes wb as active shapefile
QTimer.singleShot(3000, startstuffs) # This start stuffs
print "Done!"

Avoid using time.sleep() since that will completely stall your entire program. Instead, use processEvents() which allows your program to render in the background.
import time
def spin(seconds):
"""Pause for set amount of seconds, replaces time.sleep so program doesn't stall"""
time_end = time.time() + seconds
while time.time() < time_end:
QtGui.QApplication.processEvents()
This method should work fine for a quick fix, but in the long term it may generate difficult problems to track. It is better to use a QTimer with a event loop for a permanent solution.

Related

Python, how to execute a line of code without it stopping the rest of the code from executing?

first of all, im a beginner.
Want i want to accomplish is that music plays while the script is executing.
What it does right now it plays the music, waits until the music is over and then executes the rest of the code. That is not what i want. Here my Code:
import os
import subprocess
import multiprocessing
import threading
from playsound import playsound
CurrentPath = os.path.dirname(os.path.normpath(__file__))
os.chdir(CurrentPath)
def music():
Music = "Music.mp4"
#subprocess.run(["ffplay", "-nodisp", "-autoexit", "-hide_banner", Music])
playsound("Music.mp4")
def other_things():
print("Hello World")
#musicp = multiprocessing.Process(target=music())
#restp = multiprocessing.Process(target=other_things())
musicp = threading.Thread(target=music())
restp = threading.Thread(target=other_things())
restp.start()
musicp.start()
LIke you can see i even tried multithreading but it still waits until the music is over before it goes to the rest of the code.
Don't call the functions in the target parameter of the Thread function - delete the brackets to reference the function, not its return value
musicp = threading.Thread(target=music) # instead of music()
restp = threading.Thread(target=other_things) # instead of other_things()

Gtk.Spinner in Python GTK while importing large library

I have a GTK application in C that will spawn a Python GTK process to embed a matplotlib figure into a window in the C process using GtkSocket/GtkPlug (uses XEmbed Protocol). The problem I am having is that the import of the matplotlib library takes about 2.5 seconds and during that time the socket widget is simply transparent. I would like to place a Gtk.Spinner in the plug (so the Python side) before the matplotlib import and have the spinner animate asynchronously during the process of importing the matplotlib library. The problem is that in order for the widget to be placed in the plug, and subsequently, for the Gtk.Spinner to animate, it requires iterations of the GTK main loop. I have approached this from a ton of different angles:
(1) Using a thread. The first attempt was trying to run Gtk.main_iteration() via the thread, however, GTK can only be run on the main thread and this does not work. It stalls the program.
(2) Then I tried to use GObject.idle_add from the thread, where the main loop iterations would run from the idle function (apparently the function called via idle is done on the main thread?), but this didn't work either.
(3) Then I tried to import the modules on the thread, while the main thread runs the Gtk.main_iteration()'s to allow the spinner to spin while the imports are taking place. The idea was once the imports are complete, a boolean flag would change to trigger a break from the loop of main iterations. In this case the spinner appears and spins but the plot never shows up. I get an X Server error:
Gdk-WARNING **: master: Fatal IO error 104 (Connection reset by peer) on X server :0.
(4) In lieu of threading, I tried to use GObject.timeout_add to call a function regularly that would perform the Gtk.main_iteration()'s, but doing that results in the original behavior where the socket/plug is transparent until the plot shows up (i.e. no spinner appears nor spins).
I have run out of ideas and now I am coming here hoping for an assist. They key idea is to get the Gtk.Spinner spinning while the Python script is loading the matplotlib library, and once that is done, replace the spinner widget with the figure (while all of this is taking place in a GtkSocket/Plug). I have not created a minimal reproducible example for this since it would be rather complex given the circumstances, but if anyone that is willing to help requests one I could come up with it. However, the relevant code section is below (with previous attempts commented out):
import sys
import gi
import time
import threading
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk, GObject
from gi.repository import Pango as pango
if sys.platform != "win32":
GObject.threads_init()
Gdk.threads_init()
# Open socket ID file, read Socket ID into variable, close file
socketFile = open('resources/com/gtkSocket', 'r+')
gtkSock = socketFile.read()
print("The ID of the sockets window in Python is: ", int(gtkSock))
socketFile.close()
# Create plug, create GTK box, add box to plug, add spinner to box
spin_plug = Gtk.Plug.new(int(gtkSock))
socketbox = Gtk.Box()
spin_plug.add(socketbox)
spinner = Gtk.Spinner()
socketbox.pack_start(spinner, expand=True, fill=True, padding=False)
spinner.start()
finished = False
def thread_run():
time.sleep(4)
'''
# Loop for four seconds checking if gtk events are pending, and if so, main loop iterate
t_end = time.time() + 4
while time.time() < t_end:
if (Gtk.events_pending()):
Gtk.main_iteration()
print("Events Pending...")
'''
'''
import argparse
import collections
import csv
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.patches as patches
import matplotlib.lines as mlines
from collections import defaultdict
from enum import Enum
from matplotlib.backend_bases import MouseEvent
from matplotlib.pyplot import draw
from matplotlib.widgets import SpanSelector
from matplotlib.backends.backend_gtk3agg import FigureCanvasGTK3Agg as FC
from matplotlib.backends.backend_gtk3 import NavigationToolbar2GTK3
'''
# You cannot run GTK Code on a separate thread from the one running the main loop
# Idle add allows scheduling code to be executed on the main thread
GObject.idle_add(cleanup)
def cleanup():
# Note: Trying to add the Gtk Main Iterations to the idle add function did not work...
print("Closing Spinner Thread...")
spinner.stop()
finished = True
thread.join()
# start a separate thread and immediately return to main loop
#thread = threading.Thread(target=thread_run)
#thread.start()
spin_plug.show_all()
def spin():
busy_wait = 0
while (Gtk.events_pending() or busy_wait < 10):
Gtk.main_iteration()
if (not Gtk.events_pending()):
busy_wait = busy_wait + 1
print("Spin Call complete.")
return True
GObject.timeout_add(50, spin)
'''
# We cannot simply run an infinite Gtk.main() loop, so iterate until the plug has been filled
busy_wait = 0
while (Gtk.events_pending() or busy_wait < 10):
if (finished):
break
print("Busy Wait: %d" % busy_wait)
Gtk.main_iteration()
if (not Gtk.events_pending()):
busy_wait = busy_wait + 1
print("Gtk Main Loop iterations complete.")
'''
Any pointers or ideas would be greatly appreciated.
The solution was performing the imports on a thread while allowing the main thread to do the main loop iterations. Simply doing "import " did not work. Some previous Stack Overflow posts that were useful are here:
Python thread for pre-importing modules
import a module from a thread does not work
Import python modules in the background in REPL
The solution looks like this:
GObject.threads_init()
Gdk.threads_init()
# Open socket ID file, read Socket ID into variable, close file
socketFile = open('resources/com/gtkSocket', 'r+')
gtkSock = socketFile.read()
print("The ID of the sockets window in Python is: ", int(gtkSock))
socketFile.close()
# Create plug, create GTK box, add box to plug, add figure to box
spin_plug = Gtk.Plug.new(int(gtkSock))
socketbox = Gtk.Box()
spin_plug.add(socketbox)
# Create a spinner, pack it, and start it spinning
spinner = Gtk.Spinner()
socketbox.pack_start(spinner, expand=True, fill=True, padding=False)
spinner.start()
spinner.show()
# Flag to break from the Gtk.events_pending() loop
finished = False
# This will load modules on a thread. A simple "import module" does not work
def do_import(module_name):
thismodule = sys.modules[__name__]
module = importlib.import_module(module_name)
setattr(thismodule, module_name, module)
print(module_name, 'imported')
# Use the last module being imported so we know when to break from the Gtk.events_pending()
if (module_name == "matplotlib.pyplot"):
global finished
finished = True
spin_plug.show_all()
modules_to_load = ['argparse', 'collections', 'csv', 'matplotlib', 'matplotlib.pyplot']
# Loop through and create a thread for each module to import from the list
for module_name in modules_to_load:
thread = threading.Thread(target=do_import, args=(module_name,))
thread.start()
# We cannot simply run an infinite Gtk.main() loop, so iterate until the plug has been filled
# Busy wait continues to allow the spinner to spin until the computer loads the modules. Since
# each computer will have a different loading speed, a busy wait of 300 should cover slower
# machines. We can break out of the loop early once the last module is loaded.
busy_wait = 0
while (Gtk.events_pending() or busy_wait < 300):
#print("Busy Wait: %d" % busy_wait)
#print ("finished: %d" % finished)
if (finished):
break
Gtk.main_iteration()
if (not Gtk.events_pending()):
busy_wait = busy_wait + 1

python console terminal sholuld stay on top of any window

Would like your opinion and support on an issue i am trying to overcome. This will be the last piece of puzzle for completion of a small project i am building. Its based on OCR. I am reading text from a live screen ( using below python script ) and able to get the results logged into a file. However, the output is only getting logged once i make the python console window ( in which the script prints the output ) is active/focused by keyboad using alt+tab.
But doing this halts the software from where i am reading the text, breaking the whole process. Toggling the window to the front of the software is a failure to the scripts purpose.
So, i added code after searching from other users about keeping the python console window on top always no matter what the software is doing. I am not able to keep this python console window on top of this sw screen. The SW uses all screen for its purpose of work.
Is there an alternative to this? How can i make the python console become on top of any other window no matter what is on the screen? If not this, please suggest an alternative.
import numpy as nm
from datetime import datetime
import pytesseract
import cv2
import PIL
from PIL import ImageGrab
import win32gui, win32process, win32con
import os
hwnd = win32gui.GetForegroundWindow()
win32gui.SetWindowPos(hwnd,win32con.HWND_TOPMOST,0,0,100,300,0)
#Define function for OCR to enable on multiple screens.
def imToString():
# Path of tesseract executable
pytesseract.pytesseract.tesseract_cmd ='C:\\Tesseract-OCR\\tesseract.exe'
while(True):
# ImageGrab-To capture the screen image in a loop.
# Bbox used to capture a specific area.
#screen base
cap1 = PIL.ImageGrab.grab(bbox =(0, 917, 1913, 1065), include_layered_windows=False, all_screens=True)
date = datetime.now().strftime("%Y-%m-%d %I:%M:%S")
#str config - OCR Engine settings for ONLY text based capture.
config1 = ('-l eng --oem 2 --psm 6')
#configuring tesseract engine for OCR
tess1 = pytesseract.image_to_string(
cv2.cvtColor(nm.array(cap1), cv2.COLOR_BGR2GRAY),
config=config1)
#Defining log pattern to generate
a = [ date, " State: ", tess1 ]
#writing logging output to file
file1 = open("C:\\Users\\User\\Desktop\\rev2.log", "a", encoding='UTF8')
file1.writelines(a)
file1.writelines("\n")
file1.close()
#OUTPUT on colse for Logging verification
print (date, "State: ", tess1)
# Calling the function
imToString()
By requirement, i am not allowed to use a keyboad while operating the screen. I am fairly new to python and have been seeing similar solutions and adding it to the script to make a proper solution.
Please advise.
Here is the tkinter method:
from tkinter import Tk, Text
import subprocess
import threading
from queue import Queue, Empty
filename = 'test.py'
def stream_from(queue):
command = f'python {filename}'
with subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as process:
for out in process.stdout:
queue.put(out.decode())
def update_text(queue):
try:
data = queue.get(block=False)
except Empty:
pass
else:
text.config(state='normal')
text.insert('end', data.strip() + '\n')
text.config(state='disabled')
finally:
root.after(100, update_text, queue)
def loop():
root.attributes('-topmost', True)
root.after(1, loop)
root = Tk()
text = Text(root, state='disabled')
text.pack()
data_queue = Queue()
threading.Thread(target=stream_from, args=(data_queue, ), daemon=True).start()
update_text(data_queue)
loop()
root.mainloop()
Just change the filename to the name of the file you are running and place this script in the same directory
Change delay_in_ms (delay in milliseconds so each 1000 units is one second) and see if that helps (could also leave the 10 seconds and see if it works now, if not there is still another thing to try)
If you are using print to output then this should work (tho I maybe didn't exactly get what you want), if you use logging then there is a slightly different solution

Python save multiple images with pyautogui

Im making a screenshot program that takes a screenshot every 5 seconds. But it will only save 1 .png file. Its becuase the name is the same every time and it wont make duplicates.
How do i save them as image(1), image(2), image(3)....
This is my code:
import pyautogui
import threading
#myScreenshot = pyautogui.screenshot()
#myScreenshot.save(r'C:\Users\censored\Desktop\screenshot\imgs\image.png')
def ScreenShotTimer():
threading.Timer(5.0, ScreenShotTimer).start()
myScreenshot = pyautogui.screenshot()
myScreenshot.save(r'C:\Users\censored\Desktop\screenshot\imgs\image.png')
print('Program Is Still Running.')
ScreenShotTimer()
Thanks for helping me!
import pyautogui
# Solution 1
import time
for i in range(60):
ss = pyautogui.screenshot()
ss.save(f"SS {i}.png")
time.sleep(5)
# Solution 2
import threading
import functools as ft
def screenshot(index: int = 0):
ss = pyautogui.screenshot()
ss.save(f"SS {index}.png")
threading.Timer(5.0, ft.partial(screenshot, index+1)).start()
screenshot()
Two solutions.
Uses a for loop (could easily be a while loop).
Uses threading.Timer and functools.partial to call the
function again, with a different parameter.

Script crashes when called from a second script

I have a report generation script (let's call this script A)that generates a PDF report. I want to import this in a new script (script B) and call the functions from within this new script.
The problem:
Script A crashes when called from Script B exactly at the creation of a QPixmap object. Python console restarts.
Script A runs fine when it is run on its own, but if I try to call it from Command Prompt python.exe crashes.
The following is Script B. All it does is it imports Script A, creates an instance of a class defined in A, and uses that to call a method of that object.
import Script A as et
a = "sample"
mdbPath = "C:\\Python27\\test.mdb"
directory = "C:\\Python27"
ui = et.HawkAutomationScript()
ui.Main(mdbPath,directory,a)
Script A contains the definition of a class called HawkAutomationScript. This object contains two functions to note: one Main function and one function to generate the report. These are the imported modules:
from random import*
import sys
import os
import subprocess
import time
from datetime import datetime
from PyQt4 import QtCore, QtGui
from PyQt4.QtCore import QRect
from PyQt4.QtGui import QTextDocument, QPrinter, QApplication, QPainter
from string import Template
import shutil
import time
import pyodbc
This is the definition of the class and the Main function:
doc = QTextDocument()
myCursor = QtGui.QTextCursor(doc)
class HawkAutomationScript(object):
def Main(self,mdb,directory,name):
Ui_FormObject = HawkAutomationScript()
## Filling in the Report ##
Ui_FormObject.fnGenerateReport(directory,name)
def fnGenerateReport(self,directory,name):
now = datetime.now()
dateString = now.strftime("%d-%b-%Y") # For getting Current Date
dirPath = "C:\\"
ResultName = "SelfTest_Results"
testName = name+".pdf"
subDirPath = dirPath + name
if(os.path.isdir(dirPath)):
if(os.path.isdir(subDirPath)):
subDirPath = subDirPath + testName
else:
os.mkdir(subDirPath)
subDirPath = subDirPath + testName
else:
os.mkdir(dirPath)
os.mkdir(subDirPath)
subDirPath = subDirPath + testName
pixmap = QtGui.QPixmap(doc.size().width(), doc.size().height()) ################
printer = QPrinter()
printer.setOutputFileName(subDirPath)
printer.setOutputFormat(QPrinter.PdfFormat)
doc.print_(printer)
# Create a QPainter to draw our content
painter = QPainter(pixmap)
painter.begin( printer )
doc.drawContents(painter)
painter.end()
This is all the relevant code and it is runnable.
If I run script B, A crashes at the QPixmap line marked in the code. But if I copy the lines from B and run A on its own, the PDF report is generated. But if I run A on its own from Command Prompt python.exe crashes.
Any help would be really appreciated. Thank you!!

Categories

Resources