Source code for cellcomplex.property_topomesh.optimization

# -*- coding: utf-8 -*-
# -*- python -*-
#
#       PropertyTopomesh
#
#       Copyright 2015-2016 INRIA - CIRAD - INRA
#
#       File author(s): Guillaume Cerutti <guillaume.cerutti@inria.fr>
#
#       File contributor(s): Guillaume Cerutti <guillaume.cerutti@inria.fr>
#
#       Distributed under the Cecill-C License.
#       See accompanying file LICENSE.txt or copy at
#           http://www.cecill.info/licences/Licence_CeCILL-C_V1-en.html
#
#       OpenaleaLab Website : http://virtualplants.github.io/
#
###############################################################################


"""
=============================
PropertyTopomesh Optimization
=============================

.. autosummary::
   :toctree: generated/

   property_topomesh_vertices_deformation -- Geometrical multi-criterion optimization
   property_topomesh_edge_flip_optimization -- Topological optimization by successive edge flips
   property_topomesh_edge_split_optimization -- Topological optimization by successive edge splits
   property_topomesh_edge_collapse_optimization -- Topological optimization by successive edge collapses
   property_topomesh_isotropic_remeshing -- Global triangle quality optimization
   topomesh_triangle_split -- Simple remeshing by global subdivision of triangles

"""

from time import time
from copy import deepcopy
import logging

import numpy as np
from scipy import ndimage as nd
from scipy.cluster.vq import vq

from cellcomplex.property_topomesh import PropertyTopomesh
from cellcomplex.property_topomesh.analysis import is_triangular, compute_topomesh_property, compute_topomesh_triangle_properties
from cellcomplex.property_topomesh.topological_operations import topomesh_flip_edge, topomesh_split_edge, topomesh_collapse_edge

from cellcomplex.utils.array_dict import array_dict

from cellcomplex.property_topomesh.utils.geometry_tools import triangle_geometric_features


[docs]def property_topomesh_vertices_deformation(topomesh,iterations=1,omega_forces=dict(taubin_smoothing=0.65),sigma_deformation=0.1,gradient_derivatives=None,gaussian_sigma=10.0,voxelsize=(1.0,1.0,1.0),target_normal=None,target_areas=None,fix_borders=False,verbose=False, debug=False, loglevel=0): """Optimize the positions of the mesh vertices along multiple criteria. The 'barycenter' property of the elements of degree 0 is updated following a different "force" vector for each vertex. This vector is computed as the opposite of the gradient of a composite energy functional defined over the mesh. The weights of the energy terms are to be specified in the function arguments. The deformation is constrained to a possibly small range around each vertex. The function can for instance be used to apply a smoothing to the mesh (laplacian, curvature flow, taubin), or for more complex optimization such as planarization of interfaces, or triangle quality enhancement. Parameters -------- topomesh : :class:`cellcomplex.property_topomesh.PropertyTopomesh`: The structure on which to apply the optimization. iterations : int The number of times the deformation is repeated. omega_forces : dict The weights (float) associated to each energy term: * *regularization*: energy optimizing triangle eccentricity * *area*: energy optimizing triangle size homogeneity * *gradient*: energy pushing vertices towards local maxima of a gradient field * *laplacian*: energy regularizing cell edges towards straight lines * *planarization*: energy regularizing cell interfaces towards planar surfaces * *epidermis_planarization*: energy regularizing outer cell surfaces towards planar surfaces * *convexity*: energy regularizing outer cell surfaces towards spherical surfaces * *laplacian_smoothing*: energy regularizing the mesh using a Laplacian operator * *gaussian_smoothing*: energy regularizing the mesh using a Gaussian operator * *curvature_flow_smoothing*: energy regularizing the mesh using a Cotangent Laplacian operator * *taubin_smoothing*: energy regularizing the mesh using two-pass Gaussian operator sigma_deformation : float The maximal allowed amplitude of a deformation of a vertex at each iteration. gaussian_sigma : float The standard deviation used to compute gaussian weights in gaussian/taubin smoothing. gradient_derivatives : :class:`timagetk.SpatialImage` The 3 derivatives of the gradient field (x, y, z) used for gradient optimization. target_normal : :class:`numpy.ndarray`, *optional* The normal of the desired plane for the interface planarization optimization. If None, the direction linking cell centers is chosen. target_areas : :class:`numpy.array`, *optional* The values of areas to which the mesh triangles should tend in the area optimization. If None, the average area value is chosen. fix_borders : bool Whether the border vertices should be fixed or not (in the case of open surfaces). Returns -------- None Note -------- The PropertyTopomesh passed as argument is updated. """ logging.getLogger().setLevel(logging.INFO if verbose else logging.DEBUG if debug else logging.ERROR) if fix_borders: compute_topomesh_property(topomesh,'faces',1) compute_topomesh_property(topomesh,'vertices',1) border_edges = np.array(list(topomesh.wisps(1)))[np.array(list(map(len,topomesh.wisp_property('faces',1).values()))) == 1] border_vertices = np.unique(topomesh.wisp_property('vertices',1).values(border_edges)) vertex_is_border = np.array([v in border_vertices for v in topomesh.wisps(0)]) for iteration in range(iterations): deformation_force = np.zeros_like(topomesh.wisp_property('barycenter',degree=0).values(),np.float32) if 'gradient' in omega_forces and omega_forces['gradient']!=0.0: start_time = time() logging.info("".join([" " for l in range(loglevel)])+"--> Computing vertex force") assert gradient_derivatives != None gradient_force = _property_topomesh_cell_vertex_force(topomesh,gradient_derivatives,voxelsize) # logging.info("".join([" " for l in range(loglevel)])+gradient_force deformation_force += omega_forces['gradient']*gradient_force end_time = time() logging.info("".join([" " for l in range(loglevel)])+"<-- Computing vertex force ["+str(end_time-start_time)+" s]") if 'regularization' in omega_forces and omega_forces['regularization']!=0.0: start_time = time() logging.info("".join([" " for l in range(loglevel)])+"--> Computing regularization force") regularization_force = _property_topomesh_triangle_regularization_force(topomesh) deformation_force += omega_forces['regularization']*regularization_force end_time = time() logging.info("".join([" " for l in range(loglevel)])+"<-- Computing regularization force ["+str(end_time-start_time)+" s]") if 'area' in omega_forces and omega_forces['area']!=0.0: start_time = time() logging.info("".join([" " for l in range(loglevel)])+"--> Computing area force") area_force = _property_topomesh_area_smoothing_force(topomesh,target_areas) deformation_force += omega_forces['area']*area_force end_time = time() logging.info("".join([" " for l in range(loglevel)])+"<-- Computing area force ["+str(end_time-start_time)+" s]") if 'laplacian' in omega_forces and omega_forces['laplacian']!=0.0: start_time = time() logging.info("".join([" " for l in range(loglevel)])+"--> Computing laplacian smoothing force") # laplacian_force = property_topomesh_laplacian_smoothing_force(topomesh) laplacian_force = _property_topomesh_laplacian_epidermis_convexity_force(topomesh) deformation_force += omega_forces['laplacian']*laplacian_force end_time = time() logging.info("".join([" " for l in range(loglevel)])+"<-- Computing laplacian smoothing force ["+str(end_time-start_time)+" s]") if 'laplacian_smoothing' in omega_forces and omega_forces['laplacian_smoothing']!=0.0: start_time = time() logging.info("".join([" " for l in range(loglevel)])+"--> Computing laplacian smoothing force") laplacian_force = _property_topomesh_laplacian_smoothing_force(topomesh) deformation_force += omega_forces['laplacian_smoothing']*laplacian_force end_time = time() logging.info("".join([" " for l in range(loglevel)])+"<-- Computing laplacian smoothing force ["+str(end_time-start_time)+" s]") if 'gaussian_smoothing' in omega_forces and omega_forces['gaussian_smoothing']!=0.0: start_time = time() logging.info("".join([" " for l in range(loglevel)])+"--> Computing gaussian smoothing force") gaussian_force = _property_topomesh_gaussian_smoothing_force(topomesh,gaussian_sigma=gaussian_sigma) deformation_force += omega_forces['gaussian_smoothing']*gaussian_force end_time = time() logging.info("".join([" " for l in range(loglevel)])+"<-- Computing gaussian smoothing force ["+str(end_time-start_time)+" s]") if 'curvature_flow_smoothing' in omega_forces and omega_forces['curvature_flow_smoothing']!=0.0: start_time = time() logging.info("".join([" " for l in range(loglevel)])+"--> Computing curvature flow smoothing force") curvature_flow_force = _property_topomesh_cotangent_laplacian_smoothing_force(topomesh) deformation_force += omega_forces['curvature_flow_smoothing']*curvature_flow_force end_time = time() logging.info("".join([" " for l in range(loglevel)])+"<-- Computing curvature flow smoothing force ["+str(end_time-start_time)+" s]") if 'mean_curvature_smoothing' in omega_forces and omega_forces['mean_curvature_smoothing']!=0.0: start_time = time() logging.info("".join([" " for l in range(loglevel)])+"--> Computing mean curvature smoothing force") mean_curvature_force = _property_topomesh_mean_curvature_smoothing_force(topomesh) deformation_force += omega_forces['mean_curvature_smoothing']*mean_curvature_force end_time = time() logging.info("".join([" " for l in range(loglevel)])+"<-- Computing mean curvature smoothing force ["+str(end_time-start_time)+" s]") if 'taubin_smoothing' in omega_forces and omega_forces['taubin_smoothing']!=0.0: start_time = time() logging.info("".join([" " for l in range(loglevel)])+"--> Computing taubin smoothing force") taubin_force = _property_topomesh_taubin_smoothing_force(topomesh,gaussian_sigma=gaussian_sigma) deformation_force += omega_forces['taubin_smoothing']*taubin_force end_time = time() logging.info("".join([" " for l in range(loglevel)])+"<-- Computing taubin smoothing force ["+str(end_time-start_time)+" s]") if 'global_taubin_smoothing' in omega_forces and omega_forces['global_taubin_smoothing']!=0.0: start_time = time() logging.info("".join([" " for l in range(loglevel)])+"--> Computing taubin smoothing force") taubin_force = _property_topomesh_taubin_smoothing_force(topomesh,gaussian_sigma=20.0,cellwise_smoothing=False) deformation_force += omega_forces['global_taubin_smoothing']*taubin_force end_time = time() logging.info("".join([" " for l in range(loglevel)])+"<-- Computing taubin smoothing force ["+str(end_time-start_time)+" s]") if 'planarization' in omega_forces and omega_forces['planarization']!=0.0: start_time = time() logging.info("".join([" " for l in range(loglevel)])+"--> Computing planarization force") if target_normal is not None: planarization_force = _property_topomesh_planarization_force(topomesh,target_normal) else: # planarization_force = _property_topomesh_cell_interface_planarization_force(topomesh) planarization_force = _property_topomesh_interface_planarization_force(topomesh) # planarization_force = _property_topomesh_interface_planarization_force(topomesh) + property_topomesh_epidermis_planarization_force(topomesh) # planarization_force = _property_topomesh_interface_planarization_force(topomesh) + property_topomesh_epidermis_convexity_force(topomesh) # planarization_force = _property_topomesh_epidermis_convexity_force(topomesh) # logging.info("".join([" " for l in range(loglevel)])+planarization_force deformation_force += omega_forces['planarization']*planarization_force end_time = time() logging.info("".join([" " for l in range(loglevel)])+"<-- Computing planarization force ["+str(end_time-start_time)+" s]") if 'epidermis_planarization' in omega_forces and omega_forces['epidermis_planarization']!=0.0: start_time = time() logging.info("".join([" " for l in range(loglevel)])+"--> Computing planarization force") planarization_force = _property_topomesh_epidermis_planarization_force(topomesh) deformation_force += omega_forces['epidermis_planarization']*planarization_force end_time = time() logging.info("".join([" " for l in range(loglevel)])+"<-- Computing planarization force ["+str(end_time-start_time)+" s]") if 'convexity' in omega_forces and omega_forces['convexity']!=0.0: start_time = time() logging.info("".join([" " for l in range(loglevel)])+"--> Computing epidermis convexity force") #convexity_force = _property_topomesh_epidermis_convexity_force(topomesh) convexity_force = _property_topomesh_laplacian_epidermis_convexity_force(topomesh) deformation_force += omega_forces['convexity']*convexity_force end_time = time() logging.info("".join([" " for l in range(loglevel)])+"<-- Computing epidermis convexity force ["+str(end_time-start_time)+" s]") start_time = time() logging.info("".join([" " for l in range(loglevel)])+"--> Applying Forces") deformation_force_amplitude = np.power(np.sum(np.power(deformation_force,2.0),axis=1),0.5)+np.power(10.,-8) # logging.info("".join([" " for l in range(loglevel)])+deformation_force #deformation_force[np.where(deformation_force_amplitude>sigma_deformation)[0]] = sigma_deformation*deformation_force[np.where(deformation_force_amplitude>sigma_deformation)[0]]/deformation_force_amplitude[np.where(deformation_force_amplitude>sigma_deformation)[0]][:,np.newaxis] deformation_force = np.minimum(1.0,sigma_deformation/deformation_force_amplitude)[:,np.newaxis] * deformation_force if fix_borders: deformation_force[vertex_is_border] = 0. topomesh.update_wisp_property('barycenter',degree=0,values=topomesh.wisp_property('barycenter',degree=0).values(list(topomesh.wisps(0))) + deformation_force,keys=list(topomesh.wisps(0))) end_time = time() logging.info("".join([" " for l in range(loglevel)])+"<-- Applying Forces ["+str(end_time-start_time)+" s]") start_time = time() logging.info("".join([" " for l in range(loglevel)])+"--> Updating distances") compute_topomesh_property(topomesh,'length',degree=1) end_time = time() logging.info("".join([" " for l in range(loglevel)])+"<-- Updating distances ["+str(end_time-start_time)+" s]") logging.info("".join([" " for l in range(loglevel)])+str(topomesh.nb_wisps(0))+" Vertices, "+str(topomesh.nb_wisps(2))+" Triangles, "+str(topomesh.nb_wisps(3))+" Cells")
def _property_topomesh_cell_vertex_force(topomesh,gradient_derivatives,voxelsize, verbose=False, debug=False, loglevel=0): """ Compute for each vertex of the topomesh the force guiding its displacement towards a cell vertex """ gradient_x = gradient_derivatives[0] gradient_y = gradient_derivatives[1] gradient_z = gradient_derivatives[2] vertices_coords = np.rollaxis(np.array(topomesh.wisp_property('barycenter',degree=0).values()/np.array(voxelsize),np.uint16),1) vertices_coords = np.maximum(np.minimum(vertices_coords,([gradient_x.shape[0]-1],[gradient_x.shape[1]-1],[gradient_x.shape[2]-1])),0) # logging.info("".join([" " for l in range(loglevel)])+vertices_coords gradient_force = np.rollaxis(np.array([gradient_x[tuple(vertices_coords)],gradient_y[tuple(vertices_coords)],gradient_z[tuple(vertices_coords)]]),1)*np.array(voxelsize)[np.newaxis,:] return gradient_force def _property_topomesh_triangle_regularization_force(topomesh, verbose=False, debug=False, loglevel=0): """todo""" if not topomesh.has_wisp_property('vertices',degree=2,is_computed=True): compute_topomesh_property(topomesh,'vertices',degree=2) if not topomesh.has_wisp_property('length',degree=1,is_computed=True): compute_topomesh_property(topomesh,'length',degree=1) triangle_vertices = topomesh.wisp_property('vertices',degree=2).values() rotated_triangle_vertices = np.transpose([triangle_vertices[:,2],triangle_vertices[:,0],triangle_vertices[:,1]]) antirotated_triangle_vertices = np.transpose([triangle_vertices[:,1],triangle_vertices[:,2],triangle_vertices[:,0]]) triangle_vertices = np.concatenate([triangle_vertices,rotated_triangle_vertices,antirotated_triangle_vertices],axis=0) edge_index_list = np.array([[1, 2],[0, 2],[0, 1]]) triangle_edge_vertices = triangle_vertices[:,edge_index_list] triangle_edge_vectors = topomesh.wisp_property('barycenter',degree=0).values(triangle_edge_vertices[...,1]) - topomesh.wisp_property('barycenter',degree=0).values(triangle_edge_vertices[...,0]) #triangle_edge_lengths = np.power(np.sum(np.power(triangle_edge_vectors,2.0),axis=2),0.5) triangle_edge_lengths = np.linalg.norm(triangle_edge_vectors,axis=2) triangle_edge_directions = triangle_edge_vectors/triangle_edge_lengths[...,np.newaxis] triangle_perimeters = np.sum(triangle_edge_lengths,axis=1) triangle_areas = np.sqrt((triangle_perimeters/2.0)*(triangle_perimeters/2.0-triangle_edge_lengths[:,0])*(triangle_perimeters/2.0-triangle_edge_lengths[:,1])*(triangle_perimeters/2.0-triangle_edge_lengths[:,2])) triangle_areas[np.where(triangle_areas==0.0)] = 0.001 average_area = np.mean(triangle_areas) triangle_sinuses = np.zeros_like(triangle_edge_lengths,np.float32) triangle_sinuses[:,0] = np.sqrt(np.array(1.0 - np.power(triangle_edge_lengths[:,1]**2+triangle_edge_lengths[:,2]**2-triangle_edge_lengths[:,0]**2,2.0)/np.power(2.0*triangle_edge_lengths[:,1]*triangle_edge_lengths[:,2],2.0),np.float16)) triangle_sinuses[:,1] = np.sqrt(np.array(1.0 - np.power(triangle_edge_lengths[:,2]**2+triangle_edge_lengths[:,0]**2-triangle_edge_lengths[:,1]**2,2.0)/np.power(2.0*triangle_edge_lengths[:,2]*triangle_edge_lengths[:,0],2.0),np.float16)) triangle_sinuses[:,2] = np.sqrt(np.array(1.0 - np.power(triangle_edge_lengths[:,0]**2+triangle_edge_lengths[:,1]**2-triangle_edge_lengths[:,2]**2,2.0)/np.power(2.0*triangle_edge_lengths[:,0]*triangle_edge_lengths[:,1],2.0),np.float16)) triangle_sinuses[np.where(triangle_sinuses == 0.0)] = 0.001 sinus_edge_1_force = (((np.power(triangle_edge_lengths[:,2]**2-triangle_edge_lengths[:,0]**2,2.0)-np.power(triangle_edge_lengths[:,1],4.0))*triangle_edge_lengths[:,2])/(2.0*np.power(triangle_edge_lengths[:,1]*triangle_edge_lengths[:,2],3.0)))/(2.0*triangle_sinuses[:,0]) sinus_edge_1_force += ((2.*(triangle_edge_lengths[:,2]**2+triangle_edge_lengths[:,0]**2-triangle_edge_lengths[:,1]**2)*triangle_edge_lengths[:,0]*triangle_edge_lengths[:,1]*triangle_edge_lengths[:,2])/(2.0*np.power(triangle_edge_lengths[:,0]*triangle_edge_lengths[:,2],3.0)))/(2.0*triangle_sinuses[:,1]) sinus_edge_1_force += (((np.power(triangle_edge_lengths[:,2]**2-triangle_edge_lengths[:,0]**2,2.0)-np.power(triangle_edge_lengths[:,1],4.0))*triangle_edge_lengths[:,0])/(2.0*np.power(triangle_edge_lengths[:,0]*triangle_edge_lengths[:,1],3.0)))/(2.0*triangle_sinuses[:,2]) sinus_edge_2_force = (((np.power(triangle_edge_lengths[:,1]**2-triangle_edge_lengths[:,0]**2,2.0)-np.power(triangle_edge_lengths[:,2],4.0))*triangle_edge_lengths[:,1])/(2.0*np.power(triangle_edge_lengths[:,1]*triangle_edge_lengths[:,2],3.0)))/(2.0*triangle_sinuses[:,0]) sinus_edge_2_force += (((np.power(triangle_edge_lengths[:,1]**2-triangle_edge_lengths[:,0]**2,2.0)-np.power(triangle_edge_lengths[:,2],4.0))*triangle_edge_lengths[:,0])/(2.0*np.power(triangle_edge_lengths[:,0]*triangle_edge_lengths[:,2],3.0)))/(2.0*triangle_sinuses[:,1]) sinus_edge_2_force += ((2.*(triangle_edge_lengths[:,1]**2+triangle_edge_lengths[:,0]**2-triangle_edge_lengths[:,2]**2)*triangle_edge_lengths[:,0]*triangle_edge_lengths[:,1]*triangle_edge_lengths[:,2])/(2.0*np.power(triangle_edge_lengths[:,0]*triangle_edge_lengths[:,1],3.0)))/(2.0*triangle_sinuses[:,2]) #sinus_unitary_force = -(sinus_edge_1_force[:,np.newaxis]*triangle_edge_directions[:,1] + sinus_edge_2_force[:,np.newaxis]*triangle_edge_directions[:,2])*triangle_areas[:,np.newaxis] sinus_unitary_force = -(sinus_edge_1_force[:,np.newaxis]*triangle_edge_vectors[:,1] + sinus_edge_2_force[:,np.newaxis]*triangle_edge_vectors[:,2]) area_edge_1_force = -((triangle_areas - average_area)*(triangle_edge_lengths[:,0]**2+triangle_edge_lengths[:,2]**2-triangle_edge_lengths[:,1]**2)*triangle_edge_lengths[:,1])/(4.0*triangle_areas) area_edge_2_force = -((triangle_areas - average_area)*(triangle_edge_lengths[:,0]**2+triangle_edge_lengths[:,1]**2-triangle_edge_lengths[:,2]**2)*triangle_edge_lengths[:,2])/(4.0*triangle_areas) #area_edge_1_force = -((triangle_areas - average_area)*(triangle_edge_lengths[:,0]**2+triangle_edge_lengths[:,2]**2-triangle_edge_lengths[:,1]**2)*triangle_edge_lengths[:,1])/(4.0*average_area) #area_edge_2_force = -((triangle_areas - average_area)*(triangle_edge_lengths[:,0]**2+triangle_edge_lengths[:,1]**2-triangle_edge_lengths[:,2]**2)*triangle_edge_lengths[:,2])/(4.0*average_area) area_unitary_force = -(area_edge_1_force[:,np.newaxis]*triangle_edge_directions[:,1] + area_edge_2_force[:,np.newaxis]*triangle_edge_directions[:,2]) #triangle_unitary_force = 8.0*sinus_unitary_force #triangle_unitary_force = sinus_unitary_force triangle_unitary_force = sinus_unitary_force*np.power(average_area,1) #triangle_unitary_force = sinus_unitary_force*np.power(average_area,1) + area_unitary_force #triangle_unitary_force = sinus_unitary_force + area_unitary_force #triangle_unitary_force = 0.02*area_unitary_force + 8.0*sinus_unitary_force # triangle_unitary_force = 0.02*area_unitary_force triangle_force = np.transpose([nd.sum(triangle_unitary_force[:,k],triangle_vertices[:,0],index=list(topomesh.wisps(0))) for k in range(3)]) return triangle_force def _property_topomesh_area_smoothing_force(topomesh,target_areas=None, verbose=False, debug=False, loglevel=0): compute_topomesh_property(topomesh,'vertices',degree=2) compute_topomesh_property(topomesh,'length',degree=1) triangle_vertices = topomesh.wisp_property('vertices',degree=2).values() rotated_triangle_vertices = np.transpose([triangle_vertices[:,2],triangle_vertices[:,0],triangle_vertices[:,1]]) antirotated_triangle_vertices = np.transpose([triangle_vertices[:,1],triangle_vertices[:,2],triangle_vertices[:,0]]) triangle_vertices = np.append(np.append(triangle_vertices,rotated_triangle_vertices,axis=0),antirotated_triangle_vertices,axis=0) edge_index_list = np.array([[1, 2],[0, 1],[0, 2]]) triangle_edge_vertices = triangle_vertices[:,edge_index_list] triangle_edge_vectors = topomesh.wisp_property('barycenter',degree=0).values(triangle_edge_vertices[...,1]) - topomesh.wisp_property('barycenter',degree=0).values(triangle_edge_vertices[...,0]) #triangle_edge_lengths = np.power(np.sum(np.power(triangle_edge_vectors,2.0),axis=2),0.5) triangle_edge_lengths = np.linalg.norm(triangle_edge_vectors,axis=2) triangle_edge_directions = triangle_edge_vectors/triangle_edge_lengths[...,np.newaxis] triangle_perimeters = np.sum(triangle_edge_lengths,axis=1) triangle_areas = np.sqrt((triangle_perimeters/2.0)*(triangle_perimeters/2.0-triangle_edge_lengths[:,0])*(triangle_perimeters/2.0-triangle_edge_lengths[:,1])*(triangle_perimeters/2.0-triangle_edge_lengths[:,2])) triangle_areas[np.where(triangle_areas==0.0)] = 0.001 if target_areas is None: target_areas = triangle_areas.mean()*np.ones(len(triangle_areas)) else: target_areas = np.tile(target_areas,(3)) area_edge_1_force = (target_areas-triangle_areas)*(triangle_edge_lengths[:,0]**2+triangle_edge_lengths[:,2]**2-triangle_edge_lengths[:,1]**2)*triangle_edge_lengths[:,1] area_edge_2_force = (target_areas-triangle_areas)*(triangle_edge_lengths[:,0]**2+triangle_edge_lengths[:,1]**2-triangle_edge_lengths[:,2]**2)*triangle_edge_lengths[:,2] area_unitary_force = -(area_edge_1_force[:,np.newaxis]*triangle_edge_directions[:,1] + area_edge_2_force[:,np.newaxis]*triangle_edge_directions[:,2]) triangle_force = np.transpose([nd.sum(area_unitary_force[:,d],triangle_vertices[:,0],index=list(topomesh.wisps(0))) for d in range(3)]) return triangle_force def _property_topomesh_laplacian_smoothing_force(topomesh,cellwise_smoothing=False, verbose=False, debug=False, loglevel=0): if not topomesh.has_wisp_property('vertices',degree=1,is_computed=True): compute_topomesh_property(topomesh,'vertices',degree=1) edge_vertices = topomesh.wisp_property('vertices',degree=1).values() reversed_edge_vertices = np.transpose([edge_vertices[:,1],edge_vertices[:,0]]) edge_vertices = np.append(edge_vertices,reversed_edge_vertices,axis=0) if not topomesh.has_wisp_property('valency',degree=0,is_computed=True): compute_topomesh_property(topomesh,'valency',degree=0) vertices_degrees = topomesh.wisp_property('valency',degree=0).values() if cellwise_smoothing: laplacian_force = np.zeros_like(topomesh.wisp_property('barycenter',degree=0).values(),np.float32) for c in topomesh.wisps(3): if not topomesh.has_wisp_property('vertices',degree=3,is_computed=True): compute_topomesh_property(topomesh,'vertices',degree=3) cell_vertices = topomesh.wisp_property('vertices',degree=3)[c] cell_edges = np.sum(nd.sum(np.ones_like(cell_vertices),cell_vertices,index=edge_vertices),axis=1) cell_edge_vertices = edge_vertices[np.where(cell_edges==2)[0]] cell_edge_vectors = topomesh.wisp_property('barycenter',degree=0).values(cell_edge_vertices[:,1]) - topomesh.wisp_property('barycenter',degree=0).values(cell_edge_vertices[:,0]) cell_laplacian_force = np.transpose([nd.sum(cell_edge_vectors[:,0],cell_edge_vertices[:,0],index=cell_vertices), nd.sum(cell_edge_vectors[:,1],cell_edge_vertices[:,0],index=cell_vertices), nd.sum(cell_edge_vectors[:,2],cell_edge_vertices[:,0],index=cell_vertices)]) laplacian_force[cell_vertices] += cell_laplacian_force/vertices_degrees[cell_vertices,np.newaxis] else: edge_vectors = topomesh.wisp_property('barycenter',degree=0).values(edge_vertices[:,1]) - topomesh.wisp_property('barycenter',degree=0).values(edge_vertices[:,0]) laplacian_force = np.transpose([nd.sum(edge_vectors[:,d],edge_vertices[:,0],index=list(topomesh.wisps(0))) for d in [0,1,2]]) laplacian_force = laplacian_force/vertices_degrees[:,np.newaxis] return laplacian_force def _property_topomesh_cotangent_laplacian_smoothing_force(topomesh, verbose=False, debug=False, loglevel=0): compute_topomesh_property(topomesh,'vertices',degree=2) compute_topomesh_property(topomesh,'length',degree=1) triangle_vertices = topomesh.wisp_property('vertices',degree=2).values(list(topomesh.wisps(2))) rotated_triangle_vertices = np.transpose([triangle_vertices[:,2],triangle_vertices[:,0],triangle_vertices[:,1]]) antirotated_triangle_vertices = np.transpose([triangle_vertices[:,1],triangle_vertices[:,2],triangle_vertices[:,0]]) triangle_vertices = np.append(np.append(triangle_vertices,rotated_triangle_vertices,axis=0),antirotated_triangle_vertices,axis=0) edge_index_list = np.array([[1, 2],[0, 2],[0, 1]]) triangle_edge_vertices = triangle_vertices[:,edge_index_list] triangle_edge_vectors = topomesh.wisp_property('barycenter',degree=0).values(triangle_edge_vertices[...,1]) - topomesh.wisp_property('barycenter',degree=0).values(triangle_edge_vertices[...,0]) #triangle_edge_lengths = np.power(np.sum(np.power(triangle_edge_vectors,2.0),axis=2),0.5) triangle_edge_lengths = np.linalg.norm(triangle_edge_vectors,axis=2) triangle_edge_directions = triangle_edge_vectors/triangle_edge_lengths[...,np.newaxis] triangle_perimeters = np.sum(triangle_edge_lengths,axis=1) triangle_areas = np.sqrt((triangle_perimeters/2.0)*(triangle_perimeters/2.0-triangle_edge_lengths[:,0])*(triangle_perimeters/2.0-triangle_edge_lengths[:,1])*(triangle_perimeters/2.0-triangle_edge_lengths[:,2])) triangle_cosines = np.zeros_like(triangle_edge_lengths,np.float32) triangle_cosines[:,0] = (triangle_edge_lengths[:,1]**2+triangle_edge_lengths[:,2]**2-triangle_edge_lengths[:,0]**2)/(2.0*triangle_edge_lengths[:,1]*triangle_edge_lengths[:,2]) triangle_cosines[:,1] = (triangle_edge_lengths[:,2]**2+triangle_edge_lengths[:,0]**2-triangle_edge_lengths[:,1]**2)/(2.0*triangle_edge_lengths[:,2]*triangle_edge_lengths[:,0]) triangle_cosines[:,2] = (triangle_edge_lengths[:,0]**2+triangle_edge_lengths[:,1]**2-triangle_edge_lengths[:,2]**2)/(2.0*triangle_edge_lengths[:,0]*triangle_edge_lengths[:,1]) triangle_cosines[np.where(np.abs(triangle_cosines) < np.power(10.,-6))] = np.power(10.,-6) triangle_angles = np.arccos(triangle_cosines) triangle_sinuses = np.zeros_like(triangle_edge_lengths,np.float32) triangle_sinuses[:,0] = np.sqrt(np.array(1.0 - np.power(triangle_edge_lengths[:,1]**2+triangle_edge_lengths[:,2]**2-triangle_edge_lengths[:,0]**2,2.0)/np.power(2.0*triangle_edge_lengths[:,1]*triangle_edge_lengths[:,2],2.0),np.float16)) triangle_sinuses[:,1] = np.sqrt(np.array(1.0 - np.power(triangle_edge_lengths[:,2]**2+triangle_edge_lengths[:,0]**2-triangle_edge_lengths[:,1]**2,2.0)/np.power(2.0*triangle_edge_lengths[:,2]*triangle_edge_lengths[:,0],2.0),np.float16)) triangle_sinuses[:,2] = np.sqrt(np.array(1.0 - np.power(triangle_edge_lengths[:,0]**2+triangle_edge_lengths[:,1]**2-triangle_edge_lengths[:,2]**2,2.0)/np.power(2.0*triangle_edge_lengths[:,0]*triangle_edge_lengths[:,1],2.0),np.float16)) triangle_sinuses[np.where(triangle_sinuses < np.power(10.,-6))] = np.power(10.,-6) triangle_cotangent_weights = (triangle_cosines/triangle_sinuses) #triangle_cotangent_weights = 1./triangle_edge_lengths #triangle_cotangent_weights = 0.5*(triangle_cosines/triangle_sinuses)/(triangle_edge_lengths + np.power(10.,-10)) #triangle_cotangent_vectors = (triangle_cosines/triangle_sinuses)[...,np.newaxis] * triangle_edge_vectors/triangle_edge_lengths[:,np.newaxis] triangle_cotangent_vectors = triangle_cotangent_weights[...,np.newaxis] * triangle_edge_vectors #triangle_cotangent_vectors = triangle_cotangent_weights[...,np.newaxis] * triangle_edge_directions # triangle_cotangent_vectors = 1./np.tan(triangle_angles)[...,np.newaxis] * triangle_edge_vectors vertex_cotangent_force = np.transpose([nd.sum(triangle_cotangent_vectors[:,1,d]+triangle_cotangent_vectors[:,2,d],triangle_vertices[:,0],index=np.array(list(topomesh.wisps(0)))) for d in range(3)]) vertex_cotangent_sum = nd.sum(triangle_cotangent_weights[:,1] + triangle_cotangent_weights[:,2],triangle_vertices[:,0],index=np.array(list(topomesh.wisps(0)))) vertex_area = nd.sum(triangle_areas,triangle_vertices[:,0],index=np.array(list(topomesh.wisps(0)))) #laplacian_force = vertex_cotangent_force laplacian_force = vertex_cotangent_force/vertex_cotangent_sum[...,np.newaxis] #laplacian_force = vertex_cotangent_force/(4.*vertex_area[...,np.newaxis]) laplacian_force[np.where(np.isnan(laplacian_force))] = 0.0 laplacian_force[np.where(np.isinf(laplacian_force))] = 0.0 return laplacian_force def _property_topomesh_gaussian_smoothing_force(topomesh,gaussian_sigma=10.0, verbose=False, debug=False, loglevel=0): compute_topomesh_property(topomesh,'vertices',degree=1) edge_vertices = topomesh.wisp_property('vertices',degree=1).values() reversed_edge_vertices = np.transpose([edge_vertices[:,1],edge_vertices[:,0]]) edge_vertices = np.append(edge_vertices,reversed_edge_vertices,axis=0) edge_vectors = topomesh.wisp_property('barycenter',degree=0).values(edge_vertices[:,1]) - topomesh.wisp_property('barycenter',degree=0).values(edge_vertices[:,0]) edge_lengths = np.linalg.norm(edge_vectors,axis=1) gaussian_edge_lengths = np.exp(-np.power(edge_lengths,2.0)/np.power(gaussian_sigma,2.0)) gaussian_edge_vectors = gaussian_edge_lengths[:,np.newaxis]*edge_vectors gaussian_force = np.transpose([nd.sum(gaussian_edge_vectors[:,d],edge_vertices[:,0],index=list(topomesh.wisps(0))) for d in [0,1,2]]) vertices_weights = 1.+nd.sum(gaussian_edge_lengths,edge_vertices[:,0],index=list(topomesh.wisps(0))) gaussian_force = gaussian_force/vertices_weights[:,np.newaxis] return gaussian_force def _property_topomesh_taubin_smoothing_force(topomesh,gaussian_sigma=10.0,positive_factor=0.33,negative_factor=-0.34,cellwise_smoothing=True, verbose=False, debug=False, loglevel=0): if not topomesh.has_wisp_property('vertices',degree=1,is_computed=True): compute_topomesh_property(topomesh,'vertices',degree=1) if cellwise_smoothing: edge_vertices = topomesh.wisp_property('vertices',degree=1).values(np.concatenate([np.array(list(topomesh.borders(3,c,2)),int) for c in topomesh.wisps(3)])) else: edge_vertices = topomesh.wisp_property('vertices',degree=1).values() reversed_edge_vertices = np.transpose([edge_vertices[:,1],edge_vertices[:,0]]) edge_vertices = np.append(edge_vertices,reversed_edge_vertices,axis=0) edge_vectors = topomesh.wisp_property('barycenter',degree=0).values(edge_vertices[:,1]) - topomesh.wisp_property('barycenter',degree=0).values(edge_vertices[:,0]) edge_lengths = np.linalg.norm(edge_vectors,axis=1) gaussian_edge_lengths = np.exp(-np.power(edge_lengths,2.0)/np.power(gaussian_sigma,2.0)) gaussian_edge_vectors = gaussian_edge_lengths[:,np.newaxis]*edge_vectors taubin_force = np.transpose([nd.sum(gaussian_edge_vectors[:,0],edge_vertices[:,0],index=list(topomesh.wisps(0))), nd.sum(gaussian_edge_vectors[:,1],edge_vertices[:,0],index=list(topomesh.wisps(0))), nd.sum(gaussian_edge_vectors[:,2],edge_vertices[:,0],index=list(topomesh.wisps(0)))]) #vertices_weights = 1.+nd.sum(gaussian_edge_lengths,edge_vertices[:,0],index=list(topomesh.wisps(0))) vertices_weights = nd.sum(gaussian_edge_lengths,edge_vertices[:,0],index=list(topomesh.wisps(0))) taubin_positive_force = positive_factor*taubin_force/vertices_weights[:,np.newaxis] taubin_positions = array_dict(topomesh.wisp_property('barycenter',degree=0).values(list(topomesh.wisps(0)))+taubin_positive_force, list(topomesh.wisps(0))) edge_vectors = taubin_positions.values(edge_vertices[:,1]) - taubin_positions.values(edge_vertices[:,0]) edge_lengths = np.linalg.norm(edge_vectors,axis=1) gaussian_edge_lengths = np.exp(-np.power(edge_lengths,2.0)/np.power(gaussian_sigma,2.0)) gaussian_edge_vectors = gaussian_edge_lengths[:,np.newaxis]*edge_vectors taubin_force = np.transpose([nd.sum(gaussian_edge_vectors[:,0],edge_vertices[:,0],index=list(topomesh.wisps(0))), nd.sum(gaussian_edge_vectors[:,1],edge_vertices[:,0],index=list(topomesh.wisps(0))), nd.sum(gaussian_edge_vectors[:,2],edge_vertices[:,0],index=list(topomesh.wisps(0)))]) #vertices_weights = 1.+nd.sum(gaussian_edge_lengths,edge_vertices[:,0],index=list(topomesh.wisps(0))) vertices_weights = nd.sum(gaussian_edge_lengths,edge_vertices[:,0],index=list(topomesh.wisps(0))) taubin_negative_force = negative_factor*taubin_force/vertices_weights[:,np.newaxis] taubin_force = taubin_positive_force + taubin_negative_force return taubin_force def _property_topomesh_mean_curvature_smoothing_force(topomesh, verbose=False, debug=False, loglevel=0): """todo""" if not topomesh.has_wisp_property('vertices',degree=2,is_computed=True): compute_topomesh_property(topomesh,'vertices',degree=2) if not topomesh.has_wisp_property('cells',degree=0,is_computed=True): compute_topomesh_property(topomesh,'cells',degree=0) if not topomesh.has_wisp_property('length',degree=1,is_computed=True): compute_topomesh_property(topomesh,'length',degree=1) triangle_vertices = topomesh.wisp_property('vertices',degree=2).values(list(topomesh.wisps(2))) rotated_triangle_vertices = np.transpose([triangle_vertices[:,2],triangle_vertices[:,0],triangle_vertices[:,1]]) antirotated_triangle_vertices = np.transpose([triangle_vertices[:,1],triangle_vertices[:,2],triangle_vertices[:,0]]) triangle_vertices = np.append(np.append(triangle_vertices,rotated_triangle_vertices,axis=0),antirotated_triangle_vertices,axis=0) edge_index_list = np.array([[1, 2],[0, 2],[0, 1]]) triangle_edge_vertices = triangle_vertices[:,edge_index_list] triangle_edge_vectors = topomesh.wisp_property('barycenter',degree=0).values(triangle_edge_vertices[...,1]) - topomesh.wisp_property('barycenter',degree=0).values(triangle_edge_vertices[...,0]) #triangle_edge_lengths = np.power(np.sum(np.power(triangle_edge_vectors,2.0),axis=2),0.5) triangle_edge_lengths = np.linalg.norm(triangle_edge_vectors,axis=2) triangle_edge_directions = triangle_edge_vectors/triangle_edge_lengths[...,np.newaxis] triangle_perimeters = np.sum(triangle_edge_lengths,axis=1) triangle_areas = np.sqrt((triangle_perimeters/2.0)*(triangle_perimeters/2.0-triangle_edge_lengths[:,0])*(triangle_perimeters/2.0-triangle_edge_lengths[:,1])*(triangle_perimeters/2.0-triangle_edge_lengths[:,2])) triangle_cosines = np.zeros_like(triangle_edge_lengths,np.float32) triangle_cosines[:,0] = (triangle_edge_lengths[:,1]**2+triangle_edge_lengths[:,2]**2-triangle_edge_lengths[:,0]**2)/(2.0*triangle_edge_lengths[:,1]*triangle_edge_lengths[:,2]) triangle_cosines[:,1] = (triangle_edge_lengths[:,2]**2+triangle_edge_lengths[:,0]**2-triangle_edge_lengths[:,1]**2)/(2.0*triangle_edge_lengths[:,2]*triangle_edge_lengths[:,0]) triangle_cosines[:,2] = (triangle_edge_lengths[:,0]**2+triangle_edge_lengths[:,1]**2-triangle_edge_lengths[:,2]**2)/(2.0*triangle_edge_lengths[:,0]*triangle_edge_lengths[:,1]) triangle_angles = np.arccos(triangle_cosines) triangle_sinuses = np.zeros_like(triangle_edge_lengths,np.float32) triangle_sinuses[:,0] = np.sqrt(np.array(1.0 - np.power(triangle_edge_lengths[:,1]**2+triangle_edge_lengths[:,2]**2-triangle_edge_lengths[:,0]**2,2.0)/np.power(2.0*triangle_edge_lengths[:,1]*triangle_edge_lengths[:,2],2.0),np.float16)) triangle_sinuses[:,1] = np.sqrt(np.array(1.0 - np.power(triangle_edge_lengths[:,2]**2+triangle_edge_lengths[:,0]**2-triangle_edge_lengths[:,1]**2,2.0)/np.power(2.0*triangle_edge_lengths[:,2]*triangle_edge_lengths[:,0],2.0),np.float16)) triangle_sinuses[:,2] = np.sqrt(np.array(1.0 - np.power(triangle_edge_lengths[:,0]**2+triangle_edge_lengths[:,1]**2-triangle_edge_lengths[:,2]**2,2.0)/np.power(2.0*triangle_edge_lengths[:,0]*triangle_edge_lengths[:,1],2.0),np.float16)) triangle_sinuses[np.where(triangle_sinuses == 0.0)] = 0.001 triangle_cotangent_vectors = (triangle_cosines/triangle_sinuses)[...,np.newaxis] * triangle_edge_vectors # triangle_cotangent_vectors = 1./np.tan(triangle_angles)[...,np.newaxis] * triangle_edge_vectors vertex_cotangent_sum = np.transpose([nd.sum(triangle_cotangent_vectors[:,1,0]+triangle_cotangent_vectors[:,2,0],triangle_vertices[:,0],index=np.array(list(topomesh.wisps(0)))), nd.sum(triangle_cotangent_vectors[:,1,1]+triangle_cotangent_vectors[:,2,1],triangle_vertices[:,0],index=np.array(list(topomesh.wisps(0)))), nd.sum(triangle_cotangent_vectors[:,1,2]+triangle_cotangent_vectors[:,2,2],triangle_vertices[:,0],index=np.array(list(topomesh.wisps(0))))]) vertex_area = nd.sum(triangle_areas,triangle_vertices[:,0],index=np.array(list(topomesh.wisps(0)))) # vertex_area = nd.sum(triangle_cotangent_square_lengths[:,1]+triangle_cotangent_square_lengths[:,2], triangle_vertices[:,0],index=np.array(list(topomesh.wisps(0))))/8. # vertex_mean_curvature_vectors = vertex_cotangent_sum/(2.*vertex_area[:,np.newaxis]) vertex_mean_curvature_vectors = (3.*vertex_cotangent_sum)/(4.*vertex_area[:,np.newaxis]) return vertex_mean_curvature_vectors def _property_topomesh_laplacian_epidermis_convexity_force(topomesh, verbose=False, debug=False, loglevel=0): """todo""" if not topomesh.has_wisp_property('vertices',degree=1,is_computed=True): compute_topomesh_property(topomesh,'vertices',degree=1) if not topomesh.has_wisp_property('barycenter',degree=3,is_computed=True): compute_topomesh_property(topomesh,'barycenter',degree=3) if not topomesh.has_wisp_property('epidermis',degree=1,is_computed=True): compute_topomesh_property(topomesh,'epidermis',degree=1) if not topomesh.has_wisp_property('cells',degree=0,is_computed=True): compute_topomesh_property(topomesh,'cells',degree=0) if not topomesh.has_wisp_property('cells',degree=1,is_computed=True): compute_topomesh_property(topomesh,'cells',degree=1) epidermis_convexity_force = array_dict(np.zeros_like(topomesh.wisp_property('barycenter',degree=0).values(),np.float32),np.array(list(topomesh.wisps(0)))) edge_cells_degree = np.array(list(map(len,topomesh.wisp_property('cells',degree=1).values()))) # edge_vertices = topomesh.wisp_property('vertices',degree=1).values()[np.where((topomesh.wisp_property('epidermis',degree=1).values()))] # edge_vertices = topomesh.wisp_property('vertices',degree=1).values()[np.where((topomesh.wisp_property('epidermis',degree=1).values())&(edge_cells_degree>1))] edge_vertices = topomesh.wisp_property('vertices',degree=1).values()[np.where((edge_cells_degree>2)|((topomesh.wisp_property('epidermis',degree=1).values())&(edge_cells_degree>1)))] epidermis_vertices = np.unique(edge_vertices) vertices_degrees = np.array(nd.sum(np.ones_like(edge_vertices),edge_vertices,index=epidermis_vertices),int) reversed_edge_vertices = np.transpose([edge_vertices[:,1],edge_vertices[:,0]]) edge_vertices = np.append(edge_vertices,reversed_edge_vertices,axis=0) edge_vectors = topomesh.wisp_property('barycenter',degree=0).values(edge_vertices[:,1]) - topomesh.wisp_property('barycenter',degree=0).values(edge_vertices[:,0]) laplacian_force = np.transpose([nd.sum(edge_vectors[:,0],edge_vertices[:,0],index=epidermis_vertices), nd.sum(edge_vectors[:,1],edge_vertices[:,0],index=epidermis_vertices), nd.sum(edge_vectors[:,2],edge_vertices[:,0],index=epidermis_vertices)]) laplacian_force[vertices_degrees>0] = laplacian_force[vertices_degrees>0]/vertices_degrees[vertices_degrees>0][:,np.newaxis] # vertex_cell_barycenter = np.array([np.mean(topomesh.wisp_property('barycenter',degree=3).values(c),axis=0) for c in topomesh.wisp_property('cells',0).values(epidermis_vertices)]) # vertices_directions = topomesh.wisp_property('barycenter',degree=0).values(epidermis_vertices) - vertex_cell_barycenter # vertices_directions = vertices_directions/np.linalg.norm(vertices_directions,axis=1)[:,np.newaxis] # vertices_products = np.einsum('ij,ij->i',laplacian_force,vertices_directions) # convex_points = np.where(vertices_products<0.)[0] # laplacian_force[convex_points] -= vertices_directions[convex_points]*vertices_products[convex_points,np.newaxis] # laplacian_force[convex_points] = np.array([0.,0.,0.]) epidermis_convexity_force.update(laplacian_force,keys=epidermis_vertices,erase_missing_keys=False) return epidermis_convexity_force.values() def _property_topomesh_interface_planarization_force(topomesh, verbose=False, debug=False, loglevel=0): if not topomesh.has_wisp_property('barycenter',degree=3,is_computed=True): compute_topomesh_property(topomesh,'barycenter',degree=3) if not topomesh.has_wisp_property('barycenter',degree=2,is_computed=True): compute_topomesh_property(topomesh,'barycenter',degree=2) if not topomesh.has_wisp_property('vertices',degree=2,is_computed=True): compute_topomesh_property(topomesh,'vertices',degree=2) if not topomesh.has_wisp_property('normal',degree=2,is_computed=True): compute_topomesh_property(topomesh,'normal',degree=2) if not topomesh.has_wisp_property('cell_interface',degree=2,is_computed=False): compute_topomesh_property(topomesh,'cell_interface',degree=2) if not topomesh.has_interface_property('interface',degree=3,is_computed=True): compute_topomesh_property(topomesh,'interface',degree=3) interface_planarization_force = np.zeros_like(topomesh.wisp_property('barycenter',degree=0).values(),np.float32) cell_interfaces = topomesh.interface_property('interface',degree=3).keys() interface_triangles = topomesh.wisp_property('cell_interface',degree=2).keys() triangle_cell_interface = topomesh.wisp_property('cell_interface',degree=2).values() triangle_cells = np.array(array_dict(topomesh._interface[3]).values(triangle_cell_interface)) triangle_cell_barycenters = topomesh.wisp_property('barycenter',degree=3).values(triangle_cells) triangle_interface_directions = triangle_cell_barycenters[:,1] - triangle_cell_barycenters[:,0] triangle_normal_vectors = topomesh.wisp_property('normal',degree=2).values(interface_triangles) # reversed_normals = np.where(np.dot(triangle_normal_vectors,triangle_interface_directions) < 0)[0] reversed_normals = np.where(np.einsum('ij,ij->i',triangle_normal_vectors,triangle_interface_directions) < 0)[0] triangle_normal_vectors[reversed_normals] = -triangle_normal_vectors[reversed_normals] interface_normal_vectors = np.transpose([nd.mean(triangle_normal_vectors[:,0],triangle_cell_interface,index=cell_interfaces), nd.mean(triangle_normal_vectors[:,1],triangle_cell_interface,index=cell_interfaces), nd.mean(triangle_normal_vectors[:,2],triangle_cell_interface,index=cell_interfaces)]) interface_normal_vectors = interface_normal_vectors/np.linalg.norm(interface_normal_vectors,axis=1)[:,np.newaxis] triangle_target_normals = interface_normal_vectors[triangle_cell_interface] triangle_barycenters = topomesh.wisp_property('barycenter',degree=2).values(interface_triangles) interface_barycenters = np.transpose([nd.mean(triangle_barycenters[:,0],triangle_cell_interface,index=cell_interfaces), nd.mean(triangle_barycenters[:,1],triangle_cell_interface,index=cell_interfaces), nd.mean(triangle_barycenters[:,2],triangle_cell_interface,index=cell_interfaces)]) triangle_interface_barycenters = interface_barycenters[triangle_cell_interface] triangle_vertices = topomesh.wisp_property('vertices',degree=2).values(interface_triangles) interface_vertices = np.unique(triangle_vertices) rotated_triangle_vertices = np.transpose([triangle_vertices[:,2],triangle_vertices[:,0],triangle_vertices[:,1]]) antirotated_triangle_vertices = np.transpose([triangle_vertices[:,1],triangle_vertices[:,2],triangle_vertices[:,0]]) triangle_vertices = np.append(np.append(triangle_vertices,rotated_triangle_vertices,axis=0),antirotated_triangle_vertices,axis=0) triangle_point_vectors = topomesh.wisp_property('barycenter',degree=0).values(triangle_vertices[:,0]) - np.tile(triangle_interface_barycenters,(3,1)) # triangle_point_projectors = -np.dot(triangle_point_vectors,triange_target_normals)[:,np.newaxis]*triangle_target_normals # logging.info("".join([" " for l in range(loglevel)])+triangle_point_vectors.shape,triangle_target_normals.shape triangle_point_projectors = -np.einsum('ij,ij->i',triangle_point_vectors,np.tile(triangle_target_normals,(3,1)))[:,np.newaxis]*np.tile(triangle_target_normals,(3,1)) # logging.info("".join([" " for l in range(loglevel)])+triangle_point_projectors.shape triangle_projectors_norm = np.linalg.norm(triangle_point_projectors,axis=1) non_planar_points = np.where(triangle_projectors_norm > 1.)[0] triangle_point_projectors[non_planar_points] = triangle_point_projectors[non_planar_points]/triangle_projectors_norm[non_planar_points,np.newaxis] # planarization_unitary_force = normal_errors[:,np.newaxis]*point_projectors planarization_unitary_force = triangle_point_projectors interface_planarization_force = np.transpose([nd.sum(planarization_unitary_force[:,0],triangle_vertices[:,0],index=list(topomesh.wisps(0))), nd.sum(planarization_unitary_force[:,1],triangle_vertices[:,0],index=list(topomesh.wisps(0))), nd.sum(planarization_unitary_force[:,2],triangle_vertices[:,0],index=list(topomesh.wisps(0)))]) interface_planarization_force[np.isnan(interface_planarization_force)] = 0. return interface_planarization_force def _property_topomesh_epidermis_convexity_force(topomesh, verbose=False, debug=False, loglevel=0): if not topomesh.has_wisp_property('barycenter',degree=3,is_computed=True): compute_topomesh_property(topomesh,'barycenter',degree=3) if not topomesh.has_wisp_property('barycenter',degree=2,is_computed=True): compute_topomesh_property(topomesh,'barycenter',degree=2) if not topomesh.has_wisp_property('vertices',degree=2,is_computed=True): compute_topomesh_property(topomesh,'vertices',degree=2) if not topomesh.has_wisp_property('normal',degree=2,is_computed=True): compute_topomesh_property(topomesh,'normal',degree=2) if not topomesh.has_wisp_property('cells',degree=2,is_computed=True): compute_topomesh_property(topomesh,'cells',degree=2) if not topomesh.has_wisp_property('epidermis',degree=2,is_computed=True): compute_topomesh_property(topomesh,'epidermis',degree=2) epidermis_triangles = np.array(list(topomesh.wisps(2)))[np.where(topomesh.wisp_property('epidermis',2).values())] triangle_cell = np.array([c[0] for c in topomesh.wisp_property('cells',2).values(epidermis_triangles)]) epidermis_cells = np.unique(triangle_cell) triangle_cell_barycenters = topomesh.wisp_property('barycenter',degree=3).values(triangle_cell) triangle_barycenters = topomesh.wisp_property('barycenter',degree=2).values(epidermis_triangles) cell_epidermis_barycenters = np.transpose([nd.sum(triangle_barycenters[:,k],triangle_cell,index=list(topomesh.wisps(3))) for k in range(3)]) cell_epidermis_n_triangles = nd.sum(np.ones_like(triangle_cell),triangle_cell,index=list(topomesh.wisps(3))) cell_epidermis_barycenters[cell_epidermis_n_triangles>0] /= cell_epidermis_n_triangles[cell_epidermis_n_triangles>0][:,np.newaxis] cell_epidermis_barycenters = array_dict(cell_epidermis_barycenters,keys=list(topomesh.wisps(3))) triangle_epidermis_barycenters = cell_epidermis_barycenters.values(triangle_cell) # logging.info("".join([" " for l in range(loglevel)])+triangle_epidermis_barycenters.shape triangle_epidermis_directions = triangle_epidermis_barycenters - triangle_cell_barycenters # logging.info("".join([" " for l in range(loglevel)])+triangle_epidermis_directions.shape triangle_normal_vectors = topomesh.wisp_property('normal',degree=2).values(epidermis_triangles) # reversed_normals = np.where(np.dot(triangle_normal_vectors,triangle_interface_directions) < 0)[0] reversed_normals = np.where(np.einsum('ij,ij->i',triangle_normal_vectors,triangle_epidermis_directions) < 0)[0] triangle_normal_vectors[reversed_normals] = -triangle_normal_vectors[reversed_normals] # cell_normal_vectors = np.transpose([nd.mean(triangle_normal_vectors[:,0],triangle_cell,index=list(topomesh.wisps(3))), # nd.mean(triangle_normal_vectors[:,1],triangle_cell,index=list(topomesh.wisps(3))), # nd.mean(triangle_normal_vectors[:,2],triangle_cell,index=list(topomesh.wisps(3)))]) # cell_normal_vectors = cell_normal_vectors/np.linalg.norm(cell_normal_vectors,axis=1)[:,np.newaxis] # cell_normal_vectors = array_dict(cell_normal_vectors,keys=list(topomesh.wisps(3))) # triangle_target_normals = cell_normal_vectors.values(triangle_cell) triangle_radius_direction = triangle_barycenters - triangle_cell_barycenters triangle_target_normals = triangle_radius_direction/np.maximum(1e-5,np.linalg.norm(triangle_radius_direction,axis=1))[:,np.newaxis] triangle_vertices = topomesh.wisp_property('vertices',degree=2).values(epidermis_triangles) epidermis_vertices = np.unique(triangle_vertices) rotated_triangle_vertices = np.transpose([triangle_vertices[:,2],triangle_vertices[:,0],triangle_vertices[:,1]]) antirotated_triangle_vertices = np.transpose([triangle_vertices[:,1],triangle_vertices[:,2],triangle_vertices[:,0]]) triangle_vertices = np.append(np.append(triangle_vertices,rotated_triangle_vertices,axis=0),antirotated_triangle_vertices,axis=0) # triangle_point_vectors = topomesh.wisp_property('barycenter',degree=0).values(triangle_vertices[:,0]) - np.tile(triangle_epidermis_barycenters,(3,1)) triangle_point_vectors = topomesh.wisp_property('barycenter',degree=0).values(triangle_vertices[:,0]) - np.tile(triangle_barycenters,(3,1)) triangle_plane_products = np.einsum('ij,ij->i',triangle_point_vectors,np.tile(triangle_target_normals,(3,1))) triangle_point_projectors = -triangle_plane_products[:,np.newaxis]*np.tile(triangle_target_normals,(3,1)) triangle_projectors_norm = np.linalg.norm(triangle_point_projectors,axis=1) non_planar_points = np.where(triangle_projectors_norm > 1.)[0] triangle_point_projectors[non_planar_points] = triangle_point_projectors[non_planar_points]/np.maximum(1e-5,triangle_projectors_norm)[non_planar_points,np.newaxis] convex_points = np.where(triangle_plane_products>0.)[0] triangle_point_projectors[convex_points] = np.array([0.,0.,0.]) convexity_unitary_force = triangle_point_projectors epidermis_convexity_force = np.transpose([nd.sum(convexity_unitary_force[:,0],triangle_vertices[:,0],index=list(topomesh.wisps(0))), nd.sum(convexity_unitary_force[:,1],triangle_vertices[:,0],index=list(topomesh.wisps(0))), nd.sum(convexity_unitary_force[:,2],triangle_vertices[:,0],index=list(topomesh.wisps(0)))]) return epidermis_convexity_force def _property_topomesh_epidermis_planarization_force(topomesh, verbose=False, debug=False, loglevel=0): if not topomesh.has_wisp_property('epidermis',degree=2,is_computed=True): compute_topomesh_property(topomesh,'epidermis',degree=2) if not topomesh.has_wisp_property('epidermis',degree=3,is_computed=True): compute_topomesh_property(topomesh,'epidermis',degree=3) if not topomesh.has_wisp_property('epidermis',degree=0,is_computed=True): compute_topomesh_property(topomesh,'epidermis',degree=0) if not topomesh.has_wisp_property('cells',degree=0,is_computed=True): compute_topomesh_property(topomesh,'cells',degree=0) if not topomesh.has_wisp_property('barycenter',degree=2,is_computed=True): compute_topomesh_property(topomesh,'barycenter',2) if not topomesh.has_wisp_property('barycenter',degree=3,is_computed=True): compute_topomesh_property(topomesh,'barycenter',3) if not topomesh.has_wisp_property('length',degree=1,is_computed=True): compute_topomesh_property(topomesh,'length',degree=1) if not topomesh.has_wisp_property('area',degree=2,is_computed=True): compute_topomesh_property(topomesh,'area',degree=2) if not topomesh.has_wisp_property('normal',degree=2,is_computed=True): compute_topomesh_property(topomesh,'normal',degree=2) epidermis_triangles = np.array(list(topomesh.wisps(2)))[topomesh.wisp_property('epidermis',2).values()] epidermis_cells = np.array(list(topomesh.wisps(3)))[topomesh.wisp_property('epidermis',3).values()] epidermis_vertices = np.array(list(topomesh.wisps(0)))[topomesh.wisp_property('epidermis',0).values()] triangle_cells = np.concatenate(topomesh.wisp_property('cells',2).values(epidermis_triangles)) triangle_areas = topomesh.wisp_property('area',2).values(epidermis_triangles) triangle_centers = topomesh.wisp_property('barycenter',2).values(epidermis_triangles) triangle_weighted_normals = triangle_areas[:,np.newaxis]*topomesh.wisp_property('normal',2).values(epidermis_triangles) cell_areas = nd.sum(triangle_areas,triangle_cells,index=epidermis_cells) cell_epidermis_centers = np.transpose([nd.sum(triangle_areas*triangle_centers[:,k],triangle_cells,index=epidermis_cells) for k in range(3)]) cell_epidermis_centers = cell_epidermis_centers/cell_areas[:,np.newaxis] cell_epidermis_centers = array_dict(cell_epidermis_centers,epidermis_cells) cell_weighted_normals = np.transpose([nd.sum(triangle_weighted_normals[:,k],triangle_cells,index=epidermis_cells) for k in range(3)]) cell_normals = cell_weighted_normals/cell_areas[:,np.newaxis] cell_normals = array_dict(cell_normals/np.linalg.norm(cell_normals,axis=1)[:,np.newaxis],epidermis_cells) vertex_cells = np.concatenate([np.intersect1d(np.array(topomesh.wisp_property('cells',0)[v],int),epidermis_cells) for v in epidermis_vertices]) vertex_cell_vertex = np.concatenate([v*np.ones_like(np.intersect1d(topomesh.wisp_property('cells',0)[v],epidermis_cells),int) for v in epidermis_vertices]) vertex_cell_normals = cell_normals.values(vertex_cells) #vertex_cell_normals = np.array([cell_normals[c] if cell_normals.has_key(c) else np.array([0,0,1]) for c in vertex_cells]) vertex_cell_centers = cell_epidermis_centers.values(vertex_cells) #vertex_cell_centers = np.array([cell_epidermis_centers[c] if cell_epidermis_centers.has_key(c) else topomesh.wisp_property('barycenter',3)[c]]) vertex_cell_vectors = topomesh.wisp_property('barycenter',0).values(vertex_cell_vertex) - vertex_cell_centers vertex_cell_projectors = -np.einsum('ij,ij->i',vertex_cell_vectors,vertex_cell_normals)[:,np.newaxis]*vertex_cell_normals vertex_projectors = np.transpose([nd.sum(vertex_cell_projectors[:,k],vertex_cell_vertex,index=list(topomesh.wisps(0))) for k in range(3)]) vertex_projectors[np.isnan(vertex_projectors)] = 0. return vertex_projectors def _property_topomesh_cell_interface_planarization_force(topomesh, verbose=False, debug=False, loglevel=0): if not topomesh.has_wisp_property('vertices',degree=3,is_computed=True): compute_topomesh_property(topomesh,'vertices',degree=3) if not topomesh.has_wisp_property('barycenter',degree=3,is_computed=True): compute_topomesh_property(topomesh,'barycenter',degree=3) if not topomesh.has_wisp_property('borders',degree=3,is_computed=True): compute_topomesh_property(topomesh,'borders',degree=3) if not topomesh.has_wisp_property('neighbors',degree=3,is_computed=True): compute_topomesh_property(topomesh,'neighbors',degree=3) planarization_force = np.zeros_like(topomesh.wisp_property('barycenter',degree=0).values(),np.float32) for c in topomesh.wisps(3): logging.info("".join([" " for l in range(loglevel)])+"Cell "+str(c)+" interface planarization") for n in topomesh.wisp_property("neighbors",degree=3)[c]: normal_direction = topomesh.wisp_property("barycenter",degree=3)[n] - topomesh.wisp_property("barycenter",degree=3)[c] # triangle_vertices = np.array([list(topomesh.borders(2,t,2)) for t in topomesh.borders(3,c) if t in topomesh.borders(3,n)]) interface_triangles = np.intersect1d(topomesh.wisp_property('borders',degree=3)[c],topomesh.wisp_property('borders',degree=3)[n]) triangle_vertices = topomesh.wisp_property('vertices',degree=2).values(interface_triangles) rotated_triangle_vertices = np.transpose([triangle_vertices[:,2],triangle_vertices[:,0],triangle_vertices[:,1]]) antirotated_triangle_vertices = np.transpose([triangle_vertices[:,1],triangle_vertices[:,2],triangle_vertices[:,0]]) triangle_vertices = np.append(np.append(triangle_vertices,rotated_triangle_vertices,axis=0),antirotated_triangle_vertices,axis=0) # interface_vertices = np.intersect1d(cell_vertices[c],cell_vertices[n]) interface_vertices = np.unique(triangle_vertices) edge_index_list = np.array([[1, 2],[0, 1],[0, 2]]) triangle_edge_vertices = triangle_vertices[:,edge_index_list] triangle_edge_vectors = topomesh.wisp_property('barycenter',degree=0).values(triangle_edge_vertices[...,1]) - topomesh.wisp_property('barycenter',degree=0).values(triangle_edge_vertices[...,0]) triangle_edge_lengths = np.power(np.sum(np.power(triangle_edge_vectors,2.0),axis=2),0.5) triangle_edge_directions = triangle_edge_vectors/triangle_edge_lengths[...,np.newaxis] normal_vectors = np.cross(triangle_edge_vectors[:,1],triangle_edge_vectors[:,2]) # reversed_normals = np.where(np.dot(normal_vectors,normal_direction) < 0)[0] reversed_normals = np.where(np.core.umath_tests.inner1d(normal_vectors,normal_direction) < 0)[0] normal_vectors[reversed_normals] = np.cross(triangle_edge_vectors[reversed_normals,2],triangle_edge_vectors[reversed_normals,1]) normal_vectors = normal_vectors/np.linalg.norm(normal_vectors,axis=1)[:,np.newaxis] if(np.isnan(normal_vectors).any()): logging.info("".join([" " for l in range(loglevel)])+"Interface "+str(c)+" -> "+str(n)+" planarization force") normal_target = np.mean(normal_vectors,axis=0) normal_target = normal_target/np.linalg.norm(normal_target) if(np.isnan(normal_target).any()): logging.info("".join([" " for l in range(loglevel)])+"Interface "+str(c)+" -> "+str(n)+" planarization force") # normal_errors = 1.0 - np.dot(normal_vectors,normal_target) barycenter = np.mean(topomesh.wisp_property('barycenter',degree=0).values(interface_vertices),axis=0) point_vectors = topomesh.wisp_property('barycenter',degree=0).values(triangle_vertices[:,0]) - barycenter point_projectors = -np.dot(point_vectors,normal_target)[:,np.newaxis]*normal_target non_planar_points = np.where(abs(np.dot(point_vectors,normal_target)) > 1.)[0] point_projectors[non_planar_points] = point_projectors[non_planar_points]/abs(np.dot(point_vectors,normal_target))[non_planar_points,np.newaxis] # planarization_unitary_force = normal_errors[:,np.newaxis]*point_projectors planarization_unitary_force = point_projectors if(np.isnan(planarization_unitary_force).any()): logging.info("".join([" " for l in range(loglevel)])+"Interface "+str(c)+" -> "+str(n)+" planarization force") interface_planarization_force = np.transpose([nd.sum(planarization_unitary_force[:,0],triangle_vertices[:,0],index=interface_vertices), nd.sum(planarization_unitary_force[:,1],triangle_vertices[:,0],index=interface_vertices), nd.sum(planarization_unitary_force[:,2],triangle_vertices[:,0],index=interface_vertices)]) planarization_force[interface_vertices] += interface_planarization_force planarization_force[np.isnan(planarization_force)] = 0. return planarization_force
[docs]def topomesh_triangle_split(input_topomesh, verbose=False, debug=False, loglevel=0): """Remesh a triangular PropertyTopomesh by spltting all faces into 4 Performs a simple remeshing of a triangular topomesh by inserting a middle vertex on each edge, and splitting each triangle into four identical triangle by linking the middle vertices. >>> " • • " >>> " / \ / \ " >>> " / \ ---> •---• " >>> " / \ / \ / \ " >>> "•-------• •---•---•" Parameters ---------- input_topomesh : :class:`cellcomplex.property_topomesh.PropertyTopomesh` The initial structure on which to perform the remeshing Returns ------- topomesh : :class:`cellcomplex.property_topomesh.PropertyTopomesh` The remeshed structure Warnings -------- The PropertyTopomesh must be a *triangular topomesh* (:meth:`cellcomplex.property_topomesh.analysis.is_triangular`) """ assert is_triangular(input_topomesh) topomesh = deepcopy(input_topomesh) triangle_vertex_positions = topomesh.wisp_property('barycenter',0).to_dict() compute_topomesh_property(topomesh,'length',1) compute_topomesh_property(topomesh,'vertices',1) topomesh_edges = list(topomesh.wisps(1)) compute_topomesh_property(topomesh,'borders',2) compute_topomesh_property(topomesh,'vertices',2) topomesh_triangles = list(topomesh.wisps(2)) start_time = time() logging.info("".join([" " for l in range(loglevel)])+"--> Splitting edges") edge_splitted = {} edge_middles = {} for e in topomesh_edges: edge_vertices = topomesh.wisp_property('vertices',1)[e] mid = topomesh.add_wisp(0) triangle_vertex_positions[mid] = topomesh.wisp_property('barycenter',0).values(edge_vertices).mean(axis=0) topomesh.unlink(1,e,edge_vertices[1]) topomesh.link(1,e,mid) eid = topomesh.add_wisp(1) topomesh.link(1,eid,edge_vertices[1]) topomesh.link(1,eid,mid) edge_splitted[e] = [e,eid] edge_middles[e] = mid end_time = time() logging.info("".join([" " for l in range(loglevel)])+"--> Splitting edges ["+str(end_time-start_time)+" s]") start_time = time() logging.info("".join([" " for l in range(loglevel)])+"--> Splitting triangles") for t in topomesh_triangles: for e in topomesh.wisp_property('borders',2)[t]: topomesh.unlink(2,t,e) eid = topomesh.add_wisp(1) for neighbor_edge in topomesh.wisp_property('borders',2)[t]: if neighbor_edge != e: topomesh.link(1,eid,edge_middles[neighbor_edge]) topomesh.link(2,t,eid) fid = topomesh.add_wisp(2) topomesh.link(2,fid,eid) opposite_pid = list(set(topomesh.wisp_property('vertices',2)[t]).difference(topomesh.wisp_property('vertices',1)[e]))[0] step_start_time = time() for neighbor_edge in topomesh.wisp_property('borders',2)[t]: if neighbor_edge != e: for opposite_edge in edge_splitted[neighbor_edge]: if opposite_pid in topomesh.borders(1,opposite_edge): topomesh.link(2,fid,opposite_edge) for c in topomesh.regions(2,t): topomesh.link(3,c,fid) end_time = time() logging.info("".join([" " for l in range(loglevel)])+"--> Splitting triangles ["+str(end_time-start_time)+" s]") topomesh.update_wisp_property('barycenter',degree=0,values=triangle_vertex_positions) compute_topomesh_property(topomesh,'length',1) compute_topomesh_property(topomesh,'vertices',1) compute_topomesh_property(topomesh,'borders',2) compute_topomesh_property(topomesh,'vertices',2) return topomesh
[docs]def property_topomesh_edge_flip_optimization(topomesh,omega_energies=dict([('regularization',0.15),('neighborhood',0.65)]),simulated_annealing=True,display=False, image_edges=None, verbose=False, debug=False, loglevel=0,**kwargs): """Perform a topological optimization of a PropertyTopomesh using edge flips. An energy functional is iteratively minimized by successively flipping edges of the triangular topomesh. Edge flips that allow a decrease of the energy while keeping the structure valid topologically and geometrically are applied in ascending order of energy variation. To avoid local minima of energy it is possible to apply energy increasing flips in a simulated annealing procedure. Parameters ---------- topomesh : :class:`cellcomplex.property_topomesh.PropertyTopomesh` The structure on which to perform the optimization omega_energies : dict The weights (float) associated to each energy term: * *regularization* energy optimizing triangle eccentricity * *length* energy minimizing total edge length * *neighborhood* energy optimizing vertex valency towards 6 * *image* energy optimizing adjacencies to observed ones simulated_annealing : bool Whether to allow or not energy increasing edge flips in the process image_edges : :class:`numpy.ndarray` The list of pairs of vertices used as reference if *image* energy is used Returns ------- n_flips : int Number of edges flipped in the last iteration Note ------- The PropertyTopomesh passed as argument is updated. Warnings -------- The PropertyTopomesh must be a *triangular topomesh* (:meth:`cellcomplex.property_topomesh.analysis.is_triangular`) """ projected = kwargs.get("projected_map_display",False) if image_edges is None: img_edges = np.array([]) else: img_edges = deepcopy(image_edges) if simulated_annealing: iterations = kwargs.get('iterations',20) lambda_temperature = kwargs.get('lambda_temperature',0.85) minimal_temperature = kwargs.get('minimal_temperature',0.1) initial_temperature = minimal_temperature*np.power(1./lambda_temperature,iterations) else: iterations = kwargs.get('iterations',3) initial_temperature = 1. lambda_temperature = 0.9 minimal_temperature = np.power(lambda_temperature,iterations) simulated_annealing_temperature = initial_temperature while simulated_annealing_temperature > minimal_temperature: simulated_annealing_temperature *= lambda_temperature compute_topomesh_property(topomesh,'faces',0) compute_topomesh_property(topomesh,'borders',1) compute_topomesh_property(topomesh,'vertices',1) compute_topomesh_property(topomesh,'faces',1) compute_topomesh_property(topomesh,'length',1) compute_topomesh_property(topomesh,'borders',2) compute_topomesh_property(topomesh,'vertices',2) compute_topomesh_triangle_properties(topomesh) # logging.info("".join([" " for l in range(loglevel)])+"Area : ",topomesh.wisp_property('area',2).values().mean()," (",topomesh.wisp_property('area',2).values().std()," ) Eccentricity : ,",topomesh.wisp_property('eccentricity',2).values().mean()," [ T = ",simulated_annealing_temperature," ]" flippable_edges = topomesh.wisp_property('faces',1).keys()[np.where(np.array(list(map(len,topomesh.wisp_property('faces',1).values()))) == 2)] flippable_edge_vertices = topomesh.wisp_property('vertices',1).values(flippable_edges) flippable_edge_triangle_vertices = topomesh.wisp_property('vertices',2).values(topomesh.wisp_property('faces',1).values(flippable_edges)) flippable_edge_flipped_vertices = np.array([list(set(list(np.unique(t))).difference(v)) for t,v in zip(flippable_edge_triangle_vertices,flippable_edge_vertices)]) wrong_edges = np.where(np.array(list(map(len,flippable_edge_flipped_vertices))) != 2)[0] flippable_edges = np.delete(flippable_edges,wrong_edges,0) flippable_edge_vertices = np.delete(flippable_edge_vertices,wrong_edges,0) flippable_edge_triangle_vertices = np.delete(flippable_edge_triangle_vertices,wrong_edges,0) flippable_edge_flipped_vertices = np.array([e for e in np.delete(flippable_edge_flipped_vertices,wrong_edges,0)]) flippable_edge_flipped_triangle_vertices = np.array([[np.concatenate([f,[v]]) for v in e] for (e,f) in zip(flippable_edge_vertices,flippable_edge_flipped_vertices)]) flippable_edge_triangle_areas = np.concatenate([triangle_geometric_features(flippable_edge_triangle_vertices[:,e],topomesh.wisp_property('barycenter',0),features=['area']) for e in [0,1]],axis=1) flippable_edge_flipped_triangle_areas = np.concatenate([triangle_geometric_features(flippable_edge_flipped_triangle_vertices[:,e],topomesh.wisp_property('barycenter',0),features=['area']) for e in [0,1]],axis=1) flippable_edge_flipped_triangle_areas[np.isnan(flippable_edge_flipped_triangle_areas)] = 100. average_area = np.nanmean(topomesh.wisp_property('area',2).values()) flat = topomesh.wisp_property('barycenter',0).values()[:,2].std() < 1e-6 if flat: wrong_edges = np.where(np.abs(flippable_edge_triangle_areas.sum(axis=1)-flippable_edge_flipped_triangle_areas.sum(axis=1)) > 1e-6) else: wrong_edges = np.where(np.abs(flippable_edge_triangle_areas.sum(axis=1)-flippable_edge_flipped_triangle_areas.sum(axis=1)) > average_area/10.) flippable_edges = np.delete(flippable_edges,wrong_edges,0) flippable_edge_vertices = np.delete(flippable_edge_vertices,wrong_edges,0) flippable_edge_triangle_vertices = np.delete(flippable_edge_triangle_vertices,wrong_edges,0) flippable_edge_flipped_vertices = np.delete(flippable_edge_flipped_vertices,wrong_edges,0) flippable_edge_flipped_triangle_vertices = np.delete(flippable_edge_flipped_triangle_vertices,wrong_edges,0) flippable_edge_triangle_areas = np.delete(flippable_edge_triangle_areas,wrong_edges,0) flippable_edge_flipped_triangle_areas = np.delete(flippable_edge_flipped_triangle_areas,wrong_edges,0) flippable_edge_energy_variation = np.zeros_like(flippable_edges,np.float) if 'regularization' in omega_energies: flippable_edge_area_error = np.power(flippable_edge_triangle_areas-average_area,2.0).sum(axis=1) flippable_edge_area_flipped_error = np.power(np.maximum(flippable_edge_flipped_triangle_areas-average_area,0),2.0).sum(axis=1) flippable_edge_area_energy_variation = array_dict(0.02*(flippable_edge_area_flipped_error-flippable_edge_area_error)/np.power(4.0*average_area,2.0),flippable_edges) # flippable_edge_area_energy_variation = array_dict(flippable_edge_flipped_triangle_areas.sum(axis=1)-flippable_edge_triangle_areas.sum(axis=1),flippable_edges) flippable_edge_triangle_eccentricities = np.concatenate([triangle_geometric_features(flippable_edge_triangle_vertices[:,e],topomesh.wisp_property('barycenter',0),features=['sinus_eccentricity']) for e in [0,1]],axis=1) flippable_edge_flipped_triangle_eccentricities = np.concatenate([triangle_geometric_features(flippable_edge_flipped_triangle_vertices[:,e],topomesh.wisp_property('barycenter',0),features=['sinus_eccentricity']) for e in [0,1]],axis=1) flippable_edge_eccentricity_energy_variation = array_dict(8.0*(flippable_edge_flipped_triangle_eccentricities.sum(axis=1)-flippable_edge_triangle_eccentricities.sum(axis=1)),flippable_edges) flippable_edge_energy_variation += omega_energies['regularization']*flippable_edge_area_energy_variation.values(flippable_edges) flippable_edge_energy_variation += omega_energies['regularization']*flippable_edge_eccentricity_energy_variation.values(flippable_edges) if 'length' in omega_energies: flippable_edge_lengths = np.linalg.norm(topomesh.wisp_property('barycenter',0).values(flippable_edge_vertices[:,1]) - topomesh.wisp_property('barycenter',0).values(flippable_edge_vertices[:,0]),axis=1) flippable_edge_flipped_lengths = np.linalg.norm(topomesh.wisp_property('barycenter',0).values(flippable_edge_flipped_vertices[:,1]) - topomesh.wisp_property('barycenter',0).values(flippable_edge_flipped_vertices[:,0]),axis=1) flippable_edge_length_energy_variation = array_dict(flippable_edge_flipped_lengths-flippable_edge_lengths,flippable_edges) flippable_edge_energy_variation += omega_energies['length']*flippable_edge_length_energy_variation.values(flippable_edges) if 'neighborhood' in omega_energies: compute_topomesh_property(topomesh,'valency',0) nested_mesh = kwargs.get("nested_mesh",False) if nested_mesh: compute_topomesh_property(topomesh,'cells',0) compute_topomesh_property(topomesh,'epidermis',0) target_neighborhood = array_dict((np.array(list(map(len,topomesh.wisp_property('cells',0).values()))) + topomesh.wisp_property('epidermis',0).values())*3,list(topomesh.wisps(0))) else: target_neighborhood = array_dict(np.ones_like(list(topomesh.wisps(0)))*6,list(topomesh.wisps(0))) flippable_edge_neighborhood_error = np.power(np.abs(topomesh.wisp_property('valency',0).values(flippable_edge_vertices)-target_neighborhood.values(flippable_edge_vertices)),2.0).sum(axis=1) flippable_edge_neighborhood_error += np.power(np.abs(topomesh.wisp_property('valency',0).values(flippable_edge_flipped_vertices)-target_neighborhood.values(flippable_edge_flipped_vertices)),2.0).sum(axis=1) flippable_edge_neighborhood_flipped_error = np.power(np.abs(topomesh.wisp_property('valency',0).values(flippable_edge_vertices)-1-target_neighborhood.values(flippable_edge_vertices)),2.0).sum(axis=1) flippable_edge_neighborhood_flipped_error += np.power(np.abs(topomesh.wisp_property('valency',0).values(flippable_edge_flipped_vertices)+1-target_neighborhood.values(flippable_edge_flipped_vertices)),2.0).sum(axis=1) flippable_edge_neighborhood_energy_variation = array_dict(flippable_edge_neighborhood_flipped_error-flippable_edge_neighborhood_error,flippable_edges) flippable_edge_energy_variation += omega_energies['neighborhood']*flippable_edge_neighborhood_energy_variation.values(flippable_edges) if 'image' in omega_energies: if img_edges is not None and len(img_edges)>0: flippable_edge_energy_variation += omega_energies['image']*((vq(flippable_edge_vertices,img_edges)[1]==0).astype(float) - (vq(flippable_edge_flipped_vertices,img_edges)[1]==0 ).astype(float)) flippable_edge_energy_variation = array_dict(flippable_edge_energy_variation,flippable_edges) if display: pass flippable_edge_sorted_energy_variation = array_dict(np.sort(flippable_edge_energy_variation.values()),flippable_edges[np.argsort(flippable_edge_energy_variation.values())]) start_time = time() logging.info("".join([" " for l in range(loglevel)])+"--> Flipping mesh edges") flipped_edges = 0 flipped_nonoptimal_edges = 0 unflippable_edges = set() for eid in flippable_edge_sorted_energy_variation.keys(): flip_probability = np.exp(-flippable_edge_sorted_energy_variation[eid]/simulated_annealing_temperature) if (simulated_annealing and (np.random.rand() < flip_probability)) or (1 < flip_probability): if not eid in unflippable_edges: # logging.info("".join([" " for l in range(loglevel)])+topomesh.wisp_property('vertices',1)[eid], flippable_edge_sorted_energy_variation[e] # neighbor_edges = list(topomesh.region_neighbors(1,eid)) flipped_edges += 1 flipped_nonoptimal_edges += (flip_probability<1) neighbor_edges = list(topomesh.border_neighbors(1,eid)) topomesh_flip_edge(topomesh,eid) # for n_eid in neighbor_edges: # unflippable_edges.append(n_eid) unflippable_edges = unflippable_edges.union(set(neighbor_edges)) end_time = time() logging.info("".join([" " for l in range(loglevel)])+" --> Flipped "+str(flipped_edges)+" edges ("+str(flipped_nonoptimal_edges)+" non-optimal) [ T = "+str(simulated_annealing_temperature)+"]") logging.info("".join([" " for l in range(loglevel)])+"<-- Flipping mesh edges ["+str(end_time-start_time)+" s]") return flipped_edges
[docs]def property_topomesh_edge_split_optimization(topomesh, maximal_length=None, split_adjacent_faces=True, iterations=1, verbose=False, debug=False, loglevel=0): """Perform a topological optimization of a PropertyTopomesh using edge splits. The edges of the topomesh are iteratively splitted in order to become all lesser than a target maximal length, while keeping the structure valid topologically and geometrically. The edges are splitted in the descending order of their lengths, until no edge is longer than *maximal_length* or the maximal number of *iterations* is reached. Parameters ---------- topomesh : :class:`cellcomplex.property_topomesh.PropertyTopomesh` The structure on which to perform the optimization maximal_length : float The edge length above which the edges will be splitted split_adjacent_faces : bool Whether to divide the faces adjacent to the splitted edges iterations : int Number of optimization passes to perform Returns ------- n_splits : int Number of edges splitted in the last iteration Note ------- The PropertyTopomesh passed as argument is updated. Warnings -------- If the PropertyTopomesh must is a *triangular topomesh* the *split_adjacent_faces* argument should be `True` """ compute_topomesh_property(topomesh,'vertices',1) compute_topomesh_property(topomesh,'length',1) if maximal_length is None: target_length = np.percentile(topomesh.wisp_property('length',1).values(),70) maximal_length = 4./3. * target_length for iteration in range(iterations): sorted_edge_length_edges = np.array(list(topomesh.wisps(1)))[np.argsort(-topomesh.wisp_property('length',1).values(list(topomesh.wisps(1))))] sorted_edge_length_edges = sorted_edge_length_edges[topomesh.wisp_property('length',1).values(sorted_edge_length_edges) > maximal_length] modified_edges = set() n_splits = 0 for eid in sorted_edge_length_edges: if not eid in modified_edges: #logging.info("".join([" " for l in range(loglevel)])+" <-- Splitting edge ",eid," [",np.min(map(len,map(np.unique,[list(topomesh.borders(1,e)) for e in topomesh.wisps(1)]))),"]" modified_edges = modified_edges.union(set(np.unique(np.concatenate([np.array(list(topomesh.borders(2,fid))) for fid in topomesh.regions(1,eid)])))) topomesh_split_edge(topomesh,eid,split_adjacent_faces=split_adjacent_faces) n_splits += 1 #logging.info("".join([" " for l in range(loglevel)])+" <-- Splitted edge ",eid," [",np.min(map(len,map(np.unique,[list(topomesh.borders(1,e)) for e in topomesh.wisps(1)]))),"]" logging.info("".join([" " for l in range(loglevel)])+"--> Splitted "+str(n_splits)+" edges") compute_topomesh_property(topomesh,'vertices',1) compute_topomesh_property(topomesh,'length',1) return n_splits
[docs]def property_topomesh_edge_collapse_optimization(topomesh, omega_energies=dict([('length',0.01),('error_quadrics',0.65)]), minimal_length=None, target_triangles=None, iterations=1, verbose=False, debug=False, loglevel=0): """Perform a topological optimization of a PropertyTopomesh using edge collapses. The topomesh is decimated by successive edge collapse operations in order to minimize an energy function, while reaching the double goal of eliminating edges under a minimal length and reducing the total number of triangles. The collapse operations are performed in the ascending order of energy variation until either the target number of triangles or the maximal number of *iterations* is reached. Parameters ---------- topomesh : :class:`cellcomplex.property_topomesh.PropertyTopomesh` The structure on which to perform the optimization omega_energies : dict The weights (float) associated to each energy term: * *error_quadics* energy minimizing the decimation error * *length* energy maximizing total edge length minimal_length : float The edge length under which the edges will be collapsed target_triangles : int The number of triangles to reach after decimation iterations : int Number of optimization passes to perform Returns ------- n_collapses : int Number of edges collapsed in the last iteration Note ------- The PropertyTopomesh passed as argument is updated. Warnings -------- The PropertyTopomesh must be a *triangular topomesh* (:meth:`cellcomplex.property_topomesh.analysis.is_triangular`) """ compute_topomesh_property(topomesh,'vertices',1) compute_topomesh_property(topomesh,'vertices',2) compute_topomesh_property(topomesh,'length',1) compute_topomesh_property(topomesh,'area',2) if minimal_length is None: target_length = np.percentile(topomesh.wisp_property('length',1).values(),70) minimal_length = 3./4. * target_length if target_triangles is None: target_triangles = topomesh.nb_wisps(2)/4 if 'error_quadrics' in omega_energies.keys(): maximal_quadrics_error = np.power(minimal_length,2.) compute_topomesh_property(topomesh,'faces',0) vertices_positions = topomesh.wisp_property('barycenter',0).values(topomesh.wisp_property('vertices',2).values()) normal_vectors = np.cross(vertices_positions[:,1]-vertices_positions[:,0],vertices_positions[:,2]-vertices_positions[:,0]) normal_norms = np.linalg.norm(normal_vectors,axis=1) normal_vectors = normal_vectors/normal_norms[:,np.newaxis] plane_d = -np.einsum('...ij,...ij->...i',normal_vectors,vertices_positions[:,0]) triangle_planes = np.concatenate([normal_vectors,plane_d[:,np.newaxis]],axis=1) triangle_plane_quadrics = array_dict(np.einsum('...i,...j->...ij',triangle_planes,triangle_planes),list(topomesh.wisps(2))) vertex_triangles = deepcopy(topomesh.wisp_property('faces',0)) # vertex_quadrics = triangle_plane_quadrics.values([t[0]+1 for t in vertex_triangles]) # homogeneous_positions = np.concatenate([topomesh.wisp_property('barycenter',0).values(),np.ones((topomesh.nb_wisps(0),1))],axis=1) for iterations in range(iterations): compute_topomesh_property(topomesh,'vertices',1) compute_topomesh_property(topomesh,'length',1) edge_energy_variation = np.zeros_like(list(topomesh.wisps(1)),np.float) if 'length' in omega_energies.keys(): edge_energy_variation += omega_energies['length']*(topomesh.wisp_property('length',1).values(list(topomesh.wisps(1)))) if 'error_quadrics' in omega_energies.keys(): edge_vertices = topomesh.wisp_property('vertices',1).values() edge_middles = topomesh.wisp_property('barycenter',0).values(edge_vertices).mean(axis=1) edge_middles_homogeneous = np.concatenate([edge_middles,np.ones((topomesh.nb_wisps(1),1))],axis=1) edge_vertex_faces = np.array([np.concatenate(vertex_triangles.values(v)) for v in edge_vertices]) edge_vertex_face_quadrics = np.array([triangle_plane_quadrics.values(t) for t in edge_vertex_faces]) edge_vertex_face_middles = np.array([ [e for t in e_v_t] for e,e_v_t in zip(edge_middles_homogeneous,edge_vertex_faces)]) edge_quadrics_errors = np.array([np.abs(np.einsum('...ij,...ij->...i',m,np.einsum('...ij,...j->...i',q,m))).sum() for q,m in zip(edge_vertex_face_quadrics,edge_vertex_face_middles)]) edge_quadrics_errors = array_dict(edge_quadrics_errors,list(topomesh.wisps(1))) edge_energy_variation += omega_energies['error_quadrics']*(edge_quadrics_errors.values(list(topomesh.wisps(1)))) sorted_energy_variation_edges = np.array(list(topomesh.wisps(1)))[np.argsort(edge_energy_variation)] if 'error_quadrics' in omega_energies.keys(): sorted_energy_variation_edges = sorted_energy_variation_edges[edge_quadrics_errors.values(sorted_energy_variation_edges) < maximal_quadrics_error] sorted_energy_variation_edges = sorted_energy_variation_edges[topomesh.wisp_property('length',1).values(sorted_energy_variation_edges) < minimal_length] # sorted_quadrics_errors_edges = sorted_quadrics_errors_edges[np.sort(edge_quadrics_errors.values()) < np.percentile(edge_quadrics_errors.values(),5)] modified_edges = set() n_collapses = 0 for eid in sorted_energy_variation_edges: if not eid in modified_edges and topomesh.nb_wisps(2)>target_triangles: # logging.info("".join([" " for l in range(loglevel)])+" <-- Collapsing edge ",eid," [",np.min(map(len,map(np.unique,[list(topomesh.borders(1,e)) for e in topomesh.wisps(1)]))),"]" modified_edges = modified_edges.union(set(np.unique(list(topomesh.border_neighbors(1,eid))))).union({eid}) collapsed = topomesh_collapse_edge(topomesh,eid,manifold=False) n_collapses += collapsed # logging.info("".join([" " for l in range(loglevel)])+" <-- Collapsed edge ",eid," [",np.min(map(len,map(np.unique,[list(topomesh.borders(1,e)) for e in topomesh.wisps(1)]))),"]" logging.info("".join([" " for l in range(loglevel)])+"--> Collapsed "+str(n_collapses)+" edges ["+str(np.min(list(map(len,list(map(np.unique,[list(topomesh.borders(1,e)) for e in topomesh.wisps(1)]))))))+"]") return n_collapses
[docs]def property_topomesh_isotropic_remeshing(initial_topomesh, maximal_length=None, minimal_length=None, collapse=False, iterations=1, verbose=False, debug=False, loglevel=0): """Perform a global optimization of a triangular PropertyTopomesh The topomesh is remeshed iteratively by applying only local topological operations and vertex deformation. Based on target minimal and maximal edge lengths, the mesh undergoes a split and collapse optimization, then a pass of edge flip optimization regularizes its topology and a Taubin smoothing its geometry. The process is repeated to perform a global isotropic remeshing of the initial mesh. Parameters ---------- input_topomesh : :class:`cellcomplex.property_topomesh.PropertyTopomesh` The initial structure on which to perform the remeshing maximal_length : float, *optional*: The edge length above which the edges will be splitted minimal_length : float, *optional* The edge length under which the edges will be collapsed collapse : bool Whether to use or not the edge collapse optimization iterations : int Number of optimization passes to perform Note -------- If no edge length is set, the target edge length *l* is set to the 8th decile of initial edge length and the heuristic setting *minimal_length* to 3/4 *l* and *maximal_length* to 4/3 *l* is used. Returns ------- topomesh : :class:`cellcomplex.property_topomesh.PropertyTopomesh` The remeshed structure Warnings -------- The PropertyTopomesh must be a *triangular topomesh* (:meth:`cellcomplex.property_topomesh.analysis.is_triangular`) """ topomesh = deepcopy(initial_topomesh) n_flips = topomesh.nb_wisps(1) n_splits = topomesh.nb_wisps(1) n_collapses = topomesh.nb_wisps(1) compute_topomesh_property(topomesh,'length',1) target_length = np.percentile(topomesh.wisp_property('length',1).values(),80) if maximal_length is None: maximal_length = 4./3. * target_length if minimal_length is None: minimal_length = 3./4. * target_length iteration = 0 while (n_flips+n_splits+n_collapses > topomesh.nb_wisps(1)/100.) and (iteration<iterations): if collapse: n_collapses = property_topomesh_edge_collapse_optimization(topomesh, minimal_length=minimal_length, iterations=1) else: n_collapses = 0 n_splits = property_topomesh_edge_split_optimization(topomesh, maximal_length=maximal_length, iterations=1) n_flips = property_topomesh_edge_flip_optimization(topomesh,omega_energies=dict([('neighborhood',0.65)]),simulated_annealing=False,iterations=5) property_topomesh_vertices_deformation(topomesh,omega_forces=dict([('taubin_smoothing',0.33)]),iterations=5) iteration += 1 return topomesh