fieldview 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.
@@ -0,0 +1,158 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ build/
8
+ develop-eggs/
9
+ dist/
10
+ downloads/
11
+ eggs/
12
+ .eggs/
13
+ lib/
14
+ lib64/
15
+ parts/
16
+ sdist/
17
+ var/
18
+ wheels/
19
+ share/python-wheels/
20
+ *.egg-info/
21
+ .installed.cfg
22
+ *.egg
23
+ MANIFEST
24
+
25
+ # PyInstaller
26
+ # Usually these files are written by a python script from a template
27
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
28
+ *.manifest
29
+ *.spec
30
+
31
+ # Installer logs
32
+ pip-log.txt
33
+ pip-delete-this-directory.txt
34
+
35
+ # Unit test / coverage reports
36
+ htmlcov/
37
+ .tox/
38
+ .nox/
39
+ .coverage
40
+ .coverage.*
41
+ .cache
42
+ nosetests.xml
43
+ coverage.xml
44
+ *.cover
45
+ *.py,cover
46
+ .hypothesis/
47
+ .pytest_cache/
48
+ cover/
49
+
50
+ # Translations
51
+ *.mo
52
+ *.pot
53
+
54
+ # Django stuff:
55
+ *.log
56
+ local_settings.py
57
+ db.sqlite3
58
+ db.sqlite3-journal
59
+
60
+ # Flask stuff:
61
+ instance/
62
+ .webassets-cache
63
+
64
+ # Scrapy stuff:
65
+ .scrapy
66
+
67
+ # Sphinx documentation
68
+ docs/_build/
69
+
70
+ # PyBuilder
71
+ .pybuilder/
72
+
73
+ # Jupyter Notebook
74
+ .ipynb_checkpoints
75
+
76
+ # IPython
77
+ profile_default/
78
+ ipython_config.py
79
+
80
+ # pyenv
81
+ # For a library or package, you might want to ignore these files since the code is
82
+ # intended to run in multiple environments; otherwise, check them in:
83
+ # .python-version
84
+
85
+ # pipenv
86
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
87
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
88
+ # with no cross-platform support, pipenv may install dependencies that don't work, or not
89
+ # install all needed dependencies.
90
+ #Pipfile.lock
91
+
92
+ # poetry
93
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
94
+ # This is especially recommended for binary dependencies to ensure reproducible builds.
95
+ #poetry.lock
96
+
97
+ # pdm
98
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
99
+ #pdm.lock
100
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
101
+ # in version control.
102
+ # https://pdm.fming.dev/#use-with-ide
103
+ .pdm.toml
104
+
105
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
106
+ __pypackages__/
107
+
108
+ # Celery stuff
109
+ celerybeat-schedule
110
+ celerybeat.pid
111
+
112
+ # SageMath parsed files
113
+ *.sage.py
114
+
115
+ # Environments
116
+ .env
117
+ .venv
118
+ env/
119
+ venv/
120
+ ENV/
121
+ env.bak/
122
+ venv.bak/
123
+
124
+ # Spyder project settings
125
+ .spyderproject
126
+ .spyproject
127
+
128
+ # Rope project settings
129
+ .ropeproject
130
+
131
+ # mkdocs documentation
132
+ /site
133
+
134
+ # mypy
135
+ .mypy_cache/
136
+ .dmypy.json
137
+ dmypy.json
138
+
139
+ # Pyre type checker
140
+ .pyre/
141
+
142
+ # pytype static type analyzer
143
+ .pytype/
144
+
145
+ # Cython debug symbols
146
+ cython_debug/
147
+
148
+ # PyCharm
149
+ .idea/
150
+
151
+ # VS Code
152
+ .vscode/
153
+
154
+ # MacOS
155
+ .DS_Store
156
+
157
+ # Generated demo files
158
+ dummy_data.csv
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 FieldView Team
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 all
13
+ 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 THE
21
+ SOFTWARE.
@@ -0,0 +1,121 @@
1
+ Metadata-Version: 2.4
2
+ Name: fieldview
3
+ Version: 0.1.0
4
+ Summary: Python + Qt based 2D data visualization solution for irregular data points
5
+ Author-email: FieldView Team <dev@fieldview.org>
6
+ License-File: LICENSE
7
+ Requires-Python: >=3.10
8
+ Requires-Dist: numpy>=1.24.1
9
+ Requires-Dist: pyside6>=6.6.0
10
+ Requires-Dist: scipy>=1.10.0
11
+ Description-Content-Type: text/markdown
12
+
13
+ # FieldView
14
+
15
+ **FieldView** is a high-performance Python + Qt (PySide6) library for 2D data visualization, specifically designed for handling irregular data points. It provides a robust rendering engine for heatmaps, markers, and text labels with minimal external dependencies.
16
+
17
+ <img src="assets/quick_start.png" alt="Quick Start" width="480">
18
+
19
+ ## Key Features
20
+
21
+ * **Fast Heatmap Rendering**: Hybrid RBF (Radial Basis Function) interpolation for high-quality visualization with real-time performance optimization.
22
+ * **Irregular Data Support**: Native handling of non-grid data points.
23
+ * **Polygon Masking**: Support for arbitrary boundary shapes (Polygon, Circle, Rectangle) to clip heatmaps.
24
+ * **Layer System**: Modular architecture with support for:
25
+ * **HeatmapLayer**: Color-based data visualization.
26
+ * **ValueLayer/LabelLayer**: Text rendering with collision avoidance.
27
+ * **PinLayer**: Marker placement.
28
+ * **SvgLayer**: Background floor plans or overlays.
29
+ * **Minimal Dependencies**: Built on `numpy`, `scipy`, and `PySide6`.
30
+
31
+ ## Installation
32
+
33
+ ```bash
34
+ pip install fieldview
35
+ ```
36
+
37
+ *Note: Requires Python 3.10+*
38
+
39
+ ## Quick Start
40
+
41
+ Here is a minimal example to get a heatmap up and running:
42
+
43
+ ```python
44
+ import sys
45
+ import os
46
+ import numpy as np
47
+ from PySide6.QtWidgets import QApplication, QGraphicsView, QGraphicsScene
48
+ from PySide6.QtCore import Qt
49
+ from fieldview.core.data_container import DataContainer
50
+ from fieldview.layers.heatmap_layer import HeatmapLayer
51
+ from fieldview.layers.text_layer import ValueLayer
52
+ from fieldview.layers.svg_layer import SvgLayer
53
+ from fieldview.layers.pin_layer import PinLayer
54
+
55
+ app = QApplication(sys.argv)
56
+
57
+ # 1. Setup Data
58
+ data = DataContainer()
59
+ np.random.seed(44)
60
+ points = (np.random.rand(20, 2) - 0.5) * 300
61
+ values = np.random.rand(20) * 100
62
+ data.set_data(points, values)
63
+
64
+ # 2. Create Scene & Layers
65
+ scene = QGraphicsScene()
66
+
67
+ # SVG Layer (Background)
68
+ # Assuming floorplan.svg exists in current dir or provide path
69
+ svg_layer = SvgLayer()
70
+ svg_layer.load_svg("examples/floorplan.svg")
71
+ svg_layer.setZValue(0)
72
+ scene.addItem(svg_layer)
73
+
74
+ # Heatmap Layer
75
+ heatmap = HeatmapLayer(data)
76
+ heatmap.setOpacity(0.6)
77
+ heatmap.setZValue(1)
78
+ heatmap.set_boundary_shape(svg_layer._bounding_rect)
79
+ scene.addItem(heatmap)
80
+
81
+ # Pin Layer
82
+ pin_layer = PinLayer(data)
83
+ pin_layer.setZValue(2)
84
+ scene.addItem(pin_layer)
85
+
86
+ # Value Layer
87
+ values_layer = ValueLayer(data)
88
+ values_layer.setZValue(3)
89
+ scene.addItem(values_layer)
90
+
91
+ # 3. Setup View
92
+ view = QGraphicsView(scene)
93
+ view.resize(800, 600)
94
+ view.show()
95
+
96
+ # Ensure content is visible
97
+ scene.setSceneRect(scene.itemsBoundingRect())
98
+ view.fitInView(scene.sceneRect(), Qt.AspectRatioMode.KeepAspectRatio)
99
+
100
+ sys.exit(app.exec())
101
+ ```
102
+
103
+
104
+ ## Running the Demo
105
+
106
+ To see all features in action, including the property editor and real-time interaction:
107
+
108
+ ```bash
109
+ # Clone the repository
110
+ git clone https://github.com/yourusername/fieldview.git
111
+ cd fieldview
112
+
113
+ # Run the demo using uv (recommended)
114
+ uv run examples/demo.py
115
+ ```
116
+
117
+ <img src="assets/demo.gif" alt="Demo" width="800">
118
+
119
+ ## License
120
+
121
+ MIT License
@@ -0,0 +1,109 @@
1
+ # FieldView
2
+
3
+ **FieldView** is a high-performance Python + Qt (PySide6) library for 2D data visualization, specifically designed for handling irregular data points. It provides a robust rendering engine for heatmaps, markers, and text labels with minimal external dependencies.
4
+
5
+ <img src="assets/quick_start.png" alt="Quick Start" width="480">
6
+
7
+ ## Key Features
8
+
9
+ * **Fast Heatmap Rendering**: Hybrid RBF (Radial Basis Function) interpolation for high-quality visualization with real-time performance optimization.
10
+ * **Irregular Data Support**: Native handling of non-grid data points.
11
+ * **Polygon Masking**: Support for arbitrary boundary shapes (Polygon, Circle, Rectangle) to clip heatmaps.
12
+ * **Layer System**: Modular architecture with support for:
13
+ * **HeatmapLayer**: Color-based data visualization.
14
+ * **ValueLayer/LabelLayer**: Text rendering with collision avoidance.
15
+ * **PinLayer**: Marker placement.
16
+ * **SvgLayer**: Background floor plans or overlays.
17
+ * **Minimal Dependencies**: Built on `numpy`, `scipy`, and `PySide6`.
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ pip install fieldview
23
+ ```
24
+
25
+ *Note: Requires Python 3.10+*
26
+
27
+ ## Quick Start
28
+
29
+ Here is a minimal example to get a heatmap up and running:
30
+
31
+ ```python
32
+ import sys
33
+ import os
34
+ import numpy as np
35
+ from PySide6.QtWidgets import QApplication, QGraphicsView, QGraphicsScene
36
+ from PySide6.QtCore import Qt
37
+ from fieldview.core.data_container import DataContainer
38
+ from fieldview.layers.heatmap_layer import HeatmapLayer
39
+ from fieldview.layers.text_layer import ValueLayer
40
+ from fieldview.layers.svg_layer import SvgLayer
41
+ from fieldview.layers.pin_layer import PinLayer
42
+
43
+ app = QApplication(sys.argv)
44
+
45
+ # 1. Setup Data
46
+ data = DataContainer()
47
+ np.random.seed(44)
48
+ points = (np.random.rand(20, 2) - 0.5) * 300
49
+ values = np.random.rand(20) * 100
50
+ data.set_data(points, values)
51
+
52
+ # 2. Create Scene & Layers
53
+ scene = QGraphicsScene()
54
+
55
+ # SVG Layer (Background)
56
+ # Assuming floorplan.svg exists in current dir or provide path
57
+ svg_layer = SvgLayer()
58
+ svg_layer.load_svg("examples/floorplan.svg")
59
+ svg_layer.setZValue(0)
60
+ scene.addItem(svg_layer)
61
+
62
+ # Heatmap Layer
63
+ heatmap = HeatmapLayer(data)
64
+ heatmap.setOpacity(0.6)
65
+ heatmap.setZValue(1)
66
+ heatmap.set_boundary_shape(svg_layer._bounding_rect)
67
+ scene.addItem(heatmap)
68
+
69
+ # Pin Layer
70
+ pin_layer = PinLayer(data)
71
+ pin_layer.setZValue(2)
72
+ scene.addItem(pin_layer)
73
+
74
+ # Value Layer
75
+ values_layer = ValueLayer(data)
76
+ values_layer.setZValue(3)
77
+ scene.addItem(values_layer)
78
+
79
+ # 3. Setup View
80
+ view = QGraphicsView(scene)
81
+ view.resize(800, 600)
82
+ view.show()
83
+
84
+ # Ensure content is visible
85
+ scene.setSceneRect(scene.itemsBoundingRect())
86
+ view.fitInView(scene.sceneRect(), Qt.AspectRatioMode.KeepAspectRatio)
87
+
88
+ sys.exit(app.exec())
89
+ ```
90
+
91
+
92
+ ## Running the Demo
93
+
94
+ To see all features in action, including the property editor and real-time interaction:
95
+
96
+ ```bash
97
+ # Clone the repository
98
+ git clone https://github.com/yourusername/fieldview.git
99
+ cd fieldview
100
+
101
+ # Run the demo using uv (recommended)
102
+ uv run examples/demo.py
103
+ ```
104
+
105
+ <img src="assets/demo.gif" alt="Demo" width="800">
106
+
107
+ ## License
108
+
109
+ MIT License
File without changes
File without changes
@@ -0,0 +1,158 @@
1
+ import numpy as np
2
+ from PySide6.QtCore import QObject, Signal
3
+
4
+ class DataContainer(QObject):
5
+ """
6
+ Manages the core data (points and values) for the FieldView library.
7
+ Emits signals when data changes.
8
+ """
9
+ dataChanged = Signal()
10
+
11
+ def __init__(self):
12
+ super().__init__()
13
+ self._points = np.empty((0, 2), dtype=float)
14
+ self._values = np.empty((0,), dtype=float)
15
+ self._labels = []
16
+
17
+ @property
18
+ def points(self):
19
+ return self._points
20
+
21
+ @property
22
+ def values(self):
23
+ return self._values
24
+
25
+ @property
26
+ def labels(self):
27
+ return self._labels
28
+
29
+ def set_data(self, points, values, labels=None):
30
+ """
31
+ Sets the data points, values, and optional labels.
32
+
33
+ Args:
34
+ points (np.ndarray): Nx2 array of (x, y) coordinates.
35
+ values (np.ndarray): N array of values.
36
+ labels (list): Optional list of N strings.
37
+ """
38
+ points = np.array(points)
39
+ values = np.array(values)
40
+
41
+ if points.ndim != 2 or points.shape[1] != 2:
42
+ raise ValueError("Points must be an Nx2 array.")
43
+ if values.ndim != 1:
44
+ raise ValueError("Values must be a 1D array.")
45
+ if len(points) != len(values):
46
+ raise ValueError("Points and values must have the same length.")
47
+
48
+ if labels is None:
49
+ labels = [""] * len(points)
50
+ elif len(labels) != len(points):
51
+ raise ValueError("Labels must have the same length as points.")
52
+
53
+ self._points = points
54
+ self._values = values
55
+ self._labels = list(labels)
56
+ self.dataChanged.emit()
57
+
58
+ def add_points(self, points, values, labels=None):
59
+ """
60
+ Adds new points, values, and optional labels.
61
+ """
62
+ points = np.array(points)
63
+ values = np.array(values)
64
+
65
+ if len(points) == 0:
66
+ return
67
+
68
+ if labels is None:
69
+ labels = [""] * len(points)
70
+ elif len(labels) != len(points):
71
+ raise ValueError("Labels must have the same length as points.")
72
+
73
+ if self._points.shape[0] == 0:
74
+ self.set_data(points, values, labels)
75
+ else:
76
+ self._points = np.vstack((self._points, points))
77
+ self._values = np.concatenate((self._values, values))
78
+ self._labels.extend(labels)
79
+ self.dataChanged.emit()
80
+
81
+ def update_point(self, index, value=None, point=None, label=None):
82
+ """
83
+ Updates the value, coordinate, or label of a specific point.
84
+ """
85
+ if index < 0 or index >= len(self._points):
86
+ raise IndexError("Point index out of range.")
87
+
88
+ changed = False
89
+ if value is not None:
90
+ self._values[index] = value
91
+ changed = True
92
+
93
+ if point is not None:
94
+ self._points[index] = point
95
+ changed = True
96
+
97
+ if label is not None:
98
+ self._labels[index] = label
99
+ changed = True
100
+
101
+ if changed:
102
+ self.dataChanged.emit()
103
+
104
+ def remove_points(self, indices):
105
+ """
106
+ Removes points at the specified indices.
107
+ """
108
+ if len(indices) == 0:
109
+ return
110
+
111
+ # Sort indices in descending order to avoid shifting issues if we were popping,
112
+ # but numpy delete handles it. For list, we need to be careful.
113
+ # It's better to create a mask or rebuild the list.
114
+
115
+ mask = np.ones(len(self._points), dtype=bool)
116
+ mask[indices] = False
117
+
118
+ self._points = self._points[mask]
119
+ self._values = self._values[mask]
120
+ self._labels = [self._labels[i] for i in range(len(self._labels)) if mask[i]]
121
+
122
+ self.dataChanged.emit()
123
+
124
+ def clear(self):
125
+ """
126
+ Removes all data.
127
+ """
128
+ self._points = np.empty((0, 2), dtype=float)
129
+ self._values = np.empty((0,), dtype=float)
130
+ self._labels = []
131
+ self.dataChanged.emit()
132
+
133
+ def get_closest_point(self, x, y, threshold=None):
134
+ """
135
+ Finds the index of the closest point to (x, y).
136
+
137
+ Args:
138
+ x, y: Coordinates.
139
+ threshold: Optional maximum distance. If closest point is further, returns None.
140
+
141
+ Returns:
142
+ int: Index of the closest point, or None.
143
+ """
144
+ if len(self._points) == 0:
145
+ return None
146
+
147
+ # Calculate squared distances
148
+ diff = self._points - np.array([x, y])
149
+ dist_sq = np.sum(diff**2, axis=1)
150
+
151
+ min_idx = np.argmin(dist_sq)
152
+ min_dist_sq = dist_sq[min_idx]
153
+
154
+ if threshold is not None:
155
+ if min_dist_sq > threshold**2:
156
+ return None
157
+
158
+ return min_idx
File without changes
@@ -0,0 +1,89 @@
1
+ from PySide6.QtCore import QRectF
2
+ import numpy as np
3
+ from fieldview.layers.layer import Layer
4
+ from fieldview.core.data_container import DataContainer
5
+
6
+ class DataLayer(Layer):
7
+ """
8
+ Base class for layers that visualize data from a DataContainer.
9
+ Handles data change signals and excluded indices.
10
+ """
11
+ def __init__(self, data_container: DataContainer, parent=None):
12
+ super().__init__(parent)
13
+ self._data_container = data_container
14
+ self._excluded_indices = set()
15
+
16
+ # Connect signal
17
+ self._data_container.dataChanged.connect(self.on_data_changed)
18
+ # Initial update
19
+ self.on_data_changed()
20
+
21
+ @property
22
+ def data_container(self):
23
+ return self._data_container
24
+
25
+ @property
26
+ def excluded_indices(self):
27
+ return self._excluded_indices
28
+
29
+ def set_excluded_indices(self, indices):
30
+ """
31
+ Sets the set of indices to exclude from visualization.
32
+ """
33
+ self._excluded_indices = set(indices)
34
+ self.update_layer()
35
+
36
+ def add_excluded_index(self, index):
37
+ self._excluded_indices.add(index)
38
+ self.update_layer()
39
+
40
+ def remove_excluded_index(self, index):
41
+ if index in self._excluded_indices:
42
+ self._excluded_indices.remove(index)
43
+ self.update_layer()
44
+
45
+ def clear_excluded_indices(self):
46
+ self._excluded_indices.clear()
47
+ self.update_layer()
48
+
49
+ def on_data_changed(self):
50
+ """
51
+ Slot called when DataContainer data changes.
52
+ """
53
+ self._update_bounding_rect()
54
+ self.update_layer()
55
+
56
+ def _update_bounding_rect(self):
57
+ points = self._data_container.points
58
+ if len(points) == 0:
59
+ return
60
+
61
+ min_x = np.min(points[:, 0])
62
+ max_x = np.max(points[:, 0])
63
+ min_y = np.min(points[:, 1])
64
+ max_y = np.max(points[:, 1])
65
+
66
+ # Add padding (e.g., for icons or text)
67
+ padding = 50
68
+ rect = QRectF(min_x - padding, min_y - padding,
69
+ max_x - min_x + 2*padding, max_y - min_y + 2*padding)
70
+ self.set_bounding_rect(rect)
71
+
72
+ def get_valid_data(self):
73
+ """
74
+ Returns points, values, and labels excluding the excluded indices.
75
+ """
76
+ points = self._data_container.points
77
+ values = self._data_container.values
78
+ labels = self._data_container.labels
79
+
80
+ if not self._excluded_indices:
81
+ return points, values, labels
82
+
83
+ # Create a mask for valid indices
84
+ mask = [i for i in range(len(points)) if i not in self._excluded_indices]
85
+
86
+ # Filter labels (list)
87
+ valid_labels = [labels[i] for i in mask]
88
+
89
+ return points[mask], values[mask], valid_labels