The scipy library of matplotlib in Python 3 contains a function scipy.signal.freqz which computes the frequency response of a digital filter.
I am trying to write my own version of this function, to relay the same output as this embedded function. I plan to use the inputs:
freqz(b, a = 1, worN=None, whole=None):
[b=numerator of linear filter;
a=denominator of linear filter;
worN=at none, compute at 512 frequencies equally spaced around the unit circle;
whole=at none, frequency computed around upper half of unit circle, from 0 to pi]
This function should output two outputs:
w and h
[w=ndarray, the normalized frequencies at which h was computed, in radians/sample;
h=ndarray, the frequency response]
current code:
def freqz(b, a=1, worN=None, whole=False):
#Initialize a
a = atleast_1d(a)
#Initialize b
b = atleast_1d(b)
#Check condition
if worN is None:
#Initialize worN
worN = 512
#Initialize
h = None
#Try block
try:
#Update
worN = operator.index(worN)
#Exception
except TypeError:
#Update
w = atleast_1d(worN)
#Otherwise
else:
#Check condition
if worN < 0:
#Throw an exception
raise ValueError('worN should be non negative, we got %s' % (worN,))
#Update
finalpoint = 2 * pi if whole else pi
#Update
w = np.linspace(0, finalpoint, worN, endpoint=False)
#Check condition
if (a.size == 1 and worN >= b.shape[0] and fftpack.next_fast_len(worN) == worN and (b.ndim == 1 or (b.shape[-1] == 1))):
#Update
nfft = worN if whole else worN * 2
#Check condition
if np.isrealobj(b) and np.isrealobj(a):
#Update
fftfunc = np.fft.rfft
#Otherwise
else:
#Update
fftfunc = fftpack.fft
#Update
h = fftfunc(b, n=nfft, axis=0)[:worN]
#Update
h /= a
#Check condition
if fftfunc is np.fft.rfft and whole:
#Update
stop = -1 if nfft % 2 == 1 else -2
#Update
hVal_flip = slice(stop, 0, -1)
#Update
h = np.concatenate((h, h[hVal_flip].conj()))
#Check condition
if b.ndim > 1:
#Update
h = h[..., 0]
#Update
h = np.rollaxis(h, 0, h.ndim)
#Delete
del worN
#Check condition
if h is None:
#Update
zmVal = exp(-1j * w)
#Update
h = (npp_polyval(zmVal, b, tensor=False)/npp_polyval(zmVal, a, tensor=False))
#Check condition
return w, h
Related
while testing the gradient vector field (gvf) with a single image, I used the an input image to get the threshold image with seed, but got stuck with the error list object has no attribute astype. I'm using google co lab. Any help and thanks in advance. I got an error at the line write_mask8([pair[i]], "pair_raw", i).
global th
global newimg
global pairtype here
# Nuclei center detection
gvf = GVF(images, th)
dismap = gvf.distancemap()
newimg = gvf.new_image(0.4, dismap) # choose alpha as 0.4.
gradimg = gvf.compute_gvf(newimg)
out = []
pair = []
pair_raw = []
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(3,3))
i = 0
for i,img in enumerate(gradimg):
imgpair_raw = gvf.find_certer(img, i)
pair_raw.append(imgpair_raw)
neighborhood_size = 20
data_max = ndimage.filters.maximum_filter(pair_raw[i], neighborhood_size)
data_max[data_max==0] = 255
pair.append((pair_raw[i] == data_max).astype(np.uint8))
write_mask8([pair[i]], "pair_raw", i)
os.chdir(".")
y, x = np.where(pair[i]>0)
#points = zip(y[:], x[:])
points = np.vstack((y, x)).T
dmap = distance.cdist(points, points, 'euclidean')
y, x = np.where(dmap<10)
ps = zip(y[:], x[:])
for p in ps:
if p[0] != p[1]:
pair[i][points[min(p[0], p[1])]] = 0
dilation = cv2.dilate((pair[i]*255).astype(np.uint8),kernel,iterations = 3)
out.append(dilation)
out = cvt_npimg(out)
vis_square(out)
where mask_8 is defined as:
def write_mask8(images, name, index=-1):
"""
Write image as 8 bits image
"""
if index == -1:
for i, img in enumerate(images):
if i < 10:
cv2.imwrite(name+"00"+str(i)+".tif", img.astype(np.uint8))
elif i >= 10 and i < 100:
cv2.imwrite(name+"0"+str(i)+".tif", img.astype(np.uint8))
else:
cv2.imwrite(name+str(i)+".tif", img.astype(np.uint8))
else:
if index < 10:
cv2.imwrite(name+"000"+str(index)+".tif", images.astype(np.uint8))
elif index >= 10 and index < 100:
cv2.imwrite(name+"00"+str(index)+".tif", images.astype(np.uint8))
elif index >= 100 and index < 1000:
cv2.imwrite(name+"0"+str(index)+".tif", images.astype(np.uint8))
elif index >= 1000 and index < 10000:
cv2.imwrite(name+str(index)+".tif", images.astype(np.uint8))
else:
raise
I wanna run this code for a wide range instead of this range. So I wanna make it better to run faster.
Is it impossible to use something else instead of these loops?
z1=3
z2=HEIGHT-1
def myfunction(z1,z2):
for l in range(z1):
vector = np.zeros(WIDTH)
vector[WIDTH//2] = 1
result = []
result.append(vector)
for i in range(z2):
vector = doPercolationStep(vector, PROP, i)
result.append(vector)
result = np.array(result)
ss = result.astype(int)
ss = np.where(ss==0, -1, ss)
ww = (ss+(ss.T))/2
re_size = ww/(np.sqrt(L))
matr5 = re_size
np.savetxt('F:/folder/matr5/'+str(l)+'.csv', matr5)
and doPercolationStep is:
WIDTH = 5
HEIGHT = 5
L=5
PROP = 0.6447
def doPercolationStep(vector, PROP, time):
even = time%2 # even is 1 or 0
vector_copy = np.copy(vector)
WIDTH = len(vector)
for i in range(even, WIDTH, 2):
if vector[i] == 1:
pro1 = random.random()
pro2 = random.random()
if pro1 < PROP:
vector_copy[(i+WIDTH-1)%WIDTH] = 1 # left neighbour of i
if pro2 < PROP:
vector_copy[(i+1)%WIDTH] = 1 # right neighbour of i
vector_copy[i] = 0
return vector_copy
I am trying to use vpython glowscript to generate some animations.
The code works fine, but the problem is the display is through a browser, and I am not able to figure out how to save the display as gif. Here is a working code:
GlowScript 2.8 VPython
# Bruce Sherwood
N = 4 # N by N by N array of atoms
# Surrounding the N**3 atoms is another layer of invisible fixed-position atoms
# that provide stability to the lattice.
k = 1
m = 1
spacing = 1
atom_radius = 0.3*spacing
L0 = spacing-1.8*atom_radius
V0 = pi*(0.5*atom_radius)**2*L0 # initial volume of spring
scene.center = 0.5*(N-1)*vector(1,1,1)
dt = 0.04*(2*pi*sqrt(m/k))
axes = [vector(1,0,0), vector(0,1,0), vector(0,0,1)]
scene.caption= """A model of a solid represented as atoms connected by interatomic bonds.
Right button drag or Ctrl-drag to rotate "camera" to view scene.
To zoom, drag with middle button or Alt/Option depressed, or use scroll wheel.
On a two-button mouse, middle is left + right.
Shift-drag to pan left/right and up/down.
Touch screen: pinch/extend to zoom, swipe or two-finger rotate."""
class crystal:
def __init__(self, N, atom_radius, spacing, momentumRange ):
self.atoms = []
self.springs = []
# Create (N+2)^3 atoms in a grid; the outermost atoms are fixed and invisible
for z in range(-1,N+1,1):
for y in range(-1,N+1,1):
for x in range(-1,N+1,1):
atom = sphere()
atom.pos = vector(x,y,z)*spacing
atom.radius = atom_radius
atom.color = vector(0,0.58,0.69)
if 0 <= x < N and 0 <= y < N and 0 <= z < N:
p = vec.random()
atom.momentum = momentumRange*p
else:
atom.visible = False
atom.momentum = vec(0,0,0)
atom.index = len(self.atoms)
self.atoms.append( atom )
for atom in self.atoms:
if atom.visible:
if atom.pos.x == 0:
self.make_spring(self.atoms[atom.index-1], atom, False)
self.make_spring(atom, self.atoms[atom.index+1], True)
elif atom.pos.x == N-1:
self.make_spring(atom, self.atoms[atom.index+1], False)
else:
self.make_spring(atom, self.atoms[atom.index+1], True)
if atom.pos.y == 0:
self.make_spring(self.atoms[atom.index-(N+2)], atom, False)
self.make_spring(atom, self.atoms[atom.index+(N+2)], True)
elif atom.pos.y == N-1:
self.make_spring(atom, self.atoms[atom.index+(N+2)], False)
else:
self.make_spring(atom, self.atoms[atom.index+(N+2)], True)
if atom.pos.z == 0:
self.make_spring(self.atoms[atom.index-(N+2)**2], atom, False)
self.make_spring(atom, self.atoms[atom.index+(N+2)**2], True)
elif atom.pos.z == N-1:
self.make_spring(atom, self.atoms[atom.index+(N+2)**2], False)
else:
self.make_spring(atom, self.atoms[atom.index+(N+2)**2], True)
# Create a grid of springs linking each atom to the adjacent atoms
# in each dimension, or to invisible motionless atoms
def make_spring(self, start, end, visible):
spring = helix()
spring.pos = start.pos
spring.axis = end.pos-start.pos
spring.visible = visible
spring.thickness = 0.05
spring.radius = 0.5*atom_radius
spring.length = spacing
spring.start = start
spring.end = end
spring.color = color.orange
self.springs.append(spring)
c = crystal(N, atom_radius, spacing, 0.1*spacing*sqrt(k/m))
while True:
rate(60)
for atom in c.atoms:
if atom.visible:
atom.pos = atom.pos + atom.momentum/m*dt
for spring in c.springs:
spring.axis = spring.end.pos - spring.start.pos
L = mag(spring.axis)
spring.axis = spring.axis.norm()
spring.pos = spring.start.pos+0.5*atom_radius*spring.axis
Ls = L-atom_radius
spring.length = Ls
Fdt = spring.axis * (k*dt * (1-spacing/L))
if spring.start.visible:
spring.start.momentum = spring.start.momentum + Fdt
if spring.end.visible:
spring.end.momentum = spring.end.momentum - Fdt
I would like to save a series of screenshots of this output. Any help would be appreciated.
From the documentation at https://www.glowscript.org/docs/VPythonDocs/canvas.html:
capture(filename) Sends to your Download folder a png screen shot of the canvas. If filename is the string "boxes" or "boxes.png" the file will be named "boxes.png".
My code dies after about 140+ iterations, and I don't know why. I guess memory leak is a possibility, but I couldn't find it. I also found out that changing some arithmetic constants can prolong the time until the crash.
I have a genetic algorithm that tries to find best (i.e. minimal steps) route from point A (src) to point B (dst).
I create a list of random chromosomes, where each chromosome has:
src + dst [always the same]
list of directions (random)
I then run the algorithm:
find best route and draw it (for visualization purposes)
Given a probability P - replace the chromosomes with cross-overs (i.e. pick 2, and take the "end" of one's directions, and replace the "end" of the second's)
Given probability Q - mutate (replace the next direction with a random direction)
This all goes well, and most of the times I do find a route (usually not the ideal one), but sometimes, when it searches for a long time (say, about 140+ iterations) it just crushes. No warning. No error.
How can I prevent that (a simple iteration limit can work, but I do want the algorithm to run for a long time [~2000+ iterations])?
I think the relevant parts of the code are:
update function inside GUI class
which calls to cross_over
When playing with the update_fitness() score values (changing score -= (weight+1)*2000*(shift_x + shift_y) to score -= (weight+1)*2*(shift_x + shift_y) it runs for a longer time. Could be some kind of an arithmetic overflow?
import tkinter as tk
from enum import Enum
from random import randint, sample
from copy import deepcopy
from time import sleep
from itertools import product
debug_flag = False
class Direction(Enum):
Up = 0
Down = 1
Left = 2
Right = 3
def __str__(self):
return str(self.name)
def __repr__(self):
return str(self.name)[0]
# A chromosome is a list of directions that should lead the way from src to dst.
# Each step in the chromosome is a direction (up, down, right ,left)
# The chromosome also keeps track of its route
class Chromosome:
def __init__(self, src = None, dst = None, length = 10, directions = None):
self.MAX_SCORE = 1000000
self.route = [src]
if not directions:
self.directions = [Direction(randint(0,3)) for i in range(length)]
else:
self.directions = directions
self.src = src
self.dst = dst
self.fitness = self.MAX_SCORE
def __str__(self):
return str(self.fitness)
def __repr__(self):
return self.__str__()
def set_src(self, pixel):
self.src = pixel
def set_dst(self, pixel):
self.dst = pixel
def set_directions(self, ls):
self.directions = ls
def update_fitness(self):
# Higher score - a better fitness
score = self.MAX_SCORE - len(self.route)
score += 4000*(len(set(self.route)) - len(self.route)) # penalize returning to the same cell
score += (self.dst in self.route) * 500 # bonus routes that get to dst
for weight,cell in enumerate(self.route):
shift_x = abs(cell[0] - self.dst[0])
shift_y = abs(cell[1] - self.dst[1])
score -= (weight+1)*2000*(shift_x + shift_y) # penalize any wrong turn
self.fitness = max(score, 0)
def update(self, mutate_chance = 0.9):
# mutate #
self.mutate(chance = mutate_chance)
# move according to direction
last_cell = self.route[-1]
try:
direction = self.directions[len(self.route) - 1]
except IndexError:
print('No more directions. Halting')
return
if direction == Direction.Down:
x_shift, y_shift = 0, 1
elif direction == Direction.Up:
x_shift, y_shift = 0, -1
elif direction == Direction.Left:
x_shift, y_shift = -1, 0
elif direction == Direction.Right:
x_shift, y_shift = 1, 0
new_cell = last_cell[0] + x_shift, last_cell[1] + y_shift
self.route.append(new_cell)
self.update_fitness()
def cross_over(p1, p2, loc = None):
# find the cross_over point
if not loc:
loc = randint(0,len(p1.directions))
# choose one of the parents randomly
x = randint(0,1)
src_parent = (p1, p2)[x]
dst_parent = (p1, p2)[1 - x]
son = deepcopy(src_parent)
son.directions[loc:] = deepcopy(dst_parent.directions[loc:])
return son
def mutate(self, chance = 1):
if 100*chance > randint(0,99):
self.directions[len(self.route) - 1] = Direction(randint(0,3))
class GUI:
def __init__(self, rows = 10, cols = 10, iteration_timer = 100, chromosomes = [], cross_over_chance = 0.5, mutation_chance = 0.3, MAX_ITER = 100):
self.rows = rows
self.cols = cols
self.canv_w = 800
self.canv_h = 800
self.cell_w = self.canv_w // cols
self.cell_h = self.canv_h // rows
self.master = tk.Tk()
self.canvas = tk.Canvas(self.master, width = self.canv_w, height = self.canv_h)
self.canvas.pack()
self.rect_dict = {}
self.iteration_timer = iteration_timer
self.iterations = 0
self.MAX_ITER = MAX_ITER
self.chromosome_list = chromosomes
self.src = chromosomes[0].src # all chromosomes share src + dst
self.dst = chromosomes[0].dst
self.prev_best_route = []
self.cross_over_chance = cross_over_chance
self.mutation_chance = mutation_chance
self.no_obstacles = True
# init grid #
for r in range(rows):
for c in range(cols):
self.rect_dict[(r, c)] = self.canvas.create_rectangle(r *self.cell_h, c *self.cell_w,
(1+r)*self.cell_h, (1+c)*self.cell_w,
fill="gray")
# init grid #
# draw src + dst #
self.color_src_dst()
# draw src + dst #
# after + mainloop #
self.master.after(iteration_timer, self.start_gui)
tk.mainloop()
# after + mainloop #
def start_gui(self):
self.start_msg = self.canvas.create_text(self.canv_w // 2,3*self.canv_h // 4, fill = "black", font = "Times 25 bold underline",
text="Starting new computation.\nPopulation size = %d\nCross-over chance = %.2f\nMutation chance = %.2f" %
(len(self.chromosome_list), self.cross_over_chance, self.mutation_chance))
self.master.after(2000, self.update)
def end_gui(self, msg="Bye Bye!"):
self.master.wm_attributes('-alpha', 0.9) # transparency
self.canvas.create_text(self.canv_w // 2,3*self.canv_h // 4, fill = "black", font = "Times 25 bold underline", text=msg)
cell_ls = []
for idx,cell in enumerate(self.prev_best_route):
if cell in cell_ls:
continue
cell_ls.append(cell)
self.canvas.create_text(cell[0]*self.cell_w, cell[1]*self.cell_h, fill = "purple", font = "Times 16 bold italic", text=str(idx+1))
self.master.after(3000, self.master.destroy)
def color_src_dst(self):
r_src = self.rect_dict[self.src]
r_dst = self.rect_dict[self.dst]
c_src = 'blue'
c_dst = 'red'
self.canvas.itemconfig(r_src, fill=c_src)
self.canvas.itemconfig(r_dst, fill=c_dst)
def color_route(self, route, color):
for cell in route:
try:
self.canvas.itemconfig(self.rect_dict[cell], fill=color)
except KeyError:
# out of bounds -> ignore
continue
# keep the src + dst
self.color_src_dst()
# keep the src + dst
def compute_shortest_route(self):
if self.no_obstacles:
return (1 +
abs(self.chromosome_list[0].dst[0] - self.chromosome_list[0].src[0]) +
abs(self.chromosome_list[0].dst[1] - self.chromosome_list[0].src[1]))
else:
return 0
def create_weighted_chromosome_list(self):
ls = []
for ch in self.chromosome_list:
tmp = [ch] * (ch.fitness // 200000)
ls.extend(tmp)
return ls
def cross_over(self):
new_chromosome_ls = []
weighted_ls = self.create_weighted_chromosome_list()
while len(new_chromosome_ls) < len(self.chromosome_list):
try:
p1, p2 = sample(weighted_ls, 2)
son = Chromosome.cross_over(p1, p2)
if son in new_chromosome_ls:
continue
else:
new_chromosome_ls.append(son)
except ValueError:
continue
return new_chromosome_ls
def end_successfully(self):
self.end_gui(msg="Got to destination in %d iterations!\nBest route length = %d" % (len(self.prev_best_route), self.compute_shortest_route()))
def update(self):
# first time #
self.canvas.delete(self.start_msg)
# first time #
# end #
if self.iterations >= self.MAX_ITER:
self.end_gui()
return
# end #
# clean the previously best chromosome route #
self.color_route(self.prev_best_route[1:], 'gray')
# clean the previously best chromosome route #
# cross over #
if 100*self.cross_over_chance > randint(0,99):
self.chromosome_list = self.cross_over()
# cross over #
# update (includes mutations) all chromosomes #
for ch in self.chromosome_list:
ch.update(mutate_chance=self.mutation_chance)
# update (includes mutations) all chromosomes #
# show all chromsome fitness values #
if debug_flag:
fit_ls = [ch.fitness for ch in self.chromosome_list]
print(self.iterations, sum(fit_ls) / len(fit_ls), fit_ls)
# show all chromsome fitness values #
# find and display best chromosome #
best_ch = max(self.chromosome_list, key=lambda ch : ch.fitness)
self.prev_best_route = deepcopy(best_ch.route)
self.color_route(self.prev_best_route[1:], 'gold')
# find and display best chromosome #
# check if got to dst #
if best_ch.dst == best_ch.route[-1]:
self.end_successfully()
return
# check if got to dst #
# after + update iterations #
self.master.after(self.iteration_timer, self.update)
self.iterations += 1
# after + update iterations #
def main():
iter_timer, ITER = 10, 350
r,c = 20,20
s,d = (13,11), (7,8)
population_size = [80,160]
cross_over_chance = [0.2,0.4,0.5]
for pop_size, CO_chance in product(population_size, cross_over_chance):
M_chance = 0.7 - CO_chance
ch_ls = [Chromosome(src=s, dst=d, directions=[Direction(randint(0,3)) for i in range(ITER)]) for i in range(pop_size)]
g = GUI(rows=r, cols=c, chromosomes = ch_ls, iteration_timer=iter_timer,
cross_over_chance=CO_chance, mutation_chance=M_chance, MAX_ITER=ITER-1)
del(ch_ls)
del(g)
if __name__ == "__main__":
main()
I do not know if you know the Python Profiling tool of Visual Studio, but it is quite useful in cases as yours (though I usually program with editors, like VS Code).
I have run your program and, as you said, it sometimes crashes. I have analyzed the code with the profiling tool and it seems that the problem is the function cross_over, specifically the random function:
I would strongly suggest reviewing your cross_over and mutation functions. The random function should not be called so many times (2 millions).
I have previously programmed Genetic Algorithms and, to me, it seems that your program is falling into a local minimum. What is suggested in these cases is playing with the percentage of mutation. Try to increase it a little bit so that you could get out of the local minimum.
We have a code to draw circles on the Location on the map with the name of each category. Now the circles and text are one color. How do we get them in different color's by category? Example: Category Garden: Blue, Category Stone: Grey.
So far the code:
size(1500,800)
background(1)
nofill()
stroke('#f91')
pen(.2)
fill('#f91', 0.05)
rotate(90)
font("Avenir", "bold", 10)
align('left')
def mapValue(value, fromMin, fromMax, toMin, toMax):
# Figure out how 'wide' each range is
fromSpan = fromMax - fromMin
toSpan = toMax - toMin
# Convert the from range into a 0-1 range (float)
valueScaled = float(value - fromMin) / float(fromSpan)
# Convert the 0-1 range into a value in the to range.
return toMin + (valueScaled * toSpan)
def xOfDot(lon):
return mapValue(lon, -100, 100, 0, WIDTH)
def yOfDot(lat):
return mapValue(lat, -90, 90, HEIGHT, 0)
with open('theft-alerts.json', 'r') as inputFile:
data = json.load(inputFile)
print len(data)
artworksPerCity = {}
for stolenArt in data:
if stolenArt.has_key('Category'):
city = stolenArt['Category']
if stolenArt.has_key('nItemsStolen'):
numbersStolen = int(stolenArt['nItemsStolen'])
if artworksPerCity.has_key(city):
# Adjust the value stored for this city
artworksPerCity[city] = artworksPerCity[city] + numbersStolen
else:
# Create new key with new value
artworksPerCity[city] = numbersStolen
# Draw circle on the map
radius = artworksPerCity[city] /2
x = xOfDot(stolenArt['Lon'])
y = yOfDot(stolenArt['Lat'])
arc(x, y, radius)
text(city, x, y)
print artworksPerCity
Here is a sketch of what I intend to include in my pure python data utility.
def hexidecimalDiget(n,deHex = false):
if(n<0):
print "negitive values not supported by call to hexidecimalDiget("+str(n)+")"
return None
elif(n < 10):
return str(n)
elif(n < 15):
return ["a","b","c","d","e"][n-10]
elif(n in ["a","b","c","d","e"]):
if deHex:
return ["a","b","c","d","e"].index(n)
return n
else:
print "call to hexidecimalDiget("+str(n)+") not supported!"
return None
def colorFormHexArray(arr):
if len(arr)!=3 and len(arr)!=6:
print "invalid length for color on call to colorFormHexArray("+str(arr)+")"
return None
elif None in arr:
print "cannot make color from None arguments in "+str(arr)
return None
else:
ret = "#"
for k in arr:
if(type(k) == list):
for k2 in k:
ret+=hexidecimalDiget(k)
else:
ret+=hexidecimalDiget(k)
return ret
def arrayFromColor(c):
c = c.replace("#","")
col = []
for n,k in enumerate(c):
if(len(c) == 3):
col.append([hexidecimalDiget(k,deHex = True)])
elif(len(c) == 6):
col.append([hexidecimalDiget(c[(n+1)*2-2],deHex = True),hexidecimalDiget(c[(n+1)*2-2],deHex = True)])
return(col)
def intFromHexPair(hp):
ret = 0
for n,k in enumerate(hp):
digBase = 16**(len(hp)-n-1)
ret+=digBase*hexidecimalDiget(hp[0],deHex = True)
return ret
def hexPairFromInt(I,minDigits = 1,maxDigits = 256):
if I<0:
print "negitive numbers not supported by hexPairFromInt"
k= 0
while(16**(k+1) <= I):
k+=1
if k < minDigits:
k = minDigits
if k > minDigits:
print("maxDigitsExceeded")
ret = []
while k>=0
dig = 16**k
ret.append(hexidecimalDiget(int(I)%(dig))
I -= dig
k-=1
return ret
def specColor(start,end,bottom,top):
start = arrayFromColor(start)
end = arrayFromColor(end)
def ret(v):
if( v<start or c>end ):
print("value out of range "+str([start,end]))
return('#aa0000') #eyo <- error red
else:
starts = [intFromHexPair(k) for k in start]
ends = [intFromHexPair(hp) for k in end]
normalized = (v-bottom)/(top-bottom)
return colorFormHexArray([hexPairFromInt(int((starts[n]-ends[n])*normalized),minDigits = 1,maxDigits = 256) for n,k in enumerate(starts)])
return ret
This seems excessive and hasn't even been slightly tested yet (just a stetch up atm) but I'll be testing and incorporating this code here tonight :: http://krewn.github.io/KPlot/