From 08eb56916bd0357a24b599c098c3e868a21b8a95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20B=C3=B6ckenhoff=20=28Laptop=29?= <dboe@ipp.mpg.de> Date: Sat, 19 May 2018 16:33:07 +0200 Subject: [PATCH] added array_Set_ops, bases as file --- tfields/array_set_ops.py | 107 ++++++ tfields/bases/__init__.py | 151 +++++++++ tfields/bases/manifold_3.py | 83 +++++ tfields/points3D.py | 630 ++++++++++++++++++++++++++++++++++++ 4 files changed, 971 insertions(+) create mode 100644 tfields/array_set_ops.py create mode 100644 tfields/bases/__init__.py create mode 100644 tfields/bases/manifold_3.py create mode 100644 tfields/points3D.py diff --git a/tfields/array_set_ops.py b/tfields/array_set_ops.py new file mode 100644 index 0000000..54025ac --- /dev/null +++ b/tfields/array_set_ops.py @@ -0,0 +1,107 @@ +#!/usr/bin/env +# encoding: utf-8 +""" +Author: Daniel Boeckenhoff +Mail: daniel.boeckenhoff@ipp.mpg.de + +Collection of additional numpy functions +part of tfields library +""" +import numpy as np +import loggingTools +logger = loggingTools.Logger(__name__) + + +def convert_nan(array, value=0.): + """ + Replace all occuring NaN values by value + """ + nanIndices = np.isnan(array) + array[nanIndices] = value + + +def view1D(ar): + """ + https://stackoverflow.com/a/44999009/ @Divakar + """ + ar = np.ascontiguousarray(ar) + voidDt = np.dtype((np.void, ar.dtype.itemsize * ar.shape[1])) + return ar.view(voidDt).ravel() + + +def argsort_unique(idx): + """ + https://stackoverflow.com/a/43411559/ @Divakar + """ + n = idx.size + sidx = np.empty(n, dtype=int) + sidx[idx] = np.arange(n) + return sidx + + +def duplicates(ar, axis=None): + """ + View1D version of duplicate search + Speed up version after + https://stackoverflow.com/questions/46284660 \ + /python-numpy-speed-up-2d-duplicate-search/46294916#46294916 + Args: + ar (array_like): array + other args: see np.isclose + Examples: + >>> import tfields + >>> import numpy as np + >>> a = np.array([[1, 0, 0], [1, 0, 0], [2, 3, 4]]) + >>> tfields.duplicates(a, axis=0) + array([0, 0, 2]) + + Returns: + list of int: int is pointing to first occurence of unique value + """ + if axis != 0: + raise NotImplementedError() + sidx = np.lexsort(ar.T) + b = ar[sidx] + + groupIndex0 = np.flatnonzero((b[1:] != b[:-1]).any(1)) + 1 + groupIndex = np.concatenate(([0], groupIndex0, [b.shape[0]])) + ids = np.repeat(range(len(groupIndex) - 1), np.diff(groupIndex)) + sidx_mapped = argsort_unique(sidx) + ids_mapped = ids[sidx_mapped] + + grp_minidx = sidx[groupIndex[:-1]] + out = grp_minidx[ids_mapped] + return out + + +def index(ar, entry, rtol=0, atol=0, equal_nan=False, axis=None): + """ + Examples: + >>> import tfields + >>> a = np.array([[1, 0, 0], [1, 0, 0], [2, 3, 4]]) + >>> tfields.index(a, [2, 3, 4], axis=0) + 2 + + >>> a = np.array([[1, 0, 0], [2, 3, 4]]) + >>> tfields.index(a, 4) + 5 + + Returns: + list of int: indices of point occuring + """ + if axis is None: + ar = ar.flatten() + elif axis != 0: + raise NotImplementedError() + for i, part in enumerate(ar): + isclose = np.isclose(part, entry, rtol=rtol, atol=atol, + equal_nan=equal_nan) + if axis is not None: + isclose = isclose.all() + if isclose: + return i + + +if __name__ == '__main__': + import doctest + doctest.testmod() diff --git a/tfields/bases/__init__.py b/tfields/bases/__init__.py new file mode 100644 index 0000000..e45d03e --- /dev/null +++ b/tfields/bases/__init__.py @@ -0,0 +1,151 @@ +#!/usr/bin/env +# encoding: utf-8 +""" +Author: Daniel Boeckenhoff +Mail: daniel.boeckenhoff@ipp.mpg.de + +part of tfields library +Tools for sympy coordinate transformation +""" +import tfields +import numpy as np +import sympy +import sympy.diffgeom +from six import string_types +import warnings + +from .manifold_3 import CARTESIAN, CYLINDER, SPHERICAL +from .manifold_3 import cartesian, cylinder, spherical + + +def get_coord_system(base): + """ + Args: + base (str or sympy.diffgeom.get_coord_system) + Return: + sympy.diffgeom.get_coord_system + """ + if isinstance(base, string_types): + base = getattr(tfields.bases, base) + if not isinstance(base, sympy.diffgeom.CoordSystem): + raise TypeError("Wrong type of coordSystem base.") + return base + + +def get_coord_system_name(base): + """ + Args: + base (str or sympy.diffgeom.get_coord_system) + Returns: + str: name of base + """ + if isinstance(base, sympy.diffgeom.CoordSystem): + base = str(getattr(base, 'name')) + # if not (isinstance(base, string_types) or base is None): + # baseType = type(base) + # raise ValueError("Coordinate system must be string_type." + # " Retrieved value '{base}' of type {baseType}." + # .format(**locals())) + return base + + +def lambdifiedTrafo(base_old, base_new): + """ + Args: + base_old (sympy.CoordSystem) + base_new (sympy.CoordSystem) + + Examples: + >>> import numpy as np + >>> import tfields + + Transform cartestian to cylinder or spherical + >>> a = np.array([[3,4,0]]) + + >>> trafo = tfields.bases.lambdifiedTrafo(tfields.bases.cartesian, + ... tfields.bases.cylinder) + >>> new = np.concatenate([trafo(*coords).T for coords in a]) + >>> assert new[0, 0] == 5 + + >>> trafo = tfields.bases.lambdifiedTrafo(tfields.bases.cartesian, + ... tfields.bases.spherical) + >>> new = np.concatenate([trafo(*coords).T for coords in a]) + >>> assert new[0, 0] == 5 + + """ + coords = tuple(base_old.coord_function(i) for i in range(base_old.dim)) + f = sympy.lambdify(coords, + base_old.coord_tuple_transform_to(base_new, + list(coords)), + modules='numpy') + return f + + +def transform(array, base_old, base_new): + """ + Transform the input array in place + Args: + array (np.ndarray) + base_old (str or sympy.CoordSystem): + base_new (str or sympy.CoordSystem): + Examples: + Cylindrical coordinates + >>> import tfields + >>> print "TEST" + >>> cart = np.array([[0, 0, 0], + ... [1, 0, 0], + ... [1, 1, 0], + ... [0, 1, 0], + ... [-1, 1, 0], + ... [-1, 0, 0], + ... [-1, -1, 0], + ... [0, -1, 0], + ... [1, -1, 0], + ... [0, 0, 1]]) + >>> cyl = tfields.bases.transform(cart, 'cartesian', 'cylinder') + >>> cyl + >>> import npTools + >>> pnp = npTools.Points3D(cart) + >>> pnp.transform('cylinder') + >>> pnp + + Transform cylinder to spherical. No connection is defined so routing via + cartesian + >>> import numpy as np + >>> import tfields + >>> b = np.array([[5, np.arctan(4. / 3), 0]]) + >>> newB = tfields.bases.transform(b, 'cylinder', 'spherical') + >>> assert newB[0, 0] == 5 + >>> assert round(newB[0, 1], 10) == round(b[0, 1], 10) + + """ + base_old = get_coord_system(base_old) + base_new = get_coord_system(base_new) + if base_new not in base_old.transforms: + for baseTmp in base_new.transforms: + if baseTmp in base_old.transforms: + transform(array, base_old, baseTmp) + transform(array, baseTmp, base_new) + return + raise ValueError("Trafo not found.") + + # very fast trafos in numpy only + shortTrafo = None + try: + shortTrafo = getattr(base_old, 'to_{base_new.name}'.format(**locals())) + except AttributeError: + pass + if shortTrafo: + shortTrafo(array) + return + + # trafo via lambdified sympy expressions + trafo = tfields.bases.lambdifiedTrafo(base_old, base_new) + with warnings.catch_warnings(): + warnings.filterwarnings('ignore', message="invalid value encountered in double_scalars") + array[:] = np.concatenate([trafo(*coords).T for coords in array]) + + +if __name__ == '__main__': # pragma: no cover + import doctest + doctest.testmod() diff --git a/tfields/bases/manifold_3.py b/tfields/bases/manifold_3.py new file mode 100644 index 0000000..337f1fd --- /dev/null +++ b/tfields/bases/manifold_3.py @@ -0,0 +1,83 @@ +import tfields +import sympy +import numpy as np + +CARTESIAN = 'cartesian' +CYLINDER = 'cylinder' +SPHERICAL = 'spherical' + +m = sympy.diffgeom.Manifold('M', 3) +patch = sympy.diffgeom.Patch('P', m) + +# cartesian +x, y, z = sympy.symbols('x, y, z') +cartesian = sympy.diffgeom.CoordSystem(CARTESIAN, patch, ['x', 'y', 'z']) + +# cylinder + +R, Phi, Z = sympy.symbols('R, Phi, Z') +cylinder = sympy.diffgeom.CoordSystem(CYLINDER, patch, ['R', 'Phi', 'Z']) +cylinder.connect_to(cartesian, + [R, Phi, Z], + [R * sympy.cos(Phi), + R * sympy.sin(Phi), + Z], + inverse=False, + fill_in_gaps=False) +cartesian.connect_to(cylinder, + [x, y, z], + [sympy.sqrt(x**2 + y**2), + sympy.Piecewise( + (sympy.pi, ((x < 0) & sympy.Eq(y, 0))), + (0., sympy.Eq(sympy.sqrt(x**2 + y**2), 0)), + (sympy.sign(y) * sympy.acos(x / sympy.sqrt(x**2 + y**2)), True)), + z], + inverse=False, + fill_in_gaps=False) + + +def cylinder_to_cartesian(array): + rPoints = np.copy(array[:, 0]) + phiPoints = np.copy(array[:, 1]) + array[:, 0] = rPoints * np.cos(phiPoints) + array[:, 1] = rPoints * np.sin(phiPoints) + + +def cartesian_to_cylinder(array): + x_vals = np.copy(array[:, 0]) + y_vals = np.copy(array[:, 1]) + problemPhiIndices = np.where((x_vals < 0) & (y_vals == 0)) + array[:, 0] = np.sqrt(x_vals**2 + y_vals**2) # r + np.seterr(divide='ignore', invalid='ignore') + # phi + array[:, 1] = np.sign(y_vals) * np.arccos(x_vals / array[:, 0]) + np.seterr(divide='warn', invalid='warn') + array[:, 1][problemPhiIndices] = np.pi + tfields.convert_nan(array, 0.) # for cases like cartesian 0, 0, 1 + + +cylinder.to_cartesian = cylinder_to_cartesian +cartesian.to_cylinder = cartesian_to_cylinder + +# spherical +r, phi, theta = sympy.symbols('r, phi, theta') +spherical = sympy.diffgeom.CoordSystem(SPHERICAL, patch, ['r', 'phi', 'theta']) +spherical.connect_to(cartesian, + [r, phi, theta], + [r * sympy.cos(phi) * sympy.sin(theta), + r * sympy.sin(phi) * sympy.sin(theta), + r * sympy.cos(theta)], + inverse=False, + fill_in_gaps=False) +cartesian.connect_to(spherical, + [x, y, z], + [sympy.sqrt(x**2 + y**2 + z**2), + sympy.Piecewise( + (0., (sympy.Eq(x, 0) & sympy.Eq(y, 0))), + (sympy.atan(y / x), True)), + sympy.Piecewise( + (0., sympy.Eq(x**2 + y**2 + z**2, 0)), + # (0., (sympy.Eq(x, 0) & sympy.Eq(y, 0) & sympy.Eq(z, 0))), + (sympy.acos(z / sympy.sqrt(x**2 + y**2 + z**2)), True))], + inverse=False, + fill_in_gaps=False) diff --git a/tfields/points3D.py b/tfields/points3D.py new file mode 100644 index 0000000..862947b --- /dev/null +++ b/tfields/points3D.py @@ -0,0 +1,630 @@ +#!/usr/bin/env +# encoding: utf-8 +""" +Author: Daniel Boeckenhoff +Mail: daniel.boeckenhoff@ipp.mpg.de + +basic threedimensional tensor +""" +import tfields +import numpy as np +import os +import osTools +import ioTools +import pyTools +import mplTools as mpt +import sympy +import scipy as sp +import scipy.spatial # NOQA: F401 +import scipy.stats as stats +import warnings +import loggingTools +logger = loggingTools.Logger(__name__) + + +class Points3D(tfields.Tensors): + # pylint: disable=R0904 + """ + Points3D is a general class for 3D Point operations and storage. + Points are stored in np.arrays of shape (len, 3). + Thus the three coordinates of the Points stay close. + + Args: + points3DInstance -> copy constructor + [points3DInstance1, points3DInstance2, ...] -> coordSys are correctly treated + list of coordinates (see examples) + + Kwargs: + coordSys (str): + Use tfields.bases.CARTESIAN -> x, y, z + Use tfields.bases.CYLINDER -> r, phi, z + Use tfields.bases.SPHERICAL -> r, phi, theta + + Examples: + Initializing with 3 vectors + >>> import tfields + >>> import numpy as np + >>> p1 = tfields.Points3D([[1., 2., 3.], [4., 5., 6.], [1, 2, -6]]) + >>> p1 + Points3D([[ 1., 2., 3.], + [ 4., 5., 6.], + [ 1., 2., -6.]]) + + Initializing with list of coordinates + >>> p2 = tfields.Points3D(np.array([[1., 2., 3., 4, 5,], + ... [4., 5., 6., 7, 8], + ... [1, 2, -6, -1, 0]]).T) + >>> p2 + Points3D([[ 1., 4., 1.], + [ 2., 5., 2.], + [ 3., 6., -6.], + [ 4., 7., -1.], + [ 5., 8., 0.]]) + >>> p2.transform(tfields.bases.CYLINDER) + >>> p2 + Points3D([[ 4.12310563, 1.32581766, 1. ], + [ 5.38516481, 1.19028995, 2. ], + [ 6.70820393, 1.10714872, -6. ], + [ 8.06225775, 1.05165021, -1. ], + [ 9.43398113, 1.01219701, 0. ]]) + + Copy constructor with one instance preserves coordSys of instance + >>> assert tfields.Points3D(p2).coordSys == p2.coordSys + + Unless you specify other: + >>> tfields.Points3D(p2, coordSys=tfields.bases.CARTESIAN) + Points3D([[ 1., 4., 1.], + [ 2., 5., 2.], + [ 3., 6., -6.], + [ 4., 7., -1.], + [ 5., 8., 0.]]) + + Copy constructor with many instances chooses majority of coordinates + systems to avoid much transformation + >>> tfields.Points3D.merged(p1, p2, p1) + Points3D([[ 1., 2., 3.], + [ 4., 5., 6.], + [ 1., 2., -6.], + [ 1., 4., 1.], + [ 2., 5., 2.], + [ 3., 6., -6.], + [ 4., 7., -1.], + [ 5., 8., 0.], + [ 1., 2., 3.], + [ 4., 5., 6.], + [ 1., 2., -6.]]) + >>> p1.transform(tfields.bases.CYLINDER) + + ... unless specified other. Here it is specified + >>> tfields.Points3D.merged(p1, p2, coordSys=tfields.bases.CYLINDER) + Points3D([[ 2.23606798, 1.10714872, 3. ], + [ 6.40312424, 0.89605538, 6. ], + [ 2.23606798, 1.10714872, -6. ], + [ 4.12310563, 1.32581766, 1. ], + [ 5.38516481, 1.19028995, 2. ], + [ 6.70820393, 1.10714872, -6. ], + [ 8.06225775, 1.05165021, -1. ], + [ 9.43398113, 1.01219701, 0. ]]) + + Shape is always (..., 3) + >>> p = tfields.Points3D([[1., 2., 3.], [4., 5., 6.], + ... [1, 2, -6], [-5, -5, -5], [1,0,-1], [0,1,-1]]) + >>> p.shape + (6, 3) + + Empty array will create an ndarray of the form (0, 3) + >>> tfields.Points3D([]) + Points3D([], shape=(0, 3), dtype=float64) + + Use it as np.ndarrays -> masking etc. is inherited + >>> mask = np.array([True, False, True, False, False, True]) + >>> mp = p[mask].copy() + + Copy constructor + >>> mp + Points3D([[ 1., 2., 3.], + [ 1., 2., -6.], + [ 0., 1., -1.]]) + >>> tfields.Points3D(mp) + Points3D([[ 1., 2., 3.], + [ 1., 2., -6.], + [ 0., 1., -1.]]) + + Coordinate system is implemented. Default is cartesian + >>> p.transform(tfields.bases.CYLINDER); p + Points3D([[ 2.23606798, 1.10714872, 3. ], + [ 6.40312424, 0.89605538, 6. ], + [ 2.23606798, 1.10714872, -6. ], + [ 7.07106781, -2.35619449, -5. ], + [ 1. , 0. , -1. ], + [ 1. , 1.57079633, -1. ]]) + >>> p.transform(tfields.bases.CARTESIAN); p + Points3D([[ 1.000000e+00, 2.000000e+00, 3.000000e+00], + [ 4.000000e+00, 5.000000e+00, 6.000000e+00], + [ 1.000000e+00, 2.000000e+00, -6.000000e+00], + [-5.000000e+00, -5.000000e+00, -5.000000e+00], + [ 1.000000e+00, 0.000000e+00, -1.000000e+00], + [ 6.123234e-17, 1.000000e+00, -1.000000e+00]]) + >>> p.getPhi()*180/3.1415 + Points3D([ 63.43681974, 51.34170594, 63.43681974, -135.00398161, + 0. , 90.00265441]) + + Appending to the array with numpy.append + >>> a = tfields.Points3D([[1,2,3]]) + >>> b = tfields.Points3D([[4,5,6]]) + >>> c = np.append(a, b, 0) + >>> c + array([[1., 2., 3.], + [4., 5., 6.]]) + + """ + def __new__(cls, tensors, **kwargs): + if not issubclass(type(tensors), Points3D): + kwargs['dim'] = 3 + return super(Points3D, cls).__new__(cls, tensors, **kwargs) + + def contains(self, other): + """ + Inspired by a speed argument @ + stackoverflow.com/questions/14766194/testing-whether-a-numpy-array-contains-a-given-row + Examples: + >>> p = tfields.Points3D([[1,2,3], [4,5,6], [6,7,8]]) + >>> p.contains([4,5,6]) + True + + """ + return any(np.equal(self, other).all(1)) + + def save(self, filePath, extension='npz', **kwargs): + """ + save a tensors object. + filePath (str or buffer) + [extension (str): only needed if filePath is buffer] + """ + if not osTools.isBuffer(filePath): + extension = ioTools.getExtension(filePath) + if extension not in ['npz', 'obj', 'txt', 'ply']: + raise NotImplementedError("Extension {0} not implemented.".format(extension)) + + # create from buffer, if file is tar file + if osTools.isTarPath(filePath): + raise NotImplementedError("Saving to tar file not intended " + "since we want to make the tar-writing outside.") + + if extension == 'npz': + self.savez(filePath) + if extension == 'obj': + return self.saveObj(filePath, **kwargs) + if extension == 'txt': + self.saveTxt(filePath) + if extension == 'ply': + self.saveTxt(filePath) + else: + raise NotImplementedError("Do not understand the format of file {0}".format(filePath)) + + def savePly(self, filePath): + from plyfile import PlyData, PlyElement + with self.tmp_transform(tfields.bases.CARTESIAN): + vertices = np.array([tuple(x) for x in self], + dtype=[('x', '<f4'), ('y', '<f4'), ('z', '<f4')]) + element = PlyElement.describe(vertices, 'vertex') + + filePath = osTools.resolve(filePath) + PlyData([element]).write(filePath) + + def saveTxt(self, filePath): + """ + Save obj as .txt file + """ + if self.coordSys != tfields.bases.CARTESIAN: + cpy = self.copy() + cpy.transform(tfields.bases.CARTESIAN) + else: + cpy = self + with ioTools.TextFile(filePath, 'w') as f: + f.writeMatrix(self, seperator=' ', lineBreak='\n') + + def saveObj(self, filePath, **kwargs): + """ + Save obj as wavefront/.obj file + """ + obj = kwargs.pop('object', None) + group = kwargs.pop('group', None) + + filePath = filePath.replace('.obj', '') + fileDir, fileName = os.path.split(filePath) + with open(osTools.resolve(filePath + '.obj'), 'w') as f: + f.write("# File saved with tensors Points3D.saveObj method\n\n") + if obj is not None: + f.write("o {0}\n".format(obj)) + if group is not None: + f.write("g {0}\n".format(group)) + for vertex in self: + f.write("v {v[0]} {v[1]} {v[2]}\n".format(v=vertex)) + + def savez(self, filePath): + """ + Args: + filePath (open file or str/unicode): destination to save file to. + Examples: + >>> from tempfile import NamedTemporaryFile + >>> outFile = NamedTemporaryFile(suffix='.npz') + >>> p = tfields.Points3D([[1., 2., 3.], [4., 5., 6.], [1, 2, -6]]) + >>> p.savez(outFile.name) + >>> _ = outFile.seek(0) + >>> p1 = tfields.Points3D.createFromFile(outFile.name) + >>> p.equal(p1) + True + + """ + kwargs = {} + for attr in self.__slots__: + if attr == '_cache': + # do not save the cache + continue + elif not hasattr(self, attr): + # attribute in __slots__ not found. + warnings.warn("When saving instance of class {0} Attribute {1} not set." + "This Attribute is not saved.".format(self.__class__, attr), Warning) + else: + kwargs[attr] = getattr(self, attr) + # kwargs = dict(zip(self.__slots__, [getattr(self, attr) for attr in self.__slots__])) + + # resolve paths. Try except for buffer objects + try: + filePath = osTools.resolve(filePath) + except AttributeError: + pass + except: + raise + np.savez(filePath, self, **kwargs) + + @classmethod + def createFromObjFile(cls, filePath, *groupNames): + """ + Factory method + Given a filePath to a obj/wavefront file, construct the object + """ + ioCls = ioTools.ObjFile + with ioCls(filePath, 'r') as f: + f.process() + vertices = f.getVertices(*groupNames) + + log = logger.new() + if issubclass(cls, tfields.Triangles3D): + raise NotImplementedError("reading Triangles3D from obj. Use faces" + "attribute and the Mesh3D implementation.") + elif issubclass(cls, Points3D): + pass + else: + raise NotImplementedError("{cls} not implemented in createFromObjFile" + .format(**locals())) + return cls.__new__(cls, vertices) + + @classmethod + def createFromTxtFile(cls, filePath): + """ + Factory method + Given a filePath to a txt file, construct the object + """ + ioCls = ioTools.HaukeFile + with ioCls(filePath, 'r') as f: + f.process() + return f.getPoints3D() + + @classmethod + def createFromNpzFile(cls, filePath, **kwargs): + """ + Factory method + Given a filePath to a npz file, construct the object + """ + npFile = np.load(filePath) + keys = npFile.keys() + array = npFile['arr_0'] + additionalKwargs = {key: npFile[key] for key in keys if key not in ['arr_0', '_cache']} + if additionalKwargs is not None: + kwargs.update(additionalKwargs) + return cls.__new__(cls, array, **kwargs) + + @classmethod + def createFromInpFile(cls, filePath): + """ + Factory method + Given a filePath to a inp file, construct the object + """ + import ioTools.transcoding + transcoding = ioTools.transcoding.getTranscoding('inp') + content = transcoding.read(filePath) + part = content['parts'][0] + vertices = np.array([part['x'], part['y'], part['z']]).T / 1000 + indices = np.array(part['nodeIndex']) - 1 + if not list(indices) == range(len(indices)): + raise ValueError("node index skipped") + return cls(vertices) + + @classmethod + def createFromFile(cls, filePath, *args, **kwargs): + """ + load a file as a tensors object. + Args: + filePath (str or buffer) + *args: + forwarded to extension specific method + **kwargs: + extension (str): only needed if filePath is buffer + ... remaining:forwarded to extension specific method + """ + extension = kwargs.pop('extension', 'npz') + if not osTools.isBuffer(filePath): + filePath = osTools.resolve(filePath) + extension = ioTools.getExtension(filePath) + + # create from buffer, if file is tar file + if osTools.isTarPath(filePath): + with ioTools.TarHolder() as th: + buf = th.read(filePath) + inst = cls.createFromFile(buf, extension=extension, *args, **kwargs) + return inst + + try: + fun = getattr(cls, 'createFrom{e}File'.format(e=extension.capitalize())) + except: + raise NotImplementedError("Do not understand the format of file {0}".format(filePath)) + return fun(filePath, *args, **kwargs) + + def indices(self, point): + """ + Returns: + list of int: indices of point occuring + """ + indices = [] + for i, p in enumerate(self): + if all(p == point): + indices.append(i) + return indices + + def index(self, point): + """ + Args: + point + Returns: + int: index of point occuring + """ + indices = self.indices(point) + if len(indices) == 0: + return None + if len(indices) == 1: + return indices[0] + raise ValueError("Multiple occurences of value %s" % point) + + def mirrorCoordinate(self, coordinate, condition=None): + """ + Examples: + >>> p = tfields.Points3D([[1., 2., 3.], [4., 5., 6.], [1, 2, -6]]) + >>> p.mirrorCoordinate(1) + >>> p + Points3D([[ 1., -2., 3.], + [ 4., -5., 6.], + [ 1., -2., -6.]]) + + multiple coordinates can be mirrored. Eg. a point reflection would be + >>> p = tfields.Points3D([[1., 2., 3.], [4., 5., 6.], [1, 2, -6]]) + >>> p.mirrorCoordinate([0,2]) + >>> p + Points3D([[-1., 2., -3.], + [-4., 5., -6.], + [-1., 2., 6.]]) + + You can give a condition as mask or as str. + The mirroring will only be applied to the points meeting the condition. + >>> x, y, z = sympy.symbols('x y z') + >>> p.mirrorCoordinate([0,2], y > 3) + >>> p + Points3D([[-1., 2., -3.], + [ 4., 5., 6.], + [-1., 2., 6.]]) + + """ + if condition is None: + condition = np.array([True for i in range(len(self))]) + elif isinstance(condition, sympy.Basic): + condition = self.getMask(condition) + if isinstance(coordinate, list) or isinstance(coordinate, tuple): + for c in coordinate: + self.mirrorCoordinate(c, condition) + elif isinstance(coordinate, int): + self[:, coordinate][condition] *= -1 + else: + raise TypeError() + + def getMask(self, cutExpression=None, coordSys=None): + """ + Args: + cutExpression (sympy logical expression) + coordSys (str): coordSys to evaluate the expression in. + Returns: np.array of dtype bool with lenght of number of points in self. + This array is True, where cutExpression evaluates True. + Examples: + >>> x, y, z = sympy.symbols('x y z') + >>> p = tfields.Points3D([[1., 2., 3.], [4., 5., 6.], [1, 2, -6], + ... [-5, -5, -5], [1,0,-1], [0,1,-1]]) + >>> p.getMask(x > 0) + array([ True, True, True, False, True, False], dtype=bool) + + And combination + >>> p.getMask((x > 0) & (y < 3)) + array([ True, False, True, False, True, False], dtype=bool) + + Or combination + >>> p.getMask((x > 0) | (y > 3)) + array([ True, True, True, False, True, False], dtype=bool) + + """ + coords = sympy.symbols('x y z') + with self.tmp_transform(coordSys): + mask = tfields.getMask(self, cutExpression, coords=coords) + return mask + + def cut(self, cutExpression, coordSys=None): + """ + Default cut method for Points3D. Works on a copy. + Args: + cutExpression (sympy logical expression): logical expression which will be evaluated. + use symbols x, y and z + coordSys (str): coordSys to evaluate the expression in. + Examples: + >>> x, y, z = sympy.symbols('x y z') + >>> p = tfields.Points3D([[1., 2., 3.], [4., 5., 6.], [1, 2, -6], + ... [-5, -5, -5], [1,0,-1], [0,1,-1]]) + >>> p.cut(x > 0) + Points3D([[ 1., 2., 3.], + [ 4., 5., 6.], + [ 1., 2., -6.], + [ 1., 0., -1.]]) + + combinations of cuts + >>> p.cut((x > 0) & (z < 0)) + Points3D([[ 1., 2., -6.], + [ 1., 0., -1.]]) + + Returns: + copy of self with cut applied + + """ + if len(self) == 0: + return self.copy() + mask = self.getMask(cutExpression, coordSys=coordSys) + mask.astype(bool) + inst = self[mask].copy() + return inst + + def mapToCut(self, cutOrMeshMap, coordSys=None, atIntersection="remove"): + """ + Args: + cutOrMeshMap (cutExpression / Mesh3D with faceScalars) + """ + # in current implementation Points3D use always cut method, never MeshMap + return self.cut(cutOrMeshMap, coordSys=coordSys), None + + def to3DArrays(self, arrayShape): + """ + Args: + arrayShape (tuple of lenght 3): how coordinates will be devided. + If you initialize Points3D with a grid of dimensions a,b,c and + want to retrieve the grid-points use (a,b,c) + """ + if not isinstance(arrayShape, tuple): + raise TypeError("ArrayShape is not of type tuple but %s" % type(arrayShape)) + if not len(arrayShape) == 3: + raise TypeError("ArrayShape is not of length 3 but %i" % len(arrayShape)) + return self.T.reshape((3,) + arrayShape) + + def closestPoints(self, other, **kwargs): + """ + Args: + other (Points3D): closest points to what? -> other + **kwargs: forwarded to scipy.spatial.cKDTree.query + Returns: + array shape(len(self)): Indices of other points that are closest to own points + Examples: + >>> m = tfields.Points3D([[1,0,0], [0,1,0], [1,1,0], [0,0,1], + ... [1,0,1]]) + >>> p = tfields.Points3D([[1.1,1,0], [0,0.1,1], [1,0,1.1]]) + >>> p.closestPoints(m) + array([2, 3, 4]) + + """ + with other.tmp_transform(self.coordSys): + # balanced_tree option gives huge speedup! + kdTree = sp.spatial.cKDTree(other, 1000, + balanced_tree=False) + res = kdTree.query(self, **kwargs) + array = res[1] + + return array + + def distances(self, other, metric='euclidean', **kwargs): + """ + Examples: + >>> p = tfields.Points3D.createMeshGrid(np.linspace(0, 2, 3), + ... np.linspace(0, 2, 3), + ... np.linspace(0,0,1)) + >>> p[4,2] = 1 + >>> p.distances(p)[0,0] + 0.0 + >>> p.distances(p)[5,1] + 1.4142135623730951 + >>> p.distances([[0,1,2]])[-1] + array([ 3.]) + + """ + return sp.spatial.distance.cdist(self, other, metric=metric, **kwargs) + + def minDistances(self, other=None, metric='euclidean', **kwargs): + """ + Examples: + >>> p = tfields.Points3D.createMeshGrid(np.linspace(0, 2, 3), + ... np.linspace(0, 2, 3), + ... np.linspace(0,0,1)) + >>> p[4,2] = 1 + >>> dMin = p.minDistances() + >>> dMin + array([ 1. , 1. , 1. , 1. , 1.41421356, + 1. , 1. , 1. , 1. ]) + + >>> dMin2 = p.minDistances(memorySaving=True) + >>> bool((dMin2 == dMin).all()) + True + + """ + memorySaving = kwargs.pop('memorySaving', False) + + if other is not None: + raise NotImplementedError("Should be easy but make shure not to remove 0s") + else: + other = self + + try: + if memorySaving: + raise MemoryError() + d = self.distances(other, metric=metric, **kwargs) + return d[d > 0].reshape(d.shape[0], - 1).min(axis=1) + except MemoryError: + minDists = np.empty(self.shape[0]) + for i, point in enumerate(other): + d = self.distances([point], metric=metric, **kwargs) + minDists[i] = d[d > 0].reshape(-1).min() + return minDists + + def epsilonNeighbourhood(self, epsilon): + """ + Returns: + indices for those sets of points that lie within epsilon around the other + Examples: + Create mesh grid with one extra point that will have 8 neighbours + within epsilon + >>> p = tfields.Points3D.createMeshGrid(np.linspace(0, 1, 2), + ... np.linspace(0, 1, 2), + ... np.linspace(0, 1, 2)) + >>> p = tfields.Points3D([p, [0.5, 0.5, 0.5]]) + >>> [len(en) for en in p.epsilonNeighbourhood(0.9)] + [2, 2, 2, 2, 2, 2, 2, 2, 9] + + """ + indices = np.arange(self.shape[0]) + dists = self.distances(self) + distsInEpsilon = dists <= epsilon + return [indices[die] for die in distsInEpsilon] + + def plot(self, **kwargs): + """ + Frowarding to mpt.plotArray + """ + artist = mpt.plotArray(self, **kwargs) + return artist + + +if __name__ == '__main__': + import doctest + # doctest.testmod() + doctest.run_docstring_examples(Points3D, globals()) -- GitLab