-
Notifications
You must be signed in to change notification settings - Fork 8
/
utils.py
147 lines (112 loc) · 4.58 KB
/
utils.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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
"""
General utilities
"""
import numpy as np
def rotX(theta, mode = 'radians'):
"""Generate a rotation matrix that rotates a point by an angle theta about the X-axis.
By default, assumes that the angle is passed in ``radians``.
Args:
theta (float): angle of rotation (by default, in ``radians``)
mode (:obj:`str`, optional): one of ``radians`` or ``degrees``
Returns:
R (:obj:`np.matrix`): 3 x 3 rotation matrix about the X-axis
Examples:
>>> R = rotX(np.pi/2)
>>> R = rotX(90, mode = 'degrees')
"""
if mode != 'radians' and mode != 'degrees':
raise ValueError('Mode should either be ``radians`` or ``degrees``.')
if mode == 'degrees':
theta = np.deg2rad(theta)
return np.matrix([[1., 0., 0.], [0, np.cos(theta), -np.sin(theta)], \
[0, np.sin(theta), np.cos(theta)]])
def rotY(theta, mode = 'radians'):
"""Generate a rotation matrix that rotates a point by an angle theta about the Y-axis.
By default, assumes that the angle is passed in ``radians``.
Args:
theta (float): angle of rotation (by default, in ``radians``)
mode (:obj:`str`, optional): one of ``radians`` or ``degrees``
Returns:
R (:obj:`np.matrix`): 3 x 3 rotation matrix about the Y-axis
Examples:
>>> R = rotY(np.pi/2)
>>> R = rotY(90, mode = 'degrees')
"""
if mode != 'radians' and mode != 'degrees':
raise ValueError('Mode should either be ``radians`` or ``degrees``.')
if mode == 'degrees':
theta = np.deg2rad(theta)
return np.matrix([[np.cos(theta), 0., np.sin(theta)], [0, 1, 0], \
[-np.sin(theta), 0, np.cos(theta)]])
def rotZ(theta, mode = 'radians'):
"""Generate a rotation matrix that rotates a point by an angle theta about the Z-axis.
By default, assumes that the angle is passed in ``radians``.
Args:
theta (float): angle of rotation (by default, in ``radians``)
mode (:obj:`str`, optional): one of ``radians`` or ``degrees``
Returns:
R (:obj:`np.matrix`): 3 x 3 rotation matrix about the Z-axis
Examples:
>>> R = rotZ(np.pi/2)
>>> R = rotZ(90, mode = 'degrees')
"""
if mode != 'radians' and mode != 'degrees':
raise ValueError('Mode should either be ``radians`` or ``degrees``.')
if mode == 'degrees':
theta = np.deg2rad(theta)
return np.matrix([[np.cos(theta), -np.sin(theta), 0], [np.sin(theta), np.cos(theta), 0], \
[0., 0., 1.]])
def getDistinguishableColors(numColors, bgColors = [(1, 1, 1)]):
"""Pick a set of `numColors` colors that are maximally perceptually distinct.
When plotting a set of lines/curves/points, you might want to distinguish them
by color. This module generates a set of colors that are ``maximally perceptually
distinguishable`` in the RGB colorspace. Given an initial seed list of candidate colors,
it iteratively picks the color from the list that is the farthest (in the RGB colorspace)
from all previously chosen entries. This is a ``greedy`` method and does not yield
a global maximum.
Inspired by the MATLAB implementation from Timothy E. Holy.
Args:
numColors (int): number of distinguishable colors to generate
bgColors (:obj:`list`, optional): list of background colors
Returns:
colors (:obj:`list`): list of `numColors` distinguishable colors
Examples:
>>> colors = getDistinguishableColors(25)
"""
# Start out by generating a sizeable number of RGB triples. This represents our space
# of possible choices. By starting out in the RGB space, we ensure that all of the colors
# can be generated by the monitor.
# Number of grid divisions along each axis in RGB space
numGrid = 30
x = np.linspace(0, 1, numGrid)
[R, G, B] = np.meshgrid(x, x, x)
rgb = np.concatenate((R.T.reshape((numGrid*numGrid*numGrid, 1)), \
G.T.reshape((numGrid*numGrid*numGrid, 1)), \
B.T.reshape((numGrid*numGrid*numGrid, 1))), axis = 1)
if numColors > rgb.shape[0] / 3:
raise ValueError('You cannot really distinguish that many colors! At most 9000 colors')
# If the user specified multiple bgColors, compute distance from the candidate colors
# to the background colors.
mindist = np.full(rgb.shape[0], np.inf)
for c in bgColors:
col = np.full(rgb.shape, 1)
col[:,0] = c[0]
col[:,1] = c[1]
col[:,2] = c[2]
dx = np.sum(np.abs(rgb - col), axis = 1)
mindist = np.minimum(mindist, dx)
# Initialize a list of colors
colors = []
lastColor = bgColors[-1]
for i in range(numColors):
col = np.full(rgb.shape, 1)
col[:,0] = lastColor[0]
col[:,1] = lastColor[1]
col[:,2] = lastColor[2]
dx = np.sum(np.abs(rgb - lastColor), axis = 1)
mindist = np.minimum(mindist, dx)
index = np.argmax(mindist)
chosenColor = (rgb[index,0], rgb[index,1], rgb[index,2])
colors.append(chosenColor)
lastColor = chosenColor
return colors