Exemple #1
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 %.2fms" % (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 %.2fms" % (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 %.2fms" % (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 %.2fms" % (len(rectangles), n, (end-start)*1000.0/N))
Exemple #2
0
def test_intersection():
    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
Exemple #3
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 %.2fms" % (len(R), n, (end-start)*1000.0/N))
Exemple #4
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 %.2fms" %
          (len(R), n, (end - start) * 1000.0 / N))
Exemple #5
0
def test_substract():
    #  ##########          ##########
    #  #        #          ##########
    #  #        #          ##########
    #  #        #          ##########
    #  #   ##   #    ->                + ####  +  ####  +
    #  #   ##   #                        ####     ####
    #  #        #                                          ##########
    #  #        #                                          ##########
    #  #        #                                          ##########
    #  ##########                                          ##########
    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
Exemple #6
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 %.2fms" %
          (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 %.2fms" %
          (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 %.2fms" %
          (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 %.2fms" %
          (len(rectangles), n, (end - start) * 1000.0 / N))
Exemple #7
0
#!/usr/bin/env python
# coding=utf8
# This file is part of Xpra.
# Copyright (C) 2014 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.

from xpra.server.region import rectangle

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)


def test_eq():
    assert R1==R2
    assert R1!=R3
    assert R2!=R3

def test_merge():
    r1 = R1.clone()
    r1.merge_rect(R4)
    assert r1.x==0 and r1.x==0 and r1.width==60 and r1.height==60
    r2 = R2.clone()
    r2.merge_rect(R5)
    assert r2.x==0 and r2.y==0 and r2.width==200 and r2.height==200

def test_intersection():
    r1 = rectangle(0, 0, 100, 100)
Exemple #8
0
    def identify_video_subregion(self,
                                 ww,
                                 wh,
                                 damage_events_count,
                                 last_damage_events,
                                 starting_at=0):
        def setnewregion(rect, msg="", *args):
            if rect.x <= 0 and rect.y <= 0 and rect.width >= ww and rect.height >= wh:
                #same size as the window, don't use a region!
                self.novideoregion("region is full window")
                return
            sslog("setting new region %s: " + msg, rect, *args)
            self.set_at = damage_events_count
            self.counter = damage_events_count
            self.rectangle = rect

        if damage_events_count < self.set_at:
            #stats got reset
            self.video_subregion_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!
            return self.novideoregion(
                "window is now smaller than current region")
        #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:
            return self.novideoregion("window is too small: %sx%s", MIN_W,
                                      MIN_H)

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

        def few_damage_events(event_types, event_count):
            elapsed = time.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 = 10 + math.log(2 + event_count, 1.5)
            if rect and elapsed >= slow_region_timeout:
                update_markers()
                return self.novideoregion(
                    "too much time has passed (%is for %s %s events)", elapsed,
                    event_types, event_count)
            sslog(
                "identify video: waiting for more %s damage events (%s) counters: %s / %s",
                event_types, event_count, self.counter, damage_events_count)

        if self.counter + 10 > damage_events_count:
            #less than 10 events since last time we called update_markers:
            event_count = damage_events_count - self.counter
            few_damage_events("total", event_count)
            return

        #create a list (copy) to work on:
        lde = [x for x in list(last_damage_events) if x[0] >= starting_at]
        dc = len(lde)
        if dc <= MIN_EVENTS:
            return self.novideoregion("not enough damage events yet (%s)", dc)
        #structures for counting areas and sizes:
        wc = {}
        hc = {}
        dec = {}
        #count how many times we see each area, each width/height and where:
        for _, x, y, w, h in lde:
            r = rectangle(x, y, w, h)
            dec.setdefault(r, MutableInteger()).increase()
            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)

        def score_region(info, region):
            #check if the region given is a good candidate, and if so we use it
            #clamp it:
            region.width = min(ww, region.width)
            region.height = min(wh, region.height)
            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:
            insize = region.width * region.height
            if ww * wh <= insize:
                return 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:
                    outcount += x.width * x.height * int(count)
            total = incount + outcount
            assert total > 0
            inpct = 100 * incount / total
            outpct = 100 * outcount / total
            #devaluate by taking into account the number of pixels in the area
            #so that a large video region only wins if it really
            #has a larger proportion of the pixels
            #(offset the "insize" to even things out a bit:
            # if we have a series of vertical or horizontal bands that we merge,
            # we would otherwise end up excluding the ones on the edge
            # if they ever happen to have a slightly lower hit count)
            score = inpct * ww * wh * 2 / (ww * wh + insize)
            sslog(
                "testing %12s video region %34s: %3i%% in, %3i%% out, score=%2i",
                info, region, inpct, outpct, score)
            return score

        update_markers()

        #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 >= 125:
                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 = [
                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)
                if score > 120:
                    if r.width >= (ww - 16) and r.height >= (wh - 16):
                        return self.novideoregion(
                            "most damaged region is (almost?) the whole window: %s",
                            r)
                    setnewregion(r, "%s%% of large damage requests, score=%s",
                                 most_pct, score)
                    return

        #try harder: try combining regions with the same width or height:
        #(some video players update the video region in bands)
        scores = {None: 0}
        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 = [
                        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)
        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 = [
                        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)

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

        #retry existing region, tolerate lower score:
        if cur_score >= 90:
            sslog("keeping existing video region %s with score %s", rect,
                  cur_score)
            return

        if highscore >= 105:
            region = [r for r, s in scores.items() if s == highscore][0]
            return setnewregion(region, "high score: %s", highscore)

        #FIXME: re-add some scrolling detection

        #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(damage_count.keys())
            score = score_region("merged", merged)
            if score >= 110:
                return setnewregion(merged, "merged all regions, score=%s",
                                    score)

        self.novideoregion("failed to identify a video region")
    def test_eq(self):
        log = video_subregion.sslog

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

        r = video_subregion.VideoSubregion(gobject.timeout_add,
                                           gobject.source_remove, refresh_cb,
                                           150)

        ww = 1024
        wh = 768

        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 = (time.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 == region.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 is not a region")
        vr = (time.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 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 = (time.time(), ww * x / 4, wh * y / 4, ww / 4, wh / 4)
                last_damage_events.append(vr)
                last_damage_events.append(vr)
                last_damage_events.append(vr)
        r.identify_video_subregion(ww, wh, 150, last_damage_events)
        assert r.rectangle is None

        vr = (time.time(), ww / 4, wh / 4, ww / 2, wh / 2)
        log("* mixed with region using 1/5 of window and 1/3 of updates: %s",
            vr)
        for _ in range(30):
            last_damage_events.append(vr)
        r.identify_video_subregion(ww, wh, 200, last_damage_events)
        assert r.rectangle is not None

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

        log("* checking that two video regions with the same characteristics get merged"
            )
        last_damage_events = deque(maxlen=150)
        r.reset()
        v1 = (time.time(), 100, 100, 320, 240)
        v2 = (time.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)
        m = region.merge_all(
            [region.rectangle(*v1[1:]),
             region.rectangle(*v2[1:])])
        assert r.rectangle == m, "expected %s but got %s" % (m, r.rectangle)

        log("* but not if they are too far apart")
        last_damage_events = deque(maxlen=150)
        r.reset()
        v1 = (time.time(), 20, 20, 320, 240)
        v2 = (time.time(), ww - 20 - 320, wh - 240 - 20, 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
Exemple #10
0
    def identify_video_subregion(self, ww, wh, damage_events_count, last_damage_events, starting_at=0):

        def setnewregion(rect, msg="", *args):
            if rect.x<=0 and rect.y<=0 and rect.width>=ww and rect.height>=wh:
                #same size as the window, don't use a region!
                self.novideoregion("region is full window")
                return
            sslog("setting new region %s: "+msg, rect, *args)
            self.set_at = damage_events_count
            self.counter = damage_events_count
            self.rectangle = rect

        if damage_events_count < self.set_at:
            #stats got reset
            self.video_subregion_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!
            return self.novideoregion("window is now smaller than current region")
        #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:
            return self.novideoregion("window is too small: %sx%s", MIN_W, MIN_H)

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

        def few_damage_events(event_types, event_count):
            elapsed = time.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 = 10 + math.log(2+event_count, 1.5)
            if rect and elapsed>=slow_region_timeout:
                update_markers()
                return self.novideoregion("too much time has passed (%is for %s %s events)", elapsed, event_types, event_count)
            sslog("identify video: waiting for more %s damage events (%s) counters: %s / %s", event_types, event_count, self.counter, damage_events_count)

        if self.counter+10>damage_events_count:
            #less than 10 events since last time we called update_markers:
            event_count = damage_events_count-self.counter
            few_damage_events("total", event_count)
            return

        #create a list (copy) to work on:
        lde = [x for x in list(last_damage_events) if x[0]>=starting_at]
        dc = len(lde)
        if dc<=MIN_EVENTS:
            return self.novideoregion("not enough damage events yet (%s)", dc)
        #structures for counting areas and sizes:
        wc = {}
        hc = {}
        dec = {}
        #count how many times we see each area, each width/height and where:
        for _,x,y,w,h in lde:
            r = rectangle(x,y,w,h)
            dec.setdefault(r, MutableInteger()).increase()
            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)

        def score_region(info, region):
            #check if the region given is a good candidate, and if so we use it
            #clamp it:
            region.width = min(ww, region.width)
            region.height = min(wh, region.height)
            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:
            insize = region.width*region.height
            if ww*wh<=insize:
                return 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:
                    outcount += x.width*x.height*int(count)
            total = incount+outcount
            assert total>0
            inpct = 100*incount/total
            outpct = 100*outcount/total
            #devaluate by taking into account the number of pixels in the area
            #so that a large video region only wins if it really
            #has a larger proportion of the pixels
            #(offset the "insize" to even things out a bit:
            # if we have a series of vertical or horizontal bands that we merge,
            # we would otherwise end up excluding the ones on the edge
            # if they ever happen to have a slightly lower hit count)
            score = inpct * ww*wh*2 / (ww*wh + insize)
            sslog("testing %12s video region %34s: %3i%% in, %3i%% out, score=%2i", info, region, inpct, outpct, score)
            return score

        update_markers()

        #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>=125:
                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 = [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)
                if score>120:
                    if r.width>=(ww-16) and r.height>=(wh-16):
                        return self.novideoregion("most damaged region is (almost?) the whole window: %s", r)
                    setnewregion(r, "%s%% of large damage requests, score=%s", most_pct, score)
                    return

        #try harder: try combining regions with the same width or height:
        #(some video players update the video region in bands)
        scores = {None : 0}
        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 = [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)
        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 = [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)

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

        #retry existing region, tolerate lower score:
        if cur_score>=90:
            sslog("keeping existing video region %s with score %s", rect, cur_score)
            return

        if highscore>=105:
            region = [r for r,s in scores.items() if s==highscore][0]
            return setnewregion(region, "high score: %s", highscore)

        #FIXME: re-add some scrolling detection

        #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(damage_count.keys())
            score = score_region("merged", merged)
            if score>=110:
                return setnewregion(merged, "merged all regions, score=%s", score)

        self.novideoregion("failed to identify a video region")
Exemple #11
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(gobject.timeout_add, gobject.source_remove, refresh_cb, 150)

        ww = 1024
        wh = 768

        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 = (time.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==region.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 is not a region")
        vr = (time.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 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 = (time.time(), ww*x/4, wh*y/4, ww/4, wh/4)
                last_damage_events.append(vr)
                last_damage_events.append(vr)
                last_damage_events.append(vr)
        r.identify_video_subregion(ww, wh, 150, last_damage_events)
        assert r.rectangle is None

        vr = (time.time(), ww/4, wh/4, ww/2, wh/2)
        log("* mixed with region using 1/5 of window and 1/3 of updates: %s", vr)
        for _ in range(30):
            last_damage_events.append(vr)
        r.identify_video_subregion(ww, wh, 200, last_damage_events)
        assert r.rectangle is not None

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

        log("* checking that two video regions with the same characteristics get merged")
        last_damage_events = deque(maxlen=150)
        r.reset()
        v1 = (time.time(), 100, 100, 320, 240)
        v2 = (time.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)
        m = region.merge_all([region.rectangle(*v1[1:]), region.rectangle(*v2[1:])])
        assert r.rectangle==m, "expected %s but got %s" % (m, r.rectangle)

        log("* but not if they are too far apart")
        last_damage_events = deque(maxlen=150)
        r.reset()
        v1 = (time.time(), 20, 20, 320, 240)
        v2 = (time.time(), ww-20-320, wh-240-20, 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