sikuli observing multiple images with priority - python

I've defined a sikuli module which is used to click on an image when something appears inside a region.
# observer.py
from sikuli import *
class Observer:
# When "observedImage" appears inside "region", double click on "reactImage"
def __init__(self, region, observedImage, reactImage):
self.region = region
self.observedImage = observedImage
self.reactImage = reactImage
def start(self):
self.region.onAppear(self.observedImage, self.appearHandler)
self.region.observe(FOREVER, background = True)
def appearHandler(self, event):
doubleClick(self.reactImage)
event.repeat()
def stop(self):
self.region.stopObserver()
Here's how to use it:
import observer
import time
observer.Observer(Region(111,222,333,444), "imageToBeDetected1.png", "imageToBeClicked1.png").start()
observer.Observer(Region(555,666,66,666), "imageToBeDetected2.png", "imageToBeClicked2.png").start()
while True:
print('waiting')
time.sleep(1)
The problem with the above code is that when imageToBeDetected1 and imageToBeDetected2 both appear in Region(111,222,333,444) and Region(555,666,66,666) respectively, my mouse will move between imageToBeClicked1 and imageToBeClicked2. I want only imageToBeDetected1 to be clicked in this situation.
imageToBeDetected2 should be ignored when imageToBeDetected1 and imageToBeDetected2 both appear in Region(111,222,333,444) and Region(555,666,66,666), respectively.
How can I modify my code so that imageToBeDetected1 has a higher priority over imageToBeDetected2?
Or is there a better way to observe multiple images with sikuli?

Related

Implementing scroll on Python courses library

Just for fun, I am implementing a python program to generate Ulam spirals (https://en.wikipedia.org/wiki/Ulam_spiral) of arbitrary size. The program receives the size of the spiral and eventually the program prints the spiral on stdout.
I was looking at how to improve the display of the spiral on the terminal, especially if the spiral is very large or the terminal window is a bit short, causing characters to overlap and break the image. I found out that ncourses (https://en.wikipedia.org/wiki/Ncurses) can be used for this, as it can create scrollable pads inside the windows that should not overlap.
With this in mind, I am thinking of a layout like this:
|--------------------------------------|--------------------------------------|
| Application logs | Spiral goes here |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
|--------------------------------------|--------------------------------------|
The "Application logs" are implemented with the python logging library, with a custom handler that makes the ads and refreshes the pad itself. So far, the code I have for this:
main.py
import logging
import curses
from curses import wrapper
from utils.log_handler import CursesHandler
import signal
##logger config
log = logging.getLogger()
log.setLevel(logging.getLevelName('INFO'))
log_formatter = logging.Formatter("%(asctime)s [%(processName)s]-[%(levelname)s] [%(threadName)s]: %(message)s")
def start_cli_windows(stdscr):
current_windows = []
height,width = stdscr.getmaxyx()
middle_columns = width // 2
ulam_spiral_pad = curses.newpad(height, middle_columns)
ulam_spiral_pad.addstr(0, 0, "*** SPIRAL GOES HERE ***")
##actual spiral on the right, from the middle to the end
ulam_spiral_pad.refresh(0, 0, 0, middle_columns ,height, width)
##set up logger, with the position for the logger
console_handler = CursesHandler(screen_top_left_col=0,screen_top_left_row=0,screen_bottom_right_col=middle_columns,screen_bottom_right_row=height)
console_handler.setFormatter(log_formatter)
log.addHandler(console_handler)
##Add our own handler as a window for our application
current_windows.append(console_handler)
## let's do some testing
for i in range(1000):
log.info(f"This is the log number {i}")
console_handler.poll_for_input()
def handler(signum, frame):
#just quit, the wrapper will clean up
exit(0)
if __name__ == "__main__":
signal.signal(signal.SIGINT, handler)
wrapper(start_cli_windows)
utils/log_handler.py
import logging
import curses
from utils.curses_wrapper import CursesWindow
class CursesHandler(logging.Handler, CursesWindow):
"""
Class that overrides a Logging handler, so it will point to a curses window instead of a regular
terminal or file. The class will create the padding at the specified parameters, and also will scroll if required
"""
def __init__(self, screen_top_left_col,screen_top_left_row,screen_bottom_right_col,screen_bottom_right_row):
logging.Handler.__init__(self)
CursesWindow.__init__(self, screen_top_left_col,screen_top_left_row,screen_bottom_right_col,screen_bottom_right_row)
def emit(self, record):
try:
msg = self.format(record)
self._screen.addstr(f"\n{msg}")
self._refresh_screen()
except (KeyboardInterrupt, SystemExit):
raise
except Exception as e:
self.handleError(record)
utils/curses_wrapper.py
import curses
class CursesWindow():
def __init__(self,screen_top_left_col,screen_top_left_row,screen_bottom_right_col,screen_bottom_right_row):
self._screen = curses.newpad(screen_bottom_right_row,screen_bottom_right_col)
self._screen.scrollok(True)
self._screen.keypad(1)
# The upper left corner of the pad region to be displayed are set by the following parameters. As this is new,
# init with 0, but scrolling may change this
self._min_row_to_display = 0
self._min_col_to_display = 0
# clipping box on the screen within which the pad region is to be displayed
self._screen_top_left_col = screen_top_left_col
self._screen_top_left_row = screen_top_left_row
self._screen_bottom_right_col = screen_bottom_right_col
self._screen_bottom_right_row = screen_bottom_right_row
#property
def min_row_to_display(self):
return self._min_row_to_display
#property
def min_col_to_display(self):
return self._min_col_to_display
#property
def window_position(self):
return (self._screen_top_left_col,self._screen_top_left_row,self._screen_bottom_right_col,self._screen_bottom_right_row)
#window_position.setter
def set_window_position(self, position_tuple):
self._screen_top_left_col,self._screen_top_left_row,self._screen_bottom_right_col,self._screen_bottom_right_row = position_tuple
self._refresh_screen()
def _refresh_screen(self):
self._screen.refresh(self._min_row_to_display,
self._min_col_to_display,
self._screen_top_left_row,
self._screen_top_left_col,
self._screen_bottom_right_row,
self._screen_bottom_right_col)
def scroll_window_up(self):
self._screen.scroll(-1)
self._refresh_screen()
def scroll_window_down(self):
self._screen.scroll(1)
self._refresh_screen()
def poll_for_input(self):
QUIT_PROGRAM = False
while not QUIT_PROGRAM:
c = self._screen.getch()
if c == ord('q'):
QUIT_PROGRAM = True
elif c == curses.KEY_UP:
self.scroll_window_up()
elif c == curses.KEY_DOWN:
self.scroll_window_down()
This code kinds of works, as it will let me add strings, and auto scroll when the end of the space is reached. However, when I try to scroll, it kinds of removes the data that is there. Showing gif as an example:
As you can see, the lines are lost forever once the scroll moves. My hypothesis is that the refresh function repaints the viewport, however, it is not painting the content that is inside the pad. A possible reason is that I am telling the refresh to always show the coordinates (0,0) when in reality I should use the line number I want the scroll start from.
Most of the samples I saw online assume the number of lines to show is known, either because the data is on an array, or there is a way to calculate the number beforehand. That way, the new position, and refresh can be done accurately. However, in this case, I don't know how many lines will be on the log, as I just receive the stream of data to print, and then add it to the pad.
So, the question: Is there a way to get how many lines are printed on the pad? Lines can be wrapped, so one line not necessarily correlates to a one-log message in this case. I tried the following approaches:
Using window.getyx(), which returns the position of the cursor relative to the pad but it returns the height of the component when the cursor is at the button. For example, I expected to return 1000 (as those are the number of lines on the pad) but it returns 100 (the number of lines of my terminal)
Using window.getyx(), however, it returns the boundaries of the pad, not the data.
If needed, I can implement a circular buffer or something that will hold the data and use that as a backend, I think. However, first I want to know if I am missing something here.
Thanks in advance for the help!

custom Tkinter button won't grid properly inside Frame

I have created a custom Tkinter Button widget which inherits from the standard Button, purely for aesthetic purposes so that I can repeat the same style throughout all my program without have to configure it manually every time. The code for my custom Button is here:
import tkinter as tk
class gameButton(tk.Button):
def __init__(self,*args,**kwargs):
super(gameButton,self).__init__()
self.root=args[0]
self.configure(text=kwargs["text"])
self.configure(fg=kwargs["fg"],
activeforeground=kwargs["fg"])
try:
self.configure(command=kwargs["command"])
except KeyError:
pass
self.configure(relief="flat",cursor="hand2")
self.bind("<Enter>",self.hover)
self.bind("<Leave>",self.leave)
self.old=kwargs["bg"]
self.dark=self.darken(self.old)
self.configure(bg=self.old,activebackground=self.dark,
bd=1,relief="solid")
def getRGB(self,h):
it=tuple(int(h[i:i+2], 16) for i in (0, 2 ,4))
return it
def getHex(self,h):
it='#%02x%02x%02x' % h
return it
def darken(self,h):
currentHex=self.old.replace("#","")
currentRGB=self.getRGB(currentHex)
currentR=currentRGB[0]
currentG=currentRGB[1]
currentB=currentRGB[2]
if currentR>30:
newR=round(currentR-30)
else:
newR=0
if currentG>30:
newG=round(currentG-30)
else:
newG=0
if currentB>30:
newB=round(currentB-30)
else:
newB=0
newRGB=(newR,newG,newB)
newHex=self.getHex(newRGB)
return newHex
def hover(self,event):
self.configure(bg=self.dark)
def leave(self,event):
self.configure(bg=self.old)
The section of the code where I'm trying to grid them in a Frame is here (I imported gameButton as GB):
game=Frame(notebook,bg=bg)
notebook.add(game,text="Game")
mapb=GB(game,text="Map",compound="left",bg=bg,fg=fg)
travelb=GB(game,text="Travel",compound="left",bg=bg,fg=fg)
bagb=GB(game,text="Bag",compound="left",bg=bg,fg=fg)
pokedexb=GB(game,text="Pokédex",compound="left",bg=bg,fg=fg)
partyb=GB(game,text="Party",compound="left",bg=bg,fg=fg)
saveb=GB(game,text="Save",compound="left",bg=bg,fg=fg)
mapb.grid(row=0,column=0,padx=5,pady=5,sticky="nesw")
travelb.grid(row=0,column=1,padx=5,pady=5,sticky="nesw")
bagb.grid(row=0,column=2,padx=5,pady=5,sticky="nesw")
pokedexb.grid(row=1,column=0,padx=5,pady=5,sticky="nesw")
partyb.grid(row=1,column=1,padx=5,pady=5,sticky="nesw")
saveb.grid(row=1,column=2,padx=5,pady=5,sticky="nesw")
The result is this:
The button grids fine in a normal root window, but it refuses to behave properly inside a Frame inside a root.

Pyglet, exit after all sounds played

AVbin is installed. Both .wav and .mp3 files work.
import pyglet
music = pyglet.media.load('A.mp3')
music.play()
player = pyglet.media.Player()
player.queue( pyglet.media.load('B.mp3'))
player.queue( pyglet.media.load('C.wav'))
player.play()
pyglet.app.run()
pyglet.app.exit()
I want to create a program that plays A, then plays the queue with B and then C, and finally quits after all three sounds play.
I tried the code above but according to this post, "this is [solely] because app.run() is a never-ending loop."
How can I modify my code minimally so that the program quits after the three sounds are played?
Bonus, but how can I modify my code minimally so that the program can play two (or more) sound files, E.mp3 and F.mp3, at once?
Thanks!
Because what you're asking is not as simple as you'd might think it is.
I've put together a code example with as much comments as I possibly could fit in without making the example to hard to read.
Below the code, I'll try to explain a few key functions as detailed as possible.
import pyglet
from pyglet.gl import *
from collections import OrderedDict
key = pyglet.window.key
class main(pyglet.window.Window):
def __init__ (self, width=800, height=600, fps=False, *args, **kwargs):
super(main, self).__init__(width, height, *args, **kwargs)
self.keys = OrderedDict() # This just keeps track of which keys we're holding down. In case we want to do repeated input.
self.alive = 1 # And as long as this is True, we'll keep on rendering.
## Add more songs to the list, either here, via input() from the console or on_key_ress() function below.
self.songs = ['A.wav', 'B.wav', 'C.wav']
self.song_pool = None
self.player = pyglet.media.Player()
for song in self.songs:
media = pyglet.media.load(song)
if self.song_pool is None:
## == if the Song Pool hasn't been setup,
## we'll set one up. Because we need to know the audio_format()
## we can't really set it up in advance (consists more information than just 'mp3' or 'wav')
self.song_pool = pyglet.media.SourceGroup(media.audio_format, None)
## == Queue the media into the song pool.
self.song_pool.queue(pyglet.media.load(song))
## == And then, queue the song_pool into the player.
## We do this because SourceGroup (song_pool) as a function called
## .has_next() which we'll require later on.
self.player.queue(self.song_pool)
## == Normally, you would do self.player.eos_action = self.function()
## But for whatever windows reasons, this doesn't work for me in testing.
## So below is a manual workaround that works about as good.
self.current_track = pyglet.text.Label('', x=width/2, y=height/2+50, anchor_x='center', anchor_y='center')
self.current_time = pyglet.text.Label('', x=width/2, y=height/2-50, anchor_x='center', anchor_y='center')
def on_draw(self):
self.render()
def on_close(self):
self.alive = 0
def on_key_release(self, symbol, modifiers):
try:
del self.keys[symbol]
except:
pass
def on_key_press(self, symbol, modifiers):
if symbol == key.ESCAPE: # [ESC]
self.alive = 0
elif symbol == key.SPACE:
if self.player.playing:
self.player.pause()
else:
self.player.play()
elif symbol == key.RIGHT:
self.player.seek(self.player.time + 15)
## == You could check the user input here,
## and add more songs via the keyboard here.
## For as long as self.song_pool has tracks,
## this player will continue to play.
self.keys[symbol] = True
def end_of_tracks(self, *args, **kwargs):
self.alive=0
def render(self):
## Clear the screen
self.clear()
## == You could show some video, image or text here while the music plays.
## I'll drop in a example where the current Track Name and time are playing.
## == Grab the media_info (if any, otherwise this returns None)
media_info = self.player.source.info
if not media_info:
## == if there were no meta-data, we'll show the file-name instead:
media_info = self.player.source._file.name
else:
## == But if we got meta data, we'll show "Artist - Track Title"
media_info = media_info.author + ' - ' + media_info.title
self.current_track.text = media_info
self.current_track.draw()
## == This part exists of two things,
## 1. Grab the Current Time Stamp and the Song Duration.
## Check if the song_pool() is at it's end, and if the track Cur>=Max -> We'll quit.
## * (This is the manual workaround)
cur_t, end_t = int(self.player.time), int(self.player.source._get_duration())
if self.song_pool.has_next() is False and cur_t >= end_t:
self.alive=False
## 2. Show the current time and maximum time in seconds to the user.
self.current_time.text = str(cur_t)+'/'+str(end_t) + 'seconds'
self.current_time.draw()
## This "renders" the graphics:
self.flip()
def run(self):
while self.alive == 1:
self.render()
# -----------> This is key <----------
# This is what replaces pyglet.app.run()
# but is required for the GUI to not freeze
#
event = self.dispatch_events()
x = main()
x.run()
Now, normally you'd decorate your way trough this with a bunch of functions.
But I like to subclass and OOP my way through any graphical libraries, because it gets messy quite fast otherwise.
So instead of pyglet.app.run(), I've got a custom made run() function.
All this does is mimic the pyglet.app.run(), for the most part. Enough to get going at least.
Because player.eos_* events appears to be broken.
I've added a manual example of how you could check if the songs are done playing or not.
This is a combination of self.song_pool pyglet.media.SourceGroup, self.player.time pyglet.media.player.time and self.player.source._get_duration() which returns the track duration.
The SourceGroup gives us a has_next() function which tells us if we're at the end of the queued songs. The other two variables tells us if we've reached the end of the current track. This is all we need to determinate if we want to exit or not.
Now, I haven't technically added a way to add more songs. Because again, that would also be harder than you think. Unless you opt in for if symbol == key.LCTRL: self.song_pool.queue(pyglet.media.load(input('Song: '))) for instance. But again, all you would need to do, is add more songs to the self.song_pool queue, and there you go.
I hope this answers your question. Even the bonus one.

Timed Threaded function as an attribute in a class

So I am making a text based adventure game. I am working on the engine right now and I am stuck after long hours searching for a solution for this problem.
I have a class called use_action. One of the arguments for that class is a name of a function. I would like to be able to create this action and have a possible custom function incase the item that calls this use_action does something specific.
The custom function I am working with right now is where the player is hurt and is losing 5 HP every so many seconds.
This should start when he uses a specific item and then stops when he uses the medicine that will link to the stop function. The problem I have is that the function gets called immediately. Even though I am trying to call it at the end of a long if else statement. And then when i get to where i am trying to call it it doesn't call.
I am not posting the whole class as it along with its functions are about 150 lines of code.
class use_action(object):
def __init__(self, function = None):
self.function = function
pizza_act = use_action(function = mechanics.tmr.start())
#This is located at the end of an if else statement after the player types use . . .
if self.function != None:
self.function
else:
pass
From Mechanics:
thread_list = []
class TimerClass(threading.Thread):
def __init__(self, function, time):
threading.Thread.__init__(self)
self.event = threading.Event()
self.function = function
self.time = time
thread_list.append(self)
def run(self):
while not self.event.is_set():
self.event.wait( self.time )
self.function()
def stop(self):
self.event.set()
def blank_current_readline():
# Next line said to be reasonably portable for various Unixes
(rows,cols) = struct.unpack('hh', fcntl.ioctl(sys.stdout, termios.TIOCGWINSZ,'1234'))
text_len = len(readline.get_line_buffer())+2
# ANSI escape sequences (All VT100 except ESC[0G)
sys.stdout.write('\x1b[2K') # Clear current line
sys.stdout.write('\x1b[1A\x1b[2K'*(text_len/cols)) # Move cursor up and clear line
sys.stdout.write('\x1b[0G') # Move to start of line
def pizza_poisoned_action():
# threading.Timer(10, pizza_poisoned_action).start()
blank_current_readline()
print "You lost 5 hp."
initialization.gamer.hp -= 5
sys.stdout.write('> ' + readline.get_line_buffer())
sys.stdout.flush() # Needed or text doesn't show until a key is pressed
tmr = TimerClass(pizza_poisoned_action, 5)
Sorry about the length, I tried to only post the relevant stuff for this. If you think i should post some other piece of code that may be relevant let me know!
If you want to pass a function, don't call it. Or else, you'll be passing the return value.
pizza_act = use_action(function = mechanics.test()) #Wrong!
pizza_act = use_action(function = mechanics.test) #Right

Qprogressbar with two values

I have some unusual question :
For visualization of packing progress i think about qprogressbar with two values in one bar - one showing bytes read, and another showing write-out bytes, which gives also imagine about compress ratio.
It is possible with QT4 ?
Also, I have very little experience with C++ coding, my current work is based on Python, PyQT4,
Yes it's possible, but you will have to implement your own "DualValueProgressbar" here you have an example, is not complete production code but it will point to you in the right direction.
A note before continue:
Will this you will be able to show two values in the bar, but show two colours in the same bar is a very different thing. So I'll recomend you to use two prograssbar for doing what you want, keep it simple.
Before see any code let me explain what I did.
Subclass QProgressBar
Add a variable member called self.__value_1. This will be the second value.
Override the method paintEvent in order to draw self.__value_1 inside the bar.
Recomendations:
Write code for establishing limits on the second value. (Minimun and maximun)
Write code for handle the format property.
Write code for habdle the aligment property.
This is the result:
Here is the code:
from PyQt4.QtGui import *
from PyQt4.QtCore import *
class DualValueProgressBar(QProgressBar):
def __init__(self, parent=None):
super(DualValueProgressBar, self).__init__(parent)
# The other value you want to show
self.__value_1 = 0
def paintEvent(self, event):
# Paint the parent.
super(DualValueProgressBar, self).paintEvent(event)
# In the future versions if your custom object you
# should use this to set the position of the value_1
# in the progressbar, right now I'm not using it.
aligment = self.alignment()
geometry = self.rect() # You use this to set the position of the text.
# Start to paint.
qp = QPainter()
qp.begin(self)
qp.drawText(geometry.center().x() + 20, geometry.center().y() + qp.fontMetrics().height()/2.0, "{0}%".format(str(self.value1)))
qp.end()
#property
def value1(self):
return self.__value_1
#pyqtSlot("int")
def setValue1(self, value):
self.__value_1 = value
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
window = QWidget()
hlayout = QHBoxLayout(window)
dpb = DualValueProgressBar(window)
dpb.setAlignment(Qt.AlignHCenter)
# This two lines are important.
dpb.setValue(20)
dpb.setValue1(10) # Look you can set another value.
hlayout.addWidget(dpb)
window.setLayout(hlayout)
window.show()
sys.exit(app.exec())
Finally the code sample:

Categories