I'm just starting to understand the Python syntax and I created a module that does what I wanted, but really slow.
Here are the stats of cProfile, top 10 ordered by internal time
:
ncalls tottime percall cumtime percall filename:lineno(function)
1291716 45.576 0.000 171.672 0.000 geometry.py:10(barycentric_coords)
6460649 31.617 0.000 31.617 0.000 {numpy.core.multiarray.array}
2583432 15.979 0.000 15.979 0.000 {method 'reduce' of 'numpy.ufunc'
objects}
2031 12.032 0.006 193.333 0.095 geometry.py:26(containing_tet)
1291716 10.944 0.000 58.323 0.000 linalg.py:244(solve)
1291716 7.075 0.000 7.075 0.000 {numpy.linalg.lapack_lite.dgesv}
1291716 5.750 0.000 9.865 0.000 linalg.py:99(_commonType)
2583432 5.659 0.000 5.659 0.000 {numpy.core.multiarray._fastCopyAn
dTranspose}
1291716 5.526 0.000 7.299 0.000 twodim_base.py:169(eye)
1291716 5.492 0.000 12.791 0.000 numeric.py:1884(identity)
To process data and create a 24 bitmap of 300*300 pixels it would take about 1 1/2 hour on my laptop with the latest intel i5, a SSD disk and 12gb RAM!
I am not sure if it's a good idea to post all the code, but if not how can you get the entire picture?
The first thing colors_PPM.py
does is to access a function called geometry
.
geometry.py
itself calls tetgen
, tetgen.py
calls tetgen.exe
.
The module colors_PPM.py
reads a list in targets.csv
and a list in XYZcolorlist_D65.csv
, it excludes some elements of XYZcolorlist_D65.csv
and then reads one by one the rows of targets.csv
, performs a delaunay triangulation via tetgen
and returns 4 names[]
and 4 bcoords[]
.
Then random
is used to choose one name
by a series of if
elif
else
tests.
Finally the result is exported in a bitmap file epson_gamut.pbm
.
Do you see any way this could run faster? I know it should seem quite a mess.
.csv
files structures examples:
'XYZcolorlist_D65.csv'
255 63 127,35.5344302104,21.380721966,20.3661095969
255 95 127,40.2074945517,26.5282949405,22.7094284437
255 127 127,43.6647438365,32.3482625492,23.6181801523
255 159 127,47.1225628354,39.1780944388,22.9366615044
255 223 159,61.7379149646,62.8387601708,32.3936200864
255 255 159,70.7428790853,78.6134546144,29.5579371353
255 0 127,32.0951763469,18.3503537537,19.0863164396
255 31 127,32.281389139,18.5592317077,18.6802029444
255 191 127,52.6108977261,48.5621713952,21.7645428218
255 223 127,59.7600830083,60.9770436618,20.9338174593
...
'targets.csv'
30,5,3
30.34,5,3
30.68,5,3
31.02,5,3
31.36,5,3
31.7,5,3
32.04,5,3
32.38,5,3
32.72,5,3
33.06,5,3
33.4,5,3
33.74,5,3
...
This is geometry.py
:
#geometry.py
import numpy as np
import numpy.linalg as la
import tetgen
def barycentric_coords(vertices, point):
T = (np.array(vertices[:-1])-vertices[-1]).T
v = np.dot(la.inv(T), np.array(point)-vertices[-1])
v.resize(len(vertices))
v[-1] = 1-v.sum()
return v
def tetgen_of_hull(points):
tg_all = tetgen.TetGen(points)
hull_i = set().union(*tg_all.hull)
hull_points = [points[i] for i in hull_i]
tg_hull = tetgen.TetGen(hull_points)
return tg_hull, hull_i
def containing_tet(tg, point):
for i, tet in enumerate(tg.tets):
verts = [tg.points[j] for j in tet]
bcoords = barycentric_coords(verts, point)
if (bcoords >= 0).all():
return i, bcoords
return None, None
This is tetgen.py
:
#tetgen
import tempfile, subprocess, os
class TetGen:
def __init__(self, points):
self.points = points
node_f = tempfile.NamedTemporaryFile(suffix=".node", delete=False);
node_f.write("%i 3 0 0\n" % len(points))
for i, point in enumerate(points):
node_f.write("%i %f %f %f\n" % (i, point[0], point[1], point[2]))
node_f.close()
subprocess.call(["tetgen", node_f.name], stdout=open(os.devnull, 'wb'))
#subprocess.call(["C:\Users\gary\Documents\eclipse\Light Transformer Setup\tetgen", node_f.name], stdout=open(os.devnull, 'wb'))
ele_f_name = node_f.name[:-5] + ".1.ele"
face_f_name = node_f.name[:-5] + ".1.face"
ele_f_lines = [line.split() for line in open(ele_f_name)][1:-1]
face_f_lines = [line.split() for line in open(face_f_name)][1:-1]
self.tets = [map(int, line[1:]) for line in ele_f_lines]
self.hull = [map(int, line[1:]) for line in face_f_lines]
if __name__ == '__main__':
from pprint import pprint
points = [(0,0,0),(0,0,1),(0,1,0),(0,1,1),(1,0,0),(1,0,1),(1,1,0),(1,1,1)]
tg = TetGen(points)
pprint(tg.tets)
pprint(tg.hull)
And this is the module colors_PPM.py
:
import geometry
import csv
import numpy as np
import random
import cv2
S = 0
img = cv2.imread("MAP.tif", -1)
height, width = img.shape
ppm = file("epson gamut.ppm", 'w')
ppm.write("P3" + "\n" + str(width) + " " + str(height) + "\n" + "255" + "\n")
# PPM file header
all_colors = [(name, float(X), float(Y), float(Z))
for name, X, Y, Z in csv.reader(open('XYZcolorlist_D65.csv'))]
tg, hull_i = geometry.tetgen_of_hull([(X,Y,Z) for name, X, Y, Z in all_colors])
colors = [all_colors[i] for i in hull_i]
print ("thrown out: "
+ ", ".join(set(zip(*all_colors)[0]).difference(zip(*colors)[0])))
targets = ((float(X), float(Y), float(Z))
for X, Y, Z in csv.reader(open('targets.csv')))
for target in targets:
X, Y, Z = target
target_point = (np.array([X,Y,Z]))
tet_i, bcoords = geometry.containing_tet(tg, target_point)
if tet_i == None:
ppm.write(str("255 255 255") + "\n")
continue
# not in gamut
else:
A = bcoords[0]
B = bcoords[1]
C = bcoords[2]
D = bcoords[3]
R = random.uniform(0,1)
names = [colors[i][0] for i in tg.tets[tet_i]]
if R <= A:
S = names[0]
elif R <= A+B:
S = names[1]
elif R <= A+B+C:
S = names[2]
else:
S = names[3]
ppm.write(str(S) + "\n")
print "done"
ppm.close()
tetgen_of_hull
? Or is it the later loop over thetargets
? As Janne says, it would be worth starting by learning to use the profiler. \$\endgroup\$geometry.py
\$\endgroup\$