Optimal way to scan constantly in main loop

Asked by bengalih

I'm trying to determine the most optimal way to scan for an image on the screen constantly and then perform an operation when found.

My application defs are all triggered by hotkeys so I have a main loop as:

while True:
    wait(1)

I add into my main loop code similar to the following:

while True:
    wait(1)
    Settings.ObserveScanRate = 0.2
    if TestEpisodeRegion.exists("nextepisode.png"):
        print("i see you")
    Settings.ObserveScanRate = 1

I am not entirely sure what measurement the wait is being calculated at...is it seconds or ms?
Also, if I understand correctly a 0.2 scan rate will scan only about every 4-5 seconds?

My goal is to have the scan rate for all the hotkey events to function with a normal scan rate (3/second?) as I need quick response on those events, but the detection of the "nextepisode.png" I am willing to live with about a 5 second delay before it is pressed in order to minimize CPU usage.

Can you comment if my code is proper for the desired effect, or what I should modify?

thanks!

Question information

Language:
English Edit question
Status:
Answered
For:
SikuliX Edit question
Assignee:
No assignee Edit question
Last query:
Last reply:
Revision history for this message
RaiMan (raimund-hocke) said :
#1

--- Settings.ObserveScanRate
is only for the observer feature. for normal find operations like used with exists() it is the Settings.WaitScanRate

So if you use this, your solution will work as expected.

But it looks a little weird. this is clearer:

while True:
    wait(1)
    # search once every five seconds
    while not TestEpisodeRegion.exists("nextepisode.png", 0):
        wait(5)
    print("i see you")

This might need some adaption in your concrete situation.

Revision history for this message
j (j-the-k) said :
#2

why don't you use an observer instead of an ugly while True ? ^^

Settings.ObserveScanRate = 0.2

def handler():
    print "I see you"
    wait(5)

onAppear("nextepisode.png",handler)
observe()

I think this is much better than "while True" ?

Revision history for this message
RaiMan (raimund-hocke) said :
#3

@ j-the-k

your observe solution:
- will scan once and give up after 3 seconds (standard waiting time), because the next scan would only start after 5 seconds

... and it is functional the same as (timing might be slightly different):

if exists(image, 0): wait(5)
wait(3)

which surely is much more compact and clear ;-)

if using observe() with some sense:

def handler(e):
    print "I see you"
    e.region.stopObserver()
    wait(5)

onAppear("nextepisode.png",handler)
observe(20)

This would wait max 20 seconds and if found would stop observation and wait 5 seconds in the handler (I do not know, wether this influences the main workflow).

I think, that for these simple situations (waiting for one or two things, while the script is pausing) using exists(image, 0) is much more compact, transparent and easier.

observe() used in background though, while the script continues, is really a mighty Sikuli feature, but still rather complex concerning communication between main flow and handlers.

Revision history for this message
bengalih (bengalih) said :
#4

Rai....

This seems to work...

while True:
    wait(1)
    # search once every five seconds
    while not TestEpisodeRegion.exists("nextepisode.png", 0):
        wait(5)
    print("i see you")

However I have some questions

I would have expected this to stop the processing of the rest of the script (in terms of hotkey handling) and only pickup the hotkeys every 5 seconds. I guess then that a hotkey event will interrupt the script and run at any time?

Also, I have the search region relatively narrow, it is probably set to about 1/50th of the screen.
I still see that Sikuli spikes the java process to about 3-5% every x seconds (with X being my wait interval above).
Short of narrowing the scan area, or increasing the interval between checks with the wait, are there any other tuning operations I can perform that might lessen CPU?

I had narrowed down the area by about half and didn't see any real changes in cpu consumption, so I'm wondering if narrowing the area will really help (while still keeping it large enough to be accurate for my image).

thanks

Revision history for this message
RaiMan (raimund-hocke) said :
#5

I made this test in the IDE.

shouldStop = False
inHandler = 0
modi = KeyModifier.CTRL+KeyModifier.ALT

def handlerx(e):
    global shouldStop
    shouldStop = True
    print "stopping now"

def handlery(e):
    global inHandler
    xhk = inHandler+0 # to get a copy
    inHandler += 1
    popup("Hallo from "+str(xhk))
    wait(5)
    print "leaving "+str(xhk)
    inHandler -= 1

Env.addHotkey("x", modi, handlerx)
Env.addHotkey("z", modi, handlery)

while not shouldStop: wait(1)

# to get rid of the hot keys while running in IDE
Env.removeHotkey("x", modi)
Env.removeHotkey("z", modi) # for y on my german keyboard

# without this, the script will terminate, while handlers still continue
# as long as the JVM is active
while inHandler: wait(1)

print "stopped"

--- I learned the following:
- as long as you run the script in the IDE, the added hotkeys stay active even if the script has terminated. So in IDE it is mandatory to remove them before terminating the script
- every time, a hotkey is pressed, the corresponding handler is started. there is no serialization or communication. If you need something like that (see above: inHandler), you have to implement it via globals or e.g. the Sikuli class Settings.
- the main workflow does not have any information, that a handler is entered (same as for observe()). If you need this, you have to implement it like for serialization.
- main workflow and handlers run independently in parallel

--- cpu consumption
I do not know, how you measure or observe that.
But depending on your kind of observation, there might not be a difference to see, in CPU usage (depends on when and how often it is measured). But the time, the search takes, decreases with the number of pixels to compare. So if you have a search region of 1000x800 (800.000 pixels) and this takes e.g. 1 second for an image of 20x20 (400 pixels) and you search instead in a region of 100x100 (10.000 pixels) the search time should go down to a value below 0.1. But the observed percentage of cpu usage during this time period might even go towards 100% depending on the demand of other running applications.

So I think the problem is not the cpu consumption as such, but the time, that Sikuli is consuming high % of cpu - and this can be reduced dramatically with smaller search regions.

might be interesting: bug 988206 and related question.

Can you help with this problem?

Provide an answer of your own, or ask bengalih for more information if necessary.

To post a message you must log in.