Пример #1
0
 def test_cases(self):
     from xpra.server.window.video_subregion import scoreinout  #, sslog
     #sslog.enable_debug()
     r = rectangle.rectangle(35, 435, 194, 132)
     score = scoreinout(1200, 1024, r, 1466834, 21874694)
     assert score < 100
     r = rectangle.rectangle(100, 600, 320, 240)
     score = scoreinout(1200, 1024, r, 320 * 240 * 10, 320 * 240 * 25)
     assert score < 100
Пример #2
0
 def test_intersection(self):
     r1 = rectangle(0, 0, 100, 100)
     r2 = rectangle(50, 50, 200, 200)
     i = r1.intersection_rect(r2)
     assert i.x == 50 and i.y == 50 and i.width == 50 and i.height == 50
     i2 = r2.intersection_rect(r1)
     assert i2 == i
     r3 = rectangle(100, 100, 50, 50)
     i = r2.intersection_rect(r3)
     assert i == r3
     r4 = rectangle(0, 0, 10, 10)
     i = r3.intersection_rect(r4)
     assert i is None
Пример #3
0
 def set_exclusion_zones(self, zones):
     rects = []
     for (x, y, w, h) in zones:
         rects.append(rectangle(int(x), int(y), int(w), int(h)))
     self.exclusion_zones = rects
     #force expire:
     self.counter = 0
Пример #4
0
 def set_region(self, x, y, w, h):
     sslog("set_region%s", (x, y, w, h))
     if self.detection:
         sslog("video region detection is on - the given region may or may not stick")
     if x==0 and y==0 and w==0 and h==0:
         self.novideoregion("empty")
     else:
         self.rectangle = rectangle(x, y, w, h)
Пример #5
0
def test_merge_all():
    start = time.time()
    R = [rectangle(*v) for v in R1 + R2]
    n = N * 10
    for _ in range(n):
        v = merge_all(R)
    end = time.time()
    print("merged %s rectangles %s times in %.3fms" %
          (len(R), n, (end - start) * 1000.0 / N))
Пример #6
0
 def test_substract(self):
     #  ##########          ##########
     #  #        #          ##########
     #  #        #          ##########
     #  #        #          ##########
     #  #   ##   #    ->                + ####  +  ####  +
     #  #   ##   #                        ####     ####
     #  #        #                                          ##########
     #  #        #                                          ##########
     #  #        #                                          ##########
     #  ##########                                          ##########
     r = rectangle(0, 0, 100, 100)
     sub = rectangle(40, 40, 20, 20)
     l = r.substract_rect(sub)
     assert len(l) == 4
     #verify total area has not changed:
     total = r.width * r.height
     assert total == sum(r.width * r.height for r in l + [sub])
     assert rectangle(0, 0, 100, 40) in l
     assert rectangle(0, 40, 40, 20) in l
     assert rectangle(0, 40, 40, 20) in l
     # at (0,0)
     # ##########
     # #        #
     # #        #
     # #        #
     # #        #         at (50, 50)
     # #        #         ##########
     # #        #         #        #
     # #        #    -    #        #
     # #        #         #        #
     # ##########         #        #
     #                    #        #
     #                    #        #
     #                    #        #
     #                    #        #
     #                    ##########
     r = rectangle(0, 0, 100, 100)
     sub = rectangle(50, 50, 100, 100)
     l = r.substract_rect(sub)
     assert len(l) == 2
     assert rectangle(0, 0, 100, 50) in l
     assert rectangle(0, 50, 50, 50) in l
     assert rectangle(200, 200, 0, 0) not in l
Пример #7
0
 def do_screen_refresh(self, rlist):
     #TODO: improve damage method to handle lists directly:
     from xpra.rectangle import rectangle     #@UnresolvedImport
     model_rects = {}
     for model in self._id_to_window.values():
         model_rects[model] = rectangle(*model.geometry)
     for x, y, w, h in rlist:
         for model, rect in model_rects.items():
             mrect = rect.intersection(x, y, w, h)
             #log("screen refresh intersection of %s and %24s: %s", model, (x, y, w, h), mrect)
             if mrect:
                 self._damage(model, mrect.x-rect.x, mrect.y-rect.y, mrect.width, mrect.height)
Пример #8
0
def test_gvim_damage_performance(rectangles):
    start = time.time()
    for _ in range(N):
        rects = []
        for x, y, width, height in rectangles:
            r = rectangle(x, y, width, height)
            rects.append(r)
    end = time.time()
    print("created %s rectangles %s times in %.3fms" %
          (len(rectangles), N, (end - start) * 1000.0 / N))
    #now try add rectangle:
    start = time.time()
    for _ in range(N):
        rects = []
        for x, y, width, height in rectangles:
            r = rectangle(x, y, width, height)
            add_rectangle(rects, r)
    end = time.time()
    print("add_rectangle %s rectangles %s times in %.3fms" %
          (len(rectangles), N, (end - start) * 1000.0 / N))
    #now try remove rectangle:
    start = time.time()
    for _ in range(N):
        rects = []
        for x, y, width, height in rectangles:
            r = rectangle(x + width // 4, y + height // 3, width // 2,
                          height // 2)
            remove_rectangle(rects, r)
    end = time.time()
    print("remove_rectangle %s rectangles %s times in %.3fms" %
          (len(rectangles), N, (end - start) * 1000.0 / N))

    start = time.time()
    n = N * 1000
    for _ in range(n):
        for r in rects:
            contains_rect(rects, r)
    end = time.time()
    print("contains_rect %s rectangles %s times in %.3fms" %
          (len(rectangles), n, (end - start) * 1000.0 / N))
Пример #9
0
 def damaged_ratio(rect):
     if all_damaged:
         return 1
     rects = (rect, )
     for _,x,y,w,h in lde:
         r = rectangle(x,y,w,h)
         new_rects = []
         for cr in rects:
             new_rects += cr.substract_rect(r)
         if not new_rects:
             #nothing left: damage covered the whole rect
             return 1.0
         rects = new_rects
     not_damaged_pixels = sum((r.width*r.height) for r in rects)
     rect_pixels = rect.width*rect.height
     #sslog("damaged_ratio: not damaged pixels(%s)=%i, rect pixels(%s)=%i",
     #     rects, not_damaged_pixels, rect, rect_pixels)
     return max(0, min(1, 1.0-float(not_damaged_pixels)/float(rect_pixels)))
Пример #10
0
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# This file is part of Xpra.
# Copyright (C) 2014-2019 Antoine Martin <*****@*****.**>
# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
# later version. See the file COPYING for details.

import unittest

try:
    from xpra.rectangle import rectangle  #@UnresolvedImport

    R1 = rectangle(0, 0, 20, 20)
    R2 = rectangle(0, 0, 20, 20)
    R3 = rectangle(0, 0, 40, 40)
    R4 = rectangle(10, 10, 50, 50)
    R5 = rectangle(100, 100, 100, 100)
except ImportError:
    rectangle, R1, R2, R3, R4, R5 = None, None, None, None, None, None


class TestRegion(unittest.TestCase):
    def test_eq(self):
        assert R1 == R2
        assert R1 != R3
        assert R2 != R3

    def test_intersection(self):
        r1 = rectangle(0, 0, 100, 100)
        r2 = rectangle(50, 50, 200, 200)
        i = r1.intersection_rect(r2)
Пример #11
0
    def identify_video_subregion(self, ww, wh, damage_events_count, last_damage_events, starting_at=0, children=None):
        if not self.enabled or not self.supported:
            self.novideoregion("disabled")
            return
        if not self.detection:
            if not self.rectangle:
                return
            #just update the fps:
            from_time = max(starting_at, monotonic_time()-MAX_TIME, self.min_time)
            self.time = monotonic_time()
            lde = tuple(x for x in tuple(last_damage_events) if x[0]>=from_time)
            incount = 0
            for _,x,y,w,h in lde:
                r = rectangle(x,y,w,h)
                inregion = r.intersection_rect(self.rectangle)
                if inregion:
                    incount += inregion.width*inregion.height
            elapsed = monotonic_time()-from_time
            if elapsed<=0:
                self.fps = 0
            else:
                self.fps = int(incount/(self.rectangle.width*self.rectangle.height) / elapsed)
            return
        sslog("%s.identify_video_subregion(..)", self)
        sslog("identify_video_subregion%s",
              (ww, wh, damage_events_count, last_damage_events, starting_at, children))

        children_rects = ()
        if children:
            children_rects = tuple(rectangle(x, y, w, h)
                                   for _xid, x, y, w, h, _border, _depth in children
                                   if w>=MIN_W and h>=MIN_H)

        if damage_events_count < self.set_at:
            #stats got reset
            self.set_at = 0
        #validate against window dimensions:
        rect = self.rectangle
        if rect and (rect.width>ww or rect.height>wh):
            #region is now bigger than the window!
            self.novideoregion("window is now smaller than current region")
            return
        #arbitrary minimum size for regions we will look at:
        #(we don't want video regions smaller than this - too much effort for little gain)
        if ww<MIN_W or wh<MIN_H:
            self.novideoregion("window is too small: %sx%s", MIN_W, MIN_H)
            return

        def update_markers():
            self.counter = damage_events_count
            self.time = monotonic_time()

        if self.counter+10>damage_events_count:
            #less than 10 events since last time we called update_markers:
            elapsed = monotonic_time()-self.time
            #how many damage events occurred since we chose this region:
            event_count = max(0, damage_events_count - self.set_at)
            #make the timeout longer when the region has worked longer:
            slow_region_timeout = 2 + math.log(2+event_count, 1.5)
            if rect and elapsed>=slow_region_timeout:
                update_markers()
                self.novideoregion("too much time has passed (%is for %i total events)", elapsed, event_count)
                return
            sslog("identify video: waiting for more damage events (%i) counters: %i / %i",
                  event_count, self.counter, damage_events_count)
            return

        from_time = max(starting_at, monotonic_time()-MAX_TIME, self.min_time)
        #create a list (copy) to work on:
        lde = tuple(x for x in tuple(last_damage_events) if x[0]>=from_time)
        dc = len(lde)
        if dc<=MIN_EVENTS:
            self.novideoregion("not enough damage events yet (%s)", dc)
            return
        #structures for counting areas and sizes:
        wc = {}
        hc = {}
        dec = {}
        #count how many times we see each area, each width/height and where,
        #after removing any exclusion zones:
        for _,x,y,w,h in lde:
            rects = self.excluded_rectangles(rectangle(x,y,w,h), ww, wh)
            for r in rects:
                dec[r] = dec.get(r, 0)+1
                if w>=MIN_W:
                    wc.setdefault(w, dict()).setdefault(x, set()).add(r)
                if h>=MIN_H:
                    hc.setdefault(h, dict()).setdefault(y, set()).add(r)
        #we can shortcut the damaged ratio if the whole window got damaged at least once:
        all_damaged = dec.get(rectangle(0, 0, ww, wh), 0) > 0

        def inoutcount(region, ignore_size=0):
            #count how many pixels are in or out if this region
            incount, outcount = 0, 0
            for r, count in dec.items():
                inregion = r.intersection_rect(region)
                if inregion:
                    incount += inregion.width*inregion.height*int(count)
                outregions = r.substract_rect(region)
                for x in outregions:
                    if ignore_size>0 and x.width*x.height<ignore_size:
                        #skip small region outside rectangle
                        continue
                    outcount += x.width*x.height*int(count)
            return incount, outcount

        def damaged_ratio(rect):
            if all_damaged:
                return 1
            rects = (rect, )
            for _,x,y,w,h in lde:
                r = rectangle(x,y,w,h)
                new_rects = []
                for cr in rects:
                    new_rects += cr.substract_rect(r)
                if not new_rects:
                    #nothing left: damage covered the whole rect
                    return 1.0
                rects = new_rects
            not_damaged_pixels = sum((r.width*r.height) for r in rects)
            rect_pixels = rect.width*rect.height
            #sslog("damaged_ratio: not damaged pixels(%s)=%i, rect pixels(%s)=%i",
            #     rects, not_damaged_pixels, rect, rect_pixels)
            return max(0, min(1, 1.0-float(not_damaged_pixels)/float(rect_pixels)))

        scores = {None : 0}
        def score_region(info, region, ignore_size=0, d_ratio=0):
            score = scores.get(region)
            if score is not None:
                return score
            #check if the region given is a good candidate, and if so we use it
            #clamp it:
            if region.width<MIN_W or region.height<MIN_H:
                #too small, ignore it:
                return 0
            #and make sure this does not end up much bigger than needed:
            if ww*wh<(region.width*region.height):
                return 0
            incount, outcount = inoutcount(region, ignore_size)
            total = incount+outcount
            score = scoreinout(ww, wh, region, incount, outcount)
            #discount score if the region contains areas that were not damaged:
            #(apply sqrt to limit the discount: 50% damaged -> multiply by 0.7)
            if d_ratio==0:
                d_ratio = damaged_ratio(region)
            score = int(score * math.sqrt(d_ratio))
            children_boost = int(region in children_rects)*SUBWINDOW_REGION_BOOST
            sslog("testing %12s video region %34s: %3i%% in, %3i%% out, %3i%% of window, damaged ratio=%.2f, children_boost=%i, score=%2i",
                  info, region, 100*incount//total, 100*outcount//total, 100*region.width*region.height/ww/wh, d_ratio, children_boost, score)
            scores[region] = score
            return score

        def updateregion(rect):
            self.rectangle = rect
            self.time = monotonic_time()
            self.inout = inoutcount(rect)
            self.score = scoreinout(ww, wh, rect, *self.inout)
            elapsed = monotonic_time()-from_time
            if elapsed<=0:
                self.fps = 0
            else:
                self.fps = int(self.inout[0]/(rect.width*rect.height) / elapsed)
            self.damaged = int(100*damaged_ratio(self.rectangle))
            self.last_scores = scores
            sslog("score(%s)=%s, damaged=%i%%", self.inout, self.score, self.damaged)

        def setnewregion(rect, msg, *args):
            rects = self.excluded_rectangles(rect, ww, wh)
            if not rects:
                self.novideoregion("no match after removing excluded regions")
                return
            if len(rects)==1:
                rect = rects[0]
            else:
                #use the biggest one of what remains:
                def get_rect_size(rect):
                    return -rect.width * rect.height
                biggest_rects = sorted(rects, key=get_rect_size)
                rect = biggest_rects[0]
                if rect.width<MIN_W or rect.height<MIN_H:
                    self.novideoregion("match is too small after removing excluded regions")
                    return
            if not self.rectangle or self.rectangle!=rect:
                sslog("setting new region %s: "+msg, rect, *args)
                sslog(" is child window: %s", rect in children_rects)
                self.set_at = damage_events_count
                self.counter = damage_events_count
            if not self.enabled:
                #could have been disabled since we started this method!
                self.novideoregion("disabled")
                return
            if not self.detection:
                return
            updateregion(rect)

        update_markers()

        if len(dec)==1:
            rect, count = tuple(dec.items())[0]
            setnewregion(rect, "only region damaged")
            return

        #see if we can keep the region we already have (if any):
        cur_score = 0
        if rect:
            cur_score = score_region("current", rect)
            if cur_score>=KEEP_SCORE:
                sslog("keeping existing video region %s with score %s", rect, cur_score)
                return

        #split the regions we really care about (enough pixels, big enough):
        damage_count = {}
        min_count = max(2, len(lde)/40)
        for r, count in dec.items():
            #ignore small regions:
            if count>min_count and r.width>=MIN_W and r.height>=MIN_H:
                damage_count[r] = count
        c = sum(int(x) for x in damage_count.values())
        most_damaged = -1
        most_pct = 0
        if c>0:
            most_damaged = int(sorted(damage_count.values())[-1])
            most_pct = 100*most_damaged/c
            sslog("identify video: most=%s%% damage count=%s", most_pct, damage_count)
            #is there a region that stands out?
            #try to use the region which is responsible for most of the large damage requests:
            most_damaged_regions = tuple(r for r,v in damage_count.items() if v==most_damaged)
            if len(most_damaged_regions)==1:
                r = most_damaged_regions[0]
                score = score_region("most-damaged", r, d_ratio=1.0)
                sslog("identify video: score most damaged area %s=%i%%", r, score)
                if score>120:
                    setnewregion(r, "%s%% of large damage requests, score=%s", most_pct, score)
                    return
                if score>=100:
                    scores[r] = score

        #try children windows:
        for region in children_rects:
            scores[region] = score_region("child-window", region, 48*48)

        #try harder: try combining regions with the same width or height:
        #(some video players update the video region in bands)
        for w, d in wc.items():
            for x,regions in d.items():
                if len(regions)>=2:
                    #merge regions of width w at x
                    min_count = max(2, len(regions)//25)
                    keep = tuple(r for r in regions if int(dec.get(r, 0))>=min_count)
                    sslog("vertical regions of width %i at %i with at least %i hits: %s", w, x, min_count, keep)
                    if keep:
                        merged = merge_all(keep)
                        scores[merged] = score_region("vertical", merged, 48*48)
        for h, d in hc.items():
            for y,regions in d.items():
                if len(regions)>=2:
                    #merge regions of height h at y
                    min_count = max(2, len(regions)//25)
                    keep = tuple(r for r in regions if int(dec.get(r, 0))>=min_count)
                    sslog("horizontal regions of height %i at %i with at least %i hits: %s", h, y, min_count, keep)
                    if keep:
                        merged = merge_all(keep)
                        scores[merged] = score_region("horizontal", merged, 48*48)

        sslog("merged regions scores: %s", scores)
        highscore = max(scores.values())
        #a score of 100 is neutral
        if highscore>=120:
            region = next(iter(r for r,s in scores.items() if s==highscore))
            setnewregion(region, "very high score: %s", highscore)
            return

        #retry existing region, tolerate lower score:
        if cur_score>=90 and (highscore<100 or cur_score>=highscore):
            sslog("keeping existing video region %s with score %s", rect, cur_score)
            setnewregion(self.rectangle, "existing region with score: %i" % cur_score)
            return

        if highscore>=100:
            region = next(iter(r for r,s in scores.items() if s==highscore))
            setnewregion(region, "high score: %s", highscore)
            return

        #TODO:
        # * re-add some scrolling detection: the region may have moved
        # * re-try with a higher "from_time" and a higher score threshold

        #try harder still: try combining all the regions we haven't discarded
        #(flash player with firefox and youtube does stupid unnecessary repaints)
        if len(damage_count)>=2:
            merged = merge_all(tuple(damage_count.keys()))
            score = score_region("merged", merged)
            if score>=110:
                setnewregion(merged, "merged all regions, score=%s", score)
                return

        self.novideoregion("failed to identify a video region")
        self.last_scores = scores
Пример #12
0
    def test_eq(self):
        log = video_subregion.sslog

        def refresh_cb(window, regions):
            log("refresh_cb(%s, %s)", window, regions)

        r = video_subregion.VideoSubregion(GLib.timeout_add,
                                           GLib.source_remove, refresh_cb, 150,
                                           True)
        assert repr(r)
        r.set_detection(True)
        r.set_region(0, 0, 10, 10)
        r.set_detection(False)
        r.set_region(0, 0, 0, 0)
        r.set_detection(True)

        ww = 1024
        wh = 768

        def assertiswin():
            if not r.rectangle:
                raise Exception(
                    "region not found, should have matched the whole window")
            if r.rectangle.get_geometry() != (0, 0, ww, wh):
                raise Exception(
                    "rectangle %s does not match whole window %ix%i" %
                    (r.rectangle, ww, wh))

        log("* checking that we need some events")
        last_damage_events = []
        for x in range(video_subregion.MIN_EVENTS):
            last_damage_events.append((0, 0, 0, 1, 1))
        r.identify_video_subregion(ww, wh, video_subregion.MIN_EVENTS,
                                   last_damage_events)
        assert r.rectangle is None

        vr = (monotonic_time(), 100, 100, 320, 240)
        log("* easiest case: all updates in one region")
        last_damage_events = []
        for _ in range(50):
            last_damage_events.append(vr)
        r.identify_video_subregion(ww, wh, 50, last_damage_events)
        assert r.rectangle
        assert r.rectangle == rectangle.rectangle(*vr[1:])

        log("* checking that empty damage events does not cause errors")
        r.reset()
        r.identify_video_subregion(ww, wh, 0, [])
        assert r.rectangle is None

        log("* checking that full window can be a region")
        vr = (monotonic_time(), 0, 0, ww, wh)
        last_damage_events = []
        for _ in range(50):
            last_damage_events.append(vr)
        r.identify_video_subregion(ww, wh, 50, last_damage_events)
        assert r.rectangle is not None

        log("* checking that regions covering the whole window give the same result"
            )
        last_damage_events = deque(maxlen=150)
        for x in range(4):
            for y in range(4):
                vr = (monotonic_time(), ww * x / 4, wh * y / 4, ww / 4, wh / 4)
                for _ in range(3):
                    last_damage_events.append(vr)
        r.identify_video_subregion(ww, wh, 150, last_damage_events)
        assertiswin()

        vr = (monotonic_time(), ww / 4, wh / 4, ww / 2, wh / 2)
        log("* mixed with region using 1/4 of window and 1/3 of updates: %s",
            vr)
        for _ in range(24):
            last_damage_events.append(vr)
        r.identify_video_subregion(ww, wh, 200, last_damage_events)
        assertiswin()

        log("* info=%s", r.get_info())

        log("* checking that two video regions quite far apart do not get merged"
            )
        last_damage_events = deque(maxlen=150)
        r.reset()
        v1 = (monotonic_time(), 100, 100, 320, 240)
        v2 = (monotonic_time(), 500, 500, 320, 240)
        for _ in range(50):
            last_damage_events.append(v1)
            last_damage_events.append(v2)
        r.identify_video_subregion(ww, wh, 100, last_damage_events)
        assert r.rectangle is None

        log("* checking that two video regions close to each other can be merged"
            )
        for N1, N2 in ((50, 50), (60, 40), (50, 30)):
            last_damage_events = deque(maxlen=150)
            r.reset()
            v1 = (monotonic_time(), 100, 100, 320, 240)
            for _ in range(N1):
                last_damage_events.append(v1)
            v2 = (monotonic_time(), 460, 120, 320, 240)
            for _ in range(N2):
                last_damage_events.append(v2)
            r.identify_video_subregion(ww, wh, 100, last_damage_events)
            m = rectangle.merge_all(
                [rectangle.rectangle(*v1[1:]),
                 rectangle.rectangle(*v2[1:])])
            assert r.rectangle and r.rectangle == m, "expected %s but got %s for N1=%i, N2=%i" % (
                m, r.rectangle, N1, N2)
        r.set_enabled(False)
        r.remove_refresh_region(rectangle.rectangle(0, 0, 10, 10))
        r.cleanup()