Ticket #1426: scroll-cythonized.patch
File scroll-cythonized.patch, 27.9 KB (added by , 4 years ago) |
---|
-
xpra/server/window/motion.pyx
1 1 # coding=utf8 2 2 # This file is part of Xpra. 3 # Copyright (C) 2016 Antoine Martin <antoine@devloop.org.uk>3 # Copyright (C) 2016-2017 Antoine Martin <antoine@devloop.org.uk> 4 4 # Xpra is released under the terms of the GNU GPL v2, or, at your option, any 5 5 # later version. See the file COPYING for details. 6 6 … … 9 9 10 10 import os 11 11 import time 12 import collections 12 13 13 14 from xpra.util import envbool 14 15 from xpra.log import Logger 15 log ger = Logger("encoding")16 log = Logger("encoding", "scroll") 16 17 17 import zlib 18 19 cdef int DEBUG = envbool("XPRA_SCROLL_DEBUG", False) 18 20 hashfn = None 19 21 if envbool("XPRA_XXHASH", True): 20 22 try: … … 22 24 def hashfn(x): 23 25 return xxhash.xxh64(x).intdigest() 24 26 except ImportError as e: 25 log ger.warn("Warning: xxhash python bindings not found")27 log.warn("Warning: xxhash python bindings not found") 26 28 else: 27 log ger.warn("Warning: xxhash disabled")29 log.warn("Warning: xxhash disabled") 28 30 if hashfn is None: 29 log ger.warn(" no scrolling detection")31 log.warn(" no scrolling detection") 30 32 31 33 32 cdef extern from "math.h": 33 double log(double x) 34 from libc.stdint cimport int32_t, uint8_t, uint16_t, int16_t, uint32_t, int64_t 34 35 35 from libc.stdint cimport int32_t, uint8_t, uint32_t, int64_t36 37 cdef extern from "stdlib.h":38 int abs(int number)39 40 36 cdef extern from "string.h": 41 37 void free(void * ptr) nogil 42 38 void *memset(void * ptr, int value, size_t num) nogil 43 int memcmp(const void *a1, const void *a2, size_t size)44 39 45 40 cdef extern from "../../buffers/memalign.h": 46 41 void *xmemalign(size_t size) nogil … … 76 71 #assert v>=0, "invalid int to cast: %s" % v 77 72 return v 78 73 79 def calculate_distances(array1, array2, int min_score=0, int max_distance=1000): 80 #print("calculate_distances(..)") 81 assert len(array1)==len(array2) 82 cdef int l = len(array1) 83 cdef int i, y1, y2, miny, maxy, d 84 #we want fast array access, 85 #so cache both arrays in C arrays: 86 assert sizeof(int64_t)==64//8, "uint64_t is not 64-bit: %i!" % sizeof(int64_t) 87 cdef size_t asize = l*(sizeof(int64_t)) 88 cdef int64_t *a1 = NULL 89 cdef int64_t *a2 = NULL 90 cdef int64_t a2v = 0 91 cdef int32_t *distances = NULL 92 #print("calculate_distances(%s, %s, %i, %i)" % (array1, array2, elen, min_score)) 93 try: 94 a1 = <int64_t*> xmemalign(asize) 95 a2 = <int64_t*> xmemalign(asize) 96 assert a1!=NULL and a2!=NULL, "failed to allocate %i bytes of scroll array memory" % asize 97 for i in range(l): 98 a1[i] = castint64(array1[i]) 99 a2[i] = castint64(array2[i]) 74 cdef inline da(int64_t *a, uint16_t l): 75 return [a[i] for i in range(l)] 76 77 cdef inline dd(uint16_t *d, uint16_t l): 78 return [d[i] for i in range(l)] 79 80 cdef inline ds(int16_t *d, uint16_t l): 81 return [d[i] for i in range(l)] 82 83 84 assert sizeof(int64_t)==64//8, "uint64_t is not 64-bit: %i!" % sizeof(int64_t) 85 86 87 cdef class ScrollDistances: 88 89 cdef object __weakref__ 90 #for each distance, keep track of the hit count: 91 cdef uint16_t* distances 92 cdef uint16_t l 93 cdef int64_t *a1 94 cdef int64_t *a2 95 96 def init(self, array1, array2, uint16_t max_distance=1000): 97 assert len(array1)==len(array2) 98 assert len(array1)<2**15 and len(array1)>0, "invalid array length: %i" % len(array1) 99 self.l = len(array1) 100 self.distances = <uint16_t*> xmemalign(2*self.l*sizeof(uint16_t)) 101 cdef size_t asize = self.l*(sizeof(int64_t)) 102 self.a1 = <int64_t*> xmemalign(asize) 103 self.a2 = <int64_t*> xmemalign(asize) 104 assert self.distances!=NULL and self.a1!=NULL and self.a2!=NULL, "scroll memory allocation failed" 105 for i in range(self.l): 106 self.a1[i] = castint64(array1[i]) 107 self.a2[i] = castint64(array2[i]) 100 108 #now compare all the values 101 distances = <int32_t*> xmemalign(2*l*sizeof(int32_t)) 102 assert distances!=NULL 109 self.calculate(max_distance) 110 111 def __repr__(self): 112 return "ScrollDistances(%i)" % self.l 113 114 cdef calculate(self, uint16_t max_distance=1000): 115 cdef int64_t *a1 = self.a1 116 cdef int64_t *a2 = self.a2 117 cdef uint16_t l = self.l 118 cdef uint16_t y1, y2 119 cdef uint16_t miny=0, maxy=0 120 cdef int64_t a2v 103 121 with nogil: 104 memset(<void*> distances, 0, 2*l*sizeof(int32_t))122 memset(<void*> self.distances, 0, 2*self.l*sizeof(uint16_t)) 105 123 for y2 in range(l): 106 miny = max(0, y2-max_distance) 107 maxy = min(l, y2+max_distance) 124 #miny = max(0, y2-max_distance): 125 if y2>max_distance: 126 miny = y2-max_distance 127 else: 128 miny = 0 129 #maxy = min(l, y2+max_distance) 130 if y2+max_distance<l: 131 maxy = y2+max_distance 132 else: 133 maxy = l 108 134 a2v = a2[y2] 109 135 if a2v==0: 110 136 continue … … 111 137 for y1 in range(miny, maxy): 112 138 if a1[y1]==a2v: 113 139 #distance = y1-y2 114 distances[l+y1-y2] += 1 115 r = {} 116 for i in range(2*l): 117 d = distances[i] 118 if abs(d)>=min_score: 119 r[i-l] = d 120 return r 121 finally: 122 if a1!=NULL: 123 free(a1) 124 if a2!=NULL: 125 free(a2) 126 if distances!=NULL: 127 free(distances) 140 self.distances[l-(y1-y2)] += 1 141 if DEBUG: 142 log("ScrollDistance: l=%i, calculate(%s, %s, %i)=%s", self.l, da(self.a1, self.l), da(self.a2, self.l), max_distance, dd(self.distances, self.l*2)) 128 143 129 def match_distance(array1, array2, int distance): 130 assert len(array1)==len(array2) 131 l = len(array1) 132 if distance>=0: 133 return [i for i,v in enumerate(array1) if (i+distance)<l and array2[i+distance]==v] 134 distance = abs(distance) 135 return [i+distance for i,v in enumerate(array2) if (i+distance)<l and array1[i+distance]==v] 144 def get_best_scroll_values(self, uint16_t min_hits=2): 145 DEF MAX_MATCHES = 20 146 cdef uint16_t m_arr[MAX_MATCHES] #number of hits 147 cdef int16_t s_arr[MAX_MATCHES] #scroll distance 148 cdef int16_t i 149 cdef uint8_t j 150 memset(<void*> &m_arr, 0, MAX_MATCHES*sizeof(uint16_t)) 151 memset(<void*> &s_arr, 0, MAX_MATCHES*sizeof(int16_t)) 152 cdef int16_t low = 0 153 cdef int16_t matches 154 cdef uint16_t* distances = self.distances 155 cdef uint16_t l = self.l 156 with nogil: 157 for i in range(2*l): 158 matches = distances[i] 159 if matches>low and matches>min_hits: 160 #add this candidate match to the arrays: 161 for j in range(MAX_MATCHES): 162 if m_arr[j]==low: 163 break 164 m_arr[j] = matches 165 s_arr[j] = i-l 166 #find the new lowest value: 167 low = matches 168 for j in range(MAX_MATCHES): 169 if m_arr[j]<low: 170 low = m_arr[j] 171 if low==0: 172 break 173 if DEBUG: 174 log("get_best_scroll_values: arrays: matches=%s, scroll=%s", dd(m_arr, MAX_MATCHES), ds(s_arr, MAX_MATCHES)) 175 #first collect the list of distances sorted by highest number of matches: 176 #(there can be more than one distance value for each match count): 177 scroll_hits = {} 178 for i in range(MAX_MATCHES): 179 if m_arr[i]>min_hits: 180 scroll_hits.setdefault(m_arr[i], []).append(s_arr[i]) 181 if DEBUG: 182 log("scroll hits=%s", scroll_hits) 183 #return a dict with the scroll distance as key, 184 #and the list of matching lines in a dictionary: 185 # {line-start : count, ..} 186 #this is destructive as we clear the checksums after use 187 scrolls = collections.OrderedDict() 188 cdef uint16_t m 189 #starting with the highest matches 190 for m in reversed(sorted(scroll_hits.keys())): 191 v = scroll_hits[m] 192 for scroll in v: 193 #find matching lines: 194 line_defs = self.match_distance(scroll) 195 if line_defs: 196 scrolls[scroll] = line_defs 197 return scrolls 136 198 137 def consecutive_lines(line_numbers): 138 #print("line_numbers_to_rectangles(%s)" % (line_numbers, )) 139 #aggregates consecutive lines: 140 #[1,2,3,4,8,9] -> [(1,3), (8,1)] 141 assert len(line_numbers)>0 142 if len(line_numbers)==1: 143 return [(line_numbers[0], 1)] 144 cdef int start = line_numbers[0] 145 cdef int last = start 146 cdef int line = 0 147 r = [] 148 for line in line_numbers[1:]: 149 if line!=last+1: 150 #new rectangle 151 r.append((start, last-start+1)) 152 start = line 153 last = line 154 if last!=line_numbers[0]: 155 r.append((start, last-start+1)) 156 return r 199 def get_remaining_areas(self): 200 #all the lines which have not been zeroed out 201 #when we matched them with match_distance 202 cdef int64_t *a2 = self.a2 203 cdef uint16_t i, start = 0, count = 0 204 line_defs = collections.OrderedDict() 205 for i in range(self.l): 206 if a2[i]!=0: 207 if count==0: 208 start = i 209 count += 1 210 elif count>0: 211 line_defs[start] = count 212 count = 0 213 if count>0: 214 line_defs[start] = count 215 return line_defs 216 217 def match_distance(self, int16_t distance): 218 """ find the lines that match the given scroll distance """ 219 cdef int64_t *a1 = self.a1 220 cdef int64_t *a2 = self.a2 221 cdef char swap = 0 222 if distance<0: 223 #swap order: 224 swap = 1 225 a1 = self.a2 226 a2 = self.a1 227 distance = -distance 228 if DEBUG: 229 log("match_distance(%i) l=%i, a1=%s, a2=%s", distance, self.l, da(a1, self.l), da(a2, self.l)) 230 assert distance<self.l, "invalid distance %i for size %i" % (distance, self.l) 231 cdef uint16_t i, start = 0, count = 0 232 line_defs = collections.OrderedDict() 233 for i in range(self.l-distance): 234 #if DEBUG: 235 # log("%i: a1=%i / a2=%i", i, a1[i], a2[i+distance]) 236 if a1[i]!=0 and a1[i]==a2[i+distance]: 237 #if DEBUG: 238 # log("match at %i: %i", i, a1[i]) 239 if count==0: 240 #first match 241 start = i 242 count += 1 243 #mark the target line as dealt with: 244 if swap: 245 a1[i] = 0 246 else: 247 a2[i+distance] = 0 248 elif count>0: 249 #we had a match 250 line_defs[start] = count 251 count = 0 252 if count>0: 253 #last few lines ended as a match: 254 line_defs[start] = count 255 if DEBUG: 256 log("match_distance(%i)=%s", distance, line_defs) 257 return line_defs 258 259 260 def get_best_match(self): 261 cdef int16_t max = 0 262 cdef int d = 0 263 cdef unsigned int i 264 for i in range(2*self.l): 265 if self.distances[i]>max: 266 max = self.distances[i] 267 d = i-self.l 268 return d, max 269 270 def __dealloc__(self): 271 cdef void* ptr = <void*> self.distances 272 if ptr: 273 self.distances = NULL 274 free(ptr) 275 ptr = <void*> self.a1 276 if ptr: 277 self.a1 = NULL 278 free(ptr) 279 ptr = <void*> self.a2 280 if ptr: 281 self.a2 = NULL 282 free(ptr) 283 284 285 def scroll_distances(array1, array2, unsigned int min_score=0, uint16_t max_distance=1000): 286 cdef ScrollDistances sd = ScrollDistances() 287 sd.init(array1, array2, max_distance) 288 return sd -
xpra/server/window/window_video_source.py
14 14 from xpra.codecs.codec_constants import TransientCodecException, RGB_FORMATS, PIXEL_SUBSAMPLING 15 15 from xpra.server.window.window_source import WindowSource, STRICT_MODE, AUTO_REFRESH_SPEED, AUTO_REFRESH_QUALITY 16 16 from xpra.server.window.region import rectangle, merge_all #@UnresolvedImport 17 from xpra.server.window.motion import match_distance, consecutive_lines, calculate_distances, CRC_Image #@UnresolvedImport17 from xpra.server.window.motion import scroll_distances, CRC_Image #@UnresolvedImport 18 18 from xpra.server.window.video_subregion import VideoSubregion, VIDEO_SUBREGION 19 19 from xpra.server.window.video_scoring import get_pipeline_score 20 20 from xpra.codecs.loader import PREFERED_ENCODING_ORDER, EDGE_ENCODING_ORDER … … 1453 1453 return packet 1454 1454 1455 1455 1456 def encode_scrolling(self, image, distances, o ld_csums, csums, options):1456 def encode_scrolling(self, image, distances, options): 1457 1457 start = time.time() 1458 1458 try: 1459 1459 del options["av-sync"] … … 1460 1460 except: 1461 1461 pass 1462 1462 #tells make_data_packet not to invalidate the scroll data: 1463 scrolllog("encode_scrolling(%s, {..}, [], [], %s) window-dimensions=%s", image, options, self.window_dimensions)1463 scrolllog("encode_scrolling(%s, %s, [], [], %s) window-dimensions=%s", image, distances, options, self.window_dimensions) 1464 1464 x, y, w, h = image.get_geometry()[:4] 1465 yscroll_values = [] 1466 max_scroll_regions = 50 1467 #process distances with the highest score first: 1468 for hits in reversed(sorted(distances.values())): 1469 for scroll in (d for d,v in distances.items() if v==hits): 1470 assert scroll<h 1471 yscroll_values.append(scroll) 1472 if len(yscroll_values)>=max_scroll_regions: 1473 break 1474 assert yscroll_values 1475 #always add zero=no-change so we can drop those updates! 1476 if 0 not in yscroll_values and 0 in distances: 1477 #(but do this last so we don't end up cutting too many rectangles) 1478 yscroll_values.append(0) 1479 scrolllog(" will send scroll packets for yscroll=%s", yscroll_values) 1480 #keep track of the lines we have handled already: 1481 #(the same line may be available from multiple scroll directions) 1482 handled = set() 1465 raw_scroll = distances.get_best_scroll_values() 1466 non_scroll = distances.get_remaining_areas() 1467 scrolllog(" will send scroll data=%s, non-scroll=%s", raw_scroll, non_scroll) 1468 flush = len(non_scroll) 1469 assert raw_scroll, "failed to detect scroll values" 1470 #convert to a screen rectangle list for the client: 1483 1471 scrolls = [] 1484 max_scrolls = 1000 1485 for s in yscroll_values: 1486 #find all the lines that scroll by this much: 1487 slines = match_distance(old_csums, csums, s) 1488 assert slines, "no lines matching distance %i" % s 1489 #remove any lines we have already handled: 1490 lines = [v+s for v in slines if ((v+s) not in handled and v not in handled)] 1491 if not lines: 1492 continue 1493 #and them to the handled set so we don't try to paint them again: 1494 handled = handled.union(set(lines)) 1495 if s==0: 1496 scrolllog(" %i lines have not changed", len(lines)) 1497 else: 1498 #things have actually moved 1499 #aggregate consecutive lines into rectangles: 1500 cl = consecutive_lines(lines) 1501 #scrolllog(" scroll groups for distance=%i : %s=%s", s, lines, cl) 1502 scrolllog(" scroll groups for distance=%i : %s", s, cl) 1503 for sy,sh in cl: 1504 #new rectangle 1505 scrolls.append((x, y+sy-s, w, sh, 0, s)) 1506 if len(scrolls)>max_scrolls: 1507 break 1508 if len(scrolls)>max_scrolls: 1509 break 1510 1511 non_scroll = [] 1512 remaining = set(range(h))-handled 1513 if remaining: 1514 damaged_lines = sorted(list(remaining)) 1515 non_scroll = consecutive_lines(damaged_lines) 1516 scrolllog(" non scroll: %i packets: %s", len(non_scroll), non_scroll) 1517 flush = len(non_scroll) 1518 #send as scroll paints packets: 1519 if scrolls: 1520 client_options = options.copy() 1521 if flush>0 and self.supports_flush: 1522 client_options["flush"] = flush 1523 coding = "scroll" 1524 end = time.time() 1525 packet = self.make_draw_packet(x, y, w, h, coding, LargeStructure(coding, scrolls), 0, client_options, options) 1526 self.queue_damage_packet(packet) 1527 compresslog("compress: %5.1fms for %4ix%-4i pixels at %4i,%-4i for wid=%-5i using %6s as %3i rectangles (%5iKB) , sequence %5i, client_options=%s", 1528 (end-start)*1000.0, w, h, x, y, self.wid, coding, len(scrolls), w*h*4/1024, self._damage_packet_sequence, client_options) 1472 for scroll, line_defs in raw_scroll.items(): 1473 for line, count in line_defs.items(): 1474 scrolls.append((x, y+line, w, count, 0, scroll)) 1475 assert len(scrolls)>0 1476 client_options = options.copy() 1477 try: 1478 del client_options["scroll"] 1479 except: 1480 pass 1481 if flush>0 and self.supports_flush: 1482 client_options["flush"] = flush 1483 coding = "scroll" 1484 end = time.time() 1485 packet = self.make_draw_packet(x, y, w, h, coding, LargeStructure(coding, scrolls), 0, client_options, options) 1486 self.queue_damage_packet(packet) 1487 compresslog("compress: %5.1fms for %4ix%-4i pixels at %4i,%-4i for wid=%-5i using %6s as %3i rectangles (%5iKB) , sequence %5i, client_options=%s", 1488 (end-start)*1000.0, w, h, x, y, self.wid, coding, len(scrolls), w*h*4/1024, self._damage_packet_sequence, client_options) 1529 1489 #send the rest as rectangles: 1530 1490 if non_scroll: 1531 1491 nsstart = time.time() … … 1534 1494 else: 1535 1495 #slower but can be lossless: 1536 1496 encoding = self.get_video_fallback_encoding(PREFERED_ENCODING_ORDER) 1497 client_options = options.copy() 1537 1498 if encoding: 1538 1499 encode_fn = self._encoders[encoding] 1539 for sy, sh in non_scroll: 1500 for sy, sh in non_scroll.items(): 1501 #s = time.time() 1540 1502 sub = image.get_sub_image(0, sy, w, sh) 1541 flush -= 11503 #scrolllog("sub_image %i pixels: %.1fms", (w*sh), (time.time()-s)*1000) 1542 1504 ret = encode_fn(encoding, sub, options) 1543 1505 if not ret: 1544 1506 self.free_image_wrapper(sub) … … 1546 1508 return None 1547 1509 coding, data, client_options, outw, outh, outstride, _ = ret 1548 1510 assert data 1549 client_options = options.copy()1550 if flush>0 and self.supports_flush:1511 flush -= 1 1512 if self.supports_flush and flush>0: 1551 1513 client_options["flush"] = flush 1552 1514 packet = self.make_draw_packet(sub.get_x(), sub.get_y(), outw, outh, coding, data, outstride, client_options, options) 1553 1515 self.queue_damage_packet(packet) … … 1558 1520 compresslog("compress: %5.1fms for %4ix%-4i pixels at %4i,%-4i for wid=%-5i using %6s with ratio %5.1f%% (%5iKB to %5iKB), sequence %5i, client_options=%s", 1559 1521 (end-nsstart)*1000.0, w, sh, 0, sy, self.wid, coding, 100.0*csize/psize, psize/1024, csize/1024, self._damage_packet_sequence, client_options) 1560 1522 scrolllog("non-scroll encoding using %s (quality=%i, speed=%i) took %ims for %i rectangles", encoding, self._current_quality, self._current_speed, (time.time()-nsstart)*1000, len(non_scroll)) 1523 else: 1524 #we can't send the non-scroll areas, ouch! 1525 flush = 0 1561 1526 assert flush==0 1562 1527 self.last_scroll_time = time.time() 1563 1528 scrolllog("scroll encoding total time: %ims", (self.last_scroll_time-start)*1000) … … 1635 1600 if csums: 1636 1601 self.scroll_data = rectangle(x, y, w, h), csums 1637 1602 options["scroll"] = True 1638 scrolllog("updated scroll data , previously set: %s", bool(lsd))1603 scrolllog("updated scroll data with %s, previously set: %s", self.scroll_data[0], (lsd or [None])[0]) 1639 1604 if lsd and csums: 1640 1605 rect, lcsums = lsd 1641 1606 if rect.x!=x or rect.y!=y: … … 1646 1611 else: 1647 1612 #same size, try to find scrolling value 1648 1613 assert len(csums)==len(lcsums), "mismatch between checksums lists: %i vs %i items!" % (len(csums), len(lcsums)) 1649 distances = calculate_distances(csums, lcsums, 2, 1000) 1650 if len(distances)==0: 1614 max_distance = min(1000, (100-SCROLL_MIN_PERCENT)*h//100) 1615 distances = scroll_distances(lcsums, csums, 2, max_distance) 1616 scroll, count = distances.get_best_match() 1617 if count==0: 1651 1618 scrolllog("no scroll distances found") 1652 1619 else: 1653 best = max(distances.values())1654 scroll = distances.keys()[distances.values().index(best)]1655 1620 end = time.time() 1656 best_pct = int(100*best/h)1657 scrolllog("best scroll guess took %ims, matches %i%% of %i lines: %s", (end-start)*1000, best_pct, h, scroll)1621 match_pct = int(100*count/h) 1622 scrolllog("best scroll guess took %ims, matches %i%% of %i lines: %s", (end-start)*1000, match_pct, h, scroll) 1658 1623 #if enough scrolling is detected, use scroll encoding for this frame: 1659 if best_pct>=SCROLL_MIN_PERCENT:1660 return self.encode_scrolling(image, distances, lcsums, csums,options)1624 if match_pct>=SCROLL_MIN_PERCENT: 1625 return self.encode_scrolling(image, distances, options) 1661 1626 except Exception: 1662 1627 scrolllog.error("Error during scrolling detection!", exc_info=True) 1663 1628 -
unittests/unit/server/motion_test.py
1 1 #!/usr/bin/env python 2 2 # This file is part of Xpra. 3 # Copyright (C) 201 1-2014Antoine Martin <antoine@devloop.org.uk>3 # Copyright (C) 2016-2017 Antoine Martin <antoine@devloop.org.uk> 4 4 # Xpra is released under the terms of the GNU GPL v2, or, at your option, any 5 5 # later version. See the file COPYING for details. 6 6 … … 20 20 21 21 class TestMotion(unittest.TestCase): 22 22 23 def calculate_distances(self, array1, array2, min_hits=2, max_distance=1): 24 sd = motion.scroll_distances(array1, array2, max_distance) 25 return sd.get_best_scroll_values(min_hits) 26 23 27 def test_match_distance(self): 24 28 def t(a1, a2, distance, matches): 25 lines = motion.match_distance(a1, a2, distance) 26 assert len(lines)==matches, "expected %i matches for distance=%i but got %i for a1=%s, a2=%s, result=%s" % (matches, distance, len(lines), a1, a2, lines) 29 sd = motion.scroll_distances(a1, a2) 30 line_defs = sd.match_distance(distance) 31 linecount = sum(line_defs.values()) 32 assert linecount==matches, "expected %i matches for distance=%i but got %i for a1=%s, a2=%s, result=%s" % (matches, distance, linecount, a1, a2, line_defs) 27 33 for N in (1, 10, 100): 28 a = range( N)34 a = range(1, N+1) 29 35 t(a, a, 0, N) #identity: all match 30 36 31 a = [ 0]*N37 a = [1]*N 32 38 t(a, a, 0, N) 33 39 for M in range(N): 34 40 t(a, a, M, N-M) 35 41 42 #from a1 to a2: shift by 2, get 2 hits 43 t([3, 4, 5, 6], [1, 2, 3, 4], 2, 2) 36 44 #from a2 to a1: shift by -2, get 2 hits 37 t([ 0, 1, 2, 3], [2, 3, 4, 5], -2, 2)45 t([1, 2, 3, 4], [3, 4, 5, 6], -2, 2) 38 46 N = 100 39 a1 = range(N) 40 for M in (1, 2, 10, 100): 47 S = 1 48 a1 = range(S, S+N) 49 for M in (2, 3, 10, 100): 41 50 a2 = range(M, M+N) 42 t(a1, a2, -M, N-abs(M)) 43 a2 = range(-M, N-M) 44 t(a1, a2, M, N-abs(M)) 45 for M in (-10, -20): 46 a2 = range(M, N+M) 47 t(a1, a2, -M, N-abs(M)) 51 t(a1, a2, S-M, S+N-M) 52 t(a2, a1, M-S, S+N-M) 48 53 49 def test_consecutive_lines(self):50 def f(v):51 try:52 motion.consecutive_lines(v)53 except:54 pass55 else:56 raise Exception("consecutive_lines should have failed for value %s" % v)57 f(None)58 f("")59 f([])60 f(1)61 def t(v, e):62 r = motion.consecutive_lines(v)63 assert r==e, "expected %s but got %s for input=%s" % (e, r, v)64 t([5, 10], [(5, 1), (10, 1)])65 t([5], [(5, 1)])66 t([1,2,3], [(1,3)])67 t(range(100), [(0, 100),])68 t([1,2,3,100,200,201], [(1,3), (100, 1), (200, 2)])69 70 54 def test_calculate_distances(self): 71 55 array1 = [crc32(str(x)) for x in (1234, "abc", 99999)] 72 array2 = array1 73 d = motion.calculate_distances(array1, array2, 1)74 assert len(d)==1 and d[0]==356 array2 = array1[:] 57 d = self.calculate_distances(array1, array2, 1) 58 assert len(d)==1 and sum(d[0].values())==len(array1), "expected %i matches but got %s" % (len(array1), d[0]) 75 59 76 array1 = range(0, 3) 77 array2 = range(1, 4) 78 d = motion.calculate_distances(array1, array2, 1) 79 assert len(d)==1, "expected 1 match but got: %s" % d 80 assert d.get(1)==2, "expected distance of 1 with 2 hits but got: %s" % d 60 array1 = range(1, 5) 61 array2 = range(2, 6) 62 d = self.calculate_distances(array1, array2) 63 assert len(d)==1, "expected 1 match but got: %s" % len(d) 64 common = set(array1).intersection(set(array2)) 65 assert -1 in d, "expected distance of -1 but got: %s" % d.keys() 66 linecount = sum(d.get(-1).values()) 67 assert linecount==len(common), "expected %i hits but got: %s" % (len(common), linecount) 81 68 def cdf(v1, v2): 82 69 try: 83 motion.calculate_distances(v1, v2, 1)70 self.calculate_distances(v1, v2, 1) 84 71 except: 85 72 return 86 73 raise Exception("calculate_distances should have failed for values: %s" % (v1, v2)) … … 88 75 cdf([], None) 89 76 cdf(None, []) 90 77 cdf([1, 2], [1]) 91 assert len(motion.calculate_distances([], [], 1))==092 78 93 79 #performance: 94 80 N = 4096 … … 95 81 start = time.time() 96 82 array1 = range(N) 97 83 array2 = [N*2-x*2 for x in range(N)] 98 d = motion.calculate_distances(array1, array2, 1)84 d = self.calculate_distances(array1, array2, 1) 99 85 end = time.time() 100 86 if SHOW_PERF: 101 87 print("calculate_distances %4i^2 in %5.1f ms" % (N, (end-start)*1000)) … … 137 123 print("\nCRC_Image %ix%i (%.1fMB) in %4.2f ms" % (W, H, len(buf2)//1024//1024, 1000.0*(end-start))) 138 124 assert len(ov2)==H 139 125 start = time.time() 140 distances = motion.calculate_distances(ov1, ov2, min_score=1)126 distances = self.calculate_distances(ov1, ov2, min_hits=1) 141 127 end = time.time() 142 128 if SHOW_PERF: 143 129 print("calculate_distances %4i^2 in %5.2f ms" % (H, 1000.0*(end-start))) 144 linecount = distances.get(N, 0) 130 line_defs = distances.get(-N, 0) 131 linecount = sum(line_defs.values()) 145 132 assert linecount>0, "could not find distance %i" % N 146 133 assert linecount == (H-N), "expected to match %i lines but got %i" % (H-N, linecount) 147 134 if False: