Commit f13a4b79d6e6cc3f3ac885ccd20527fde9106719

Authored by Thiago Franco de Moraes
Committed by GitHub
1 parent 339908de

Casmoothing implemented in cython (#61)

Implements casmoothing in Cython.

- Implements in cython
- Better handling of border vertices
.gitignore
... ... @@ -24,3 +24,9 @@ tags
24 24 build
25 25 *.patch
26 26 *.tgz
  27 +
  28 +*.pyd
  29 +*.cpp
  30 +*.diff
  31 +
  32 +*.directory
... ...
invesalius/data/cy_mesh.pyx 0 → 100644
... ... @@ -0,0 +1,466 @@
  1 +# distutils: language = c++
  2 +#cython: boundscheck=False
  3 +#cython: wraparound=False
  4 +#cython: initializedcheck=False
  5 +#cython: cdivision=True
  6 +#cython: nonecheck=False
  7 +
  8 +import sys
  9 +import time
  10 +cimport numpy as np
  11 +
  12 +from libc.math cimport sin, cos, acos, exp, sqrt, fabs, M_PI
  13 +from libc.stdlib cimport abs as cabs
  14 +from cython.operator cimport dereference as deref, preincrement as inc
  15 +from libcpp.map cimport map
  16 +from libcpp.unordered_map cimport unordered_map
  17 +from libcpp.set cimport set
  18 +from libcpp.vector cimport vector
  19 +from libcpp.pair cimport pair
  20 +from libcpp cimport bool
  21 +from libcpp.deque cimport deque as cdeque
  22 +from cython.parallel import prange
  23 +
  24 +from cy_my_types cimport vertex_t, normal_t, vertex_id_t
  25 +
  26 +import numpy as np
  27 +import vtk
  28 +
  29 +from vtk.util import numpy_support
  30 +
  31 +ctypedef float weight_t
  32 +
  33 +cdef struct Point:
  34 + vertex_t x
  35 + vertex_t y
  36 + vertex_t z
  37 +
  38 +ctypedef pair[vertex_id_t, vertex_id_t] key
  39 +
  40 +
  41 +cdef class Mesh:
  42 + cdef vertex_t[:, :] vertices
  43 + cdef vertex_id_t[:, :] faces
  44 + cdef normal_t[:, :] normals
  45 +
  46 + cdef unordered_map[int, vector[vertex_id_t]] map_vface
  47 + cdef unordered_map[vertex_id_t, int] border_vertices
  48 +
  49 + cdef bool _initialized
  50 +
  51 + def __cinit__(self, pd=None, other=None):
  52 + cdef int i
  53 + cdef map[key, int] edge_nfaces
  54 + cdef map[key, int].iterator it
  55 + if pd:
  56 + self._initialized = True
  57 + _vertices = numpy_support.vtk_to_numpy(pd.GetPoints().GetData())
  58 + _vertices.shape = -1, 3
  59 +
  60 + _faces = numpy_support.vtk_to_numpy(pd.GetPolys().GetData())
  61 + _faces.shape = -1, 4
  62 +
  63 + _normals = numpy_support.vtk_to_numpy(pd.GetCellData().GetArray("Normals"))
  64 + _normals.shape = -1, 3
  65 +
  66 + self.vertices = _vertices
  67 + self.faces = _faces
  68 + self.normals = _normals
  69 +
  70 + for i in xrange(_faces.shape[0]):
  71 + self.map_vface[self.faces[i, 1]].push_back(i)
  72 + self.map_vface[self.faces[i, 2]].push_back(i)
  73 + self.map_vface[self.faces[i, 3]].push_back(i)
  74 +
  75 + edge_nfaces[key(min(self.faces[i, 1], self.faces[i, 2]), max(self.faces[i, 1], self.faces[i, 2]))] += 1
  76 + edge_nfaces[key(min(self.faces[i, 2], self.faces[i, 3]), max(self.faces[i, 2], self.faces[i, 3]))] += 1
  77 + edge_nfaces[key(min(self.faces[i, 1], self.faces[i, 3]), max(self.faces[i, 1], self.faces[i, 3]))] += 1
  78 +
  79 + it = edge_nfaces.begin()
  80 +
  81 + while it != edge_nfaces.end():
  82 + if deref(it).second == 1:
  83 + self.border_vertices[deref(it).first.first] = 1
  84 + self.border_vertices[deref(it).first.second] = 1
  85 +
  86 + inc(it)
  87 +
  88 + elif other:
  89 + _other = <Mesh>other
  90 + self._initialized = True
  91 + self.vertices = _other.vertices.copy()
  92 + self.faces = _other.faces.copy()
  93 + self.normals = _other.normals.copy()
  94 + self.map_vface = unordered_map[int, vector[vertex_id_t]](_other.map_vface)
  95 + self.border_vertices = unordered_map[vertex_id_t, int](_other.border_vertices)
  96 + else:
  97 + self._initialized = False
  98 +
  99 + cdef void copy_to(self, Mesh other):
  100 + """
  101 + Copies self content to other.
  102 + """
  103 + if self._initialized:
  104 + other.vertices[:] = self.vertices
  105 + other.faces[:] = self.faces
  106 + other.normals[:] = self.normals
  107 + other.map_vface = unordered_map[int, vector[vertex_id_t]](self.map_vface)
  108 + other.border_vertices = unordered_map[vertex_id_t, int](self.border_vertices)
  109 + else:
  110 + other.vertices = self.vertices.copy()
  111 + other.faces = self.faces.copy()
  112 + other.normals = self.normals.copy()
  113 +
  114 + other.map_vface = self.map_vface
  115 + other.border_vertices = self.border_vertices
  116 +
  117 + def to_vtk(self):
  118 + """
  119 + Converts Mesh to vtkPolyData.
  120 + """
  121 + vertices = np.asarray(self.vertices)
  122 + faces = np.asarray(self.faces)
  123 + normals = np.asarray(self.normals)
  124 +
  125 + points = vtk.vtkPoints()
  126 + points.SetData(numpy_support.numpy_to_vtk(vertices))
  127 +
  128 + id_triangles = numpy_support.numpy_to_vtkIdTypeArray(faces)
  129 + triangles = vtk.vtkCellArray()
  130 + triangles.SetCells(faces.shape[0], id_triangles)
  131 +
  132 + pd = vtk.vtkPolyData()
  133 + pd.SetPoints(points)
  134 + pd.SetPolys(triangles)
  135 +
  136 + return pd
  137 +
  138 + cdef vector[vertex_id_t]* get_faces_by_vertex(self, int v_id) nogil:
  139 + """
  140 + Returns the faces whose vertex `v_id' is part.
  141 + """
  142 + return &self.map_vface[v_id]
  143 +
  144 + cdef set[vertex_id_t]* get_ring1(self, vertex_id_t v_id) nogil:
  145 + """
  146 + Returns the ring1 of vertex `v_id'
  147 + """
  148 + cdef vertex_id_t f_id
  149 + cdef set[vertex_id_t]* ring1 = new set[vertex_id_t]()
  150 + cdef vector[vertex_id_t].iterator it = self.map_vface[v_id].begin()
  151 +
  152 + while it != self.map_vface[v_id].end():
  153 + f_id = deref(it)
  154 + inc(it)
  155 + if self.faces[f_id, 1] != v_id:
  156 + ring1.insert(self.faces[f_id, 1])
  157 + if self.faces[f_id, 2] != v_id:
  158 + ring1.insert(self.faces[f_id, 2])
  159 + if self.faces[f_id, 3] != v_id:
  160 + ring1.insert(self.faces[f_id, 3])
  161 +
  162 + return ring1
  163 +
  164 + cdef bool is_border(self, vertex_id_t v_id) nogil:
  165 + """
  166 + Check if vertex `v_id' is a vertex border.
  167 + """
  168 + return self.border_vertices.find(v_id) != self.border_vertices.end()
  169 +
  170 + cdef vector[vertex_id_t]* get_near_vertices_to_v(self, vertex_id_t v_id, float dmax) nogil:
  171 + """
  172 + Returns all vertices with distance at most `d' to the vertex `v_id'
  173 +
  174 + Params:
  175 + v_id: id of the vertex
  176 + dmax: the maximun distance.
  177 + """
  178 + cdef vector[vertex_id_t]* idfaces
  179 + cdef vector[vertex_id_t]* near_vertices = new vector[vertex_id_t]()
  180 +
  181 + cdef cdeque[vertex_id_t] to_visit
  182 + cdef unordered_map[vertex_id_t, bool] status_v
  183 + cdef unordered_map[vertex_id_t, bool] status_f
  184 +
  185 + cdef vertex_t *vip
  186 + cdef vertex_t *vjp
  187 +
  188 + cdef float distance
  189 + cdef int nf, nid, j
  190 + cdef vertex_id_t f_id, vj
  191 +
  192 + vip = &self.vertices[v_id, 0]
  193 + to_visit.push_back(v_id)
  194 + while(not to_visit.empty()):
  195 + v_id = to_visit.front()
  196 + to_visit.pop_front()
  197 +
  198 + status_v[v_id] = True
  199 +
  200 + idfaces = self.get_faces_by_vertex(v_id)
  201 + nf = idfaces.size()
  202 +
  203 + for nid in xrange(nf):
  204 + f_id = deref(idfaces)[nid]
  205 + if status_f.find(f_id) == status_f.end():
  206 + status_f[f_id] = True
  207 +
  208 + for j in xrange(3):
  209 + vj = self.faces[f_id, j+1]
  210 + if status_v.find(vj) == status_v.end():
  211 + status_v[vj] = True
  212 + vjp = &self.vertices[vj, 0]
  213 + distance = sqrt((vip[0] - vjp[0]) * (vip[0] - vjp[0]) \
  214 + + (vip[1] - vjp[1]) * (vip[1] - vjp[1]) \
  215 + + (vip[2] - vjp[2]) * (vip[2] - vjp[2]))
  216 + if distance <= dmax:
  217 + near_vertices.push_back(vj)
  218 + to_visit.push_back(vj)
  219 +
  220 + return near_vertices
  221 +
  222 +
  223 +cdef vector[weight_t]* calc_artifacts_weight(Mesh mesh, vector[vertex_id_t]& vertices_staircase, float tmax, float bmin) nogil:
  224 + """
  225 + Calculate the artifact weight based on distance of each vertex to its
  226 + nearest staircase artifact vertex.
  227 +
  228 + Params:
  229 + mesh: Mesh
  230 + vertices_staircase: the identified staircase artifact vertices
  231 + tmax: max distance the vertex must be to its nearest artifact vertex
  232 + to considered to calculate the weight
  233 + bmin: The minimun weight.
  234 + """
  235 + cdef int vi_id, vj_id, nnv, n_ids, i, j
  236 + cdef vector[vertex_id_t]* near_vertices
  237 + cdef weight_t value
  238 + cdef float d
  239 + n_ids = vertices_staircase.size()
  240 +
  241 + cdef vertex_t* vi
  242 + cdef vertex_t* vj
  243 + cdef size_t msize
  244 +
  245 + msize = mesh.vertices.shape[0]
  246 + cdef vector[weight_t]* weights = new vector[weight_t](msize)
  247 + weights.assign(msize, bmin)
  248 +
  249 + for i in prange(n_ids, nogil=True):
  250 + vi_id = vertices_staircase[i]
  251 + deref(weights)[vi_id] = 1.0
  252 +
  253 + vi = &mesh.vertices[vi_id, 0]
  254 + near_vertices = mesh.get_near_vertices_to_v(vi_id, tmax)
  255 + nnv = near_vertices.size()
  256 +
  257 + for j in xrange(nnv):
  258 + vj_id = deref(near_vertices)[j]
  259 + vj = &mesh.vertices[vj_id, 0]
  260 +
  261 + d = sqrt((vi[0] - vj[0]) * (vi[0] - vj[0])\
  262 + + (vi[1] - vj[1]) * (vi[1] - vj[1])\
  263 + + (vi[2] - vj[2]) * (vi[2] - vj[2]))
  264 + value = (1.0 - d/tmax) * (1.0 - bmin) + bmin
  265 +
  266 + if value > deref(weights)[vj_id]:
  267 + deref(weights)[vj_id] = value
  268 +
  269 + del near_vertices
  270 +
  271 + # for i in xrange(msize):
  272 + # if mesh.is_border(i):
  273 + # deref(weights)[i] = 0.0
  274 +
  275 + # cdef vertex_id_t v0, v1, v2
  276 + # for i in xrange(mesh.faces.shape[0]):
  277 + # for j in xrange(1, 4):
  278 + # v0 = mesh.faces[i, j]
  279 + # vi = &mesh.vertices[v0, 0]
  280 + # if mesh.is_border(v0):
  281 + # deref(weights)[v0] = 0.0
  282 + # v1 = mesh.faces[i, (j + 1) % 3 + 1]
  283 + # if mesh.is_border(v1):
  284 + # vi = &mesh.vertices[v1, 0]
  285 + # deref(weights)[v0] = 0.0
  286 +
  287 + return weights
  288 +
  289 +
  290 +cdef inline Point calc_d(Mesh mesh, vertex_id_t v_id) nogil:
  291 + cdef Point D
  292 + cdef int nf, f_id, nid
  293 + cdef float n=0
  294 + cdef int i
  295 + cdef vertex_t* vi
  296 + cdef vertex_t* vj
  297 + cdef set[vertex_id_t]* vertices
  298 + cdef set[vertex_id_t].iterator it
  299 + cdef vertex_id_t vj_id
  300 +
  301 + D.x = 0.0
  302 + D.y = 0.0
  303 + D.z = 0.0
  304 +
  305 + vertices = mesh.get_ring1(v_id)
  306 + vi = &mesh.vertices[v_id, 0]
  307 +
  308 + if mesh.is_border(v_id):
  309 + it = vertices.begin()
  310 + while it != vertices.end():
  311 + vj_id = deref(it)
  312 + if mesh.is_border(vj_id):
  313 + vj = &mesh.vertices[vj_id, 0]
  314 +
  315 + D.x = D.x + (vi[0] - vj[0])
  316 + D.y = D.y + (vi[1] - vj[1])
  317 + D.z = D.z + (vi[2] - vj[2])
  318 + n += 1.0
  319 +
  320 + inc(it)
  321 + else:
  322 + it = vertices.begin()
  323 + while it != vertices.end():
  324 + vj_id = deref(it)
  325 + vj = &mesh.vertices[vj_id, 0]
  326 +
  327 + D.x = D.x + (vi[0] - vj[0])
  328 + D.y = D.y + (vi[1] - vj[1])
  329 + D.z = D.z + (vi[2] - vj[2])
  330 + n += 1.0
  331 +
  332 + inc(it)
  333 +
  334 + del vertices
  335 +
  336 + D.x = D.x / n
  337 + D.y = D.y / n
  338 + D.z = D.z / n
  339 + return D
  340 +
  341 +cdef vector[vertex_id_t]* find_staircase_artifacts(Mesh mesh, double[3] stack_orientation, double T) nogil:
  342 + """
  343 + This function is used to find vertices at staircase artifacts, which are
  344 + those vertices whose incident faces' orientation differences are
  345 + greater than T.
  346 +
  347 + Params:
  348 + mesh: Mesh
  349 + stack_orientation: orientation of slice stacking
  350 + T: Min angle (between vertex faces and stack_orientation) to consider a
  351 + vertex a staircase artifact.
  352 + """
  353 + cdef int nv, nf, f_id, v_id
  354 + cdef double of_z, of_y, of_x, min_z, max_z, min_y, max_y, min_x, max_x;
  355 + cdef vector[vertex_id_t]* f_ids
  356 + cdef normal_t* normal
  357 +
  358 + cdef vector[vertex_id_t]* output = new vector[vertex_id_t]()
  359 + cdef int i
  360 +
  361 + nv = mesh.vertices.shape[0]
  362 +
  363 + for v_id in xrange(nv):
  364 + max_z = -10000
  365 + min_z = 10000
  366 + max_y = -10000
  367 + min_y = 10000
  368 + max_x = -10000
  369 + min_x = 10000
  370 +
  371 + f_ids = mesh.get_faces_by_vertex(v_id)
  372 + nf = deref(f_ids).size()
  373 +
  374 + for i in xrange(nf):
  375 + f_id = deref(f_ids)[i]
  376 + normal = &mesh.normals[f_id, 0]
  377 +
  378 + of_z = 1 - fabs(normal[0]*stack_orientation[0] + normal[1]*stack_orientation[1] + normal[2]*stack_orientation[2]);
  379 + of_y = 1 - fabs(normal[0]*0 + normal[1]*1 + normal[2]*0);
  380 + of_x = 1 - fabs(normal[0]*1 + normal[1]*0 + normal[2]*0);
  381 +
  382 + if (of_z > max_z):
  383 + max_z = of_z
  384 +
  385 + if (of_z < min_z):
  386 + min_z = of_z
  387 +
  388 + if (of_y > max_y):
  389 + max_y = of_y
  390 +
  391 + if (of_y < min_y):
  392 + min_y = of_y
  393 +
  394 + if (of_x > max_x):
  395 + max_x = of_x
  396 +
  397 + if (of_x < min_x):
  398 + min_x = of_x
  399 +
  400 +
  401 + if ((fabs(max_z - min_z) >= T) or (fabs(max_y - min_y) >= T) or (fabs(max_x - min_x) >= T)):
  402 + output.push_back(v_id)
  403 + break
  404 + return output
  405 +
  406 +
  407 +cdef void taubin_smooth(Mesh mesh, vector[weight_t]& weights, float l, float m, int steps):
  408 + """
  409 + Implementation of Taubin's smooth algorithm described in the paper "A
  410 + Signal Processing Approach To Fair Surface Design". His benefeat is it
  411 + avoids surface shrinking.
  412 + """
  413 + cdef int s, i, nvertices
  414 + nvertices = mesh.vertices.shape[0]
  415 + cdef vector[Point] D = vector[Point](nvertices)
  416 + cdef vertex_t* vi
  417 + for s in xrange(steps):
  418 + for i in prange(nvertices, nogil=True):
  419 + D[i] = calc_d(mesh, i)
  420 +
  421 + for i in prange(nvertices, nogil=True):
  422 + mesh.vertices[i, 0] += weights[i]*l*D[i].x;
  423 + mesh.vertices[i, 1] += weights[i]*l*D[i].y;
  424 + mesh.vertices[i, 2] += weights[i]*l*D[i].z;
  425 +
  426 + for i in prange(nvertices, nogil=True):
  427 + D[i] = calc_d(mesh, i)
  428 +
  429 + for i in prange(nvertices, nogil=True):
  430 + mesh.vertices[i, 0] += weights[i]*m*D[i].x;
  431 + mesh.vertices[i, 1] += weights[i]*m*D[i].y;
  432 + mesh.vertices[i, 2] += weights[i]*m*D[i].z;
  433 +
  434 +
  435 +def ca_smoothing(Mesh mesh, double T, double tmax, double bmin, int n_iters):
  436 + """
  437 + This is a implementation of the paper "Context-aware mesh smoothing for
  438 + biomedical applications". It can be used to smooth meshes generated by
  439 + binary images to remove its staircase artifacts and keep the fine features.
  440 +
  441 + Params:
  442 + mesh: Mesh
  443 + T: Min angle (between vertex faces and stack_orientation) to consider a
  444 + vertex a staircase artifact
  445 + tmax: max distance the vertex must be to its nearest artifact vertex
  446 + to considered to calculate the weight
  447 + bmin: The minimun weight
  448 + n_iters: Number of iterations.
  449 + """
  450 + cdef double[3] stack_orientation = [0.0, 0.0, 1.0]
  451 +
  452 + t0 = time.time()
  453 + cdef vector[vertex_id_t]* vertices_staircase = find_staircase_artifacts(mesh, stack_orientation, T)
  454 + print "vertices staircase", time.time() - t0
  455 +
  456 + t0 = time.time()
  457 + cdef vector[weight_t]* weights = calc_artifacts_weight(mesh, deref(vertices_staircase), tmax, bmin)
  458 + print "Weights", time.time() - t0
  459 +
  460 + del vertices_staircase
  461 +
  462 + t0 = time.time()
  463 + taubin_smooth(mesh, deref(weights), 0.5, -0.53, n_iters)
  464 + print "taubin", time.time() - t0
  465 +
  466 + del weights
... ...
invesalius/data/cy_my_types.pxd
... ... @@ -10,3 +10,7 @@ ctypedef fused image_t:
10 10 np.uint8_t
11 11  
12 12 ctypedef np.uint8_t mask_t
  13 +
  14 +ctypedef np.float32_t vertex_t
  15 +ctypedef np.float32_t normal_t
  16 +ctypedef np.int64_t vertex_id_t
... ...
invesalius/data/surface.py
... ... @@ -42,6 +42,7 @@ try:
42 42 except ImportError:
43 43 import data.ca_smoothing as ca_smoothing
44 44  
  45 +import cy_mesh
45 46 # TODO: Verificar ReleaseDataFlagOn and SetSource
46 47  
47 48 class Surface():
... ... @@ -564,16 +565,28 @@ class SurfaceManager():
564 565 # polydata.SetSource(None)
565 566 del clean
566 567  
567   - try:
568   - polydata.BuildLinks()
569   - except TypeError:
570   - polydata.BuildLinks(0)
571   - polydata = ca_smoothing.ca_smoothing(polydata, options['angle'],
572   - options['max distance'],
573   - options['min weight'],
574   - options['steps'])
  568 + # try:
  569 + # polydata.BuildLinks()
  570 + # except TypeError:
  571 + # polydata.BuildLinks(0)
  572 + # polydata = ca_smoothing.ca_smoothing(polydata, options['angle'],
  573 + # options['max distance'],
  574 + # options['min weight'],
  575 + # options['steps'])
  576 +
  577 + mesh = cy_mesh.Mesh(polydata)
  578 + cy_mesh.ca_smoothing(mesh, options['angle'],
  579 + options['max distance'],
  580 + options['min weight'],
  581 + options['steps'])
  582 + # polydata = mesh.to_vtk()
  583 +
575 584 # polydata.SetSource(None)
576 585 # polydata.DebugOn()
  586 + w = vtk.vtkPLYWriter()
  587 + w.SetInputData(polydata)
  588 + w.SetFileName('/tmp/ca_smoothing_inv.ply')
  589 + w.Write()
577 590  
578 591 else:
579 592 #smoother = vtk.vtkWindowedSincPolyDataFilter()
... ...
invesalius/gui/dialogs.py
... ... @@ -1276,7 +1276,7 @@ class CAOptions(wx.Panel):
1276 1276 max_val=100.0, increment=0.1,
1277 1277 digits=2)
1278 1278  
1279   - self.min_weight = floatspin.FloatSpin(self, -1, value=0.2, min_val=0.0,
  1279 + self.min_weight = floatspin.FloatSpin(self, -1, value=0.5, min_val=0.0,
1280 1280 max_val=1.0, increment=0.1,
1281 1281 digits=1)
1282 1282  
... ...
setup.py
... ... @@ -29,6 +29,12 @@ if sys.platform == &#39;linux2&#39;:
29 29 Extension("invesalius.data.floodfill", ["invesalius/data/floodfill.pyx"],
30 30 include_dirs=[numpy.get_include()],
31 31 language='c++',),
  32 +
  33 + Extension("invesalius.data.cy_mesh", ["invesalius/data/cy_mesh.pyx"],
  34 + include_dirs=[numpy.get_include()],
  35 + extra_compile_args=['-fopenmp', '-std=c++11'],
  36 + extra_link_args=['-fopenmp', '-std=c++11'],
  37 + language='c++',),
32 38 ])
33 39 )
34 40  
... ... @@ -50,6 +56,11 @@ elif sys.platform == &#39;win32&#39;:
50 56 Extension("invesalius.data.floodfill", ["invesalius/data/floodfill.pyx"],
51 57 include_dirs=[numpy.get_include()],
52 58 language='c++',),
  59 +
  60 + Extension("invesalius.data.cy_mesh", ["invesalius/data/cy_mesh.pyx"],
  61 + include_dirs=[numpy.get_include()],
  62 + extra_compile_args=['/openmp',],
  63 + language='c++',),
53 64 ])
54 65 )
55 66  
... ... @@ -75,5 +86,12 @@ else:
75 86 Extension("invesalius.data.floodfill", ["invesalius/data/floodfill.pyx"],
76 87 include_dirs=[numpy.get_include()],
77 88 language='c++',),
  89 +
  90 + Extension("invesalius.data.cy_mesh", ["invesalius/data/cy_mesh.pyx"],
  91 + include_dirs=[numpy.get_include()],
  92 + extra_compile_args=['-fopenmp', '-std=c++11'],
  93 + extra_link_args=['-fopenmp', '-std=c++11'],
  94 + language='c++',),
  95 +
78 96 ])
79 97 )
... ...