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))
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
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))
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))
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
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))
#!/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)
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
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