![Illustration of streamlines](https://github.com/jacquemv/evenlyspacedstreamlines/blob/main/illustration.png?raw=true)
### Objective
The objective is to generate a set of evenly-spaced streamlines tangent to a given vector field (or orientation field) on a smooth triangulated surface. The vector field is assumed to be constant over each triangle. A streamline is defined here as a polygonal line on the triangulated surface such that each segment of that polygonal line lies on a triangle and is parallel to the vector associated with that triangle. The algorithm distributes streamlines over the surface in such a way that the minimal distance between streamlines never becomes smaller than a given radius $r$. The approach is inspired by Jobard and Lefer [3].
The structure of the algorithm, its performance and its limitations are discussed in our papers [1, 2], with applications to the visualization of fiber orientation in the human atria.
### Minimal example
Here is an example of streamline generation on a triangular mesh composed of 3 vertices and 1 triangle, so there is 1 orientation vector.
```python
from evenlyspacedstreamlines import evenly_spaced_streamlines
vertices = [[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]
triangles = [[0, 1, 2]]
orientation = [[1.0, 1.0, 0.0]]
radius = 0.4
list_of_lines, list_of_indices, infos = \
evenly_spaced_streamlines(vertices, triangles, orientation, radius)
```
The output gives the xyz coordinates of 3 streamlines, the indices of the triangles the streamlines pass through (here always triangle 0), and some geometrical information such as streamline lengths:
```python
>>> list_of_lines
[array([[0.4803, 0.5197, 0.],
[0., 0.0394, 0.]]),
array([[0.7674, 0.2326, 0.],
[0.5348, 0., 0.]]),
array([[0.1963, 0.8037, 0.],
[0., 0.6074, 0.]])]
>>> list_of_indices
[array([0], dtype=int32), array([0], dtype=int32), array([0], dtype=int32)]
>>> infos
StreamlinesInfos(lengths=array([0.6793, 0.3289, 0.2776]), min_altitude=0.7071,
max_base=1.4142, neighborhood_size=0.0, euler_characteristic=1,
random_seed=1698460061)
```
### Syntax
```python
list_of_lines, list_of_indices, infos = \
evenly_spaced_streamlines(vertices, triangles, orientation, radius,
orthogonal=False, oriented_streamlines=False,
seed_points=32, seed_region=None, max_length=0,
avoid_u_turns=True, max_angle=90, singularity_mask_radius=0.1,
allow_tweaking_orientation=True,
random_seed=0, parallel=True, num_threads=-1)
```
### Positional arguments
The arguments **vertices** and **triangles** define the triangulated surface, and **orientation** the vector field on that surface. The parameter **radius** specifies streamlines spacing.
- **vertices** ($n_v$-by-3 float array): x, y, z coordinates of the $n_v$ vertices
- **triangles** ($n_t$-by-3 int array): indices of the vertices of the $n_t$ triangles
- **orientation** ($n_t$-by-3 float array): orientation vector in each triangle
- **radius** (float): the distance between streamlines will be larger than the radius and smaller than 2*radius
### Optional keyword arguments
- **seed_points** (int): number of seed points tested to generate each streamline; the longest streamline is kept (default: 32)
- **seed_region** (int array): list of triangle indices among which seed points are picked (default: None, which means that all triangles are considered)
- **orthogonal** (bool): if True, rotate the orientation by 90 degrees (default: False)
- **oriented_streamlines** (bool): if True, streamlines only follow the vector field in the direction it points to (and not the opposite direction); the outputted streamlines are then oriented according to the vector field. If False (default), the orientation field is defined modulo pi instead of 2pi
- **allow_tweaking_orientation** (bool): if an orientation vector is parallel to an edge of the triangle, a small random perturbation is applied to that vector to satisfy the requirement (default: True); otherwise an exception is raised when the requirement is not satisfied
- **singularity_mask_radius** (float): when the orientation field has a singularity (e.g. focus or node), prevent streamlines from entering a sphere of radius 'singularity_mask_radius' x 'radius' around that singularity (default: 0.1)
- **max_length** (int): maximal number of iterations when tracing streamlines; it is needed because of nearly-periodic streamlines (default: 0, which means equal to the number of triangles)
- **max_angle** (float): stop streamline integration if the angle between two consecutive segments is larger than max_angle in degrees; 0 means straight line and 180 means U-turn (default: 90)
- **avoid_u_turns** (bool): restrict high curvatures by maintaining a lateral (perpendicular) distance of at least 'radius' between a segment of the streamline and the other segments of the same streamline; this automatically sets 'max_angle' to at most 90 degrees (default: True)
- **random_seed** (int): initialize the seed for pseudo-random number generation (default: seed based on clock)
- **parallel** (bool): if True (default), use multithreading wherever implemented
- **num_threads** (int): if possible, use that number of threads for parallel computing (default: let OpenMP choose)
### Outputs
The function ``evenly_spaced_streamlines`` returns a 3-tuple:
- **list_of_lines** (list of $n$-by-3 matrices): xyz coordinates of each of the streamlines generated
- **list_of_indices** (list of ($n$-1)-arrays): vectors of indices indicating for each line segment of the streamline in which triangle they lie
- **infos** (namedtuple): information about streamline generation with the following attributes:
- 'lengths' ($n$-array of float): length of each streamline;
- 'min_altitude' (float): minimum altitude over all triangles;
- 'max_base' (float): maximum length of the base edge over all triangles;
- 'neighborhood_size' (float): average number of triangles at a distance < 'radius' from any triangle
- 'euler_characteristic' (int): = #vertices - #edges + #triangles
- 'random_seed' (int): random seed used for random number generation
### Visualization
A simple way to visualize the output is:
```python
import matplotlib.pyplot as plt
plt.figure().add_subplot(projection='3d')
for line in list_of_lines:
plt.plot(line[:, 0], line[:, 1], line[:, 2])
plt.show()
```
For more sophisticated visualizations, the module also provides a function ``streamlines_to_tubes`` to represent the streamlines as a set of tubes (described by triangulated surfaces).
### Implementation
The code is implemented in C++, interfaced and compiled using cython, and with a wrapper for python (``evenlyspacedstreamlines/wrapper.py``). It was designed for meshes with a number of vertices of the order of 100k and a radius $r$ not too large as compared to mesh edge length.
### Installation
The package can be installed using the command ``pip install evenlyspacedstreamlines`` (on Windows, a compiler such as Microsoft Visual C++ is required).
If the code is downloaded from github, local installation on Linux is done by running ``make local`` and including the directory 'evenlyspacedstreamlines' in the PYTHONPATH environment variable. The easiest way to check if the installation worked is:
```python
from evenlyspacedstreamlines import test; test()
```
Tested using Anaconda 2023.09 (python 3.11) on Linux and Windows.
### Acknowledgements
This work was supported by the Natural Sciences and Engineering Research
Council of Canada (NSERC grant RGPIN-2020-05252).
### References
1. V. Jacquemet. Improved algorithm for generating evenly-spaced streamlines on a triangulated surface (*in preparation*), 2023.
2. A. Saliani, A. Tsikhanovich, V. Jacquemet. [Visualization of interpolated atrial fiber orientation using evenly-spaced streamlines](https://doi.org/10.1016/j.compbiomed.2019.103349), *Comput. Biol. Med.* 2019, vol. 11, pp. 103349.
3. B. Jobard, W. Lefer. [Creating evenly-spaced streamlines of arbitrary density](https://link.springer.com/chapter/10.1007/978-3-7091-6876-9_5). In Visualization in Scientific Computing’97, pp. 43–55. Springer, 1997.
Raw data
{
"_id": null,
"home_page": "http://github.com/jacquemv/evenlyspacedstreamlines",
"name": "evenlyspacedstreamlines",
"maintainer": "",
"docs_url": null,
"requires_python": "",
"maintainer_email": "",
"keywords": "vector,field,visualization,surface,streamline",
"author": "Vincent Jacquemet",
"author_email": "vincent.jacquemet@umontreal.ca",
"download_url": "https://files.pythonhosted.org/packages/4d/fd/141baa316c33312707aa9166827b0bf3852e2bc1b442d866456eed75a27b/evenlyspacedstreamlines-0.1.0.tar.gz",
"platform": null,
"description": "\n![Illustration of streamlines](https://github.com/jacquemv/evenlyspacedstreamlines/blob/main/illustration.png?raw=true)\n\n### Objective\n\nThe objective is to generate a set of evenly-spaced streamlines tangent to a given vector field (or orientation field) on a smooth triangulated surface. The vector field is assumed to be constant over each triangle. A streamline is defined here as a polygonal line on the triangulated surface such that each segment of that polygonal line lies on a triangle and is parallel to the vector associated with that triangle. The algorithm distributes streamlines over the surface in such a way that the minimal distance between streamlines never becomes smaller than a given radius $r$. The approach is inspired by Jobard and Lefer [3].\n\nThe structure of the algorithm, its performance and its limitations are discussed in our papers [1, 2], with applications to the visualization of fiber orientation in the human atria.\n\n### Minimal example\n\nHere is an example of streamline generation on a triangular mesh composed of 3 vertices and 1 triangle, so there is 1 orientation vector.\n```python\nfrom evenlyspacedstreamlines import evenly_spaced_streamlines\nvertices = [[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]\ntriangles = [[0, 1, 2]]\norientation = [[1.0, 1.0, 0.0]]\nradius = 0.4\nlist_of_lines, list_of_indices, infos = \\\n evenly_spaced_streamlines(vertices, triangles, orientation, radius)\n```\nThe output gives the xyz coordinates of 3 streamlines, the indices of the triangles the streamlines pass through (here always triangle 0), and some geometrical information such as streamline lengths:\n```python\n>>> list_of_lines\n[array([[0.4803, 0.5197, 0.],\n [0., 0.0394, 0.]]),\n array([[0.7674, 0.2326, 0.],\n [0.5348, 0., 0.]]),\n array([[0.1963, 0.8037, 0.],\n [0., 0.6074, 0.]])]\n>>> list_of_indices\n[array([0], dtype=int32), array([0], dtype=int32), array([0], dtype=int32)]\n>>> infos\nStreamlinesInfos(lengths=array([0.6793, 0.3289, 0.2776]), min_altitude=0.7071,\nmax_base=1.4142, neighborhood_size=0.0, euler_characteristic=1, \nrandom_seed=1698460061)\n```\n\n\n### Syntax\n\n```python\nlist_of_lines, list_of_indices, infos = \\\n evenly_spaced_streamlines(vertices, triangles, orientation, radius,\n orthogonal=False, oriented_streamlines=False,\n seed_points=32, seed_region=None, max_length=0,\n avoid_u_turns=True, max_angle=90, singularity_mask_radius=0.1,\n allow_tweaking_orientation=True,\n random_seed=0, parallel=True, num_threads=-1)\n```\n\n### Positional arguments\n\nThe arguments **vertices** and **triangles** define the triangulated surface, and **orientation** the vector field on that surface. The parameter **radius** specifies streamlines spacing.\n- **vertices** ($n_v$-by-3 float array): x, y, z coordinates of the $n_v$ vertices\n- **triangles** ($n_t$-by-3 int array): indices of the vertices of the $n_t$ triangles\n- **orientation** ($n_t$-by-3 float array): orientation vector in each triangle\n- **radius** (float): the distance between streamlines will be larger than the radius and smaller than 2*radius\n\n### Optional keyword arguments\n\n- **seed_points** (int): number of seed points tested to generate each streamline; the longest streamline is kept (default: 32)\n- **seed_region** (int array): list of triangle indices among which seed points are picked (default: None, which means that all triangles are considered)\n- **orthogonal** (bool): if True, rotate the orientation by 90 degrees (default: False)\n- **oriented_streamlines** (bool): if True, streamlines only follow the vector field in the direction it points to (and not the opposite direction); the outputted streamlines are then oriented according to the vector field. If False (default), the orientation field is defined modulo pi instead of 2pi\n- **allow_tweaking_orientation** (bool): if an orientation vector is parallel to an edge of the triangle, a small random perturbation is applied to that vector to satisfy the requirement (default: True); otherwise an exception is raised when the requirement is not satisfied\n- **singularity_mask_radius** (float): when the orientation field has a singularity (e.g. focus or node), prevent streamlines from entering a sphere of radius 'singularity_mask_radius' x 'radius' around that singularity (default: 0.1)\n- **max_length** (int): maximal number of iterations when tracing streamlines; it is needed because of nearly-periodic streamlines (default: 0, which means equal to the number of triangles)\n- **max_angle** (float): stop streamline integration if the angle between two consecutive segments is larger than max_angle in degrees; 0 means straight line and 180 means U-turn (default: 90)\n- **avoid_u_turns** (bool): restrict high curvatures by maintaining a lateral (perpendicular) distance of at least 'radius' between a segment of the streamline and the other segments of the same streamline; this automatically sets 'max_angle' to at most 90 degrees (default: True)\n- **random_seed** (int): initialize the seed for pseudo-random number generation (default: seed based on clock)\n- **parallel** (bool): if True (default), use multithreading wherever implemented\n- **num_threads** (int): if possible, use that number of threads for parallel computing (default: let OpenMP choose)\n\n### Outputs\n\nThe function ``evenly_spaced_streamlines`` returns a 3-tuple:\n- **list_of_lines** (list of $n$-by-3 matrices): xyz coordinates of each of the streamlines generated\n- **list_of_indices** (list of ($n$-1)-arrays): vectors of indices indicating for each line segment of the streamline in which triangle they lie\n- **infos** (namedtuple): information about streamline generation with the following attributes:\n - 'lengths' ($n$-array of float): length of each streamline;\n - 'min_altitude' (float): minimum altitude over all triangles;\n - 'max_base' (float): maximum length of the base edge over all triangles;\n - 'neighborhood_size' (float): average number of triangles at a distance < 'radius' from any triangle\n - 'euler_characteristic' (int): = #vertices - #edges + #triangles\n - 'random_seed' (int): random seed used for random number generation\n\n### Visualization\n\nA simple way to visualize the output is:\n```python\nimport matplotlib.pyplot as plt\nplt.figure().add_subplot(projection='3d')\nfor line in list_of_lines:\n plt.plot(line[:, 0], line[:, 1], line[:, 2])\nplt.show()\n```\nFor more sophisticated visualizations, the module also provides a function ``streamlines_to_tubes`` to represent the streamlines as a set of tubes (described by triangulated surfaces).\n\n### Implementation\n\nThe code is implemented in C++, interfaced and compiled using cython, and with a wrapper for python (``evenlyspacedstreamlines/wrapper.py``). It was designed for meshes with a number of vertices of the order of 100k and a radius $r$ not too large as compared to mesh edge length.\n\n### Installation\n\nThe package can be installed using the command ``pip install evenlyspacedstreamlines`` (on Windows, a compiler such as Microsoft Visual C++ is required).\n\nIf the code is downloaded from github, local installation on Linux is done by running ``make local`` and including the directory 'evenlyspacedstreamlines' in the PYTHONPATH environment variable. The easiest way to check if the installation worked is:\n```python\nfrom evenlyspacedstreamlines import test; test()\n```\nTested using Anaconda 2023.09 (python 3.11) on Linux and Windows.\n\n### Acknowledgements\n\nThis work was supported by the Natural Sciences and Engineering Research\nCouncil of Canada (NSERC grant RGPIN-2020-05252).\n\n### References\n\n1. V. Jacquemet. Improved algorithm for generating evenly-spaced streamlines on a triangulated surface (*in preparation*), 2023.\n\n2. A. Saliani, A. Tsikhanovich, V. Jacquemet. [Visualization of interpolated atrial fiber orientation using evenly-spaced streamlines](https://doi.org/10.1016/j.compbiomed.2019.103349), *Comput. Biol. Med.* 2019, vol. 11, pp. 103349. \n\n3. B. Jobard, W. Lefer. [Creating evenly-spaced streamlines of arbitrary density](https://link.springer.com/chapter/10.1007/978-3-7091-6876-9_5). In Visualization in Scientific Computing\u201997, pp. 43\u201355. Springer, 1997.\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Generate evenly-spaced streamlines from an orientation field on a triangulated 3D surface",
"version": "0.1.0",
"project_urls": {
"Homepage": "http://github.com/jacquemv/evenlyspacedstreamlines"
},
"split_keywords": [
"vector",
"field",
"visualization",
"surface",
"streamline"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "4dfd141baa316c33312707aa9166827b0bf3852e2bc1b442d866456eed75a27b",
"md5": "38412b2fb430028d323f86ebacfe8070",
"sha256": "2f43075a281321ecff6ec37cc0d84546d8610399e5dbbfb4d2070d7e34da6c33"
},
"downloads": -1,
"filename": "evenlyspacedstreamlines-0.1.0.tar.gz",
"has_sig": false,
"md5_digest": "38412b2fb430028d323f86ebacfe8070",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 182122,
"upload_time": "2023-11-07T20:44:48",
"upload_time_iso_8601": "2023-11-07T20:44:48.832646Z",
"url": "https://files.pythonhosted.org/packages/4d/fd/141baa316c33312707aa9166827b0bf3852e2bc1b442d866456eed75a27b/evenlyspacedstreamlines-0.1.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2023-11-07 20:44:48",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "jacquemv",
"github_project": "evenlyspacedstreamlines",
"travis_ci": false,
"coveralls": false,
"github_actions": false,
"lcname": "evenlyspacedstreamlines"
}