Coverage for paintmaskanimator/auto_motion.py: 93%

83 statements  

« prev     ^ index     » next       coverage.py v7.17.0, created at 2026-11-02 04:50 +0000

1"""セル間で共有する動きの候補と、全探索を省略できる場合の厳密な上界検査。""" 

2from collections import OrderedDict 

3 

4import numpy as np 

5 

6 

7class LocalShift(tuple): 

8 gap: float 

9 

10 def __new__(cls, dy, dx, gap): 

11 result = super().__new__(cls, (int(dy), int(dx))) 

12 result.gap = float(gap) 

13 return result 

14 

15 

16class MotionMemory: 

17 def __init__(self, capacity=64): 

18 self.fields = OrderedDict() 

19 self.samples = OrderedDict() 

20 self.capacity = capacity 

21 

22 def register(self, key, flow, x0, y0): 

23 self.fields[key] = flow, x0, y0 

24 self.fields.move_to_end(key) 

25 while len(self.fields) > self.capacity: 

26 self.fields.popitem(last=False) 

27 

28 def learn(self, key, bounds, result): 

29 shift, score = result 

30 if score < 0.95: 

31 return 

32 x0, y0, x1, y1 = bounds 

33 grid = ((x0 + x1) // 256, (y0 + y1) // 256) 

34 cells = self.samples.setdefault(key, {}) 

35 cells[grid] = tuple(shift) 

36 if len(cells) > 512: 36 ↛ 37line 36 didn't jump to line 37 because the condition on line 36 was never true

37 cells.pop(next(iter(cells))) 

38 self.samples.move_to_end(key) 

39 while len(self.samples) > self.capacity: 

40 self.samples.popitem(last=False) 

41 

42 def predict(self, key, bounds): 

43 x0, y0, x1, y1 = bounds 

44 x, y = (x0 + x1) // 2, (y0 + y1) // 2 

45 if key in self.fields: 

46 flow, ox, oy = self.fields[key] 

47 if float(flow.score_at(y - oy, x - ox)) >= 0.8: 

48 dy, dx = flow.at(y - oy, x - ox) 

49 return int(np.rint(dy)), int(np.rint(dx)) 

50 cells = self.samples.get(key) 

51 if cells: 

52 grid = (x // 128, y // 128) 

53 nearby = min(cells, key=lambda at: abs(at[0] - grid[0]) + abs(at[1] - grid[1])) 

54 if abs(nearby[0] - grid[0]) + abs(nearby[1] - grid[1]) <= 2: 

55 return cells[nearby] 

56 return None 

57 

58 

59def certify(problem, hint): 

60 """未検査候補の理論上限を超えた場合だけ、FFTと同じ最良位置を返す。""" 

61 from . import auto_paint as ap 

62 if hint is None: 62 ↛ 63line 62 didn't jump to line 63 because the condition on line 62 was never true

63 return None 

64 before, after = problem['before'], problem['after'] 

65 step, total = problem['step'], problem['total'] 

66 if not hint or total < 4 or not np.all((before == 0) | (before == 1)): 

67 return None 

68 ys, xs = np.nonzero(before) 

69 dys, dxs = problem['dys'], problem['dxs'] 

70 dy, dx = (int(np.rint(value / step)) for value in hint) 

71 if dy not in dys or dx not in dxs: 71 ↛ 72line 71 didn't jump to line 72 because the condition on line 71 was never true

72 return None 

73 rows, cols = problem['size'] 

74 

75 def overlaps(shifts): 

76 shifts = np.asarray(shifts, dtype=np.int64) 

77 y = (ys[None] + shifts[:, 0, None]) % rows 

78 x = (xs[None] + shifts[:, 1, None]) % cols 

79 valid = (y < after.shape[0]) & (x < after.shape[1]) 

80 values = after[np.minimum(y, after.shape[0] - 1), np.minimum(x, after.shape[1] - 1)] 

81 return np.where(valid, values, 0).sum(axis=1, dtype=np.float64) / total 

82 

83 penalty = ap.MOTION_PENALTY * step 

84 score = float(overlaps([(dy, dx)])[0]) - penalty * (abs(dy) + abs(dx)) 

85 # 大きな証明表が必要なら、従来のFFTを使う方が速い。 

86 radius = int(np.floor((float(after.max()) - score + 2e-5) / penalty)) 

87 if radius < 0 or radius > 8: 

88 return None 

89 shifts = [(int(y), int(x)) for y in dys for x in dxs if abs(y) + abs(x) <= radius] 

90 if not shifts or len(shifts) * len(ys) > 32768: 

91 return None 

92 near = overlaps(shifts) 

93 scores = near - penalty * np.array([abs(y) + abs(x) for y, x in shifts]) 

94 best = int(np.argmax(scores)) 

95 runner = np.partition(scores, -2)[-2] if len(scores) > 1 else -np.inf 

96 unseen = float(after.max()) - penalty * (radius + 1) 

97 if scores[best] - max(runner, unseen) <= 2e-5: 97 ↛ 98line 97 didn't jump to line 98 because the condition on line 97 was never true

98 return None 

99 return LocalShift(shifts[best][0] * step, shifts[best][1] * step, 

100 scores[best] - max(runner, unseen)), float(near[best])