vistools 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- vistools-0.1.0/LICENSE +21 -0
- vistools-0.1.0/PKG-INFO +19 -0
- vistools-0.1.0/README.md +19 -0
- vistools-0.1.0/pyproject.toml +35 -0
- vistools-0.1.0/setup.cfg +4 -0
- vistools-0.1.0/src/vistools/__init__.py +22 -0
- vistools-0.1.0/src/vistools/pyvista/__init__.py +24 -0
- vistools-0.1.0/src/vistools/pyvista/camera.py +48 -0
- vistools-0.1.0/src/vistools/pyvista/polyline_cross_section.py +86 -0
- vistools-0.1.0/src/vistools/pyvista/scalar_bar_to_tikz.py +241 -0
- vistools-0.1.0/src/vistools/pyvista/sort_grid.py +172 -0
- vistools-0.1.0/src/vistools/pyvista/temporal_interpolator.py +78 -0
- vistools-0.1.0/src/vistools/vtk/__init__.py +26 -0
- vistools-0.1.0/src/vistools/vtk/compare_grids.py +181 -0
- vistools-0.1.0/src/vistools/vtk/geometric_search.py +63 -0
- vistools-0.1.0/src/vistools/vtk/merge_polylines.py +503 -0
- vistools-0.1.0/src/vistools/vtk/polyline_cross_section.py +189 -0
- vistools-0.1.0/src/vistools/vtk/vtk_data_structures_utils.py +29 -0
- vistools-0.1.0/src/vistools.egg-info/PKG-INFO +19 -0
- vistools-0.1.0/src/vistools.egg-info/SOURCES.txt +28 -0
- vistools-0.1.0/src/vistools.egg-info/dependency_links.txt +1 -0
- vistools-0.1.0/src/vistools.egg-info/requires.txt +12 -0
- vistools-0.1.0/src/vistools.egg-info/top_level.txt +1 -0
- vistools-0.1.0/tests/test_pyvista_polyline_cross_section.py +71 -0
- vistools-0.1.0/tests/test_pyvista_scalar_bar_to_tikz.py +69 -0
- vistools-0.1.0/tests/test_pyvista_sort_grid.py +151 -0
- vistools-0.1.0/tests/test_pyvista_temporal_interpolator.py +51 -0
- vistools-0.1.0/tests/test_vtk_merge_polylines.py +89 -0
- vistools-0.1.0/tests/test_vtk_merge_polylines_create_reference_files.py +199 -0
- vistools-0.1.0/tests/test_vtk_polyline_cross_section.py +57 -0
vistools-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023-2025 Ivo Steinbrecher
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
vistools-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: vistools
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Utility functionality for vtk and pyvista
|
|
5
|
+
Author-email: Ivo Steinbrecher <ivo.steinbrecher@unibw.de>
|
|
6
|
+
Project-URL: Repository, https://github.com/isteinbrecher/vtktools/
|
|
7
|
+
Project-URL: Issues, https://github.com/isteinbrecher/vtktools/issues/
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Dist: numpy
|
|
10
|
+
Requires-Dist: scipy
|
|
11
|
+
Requires-Dist: vtk
|
|
12
|
+
Provides-Extra: pyvista
|
|
13
|
+
Requires-Dist: pyvista<0.45.0; extra == "pyvista"
|
|
14
|
+
Requires-Dist: opencv-python; extra == "pyvista"
|
|
15
|
+
Provides-Extra: dev
|
|
16
|
+
Requires-Dist: pre-commit; extra == "dev"
|
|
17
|
+
Requires-Dist: pytest; extra == "dev"
|
|
18
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
|
19
|
+
Dynamic: license-file
|
vistools-0.1.0/README.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# VisTools
|
|
2
|
+
|
|
3
|
+
**VisTools** is a utility library for scientific visualization workflows, focused on the pure VTK data format as well as PyVista.
|
|
4
|
+
|
|
5
|
+
## Modules
|
|
6
|
+
|
|
7
|
+
- `vistools.vtk`: Utility functions based only on the plain `vtk` python package.
|
|
8
|
+
|
|
9
|
+
- `vistools.pyvista`: Utility functions for PvVista, depends on `vistools.vtk`.
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
VisTools is available via `pip` and can be installed with
|
|
14
|
+
```bash
|
|
15
|
+
# Install only vistools.vtk
|
|
16
|
+
pip install vistools
|
|
17
|
+
# Install all modules
|
|
18
|
+
pip install "vistools[pyvista]"
|
|
19
|
+
```
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "vistools"
|
|
7
|
+
authors = [
|
|
8
|
+
{name = "Ivo Steinbrecher", email = "ivo.steinbrecher@unibw.de"},
|
|
9
|
+
]
|
|
10
|
+
description = "Utility functionality for vtk and pyvista"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"numpy",
|
|
13
|
+
"scipy",
|
|
14
|
+
"vtk"
|
|
15
|
+
]
|
|
16
|
+
version = "0.1.0"
|
|
17
|
+
|
|
18
|
+
[project.urls]
|
|
19
|
+
Repository = "https://github.com/isteinbrecher/vtktools/"
|
|
20
|
+
Issues = "https://github.com/isteinbrecher/vtktools/issues/"
|
|
21
|
+
|
|
22
|
+
[project.optional-dependencies]
|
|
23
|
+
pyvista = [
|
|
24
|
+
"pyvista<0.45.0",
|
|
25
|
+
"opencv-python"
|
|
26
|
+
]
|
|
27
|
+
dev = [
|
|
28
|
+
"pre-commit",
|
|
29
|
+
"pytest",
|
|
30
|
+
"pytest-cov"
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[tool.pytest.ini_options]
|
|
34
|
+
testpaths = ["tests"]
|
|
35
|
+
addopts = "-p pytest_cov --cov-report=term --cov-report=html --cov=src/"
|
vistools-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# The MIT License (MIT)
|
|
2
|
+
#
|
|
3
|
+
# Copyright (c) 2023-2025 Ivo Steinbrecher
|
|
4
|
+
#
|
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
# furnished to do so, subject to the following conditions:
|
|
11
|
+
#
|
|
12
|
+
# The above copyright notice and this permission notice shall be included in
|
|
13
|
+
# all copies or substantial portions of the Software.
|
|
14
|
+
#
|
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
# THE SOFTWARE.
|
|
22
|
+
"""Define the main namespace of vistools."""
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# The MIT License (MIT)
|
|
2
|
+
#
|
|
3
|
+
# Copyright (c) 2023-2025 Ivo Steinbrecher
|
|
4
|
+
#
|
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
# furnished to do so, subject to the following conditions:
|
|
11
|
+
#
|
|
12
|
+
# The above copyright notice and this permission notice shall be included in
|
|
13
|
+
# all copies or substantial portions of the Software.
|
|
14
|
+
#
|
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
# THE SOFTWARE.
|
|
22
|
+
"""Define the main namespace of pvtools."""
|
|
23
|
+
|
|
24
|
+
from vistools.pyvista.sort_grid import sort_grid
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# The MIT License (MIT)
|
|
2
|
+
#
|
|
3
|
+
# Copyright (c) 2023-2025 Ivo Steinbrecher
|
|
4
|
+
#
|
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
# furnished to do so, subject to the following conditions:
|
|
11
|
+
#
|
|
12
|
+
# The above copyright notice and this permission notice shall be included in
|
|
13
|
+
# all copies or substantial portions of the Software.
|
|
14
|
+
#
|
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
# THE SOFTWARE.
|
|
22
|
+
"""Camera functionality for pyvista."""
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def get_camera_settings(camera):
|
|
26
|
+
names = [
|
|
27
|
+
"clipping_range",
|
|
28
|
+
"distance",
|
|
29
|
+
"elevation",
|
|
30
|
+
"focal_point",
|
|
31
|
+
"model_transform_matrix",
|
|
32
|
+
"parallel_projection",
|
|
33
|
+
"parallel_scale",
|
|
34
|
+
"position",
|
|
35
|
+
"roll",
|
|
36
|
+
"thickness",
|
|
37
|
+
"up",
|
|
38
|
+
"view_angle",
|
|
39
|
+
]
|
|
40
|
+
camera_settings = {}
|
|
41
|
+
for name in names:
|
|
42
|
+
camera_settings[name] = getattr(camera, name)
|
|
43
|
+
return camera_settings
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def set_camera_settings(camera, settings):
|
|
47
|
+
for name, value in settings.items():
|
|
48
|
+
setattr(camera, name, value)
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# The MIT License (MIT)
|
|
2
|
+
#
|
|
3
|
+
# Copyright (c) 2023-2025 Ivo Steinbrecher
|
|
4
|
+
#
|
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
# furnished to do so, subject to the following conditions:
|
|
11
|
+
#
|
|
12
|
+
# The above copyright notice and this permission notice shall be included in
|
|
13
|
+
# all copies or substantial portions of the Software.
|
|
14
|
+
#
|
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
# THE SOFTWARE.
|
|
22
|
+
"""Extrude a profile along a polyline."""
|
|
23
|
+
|
|
24
|
+
import pyvista as pv
|
|
25
|
+
|
|
26
|
+
from vistools.vtk.polyline_cross_section import (
|
|
27
|
+
polyline_cross_section as vtk_polyline_cross_section,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def polyline_cross_section(
|
|
32
|
+
grid: pv.PolyData,
|
|
33
|
+
cross_section_points,
|
|
34
|
+
*,
|
|
35
|
+
closed: bool = True,
|
|
36
|
+
separate_surfaces: bool = False,
|
|
37
|
+
) -> pv.UnstructuredGrid:
|
|
38
|
+
"""Extrude a profile defined by the cross section coordinates along a
|
|
39
|
+
polyline.
|
|
40
|
+
|
|
41
|
+
This function calls the vtk version under the hood, but adds some additional
|
|
42
|
+
functionality.
|
|
43
|
+
|
|
44
|
+
Args
|
|
45
|
+
----
|
|
46
|
+
grid:
|
|
47
|
+
Polyline defining the centerline of the extruded structure
|
|
48
|
+
cross_section_points:
|
|
49
|
+
In-cross-section coordinates defining the profile
|
|
50
|
+
closed:
|
|
51
|
+
Flag if the profile is open or closed
|
|
52
|
+
separate_surfaces:
|
|
53
|
+
Per default all surface cells for a single polyline extrusion are connected.
|
|
54
|
+
If this is true, then the connectivity of the returned surfaces is modified.
|
|
55
|
+
The polygons at the start and end (for closed==True) will not be connected to
|
|
56
|
+
the rest. For the extruded surfaces, each segment between two points in
|
|
57
|
+
cross_section_points will be extruded along the centerline and not be connected
|
|
58
|
+
to the bounding surfaces. This can be useful if sharp edges should be shown in
|
|
59
|
+
the visualization.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
cross_section_grid = pv.UnstructuredGrid(
|
|
63
|
+
vtk_polyline_cross_section(grid, cross_section_points, closed=closed)
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
if not separate_surfaces:
|
|
67
|
+
return cross_section_grid
|
|
68
|
+
else:
|
|
69
|
+
surface_ids = cross_section_grid.celltypes != pv.CellType.POLYGON
|
|
70
|
+
grid_surfaces = cross_section_grid.extract_cells(surface_ids)
|
|
71
|
+
if closed:
|
|
72
|
+
n_surfaces = len(cross_section_points)
|
|
73
|
+
else:
|
|
74
|
+
n_surfaces = len(cross_section_points) - 1
|
|
75
|
+
n_cells = grid_surfaces.n_cells
|
|
76
|
+
surfaces = [
|
|
77
|
+
grid_surfaces.extract_cells(range(i_start, n_cells, n_surfaces))
|
|
78
|
+
for i_start in range(n_surfaces)
|
|
79
|
+
]
|
|
80
|
+
|
|
81
|
+
if closed:
|
|
82
|
+
bound_ids = cross_section_grid.celltypes == pv.CellType.POLYGON
|
|
83
|
+
grid_bound = cross_section_grid.extract_cells(bound_ids)
|
|
84
|
+
surfaces.append(grid_bound)
|
|
85
|
+
|
|
86
|
+
return pv.merge(surfaces, merge_points=False)
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
# The MIT License (MIT)
|
|
2
|
+
#
|
|
3
|
+
# Copyright (c) 2023-2025 Ivo Steinbrecher
|
|
4
|
+
#
|
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
# furnished to do so, subject to the following conditions:
|
|
11
|
+
#
|
|
12
|
+
# The above copyright notice and this permission notice shall be included in
|
|
13
|
+
# all copies or substantial portions of the Software.
|
|
14
|
+
#
|
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
# THE SOFTWARE.
|
|
22
|
+
"""Create a screenshot and the TikZ code to create the scalar bar."""
|
|
23
|
+
|
|
24
|
+
import os
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
|
|
27
|
+
import cv2
|
|
28
|
+
import pyvista as pv
|
|
29
|
+
|
|
30
|
+
# Inch to centimeter conversion
|
|
31
|
+
INCH = 2.54
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _dots_to_inch(pt, dpi):
|
|
35
|
+
"""Convert a distance in pt to cm."""
|
|
36
|
+
return float(pt) / dpi * INCH
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _hide_scalar_bar(scalar_bar):
|
|
40
|
+
"""Hide the scalar bar and return the data needed to reset it."""
|
|
41
|
+
data = {"title": scalar_bar.GetTitle()}
|
|
42
|
+
scalar_bar.SetTitle("")
|
|
43
|
+
scalar_bar.DrawTickLabelsOff()
|
|
44
|
+
return data
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _show_scalar_bar(scalar_bar, data):
|
|
48
|
+
"""Show the scalar bar and reset the options."""
|
|
49
|
+
scalar_bar.SetTitle(data["title"])
|
|
50
|
+
scalar_bar.DrawTickLabelsOn()
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _get_scalar_bar_rectangles(scalar_bars, image_pixel_data, dpi):
|
|
54
|
+
"""Get the position of the TikZ color bars.
|
|
55
|
+
|
|
56
|
+
We return the distance in cm where the scalar bar starts and the width
|
|
57
|
+
and the height:
|
|
58
|
+
[pos_x, pos_y, width, heighth]
|
|
59
|
+
|
|
60
|
+
For now we do this with image detection, but this could also be done by
|
|
61
|
+
directly interpreting the data from the scalar bar. There seems to be a
|
|
62
|
+
bug in pyvista such that the width of the scalar bar needs to be scaled
|
|
63
|
+
with 1/INCH.
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
shape = image_pixel_data.shape
|
|
67
|
+
|
|
68
|
+
# Convert to grayscale
|
|
69
|
+
gray = cv2.cvtColor(image_pixel_data, cv2.COLOR_RGB2GRAY)
|
|
70
|
+
|
|
71
|
+
# Apply edge detection
|
|
72
|
+
edges = cv2.Canny(gray, threshold1=50, threshold2=150)
|
|
73
|
+
|
|
74
|
+
# Find external contours
|
|
75
|
+
contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
|
76
|
+
|
|
77
|
+
rectangles = []
|
|
78
|
+
|
|
79
|
+
for contour in contours:
|
|
80
|
+
# Approximate contour to polygon
|
|
81
|
+
epsilon = 0.02 * cv2.arcLength(contour, True)
|
|
82
|
+
approx = cv2.approxPolyDP(contour, epsilon, True)
|
|
83
|
+
|
|
84
|
+
# Check for 4 corners and convexity
|
|
85
|
+
if len(approx) == 4 and cv2.isContourConvex(approx):
|
|
86
|
+
x, y, w, h = cv2.boundingRect(approx)
|
|
87
|
+
rectangle_pixel = [x, shape[0] - y - h + 1, w, h - 1]
|
|
88
|
+
rectangles.append([_dots_to_inch(val, dpi) for val in rectangle_pixel])
|
|
89
|
+
|
|
90
|
+
return rectangles
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _get_tikz_data(scalar_bar):
|
|
94
|
+
"""Return the data required for TikZ for this scalar bar."""
|
|
95
|
+
|
|
96
|
+
data = {}
|
|
97
|
+
|
|
98
|
+
min_max = list(scalar_bar.GetLookupTable().GetRange())
|
|
99
|
+
data["min_max"] = min_max
|
|
100
|
+
|
|
101
|
+
ticks = []
|
|
102
|
+
ticks.extend(min_max)
|
|
103
|
+
ticks.sort()
|
|
104
|
+
data["ticks"] = ticks
|
|
105
|
+
|
|
106
|
+
data["title"] = scalar_bar.GetTitle()
|
|
107
|
+
|
|
108
|
+
data["orientation"] = "v" if scalar_bar.GetOrientation() else "h"
|
|
109
|
+
|
|
110
|
+
return data
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _get_tikz_string_continuous(rectangle, data, number_format):
|
|
114
|
+
"""Get the TikZ code for a continuous color bar."""
|
|
115
|
+
|
|
116
|
+
# Add the code that is valid for all types of labels.
|
|
117
|
+
tikz_code = [
|
|
118
|
+
"\\begin{axis}[",
|
|
119
|
+
"scale only axis,",
|
|
120
|
+
"scaled x ticks=false,",
|
|
121
|
+
"scaled y ticks=false,",
|
|
122
|
+
f"at={{({rectangle[0]}cm,{rectangle[1]}cm)}},",
|
|
123
|
+
"tick label style={font=\\footnotesize},",
|
|
124
|
+
f"title={data['title']},",
|
|
125
|
+
f"xticklabel={number_format},",
|
|
126
|
+
f"yticklabel={number_format},",
|
|
127
|
+
f"xmin={data['min_max'][0]},",
|
|
128
|
+
f"xmax={data['min_max'][1]},",
|
|
129
|
+
f"ymin={data['min_max'][0]},",
|
|
130
|
+
f"ymax={data['min_max'][1]},",
|
|
131
|
+
f"width={rectangle[2]}cm,",
|
|
132
|
+
f"height={rectangle[3]}cm,",
|
|
133
|
+
]
|
|
134
|
+
|
|
135
|
+
ticks_string = ",".join(map(str, data["ticks"]))
|
|
136
|
+
|
|
137
|
+
if data["orientation"] == "v":
|
|
138
|
+
tikz_code += [
|
|
139
|
+
"xtick=\\empty,",
|
|
140
|
+
f"ytick={{{ticks_string}}},",
|
|
141
|
+
"ytick pos=right,",
|
|
142
|
+
"ytick align=outside,",
|
|
143
|
+
]
|
|
144
|
+
elif data["orientation"] == "h":
|
|
145
|
+
raise ValueError("For horizontal orientation the rectangles are wrong!")
|
|
146
|
+
tikz_code += [
|
|
147
|
+
"ytick=\\empty,",
|
|
148
|
+
f"xtick={{{ticks_string}}},",
|
|
149
|
+
"xtick pos=right,",
|
|
150
|
+
"xtick align=outside,",
|
|
151
|
+
"title style={{yshift=10pt,}},",
|
|
152
|
+
]
|
|
153
|
+
else:
|
|
154
|
+
raise ValueError("Got unexpected value for orientation")
|
|
155
|
+
|
|
156
|
+
tikz_code += ["]", "\\end{axis}"]
|
|
157
|
+
return tikz_code
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def export_to_tikz(
|
|
161
|
+
name_or_path: Path,
|
|
162
|
+
plotter: pv.Plotter,
|
|
163
|
+
*,
|
|
164
|
+
dpi: int = 300,
|
|
165
|
+
figure_path: str = "",
|
|
166
|
+
number_format: str = "{$\\pgfmathprintnumber[sci,precision=1,sci generic={mantissa sep=,exponent={\\mathrm{e}{##1}}}]{\\tick}$}",
|
|
167
|
+
is_testing: bool = True,
|
|
168
|
+
):
|
|
169
|
+
"""Export a screenshot and also export TikZ code that overlays a TikZ
|
|
170
|
+
scalar bar.
|
|
171
|
+
|
|
172
|
+
We export the image with the raw scalar bar, i.e., no labels, ticks, title, etc..
|
|
173
|
+
We also export TikZ code that can import this image and overlay a TikZ scalar bar
|
|
174
|
+
such that the numbering of the scalar bar can be done directly within LaTeX.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
name_or_path: Name of the output files. Can include directory paths.
|
|
178
|
+
plotter: Pyvista plotter
|
|
179
|
+
dpi: Desired resolution of image in dots per inch.
|
|
180
|
+
figure_path: Relative path to figures in the TeX document structure. Default is empty.
|
|
181
|
+
number_format: Number format to be used for the TeX ticks.
|
|
182
|
+
"""
|
|
183
|
+
|
|
184
|
+
# Name of image and TikZ file.
|
|
185
|
+
path_to_name = Path(name_or_path)
|
|
186
|
+
base_dir = path_to_name.parent
|
|
187
|
+
name = path_to_name.name
|
|
188
|
+
image_name = name + ".png"
|
|
189
|
+
image_path = base_dir / image_name
|
|
190
|
+
tikz_path = base_dir / (name + ".tex")
|
|
191
|
+
|
|
192
|
+
# Get all scalar bars in the plotter
|
|
193
|
+
scalar_bars = [plotter.scalar_bars[key] for key in list(plotter.scalar_bars.keys())]
|
|
194
|
+
|
|
195
|
+
# Hide the stuff we don't want in the image
|
|
196
|
+
original_data = [_hide_scalar_bar(scalar_bar) for scalar_bar in scalar_bars]
|
|
197
|
+
|
|
198
|
+
# Save the screenshot
|
|
199
|
+
if not is_testing:
|
|
200
|
+
image_pixel_data = plotter.screenshot(image_path)
|
|
201
|
+
else:
|
|
202
|
+
image_pixel_data = plotter.screenshot(None, return_img=True)
|
|
203
|
+
|
|
204
|
+
# Reset the scalar bar properties
|
|
205
|
+
for scalar_bar, data in zip(scalar_bars, original_data):
|
|
206
|
+
_show_scalar_bar(scalar_bar, data)
|
|
207
|
+
|
|
208
|
+
# Set the header of te TikZ code
|
|
209
|
+
tikz_code = [
|
|
210
|
+
"%% This file was created with vtktools",
|
|
211
|
+
"%% Use the following includes in the LaTeX header:",
|
|
212
|
+
"%\\usepackage{tikz}",
|
|
213
|
+
"%\\usepackage{pgfplots}",
|
|
214
|
+
"%% Optional:",
|
|
215
|
+
"%\\pgfplotsset{compat=1.16}",
|
|
216
|
+
"%\\normalsize % Sometimes in figure environments a smaller font is selected -> uncomment this to activate the default font size",
|
|
217
|
+
"\\begin{tikzpicture}",
|
|
218
|
+
"% The -0.2pt here are needed, so the image is really placed at the origin.",
|
|
219
|
+
f"\\node[anchor=south west,inner sep=-0.2pt] (image) at (0,0) {{\\includegraphics[scale={72.0 / dpi}]{{{os.path.join(figure_path, image_name)}}}}};",
|
|
220
|
+
]
|
|
221
|
+
|
|
222
|
+
# Get the positions of the scalar bar
|
|
223
|
+
rectangles = _get_scalar_bar_rectangles(scalar_bars, image_pixel_data, dpi)
|
|
224
|
+
|
|
225
|
+
# Get the data we need for the TikZ plot from the scalar bar
|
|
226
|
+
tikz_data_list = [_get_tikz_data(scalar_bar) for scalar_bar in scalar_bars]
|
|
227
|
+
|
|
228
|
+
# For now this only works for 1 rectangle
|
|
229
|
+
if not len(rectangles) == 1 or not len(tikz_data_list) == 1:
|
|
230
|
+
raise ValueError("This function currently only works for 1 rectangle")
|
|
231
|
+
|
|
232
|
+
# Add the TikZ code for each color bar
|
|
233
|
+
for rectangle, data in zip(rectangles, tikz_data_list):
|
|
234
|
+
tikz_code.extend(_get_tikz_string_continuous(rectangle, data, number_format))
|
|
235
|
+
|
|
236
|
+
# Finalize the TikZ code
|
|
237
|
+
tikz_code.append("\\end{tikzpicture}%\n%}%")
|
|
238
|
+
|
|
239
|
+
# Write TikZ code to file.
|
|
240
|
+
with open(tikz_path, "w") as text_file:
|
|
241
|
+
text_file.write("\n".join(tikz_code))
|