Commit 56b235fe authored by dboe's avatar dboe
Browse files

merged rna to master

parents 3a254f1d b6a63426
Subproject commit 26fd6192f92b8b24463d4f81100f32eac88dab81
......@@ -4,7 +4,6 @@ from . import core
from . import bases
from . import lib
from .lib import *
from . import plotting
# __all__ = ['core', 'points3D']
from .core import Tensors, TensorFields, TensorMaps, Container
......
This diff is collapsed.
......@@ -116,5 +116,3 @@ else:
from . import symbolics
from . import sets
from . import util
from . import in_out
from . import log
from contextlib import contextmanager
import logging
import time
def progressbar(iterable, log=None):
"""
Examples:
>>> import logging
>>> import tfields
>>> import sys
>>> sys.modules['tqdm'] = None
>>> log = logging.getLogger(__name__)
>>> a = range(3)
>>> for value in tfields.lib.log.progressbar(a, log=log):
... _ = value * 3
"""
if log is None:
log = logging.getLogger()
try:
from tqdm import tqdm as progressor
tqdm_exists = True
except ImportError as err:
def progressor(iterable):
"""
dummy function. Doe nothing
"""
return iterable
tqdm_exists = False
try:
nTotal = len(iterable)
except:
nTotal = None
for i in progressor(iterable):
if not tqdm_exists:
if nTotal is None:
log.info("Progress: item {i}".format(**locals()))
else:
log.info("Progress: {i} / {nTotal}".format(**locals()))
yield i
@contextmanager
def timeit(msg="No Description", log=None, precision=1):
"""
Context manager for autmated timeing
Args:
msg (str): message to customize the log message
log (logger)
precision (int): show until 10^-<precision> digits
"""
if log is None:
log = logging.getLogger()
startTime = time.time()
log.log(logging.INFO, "-> " * 30)
message = "Starting Process: {0} ->".format(msg)
log.log(logging.INFO, message)
yield
log.log(logging.INFO, "\t\t\t\t\t\t<- Process Duration:"
"{value:1.{precision}f} s".format(value=time.time() - startTime,
precision=precision))
log.log(logging.INFO, "<- " * 30)
if __name__ == '__main__':
import doctest
doctest.testmod()
......@@ -8,6 +8,7 @@ part of tfields library
"""
import numpy as np
import sympy
import rna
import tfields
# obj imports
......@@ -554,11 +555,55 @@ class Mesh3D(tfields.TensorMaps):
"""
Check whether points lie within triangles with Barycentric Technique
see Triangles3D.in_triangles
If multiple requests are done on huge meshes,
this can be hugely optimized by requesting the property
self.tree or setting it to self.tree = <saved tree> before
calling in_faces
"""
masks = self.triangles().in_triangles(points, delta,
assign_multiple=assign_multiple)
key = 'mesh_tree'
if hasattr(self, '_cache') and key in self._cache:
log = logging.getLogger()
log.info(
"Using cached decision tree to speed up point - face mapping.")
masks = self.tree.in_faces(
points, delta, assign_multiple=assign_multiple)
else:
masks = self.triangles().in_triangles(
points, delta, assign_multiple=assign_multiple)
return masks
@property
def tree(self):
"""
Cached property to retrieve a bounding_box Searcher. This searcher can
hugely optimize 'in_faces' searches
Examples:
>>> mesh = tfields.Mesh3D.grid((0, 1, 3), (1, 2, 3), (2, 3, 3))
>>> _ = mesh.tree
>>> assert hasattr(mesh, '_cache')
>>> assert 'mesh_tree' in mesh._cache
>>> mask = mesh.in_faces(tfields.Points3D([[0.2, 1.2, 2.0]]),
... 0.00001)
>>> assert mask.sum() == 1 # one point in one triangle
"""
if not hasattr(self, '_cache'):
self._cache = {}
key = 'mesh_tree'
if key in self._cache:
tree = self._cache[key]
else:
tree = tfields.bounding_box.Searcher(self)
self._cache[key] = tree
return tree
@tree.setter
def tree(self, tree):
if not hasattr(self, '_cache'):
self._cache = {}
key = 'mesh_tree'
self._cache[key] = tree
def removeFaces(self, face_delete_mask):
"""
Remove faces where face_delete_mask is True
......@@ -574,11 +619,12 @@ class Mesh3D(tfields.TensorMaps):
Mesh3D: template (see cut), can be used as template to retrieve
sub_mesh from self instance
Examples:
>>> import tfields
>>> from sympy.abc import y
>>> mp = tfields.TensorFields([[0,1,2],[2,3,0],[3,2,5],[5,4,3]],
... [1, 2, 3, 4])
>>> m = tfields.Mesh3D([[0,0,0], [1,0,0], [1,1,0], [0,1,0], [0,2,0], [1,2,0]],
... maps=[mp])
>>> from sympy.abc import y
>>> m_cut = m.cut(y < 1.5, at_intersection='split')
>>> template = m.template(m_cut)
>>> assert m_cut.equal(m.cut(template))
......@@ -603,6 +649,138 @@ class Mesh3D(tfields.TensorMaps):
]
return inst
def project(self, tensor_field,
delta=None, merge_functions=None,
point_face_assignment=None,
return_point_face_assignment=False):
"""
project the points of the tensor_field to a copy of the mesh
and set the face values accord to the field to the maps field.
If no field is present in tensor_field, the number of points in a mesh
is counted.
Args:
tensor_field (Tensors | TensorFields)
delta (float | None): forwarded to Mesh3D.in_faces
merge_functions (callable): if multiple Tensors lie in the same face,
they are mapped with the merge_function to one value
point_face_assignment (np.array, dtype=int): array assigning each
point to a face. Each entry position corresponds to a point of the
tensor, each entry value is the index of the assigned face
return_point_face_assignment (bool): if true, return the
point_face_assignment
Examples:
>>> import tfields
>>> import numpy as np
>>> mp = tfields.TensorFields([[0,1,2],[2,3,0],[3,2,5],[5,4,3]],
... [1, 2, 3, 4])
>>> m = tfields.Mesh3D([[0,0,0], [1,0,0], [1,1,0], [0,1,0], [0,2,0], [1,2,0]],
... maps=[mp])
Projecting points onto the mesh gives the count
>>> points = tfields.Tensors([[0.5, 0.2, 0.0],
... [0.5, 0.02, 0.0],
... [0.5, 0.8, 0.0],
... [0.5, 0.8, 0.1]]) # not contained
>>> m_points = m.project(points, delta=0.01)
>>> assert m_points.maps[0].fields[0].equal([2, 1, 0, 0])
TensorFields with arbitrary size are projected,
combinging the fields automatically
>>> fields = [tfields.Tensors([1,3,42, -1]),
... tfields.Tensors([[0,1,2], [2,3,4], [3,4,5], [-1] * 3]),
... tfields.Tensors([[[0, 0]] * 2,
... [[2, 2]] * 2,
... [[3, 3]] * 2,
... [[9, 9]] * 2])]
>>> tf = tfields.TensorFields(points, *fields)
>>> m_tf = m.project(tf, delta=0.01)
>>> assert m_tf.maps[0].fields[0].equal([2, 42, np.nan, np.nan],
... equal_nan=True)
>>> assert m_tf.maps[0].fields[1].equal([[1, 2, 3],
... [3, 4, 5],
... [np.nan] * 3,
... [np.nan] * 3],
... equal_nan=True)
>>> assert m_tf.maps[0].fields[2].equal([[[1, 1]] * 2,
... [[3, 3]] * 2,
... [[np.nan, np.nan]] * 2,
... [[np.nan, np.nan]] * 2],
... equal_nan=True)
Returning the calculated point_face_assignment can speed up multiple
results
>>> m_tf, point_face_assignment = m.project(tf, delta=0.01,
... return_point_face_assignment=True)
>>> m_tf_fast = m.project(tf, delta=0.01, point_face_assignment=point_face_assignment)
>>> assert m_tf.equal(m_tf_fast, equal_nan=True)
"""
if not issubclass(type(tensor_field), tfields.Tensors):
tensor_field = tfields.TensorFields(tensor_field)
inst = self.copy()
# setup empty map fields and collect fields
n_faces = len(self.maps[0])
point_indices = np.arange(len(tensor_field))
if not hasattr(tensor_field, 'fields') or len(tensor_field.fields) == 0:
# if not fields is existing use int type fields and empty_map_fields
# in order to generate a sum
fields = [np.full(len(tensor_field), 1, dtype=int)]
empty_map_fields = [tfields.Tensors(np.full(n_faces, 0, dtype=int))]
if merge_functions is None:
merge_functions = [np.sum]
else:
fields = tensor_field.fields
empty_map_fields = []
for field in fields:
cls = type(field)
kwargs = {key: getattr(field, key) for key in cls.__slots__}
shape = (n_faces,) + field.shape[1:]
empty_map_fields.append(cls(np.full(shape, np.nan),
**kwargs))
if merge_functions is None:
merge_functions = [lambda x: np.mean(x, axis=0)] * len(fields)
# build point_face_assignment if not given.
if point_face_assignment is not None:
if len(point_face_assignment) != len(tensor_field):
raise ValueErrror("Template needs same lenght as tensor_field")
point_face_assignment = point_face_assignment
else:
mask = self.in_faces(tensor_field, delta=delta)
face_indices = np.arange(n_faces)
point_face_assignment = [face_indices[mask[p_index, :]]
for p_index in point_indices]
point_face_assignment = np.array([fa if len(fa) != 0 else [-1]
for fa in point_face_assignment])
point_face_assignment = point_face_assignment.reshape(-1)
point_face_assignment_set = set(point_face_assignment)
# merge the fields according to point_face_assignment
map_fields = []
for field, map_field, merge_function in \
zip(fields, empty_map_fields, merge_functions):
for i, f_index in enumerate(point_face_assignment_set):
# if i % 1000 == 0:
# print(i, len(point_face_assignment_set))
if f_index == -1:
# point could not be mapped
continue
point_in_face_indices = point_indices[point_face_assignment == f_index]
res = field[point_in_face_indices]
if len(res) == 1:
map_field[f_index] = res
else:
map_field[f_index] = merge_function(res)
map_fields.append(map_field)
inst.maps[0].fields = map_fields
if return_point_face_assignment:
return inst, point_face_assignment
return inst
def _cut_sympy(self, expression, at_intersection="remove", _in_recursion=False):
"""
Partition the mesh with the cuts given and return the template
......@@ -1004,27 +1182,11 @@ class Mesh3D(tfields.TensorMaps):
if map_index is not None:
if not len(self.maps[0]) == 0:
kwargs['color'] = self.maps[0].fields[map_index]
dim_defined = False
if 'axis' in kwargs:
dim_defined = True
if 'zAxis' in kwargs:
if kwargs['zAxis'] is not None:
kwargs['dim'] = 3
else:
kwargs['dim'] = 2
dim_defined = True
if 'dim' in kwargs:
dim_defined = True
if not dim_defined:
kwargs['dim'] = 2
return tfields.plotting.plot_mesh(self, self.faces, **kwargs)
return rna.plotting.plot_mesh(self, self.faces, **kwargs)
if __name__ == '__main__': # pragma: no cover
import doctest
doctest.run_docstring_examples(Mesh3D.cut, globals())
# doctest.testmod()
# doctest.run_docstring_examples(Mesh3D.project, globals())
# doctest.run_docstring_examples(Mesh3D.tree, globals())
doctest.testmod()
......@@ -9,6 +9,7 @@ part of tfields library
import tfields
import sympy
import numpy as np
import rna
class Planes3D(tfields.TensorFields):
......@@ -41,9 +42,11 @@ class Planes3D(tfields.TensorFields):
centers = np.array(self)
norms = np.array(self.fields[0])
for i in range(len(self)):
artists.append(tfields.plotting.plot_plane(centers[i],
norms[i],
**kwargs))
artists.append(
rna.plotting.plot_plane(
centers[i],
norms[i],
**kwargs))
# symbolic = self.symbolic()
# planeMeshes = [tfields.Mesh3D([pl.arbitrary_point(t=(i + 1) * 1. / 2 * np.pi)
# for i in range(4)],
......
"""
Core plotting tools for tfields library. Especially PlotOptions class
is basis for many plotting expansions
TODO:
* add other library backends. Do not restrict to mpl
"""
import warnings
import matplotlib.pyplot as plt
import matplotlib as mpl
import numpy as np
from .mpl import *
from six import string_types
def set_default(dictionary, attr, value):
"""
Set defaults to a dictionary
"""
if attr not in dictionary:
dictionary[attr] = value
class PlotOptions(object):
"""
processing kwargs for plotting functions and providing easy
access to axis, dimension and plotting method as well as indices
for array choice (x..., y..., zAxis)
"""
def __init__(self, kwargs):
kwargs = dict(kwargs)
self.axis = kwargs.pop('axis', None)
self.dim = kwargs.pop('dim', None)
self.method = kwargs.pop('methodName', None)
self.setXYZAxis(kwargs)
self.plot_kwargs = kwargs
@property
def method(self):
"""
Method for plotting. Will be callable together with plot_kwargs
"""
return self._method
@method.setter
def method(self, methodName):
if not isinstance(methodName, str):
self._method = methodName
else:
self._method = getattr(self.axis, methodName)
@property
def dim(self):
"""
axis dimension
"""
return self._dim
@dim.setter
def dim(self, dim):
if dim is None:
if self._axis is None:
dim = 2
dim = axis_dim(self._axis)
elif self._axis is not None:
if not dim == axis_dim(self._axis):
raise ValueError("Axis and dim argument are in conflict.")
if dim not in [2, 3]:
raise NotImplementedError("Dimensions other than 2 or 3 are not supported.")
self._dim = dim
@property
def axis(self):
"""
The plt.Axis object that belongs to this instance
"""
if self._axis is None:
return gca(self._dim)
else:
return self._axis
@axis.setter
def axis(self, axis):
self._axis = axis
def setXYZAxis(self, kwargs):
self._xAxis = kwargs.pop('xAxis', 0)
self._yAxis = kwargs.pop('yAxis', 1)
zAxis = kwargs.pop('zAxis', None)
if zAxis is None and self.dim == 3:
indicesUsed = [0, 1, 2]
indicesUsed.remove(self._xAxis)
indicesUsed.remove(self._yAxis)
zAxis = indicesUsed[0]
self._zAxis = zAxis
def getXYZAxis(self):
return self._xAxis, self._yAxis, self._zAxis
def setVminVmaxAuto(self, vmin, vmax, scalars):
"""
Automatically set vmin and vmax as min/max of scalars
but only if vmin or vmax is None
"""
if scalars is None:
return
if len(scalars) < 2:
warnings.warn("Need at least two scalars to autoset vmin and/or vmax!")
return
if vmin is None:
vmin = min(scalars)
self.plot_kwargs['vmin'] = vmin
if vmax is None:
vmax = max(scalars)
self.plot_kwargs['vmax'] = vmax
def getNormArgs(self, vminDefault=0, vmaxDefault=1, cmapDefault=None):
if cmapDefault is None:
cmapDefault = plt.rcParams['image.cmap']
cmap = self.get('cmap', cmapDefault)
vmin = self.get('vmin', vminDefault)
vmax = self.get('vmax', vmaxDefault)
return cmap, vmin, vmax
def format_colors(self, colors, fmt='rgba', length=None):
"""
format colors according to fmt argument
Args:
colors (list/one value of rgba tuples/int/float/str): This argument will
be interpreted as color
fmt (str): rgba | hex | norm
length (int/None): if not None: correct colors lenght
Returns:
colors in fmt
"""
hasIter = True
if not hasattr(colors, '__iter__') or isinstance(colors, string_types):
# colors is just one element
hasIter = False
colors = [colors]
if fmt == 'norm':
if hasattr(colors[0], '__iter__'):
# rgba given but norm wanted
cmap, vmin, vmax = self.getNormArgs(cmapDefault='NotSpecified',
vminDefault=None,
vmaxDefault=None)
colors = to_scalars(colors, cmap, vmin, vmax)
self.plot_kwargs['vmin'] = vmin
self.plot_kwargs['vmax'] = vmax
self.plot_kwargs['cmap'] = cmap
elif fmt == 'rgba':
if isinstance(colors[0], string_types):
# string color defined
colors = [mpl.colors.to_rgba(color) for color in colors]
else:
# norm given rgba wanted
cmap, vmin, vmax = self.getNormArgs(cmapDefault='NotSpecified',
vminDefault=None,
vmaxDefault=None)
self.setVminVmaxAuto(vmin, vmax, colors)
# update vmin and vmax
cmap, vmin, vmax = self.getNormArgs()
colors = to_colors(colors,
vmin=vmin,
vmax=vmax,
cmap=cmap)
elif fmt == 'hex':
colors = [mpl.colors.to_hex(color) for color in colors]
else:
raise NotImplementedError("Color fmt '{fmt}' not implemented."
.format(**locals()))
if length is not None:
# just one colors value given
if len(colors) != length:
if not len(colors) == 1:
raise ValueError("Can not correct color length")
colors = list(colors)
colors *= length
elif not hasIter:
colors = colors[0]
colors = np.array(colors)
return colors
def delNormArgs(self):
self.plot_kwargs.pop('vmin', None)
self.plot_kwargs.pop('vmax', None)
self.plot_kwargs.pop('cmap', None)
def getSortedLabels(self, labels):
"""
Returns the labels corresponding to the axes
"""
return [labels[i] for i in self.getXYZAxis() if i is not None]
def get(self, attr, default=None):
return self.plot_kwargs.get(attr, default)
def pop(self, attr, default=None):
return self.plot_kwargs.pop(attr, default)
def set(self, attr, value):
self.plot_kwargs[attr] = value
def set_default(self, attr, value):
set_default(self.plot_kwargs, attr, value)
def retrieve(self, attr, default=None, keep=True):
if keep:
return self.get(attr, default)
else:
return self.pop(attr, default)
def retrieve_chain(self, *args, **kwargs):
default = kwargs.pop('default', None)
keep = kwargs.pop('keep', True)
if len(args) > 1:
return self.retrieve(args[0],
self.retrieve_chain(*args[1:],
default=default,
keep=keep),
keep=keep)
if len(args) != 1:
raise ValueError("Invalid number of args ({0})".format(len(args)))
return self.retrieve(args[0], default, keep=keep)
if __name__ == '__main__':
import doctest
doctest.testmod()
This diff is collapsed.
legend.numpoints : 1 # the number of points in the legend line
legend.scatterpoints : 1
figure.figsize: 12.8, 8.8
axes.labelsize: 20
axes.titlesize: 24
xtick.labelsize: 20
ytick.labelsize: 20
legend.fontsize: 20
grid.linewidth: 1.6
lines.linewidth: 2.8
patch.linewidth: 0.48
lines.markersize: 11.2
lines.markeredgewidth: 0