# -*- coding: utf-8 -*-
"""
fiber objects classes
"""
import copy
import numpy as np
import numba
from .layers import Layers
"""
--------------------------------------------------------------------------------
--------------------------------------FIBER-------------------------------------
--------------------------------------------------------------------------------
"""
def _convert_to_fiber(data, dtype):
""" converts data into (n,4)-np.array"""
if data is None:
return np.empty((), dtype)
if isinstance(data, Fiber):
return data
data = np.atleast_2d(np.array(data, dtype=dtype, copy=True))
if data.ndim != 2:
raise TypeError('Wrong shape: expected (n,4)')
if data.shape[1] != 4:
raise TypeError('Wrong shape: expected (n,4)')
if not np.issubdtype(data.dtype, np.floating):
raise TypeError('Wrong type: has to be floating')
return data
[docs]class Fiber:
__is_frozen = False
def __setattr__(self, key, value):
if self.__is_frozen and not hasattr(self, key):
raise TypeError('%r is a frozen class' % self)
object.__setattr__(self, key, value)
def __freeze(self):
self.__is_frozen = True
def __init__(self, data, dtype=float):
self._data = _convert_to_fiber(data, dtype)
self.__freeze()
def __getitem__(self, item):
return self._data[item]
def __setitem__(self, item, value):
self._data[item] = value
def __str__(self):
return self._data.__str__()
def __repr__(self):
return self._data.__repr__()
[docs] def copy(self):
""" deep copy of class """
return copy.deepcopy(self)
def __iter__(self):
return self._data.__iter__()
def __next__(self):
return self._data.__next__()
def __len__(self):
return self._data.shape[-1]
@property
def shape(self):
""" returns shape of data """
return self._data.shape
@property
def dtype(self):
""" returns np.dtype of data """
return self._data.dtype
[docs] def as_array(self):
"""
Returns copy data as np.array
Returns
-------
res : (n,4)-array
fiber as np.array
"""
return self._data.copy()
[docs] def cast(self, dtype):
"""
Changes datatype to new type
Parameters
----------
dtype : type
numpy types are fully supported
Returns
-------
res : Fiber
casted fiber
"""
return Fiber(self, dtype)
@property
def points(self):
"""
Returns xyz data as np.array
Returns
-------
res : (n,3)-array
fiber points as numpy array
"""
return self._data[:, :-1]
@points.setter
def points(self, value):
self._data[:, :-1] = value
@property
def radii(self):
"""
Returns radii data as np.array
Returns
-------
res : (n,1)-array
fiber radii as numpy array
"""
return self._data[:, -1]
@radii.setter
def radii(self, value):
self._data[:, -1] = value
[docs] def scale(self, scale, mode='all'):
"""
Scales fiber
Parameters
----------
scale : float
scale factor
mode : str, optional
'all', 'points' or 'radii' will be scaled
Returns
-------
res : Fiber
scaled Fiber
"""
data = self._data.copy()
if mode == 'all':
data[:] *= scale
elif mode == 'points':
data[:, :3] *= scale
elif mode == 'radii':
data[:, -1] *= scale
else:
raise ValueError('mode = [all, points, radii]')
return Fiber(data)
[docs] def rotate(self, rot, offset=None):
"""
Rotates fiber around offset
Parameters
----------
rot : (3,3)-array_like
rotation matrix
offset : 3d-array-array_like, optional
offset for rotation center
Returns
-------
res : Fiber
rotated fiber
"""
data = self._data.copy()
rot = np.array(rot, copy=False)
if offset is None:
data[:, :3] = np.dot(rot, data[:, :3].T).T
else:
offset = np.array(offset, copy=False)
data[:, :3] = np.dot(rot, (data[:, :3] - offset).T).T + offset
return Fiber(data)
[docs] def translate(self, offset):
"""
Translates fiber
Parameters
----------
offset : 3d-array-array_like
offset to translate
Returns
-------
res : Fiber
translated fiber
"""
data = self._data.copy()
offset = np.array(offset, copy=False)
data[:, :3] += offset
return Fiber(data)
[docs] def apply(self, fun):
"""
Applies function to fiber
Parameters
----------
fun : function
Returns
-------
res : Fiber
fun(fiber)
"""
fiber = self.copy()
fiber[:] = fun(fiber[:])
return fiber
[docs] def apply_to_points(self, fun):
"""
Applies function to fiber positions
Parameters
----------
fun : function
Returns
-------
res : Fiber
fun(fiber)
"""
fiber = self.copy()
fiber[:, :-1] = fun(fiber[:, :-1])
return fiber
[docs] def apply_to_radii(self, fun):
"""
Applies function to fiber radii
Parameters
----------
fun : function
Returns
-------
res : Fiber
fun(fiber)
"""
fiber = self.copy()
fiber[:, -1] = fun(fiber[:, -1])
return fiber
[docs] def cut(self, voi):
"""
Cut fiber into voi. The cutting process can create multiple fibers.
It checks every fiber_segment_aabb if it overlapps with the voi.
Parameters
----------
voi : [xmin, ymin, zmin],[xmax,ymax,zmax]
Volume of interest of which fibers to include. E.g. same as in
Simulation
Returns
-------
res : FiberBundle
cutted fiber into fiber_bundle
"""
fibers = []
start = 0
voi = np.array(voi)
for i in range(self._data.shape[0] - 1):
if not _fiber_segment_aabb_in_aabb(
self._data[i, :], self._data[i + 1, :], voi[0], voi[1]):
if start != i:
fibers.append(self._data[start:i + 1])
start = i + 1
if start != i + 1:
fibers.append(self._data[start:])
return FiberBundle(fibers)
[docs] def cut_sphere(self, radius, center=(0, 0, 0)):
"""
Cut fiber into sphere. The cutting process can create multiple fibers.
It checks every fiber_segment_aabb if it overlapps with the sphere.
Parameters
----------
radius : float
radius of cutting sphere
center : 3d-array
center of cutting sphere
Returns
-------
res : FiberBundle
cutted fiber_bundle
"""
center = np.array(center, copy=False)
fibers = []
start = 0
for i in range(self._data.shape[0] - 1):
if not _fiber_segment_aabb_in_sphere(
self._data[i, :], self._data[i + 1, :], radius, center):
if start != i:
fibers.append(self._data[start:i + 1])
start = i + 1
if start != i + 1:
fibers.append(self._data[start:])
return FiberBundle(fibers)
@numba.njit(cache=True)
def _fiber_segment_aabb_in_aabb(c0, c1, vmin, vmax):
c_min = np.array([
min(c0[0] - c0[-1], c1[0] - c1[-1]),
min(c0[1] - c0[-1], c1[1] - c1[-1]),
min(c0[2] - c0[-1], c1[2] - c1[-1])
])
c_max = np.array([
max(c0[0] + c0[-1], c1[0] + c1[-1]),
max(c0[1] + c0[-1], c1[1] + c1[-1]),
max(c0[2] + c0[-1], c1[2] + c1[-1])
])
for i in range(3):
if c_min[i] > vmax[i] or c_max[i] < vmin[i]:
return False
return True
@numba.njit(cache=True)
def _fiber_segment_aabb_in_sphere(c0, c1, r, center):
c_min = np.array([
min(c0[0] - c0[-1], c1[0] - c1[-1]),
min(c0[1] - c0[-1], c1[1] - c1[-1]),
min(c0[2] - c0[-1], c1[2] - c1[-1])
])
c_max = np.array([
max(c0[0] + c0[-1], c1[0] + c1[-1]),
max(c0[1] + c0[-1], c1[1] + c1[-1]),
max(c0[2] + c0[-1], c1[2] + c1[-1])
])
dmin = 0
for i in range(3):
if center[i] < c_min[i]:
dmin += (center[i] - c_min[i])**2
elif center[i] > c_max[i]:
dmin += (center[i] - c_max[i])**2
return dmin <= r**2
"""
--------------------------------------------------------------------------------
-----------------------------------FIBERBUNDLE----------------------------------
--------------------------------------------------------------------------------
"""
def _convert_to_fiber_bundle(data, layers, dtype):
""" converts data into FiberBundle"""
if data is None:
return [], None
if isinstance(data, Fiber):
return [data], Layers(layers)
if isinstance(data, FiberBundle):
return data[:], Layers(layers)
if not isinstance(data, (list, tuple)):
raise TypeError(f'data is not a list: {type(data)}')
fiber_bundle = []
for fiber in data:
fiber_bundle.append(Fiber(fiber))
return fiber_bundle, Layers(layers)
[docs]class FiberBundle:
__is_frozen = False
def __setattr__(self, key, value):
if self.__is_frozen and not hasattr(self, key):
raise TypeError('%r is a frozen class' % self)
object.__setattr__(self, key, value)
def __freeze(self):
self.__is_frozen = True
def __init__(self, data=None, layers=None, dtype=None):
self._data, self._layers = _convert_to_fiber_bundle(data, layers, dtype)
self.__freeze()
def __getitem__(self, item):
return self._data[item]
def __setitem__(self, item, value):
self._data[item] = Fiber(value)
def __delitem__(self, item):
del self._data[item]
def __str__(self):
return self._data.__str__()
def __repr__(self):
return self._data.__repr__()
[docs] def copy(self):
""" deep copy of class """
return copy.deepcopy(self)
def __iter__(self):
return iter(self._data)
def __next__(self):
return next(self._data)
def __len__(self):
return len(self._data)
@property
def dtype(self):
""" dtype of containing Fibers """
if len(self) > 1:
return self._data[0].dtype
else:
return None
[docs] def as_array(self):
"""
Returns copy data as list(np.array)
Returns
-------
res : [(n,4)-array]
fiber bundle as list(np.array)
"""
return [f.as_array() for f in self]
@property
def layers(self):
"""
Returns layer properties of fiber_bundle
Returns
-------
res : Layers
Layers class containing [Layer].
"""
return self._layers
@layers.setter
def layers(self, value):
self._layers = Layers(value)
[docs] def append(self, fiber):
""" appends Fiber to FiberBundle """
self._data.append(Fiber(fiber))
[docs] def extend(self, fibers):
""" extends Fiber to FiberBundle """
for fiber in fibers:
self._data.append(Fiber(fiber))
[docs] def cast(self, dtype):
"""
Cast objects into new type
Parameters
----------
dtype : type
Returns
-------
res : fiber_bundle
casted fiber_bundle
"""
fiber_bundle = FiberBundle()
for fiber in self:
fiber_bundle.append(fiber.cast(dtype))
return fiber_bundle
[docs] def scale(self, scale, mode='all'):
"""
Rescales fiber_bundle
Parameters
----------
scale : float
scale factor
mode : str, optional
'all', 'points' or 'radii' will be scaled
Returns
-------
res : FiberBundle
scaled fiber_bundle
"""
fiber_bundle = FiberBundle()
for fiber in self:
fiber_bundle.append(fiber.scale(scale, mode))
return fiber_bundle
[docs] def rotate(self, rot, offset=None):
"""
Rotates fiber_bundle around offset
Parameters
----------
rot : (3,3)-array_like
rotation matrix
offset : 3d-array-array_like, optional
offset for rotation center
Returns
-------
res : FiberBundle
rotated fiber_bundle
"""
rot = np.array(rot, copy=False)
if offset is not None:
offset = np.array(offset, copy=False)
fiber_bundle = FiberBundle()
for fiber in self:
fiber_bundle.append(fiber.rotate(rot, offset))
return fiber_bundle
[docs] def translate(self, offset):
"""
Translates fiber_bundle
Parameters
----------
offset : 3d-array-array_like
offset to translate
Returns
-------
res : FiberBundle
translated fiber_bundle
"""
offset = np.array(offset, copy=False)
fiber_bundle = FiberBundle()
for fiber in self:
fiber_bundle.append(fiber.translate(offset))
return fiber_bundle
[docs] def apply(self, fun):
"""
Applies function to fibers
Parameters
----------
fun : function
Returns
-------
res : FiberBundle
fun(fiber_bundle)
"""
fiber_bundle = FiberBundle()
for fiber in self:
fiber_bundle.append(fiber.apply(fun))
return fiber_bundle
[docs] def apply_to_points(self, fun):
"""
Applies function to fibers positions
Parameters
----------
fun : function
Returns
-------
res : FiberBundle
fun(fiber_bundle.points)
"""
fiber_bundle = FiberBundle()
for fiber in self:
fiber_bundle.append(fiber.apply_to_points(fun))
return fiber_bundle
[docs] def apply_to_radii(self, fun):
"""
Applies function to fibers radii
Parameters
----------
fun : function
Returns
-------
res : FiberBundle
fun(fiber_bundle.radii)
"""
fiber_bundle = FiberBundle()
for fiber in self:
fiber_bundle.append(fiber.apply_to_radii(fun))
return fiber_bundle
[docs] def cut(self, voi):
"""
Cut fiber into voi. The cutting process can create multiple fibers.
It checks every fiber_segment_aabb if it overlapps with the voi.
Parameters
----------
voi : [xmin, ymin, zmin],[xmax,ymax,zmax]
Volume of interest of which fibers to include. E.g. same as in
Simulation
Returns
-------
res : FiberBundle
cutted fiber_bundle
"""
fiber_bundle = FiberBundle()
for fiber in self:
fiber_bundle.extend(fiber.cut(voi))
return fiber_bundle
[docs] def cut_sphere(self, radius, center=(0, 0, 0)):
"""
Cut fiber into sphere. The cutting process can create multiple fibers.
It checks every fiber_segment_aabb if it overlapps with the sphere.
Parameters
----------
radius : float
radius of cutting sphere
center : 3d-array
center of cutting sphere
Returns
-------
res : FiberBundle
cutted fiber_bundle
"""
fiber_bundle = FiberBundle()
for fiber in self:
fiber_bundle.extend(fiber.cut_sphere(radius, center))
return fiber_bundle
"""
--------------------------------------------------------------------------------
----------------------------------FIBERBUNDLES----------------------------------
--------------------------------------------------------------------------------
"""
def _convert_to_fiber_bundles(data, layers, dtype):
""" converts data into FiberBundle"""
if data is None:
return []
if isinstance(data, Fiber):
return [FiberBundle(data, layers)]
if isinstance(data, FiberBundle):
return [data]
if isinstance(data, FiberBundles):
return data[:]
if not isinstance(data, (list, tuple)):
raise TypeError('data is not a list')
if layers is not None:
if len(data) != len(layers):
raise TypeError('[FiberBundle] and [Layers] differ in length')
else:
layers = [None] * len(data)
fiber_bundles = []
for fiber_bundle, lys in zip(data, layers):
fiber_bundles.append(FiberBundle(fiber_bundle, lys))
return fiber_bundles
[docs]class FiberBundles():
__is_frozen = False
def __setattr__(self, key, value):
if self.__is_frozen and not hasattr(self, key):
raise TypeError('%r is a frozen class' % self)
object.__setattr__(self, key, value)
def __freeze(self):
self.__is_frozen = True
def __init__(self, data=None, layers=None, dtype=None):
self._data = _convert_to_fiber_bundles(data, layers, dtype)
self.__freeze()
def __getitem__(self, item):
return self._data[item]
def __setitem__(self, item, value):
self._data[item] = FiberBundle(value)
def __delitem__(self, item):
del self._data[item]
def __str__(self):
return self._data.__str__()
def __repr__(self):
return self._data.__repr__()
[docs] def copy(self):
""" deep copy of class """
return copy.deepcopy(self)
def __iter__(self):
return iter(self._data)
def __next__(self):
return next(self._data)
def __len__(self):
return len(self._data)
@property
def dtype(self):
if len(self) > 1:
return self[0].dtype
else:
return None
[docs] def as_array(self):
"""
Returns copy data as list(list(np.array))
Returns
-------
res : [[(n,4)-array]]
fiber bundle as list(list(np.array))
"""
return [fb.as_array() for fb in self]
@property
def layers(self):
"""
Returns layer properties of fiber_bundles
Returns
-------
res : [Layers]
[Layers] class containing [Layer].
The element position corresponds to FiberBundle index
"""
return [fb.layers for fb in self]
@layers.setter
def layers(self, value):
if len(value) != len(self):
raise ValueError('Wrong number of [layers]')
for fb, lys in zip(self, value):
fb.layers = lys
[docs] def append(self, fiber_bundle):
""" Appends FiberBundle """
self._data.append(FiberBundle(fiber_bundle))
[docs] def extend(self, fiber_bundles):
""" Extends FiberBundle """
for fiber_bundle in fiber_bundles:
self._data.append(FiberBundle(fiber_bundle))
[docs] def cast(self, dtype):
"""
Cast objects into new type
Parameters
----------
dtype : type
Returns
-------
res : FiberBundles
fiber_bundles
"""
fiber_bundles = FiberBundles()
for fb in self:
fiber_bundles.append(fb.cast(dtype))
return fiber_bundles
[docs] def scale(self, scale, mode='all'):
"""
Rescales fiber_bundles
Parameters
----------
scale : float
scale factor
mode : str, optional
'all', 'points' or 'radii' will be scaled
Returns
-------
res : FiberBundles
scaled fiber_bundles
"""
fiber_bundles = FiberBundles()
for fb in self:
fiber_bundles.append(fb.scale(scale, mode))
return fiber_bundles
[docs] def rotate(self, rot, offset=None):
"""
Rotates fiber_bundles around offset
Parameters
----------
fiber_bundles : [[(,4)-array, ...]]
list of fibers
rot : (3,3)-array_like
rotation matrix
offset : 3d-array-array_like, optional
offset for rotation center
Returns
-------
res : FiberBundles
rotated fiber_bundles
"""
rot = np.array(rot, copy=False)
if offset is not None:
offset = np.array(offset, copy=False)
fiber_bundles = FiberBundles()
for fb in self:
fiber_bundles.append(fb.rotate(rot, offset))
return fiber_bundles
[docs] def translate(self, offset):
"""
Translates fiber_bundles
Parameters
----------
offset : 3d-array-array_like
offset to translate
Returns
-------
res : FiberBundles
translated fiber_bundles
"""
offset = np.array(offset, copy=False)
fiber_bundles = FiberBundles()
for fb in self:
fiber_bundles.append(fb.translate(offset))
return fiber_bundles
[docs] def apply(self, fun):
"""
Applies function to fibers
Parameters
----------
fun : function
Returns
-------
res : FiberBundles
fun(fiber_bundles)
"""
fiber_bundles = FiberBundles()
for fb in self:
fiber_bundles.append(fb.apply(fun))
return fiber_bundles
[docs] def apply_to_points(self, fun):
"""
Applies function to fibers positions
Parameters
----------
fun : function
Returns
-------
res : FiberBundles
fun(fiber_bundles[...].points)
"""
fiber_bundles = FiberBundles()
for fb in self:
fiber_bundles.append(fb.apply_to_points(fun))
return fiber_bundles
[docs] def apply_to_radii(self, fun):
"""
Applies function to fibers radii
Parameters
----------
fun : function
Returns
-------
res : FiberBundles
fun(fiber_bundles[...].radii)
"""
fiber_bundles = FiberBundles()
for fb in self:
fiber_bundles.append(fb.apply_to_radii(fun))
return fiber_bundles
[docs] def cut(self, voi):
"""
Cut fiber into voi. The cutting process can create multiple fibers.
It checks every fiber_segment_aabb if it overlapps with the voi.
Parameters
----------
voi : [xmin, ymin, zmin],[xmax,ymax,zmax]
Volume of interest of which fibers to include. E.g. same as in
Simulation
Returns
-------
res : FiberBundles
cutted fiber_bundles
"""
fiber_bundles = FiberBundles()
for fb in self:
fiber_bundles.append(fb.cut(voi))
return fiber_bundles
[docs] def cut_sphere(self, radius, center=(0, 0, 0)):
"""
Cut fiber into voi. The cutting process can create multiple fibers.
It checks every fiber_segment_aabb if it overlapps with the voi.
Parameters
----------
radius : float
radius of cutting sphere
center : 3d-array
center of cutting sphere
Returns
-------
res : FiberBundles
cutted fiber_bundles
"""
center = np.array(center, copy=False)
fiber_bundles = FiberBundles()
for fb in self:
fiber_bundles.append(fb.cut_sphere(radius, center))
return fiber_bundles