Newer
Older
#!/bin/env python3
# -*- coding: UTF-8 -*-
# Gereric: Draw line.
# Recolic Keghart, Apr 29, 2017.
import numpy
from scipy.optimize import leastsq
import matplotlib.pyplot as plt
from matplotlib import rcParams
from scipy.interpolate import spline
from deprecated import deprecated
def dotMultiply(vctA, vctB):
if len(vctA) != len(vctB):
print("Error: While vcta is ", vctA, " and vctb is ", vctB)
raise ValueError("dotmulti needs lena == lenb.")
ans = 0
for a, b in zip(vctA, vctB):
ans += a * b
return ans
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
def __fetchAnonymousLineName():
for i in range(10000):
yield 'Unnamed Line ' + str(i)
_fetchAnonymousLineName = __fetchAnonymousLineName()
fetchAnonymousLineName = lambda :next(_fetchAnonymousLineName)
@deprecated("Use GetMultiMap(GetLine(...)) rather than GetMap() please!")
def GetMap(arrX, arrY, extendXRate=1, extendYRate=1, polyLine=False, poly_passO=False, poly_maxXPower=1, poly_inverseK=False, smoothLine=False):
'''
A wrapper to GetMultiMap(GetLine(args ...)), just check doc in GetLine().
'''
GetMultiMap(GetLine(arrX, arrY, fetchAnonymousLineName(), extendXRate, extendYRate, polyLine, poly_passO, poly_maxXPower, poly_inverseK, smoothLine))
def GetMultiMap(lines, windowSizeX=12, windowSizeY=8):
'''
Usage: GetMultiMap(GetLine([1,2,3], [1,4,9]) + GetLine([2,2.1,2.2], [6,7,8]) + ...)
'''
colors = ['red', 'orange', 'blue', 'green', 'yellow', 'magenta', 'cyan', 'black'] # You can add more color like: '#123456', '#FFFFAE', etc
def __fetchColor():
for color in colors:
yield color
raise RuntimeError('Pre-defined colors are used out...... Check quickmap.py::GetMultiMap line 29 to add more color.')
_fetchColor = __fetchColor()
fetchColor = lambda :next(_fetchColor)
plt.figure(figsize=(windowSizeX, windowSizeY))
for line in lines:
name, X, Y, px, py = line
plt.scatter(X, Y, color=fetchColor(), label="Sample Point ({})".format(name), linewidth=3)
if len(px) != len(py):
raise ValueError('Invalid line passed to GetMultiMap. Assertion len(px) == len(py) failed because {} != {}.'.format(len(px), len(py)))
if len(px) != 0:
plt.plot(px, py, color=fetchColor(), label="Fitting Line ({})".format(name), linewidth=2)
plt.grid()
plt.show()
def GetLine(arrX, arrY, name=fetchAnonymousLineName(), extendXRate=1, extendYRate=1, polyLine=False, poly_passO=False, poly_maxXPower=1, poly_inverseK=False, smoothLine=False):
'''
Arguments:
arrX and arrY: array of coordinates of points. Ex: GetLine([1,2,3,4,5], [1,2,3,4,5]) -> y=x
name: Shown in GetMultiMap().
polyLine(conflict with polyLine): Should I draw a fitting line by polynomial fitting?
smoothLine(conflict with smoothLine, **RECOMMENDED**): Should I draw a fitting line by connecting all points and smooth them?
passO: Should the fitting line pass (0,0)? That's saying, should k0 be zero?
maxXPower: If I should draw a fitting line, what polynomial function should I use? Ex: GetLine([1,2,3,4,5], [1,4,9,16,25], line=True, maxXPower) -> y=x^2
inverseK: Usually, I'm drawing the curl `y=KX`, while K=[k0,k1,k2,...], X=[x^0,x^1,x^2,...]. If this switch is set, I'll drawing the curl `KY=x`. Don't worry, this switch is transparent to you.
Ex: GetLine([0,1,1,4,4,9,9], [0,1,-1,2,-2,3,-3], poly_maxXPower=2, polyLine=True, poly_inverseK=True) -> y^2=x
ReturnValue:
void
'''
if polyLine and smoothLine:
raise RuntimeError("bad argument: polyLine and smoothLine can't been set simultaneously.")
if len(arrX) != len(arrY):
raise ValueError("arrX and arrY must in same size, but {} != {}.".format(len(arrX), len(arrY)))
maxX, maxY = max(arrX) * extendXRate, max(arrY) * extendYRate
minX, minY = min(arrX) * extendXRate, min(arrY) * extendYRate
X, Y = numpy.array(arrX), numpy.array(arrY)
print('Your input for {}: {} | {}'.format(name, arrX, arrY))
if polyLine:
print('You want a polynomial fitting, with maxXPower = {}, passO = {}, inverseK = {}.'.format(poly_maxXPower, poly_passO, poly_inverseK))
if smoothLine:
print('You want a smooth fitting.')
print('Generating result...')
# y = [k0 k1 k2 ...] dot [x^0 x^1 x^2 ...]
vctX = [x ** power for power in range(poly_maxXPower + 1)]
if poly_passO:
vctX[0] = 0
return dotMultiply(k, vctX)
if polyLine:
# Generate target line.
kInit = [1 for _ in range(poly_maxXPower + 1)]
if poly_inverseK:
print('Fitting polyLine done. k^-1=', kFinal)
else:
kFinal, _ = leastsq(lossFunc, kInit, args=(X, Y))
print('Fitting polyLine done. k=', kFinal)
if polyLine:
if poly_inverseK:
px = dotMultiply(kFinal, [py ** power for power in range(poly_maxXPower + 1)])
py = dotMultiply(kFinal, [px ** power for power in range(poly_maxXPower + 1)])
elif smoothLine:
px = numpy.linspace(minX, maxX, 1000)
py = spline(X, Y, px)
else:
px, py = [], []
return [(name, X, Y, px, py)] #Python list is addible.
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
def _str_remove_extra_space(s):
begin, end = 0, len(s)
for char in s:
if char == ' ':
begin += 1
else:
break
for char in s[::-1]:
if char == ' ':
end -= 1
else:
break
return s[begin:end]
def _str_remove_comments(s, commentSym = '#'):
pos = s.find(commentSym)
if pos == -1:
return s
else:
return s[0:pos]
def DataFileToXYArray(fname, lineDelimiter = '\n', wordDelimiter = ' ', commentSym = '#', _DataType = float):
'''
give file name, return tuple: ([_DataType], [_DataType])
(xArray, yArray)
Default _DataType is python float, and I think that's enough.
Example:
x, y = DataFileToXYArray('test.dat')
GenMap(x, y)
'''
with open(fname, 'r') as fd:
s = fd.read()
xArray, yArray = [], []
for ori_line in s.split(lineDelimiter):
line = _str_remove_comments(ori_line, commentSym)
line = _str_remove_extra_space(line)
continue
ar = line.split(wordDelimiter)
if len(ar) < 2:
print('Warning: Bad data line "{}" skipped.'.format(ori_line))
continue
if len(ar) > 2:
print('Warning: Invalid data line "{}" parsed as "{} {}"'.format(ori_line, ar[0], ar[1]))
try:
xArray.append(_DataType(ar[0]))
yArray.append(_DataType(ar[1]))
except:
print('At data line "{}":'.format(ori_line))