-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcm_scaling.py
133 lines (104 loc) · 4.43 KB
/
cm_scaling.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# -*- coding: UTF-8
from fractions import Fraction
import prettytable
from prettytable import PrettyTable
import math
INTERP_NEARES_NEIGHBOUR = 'nn'
INTERP_BILINEAR = 'bilin'
INTERPOLATIONS = [INTERP_NEARES_NEIGHBOUR, INTERP_BILINEAR]
#############
# CLI Utils #
#############
class SquareMatrix(list):
def __init__(self, rows):
for row in rows: self.append(row)
for i,row in enumerate(self):
if len(row) != len(self): raise Exception("Matrix is of size '{}', but row {} has '{}' entries".format(len(self), i, len(row)))
def pretty_print(self):
table = PrettyTable(None)
for row in self: table.add_row(row)
table.header = False
table.hrules = prettytable.ALL
print(table)
class Rectangle:
def __init__(self, top, left, bottom, right):
self.left = left
self.right = right
self.top = top
self.bottom = bottom
def __str__(self): return "({},{}) -> ({},{})".format(self.left, self.top, self.right, self.bottom)
@property
def width(self): return abs(self.right-self.left)
@property
def height(self): return abs(self.top-self.bottom)
def middle(self): return (self.left+0.5*self.width, self.top+0.5*self.height)
@property
def area(self): return self.width * self.height
def scale(self, factor):
self.left *= factor
self.right *= factor
self.top *= factor
self.bottom *= factor
@staticmethod
def from_points(a, b):
return Rectangle(min(a[1],b[1]), min(a[0], b[0]), max(a[1], b[1]), max(a[0], b[0]))
def nn_round(num):
""" Nearest Neighbor rounding"""
return max(0, num-1 if float(num).is_integer() else math.floor(num))
def scale_matrix_to_size(matrix, size, interpolation):
rows = [list() for i in range(size)]
scale = Fraction(len(matrix), size)
current = Rectangle(0,0,0,0) # Position of the new pixel relative to the original matrix
# Calculate value of each pixel in new matrix
for y in range(size):
current.top = scale*y
current.bottom = scale*(y+1)
for x in range(size):
current.left = scale*x
current.right = scale*(x+1)
if interpolation == INTERP_NEARES_NEIGHBOUR:
rows[x].append(matrix[int(nn_round(current.middle()[0]))][int(nn_round(current.middle()[1]))])
elif interpolation == INTERP_BILINEAR:
val = Fraction(0) # The value of the current pixel in the scaled up matrix
# Compose new value from overlaid pixels in original, weighted with their relative intersection area
for y_original in range(math.floor(current.top), math.ceil(current.bottom)):
for x_original in range(math.floor(current.left), math.ceil(current.right)):
intersection = Rectangle.from_points([max(x_original,current.left),max(y_original,current.top)], [min(x_original+1,current.right),min(y_original+1,current.bottom)])
intersection.scale(1/scale) # Scale pixel in original to relative size in new matrix
val+= intersection.area * matrix[x_original][y_original]
rows[x].append(math.ceil(val))
return SquareMatrix(rows)
################
# CLI Commands #
################
def cmd_scale(argv):
parser = argparse.ArgumentParser(description='Scale input Color Matrix', usage='%(prog)s {} [-h] [string] size [-i {{nn,bilin}}] [-p]'.format(CMD_SCALE))
parser.add_argument('string', nargs='?', type=str, default=None, help="The color matrix in CSV Format")
parser.add_argument('size', type=int, help="The size to scale the matrix to")
parser.add_argument('-i', '--interpolation', choices=INTERPOLATIONS, help="The type of interpolation to use")
parser.add_argument('-p', '--pretty-print', action='store_true', help='Pretty-print resulting matrix')
args = parser.parse_args(argv)
# Defaults
if not args.interpolation: args.interpolation = INTERP_NEARES_NEIGHBOUR
# Create matrix
if args.string: rows = bytes(args.string, "utf-8").decode("unicode_escape").split('\n')
else: rows = sys.stdin.readlines()
rows = list(map(lambda row: list(map(lambda val: Fraction(val), row.split(';'))), rows))
matrix = SquareMatrix(rows)
scaled = scale_matrix_to_size(matrix, args.size, args.interpolation)
if args.pretty_print:
scaled.pretty_print()
else:
for row in scaled: print(';'.join(map(lambda x:str(x), row)))
########
# Main #
########
CMD_SCALE = 'scale'
COMMANDS = [CMD_SCALE]
if __name__ == '__main__':
import argparse, sys, re, ast
parser = argparse.ArgumentParser(description='Utility for scaling color matrices')
parser.add_argument('command', type=str, choices=COMMANDS, help='Subcommand to run')
args = parser.parse_args(sys.argv[1:2])
if args.command == CMD_SCALE:
cmd_scale(sys.argv[2:])