q3dviewer 1.1.3__tar.gz → 1.1.4__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.
Files changed (47) hide show
  1. {q3dviewer-1.1.3/q3dviewer.egg-info → q3dviewer-1.1.4}/PKG-INFO +27 -7
  2. {q3dviewer-1.1.3 → q3dviewer-1.1.4}/README.md +26 -6
  3. {q3dviewer-1.1.3 → q3dviewer-1.1.4}/pyproject.toml +1 -0
  4. {q3dviewer-1.1.3 → q3dviewer-1.1.4}/q3dviewer/custom_items/cloud_io_item.py +15 -5
  5. {q3dviewer-1.1.3 → q3dviewer-1.1.4}/q3dviewer/custom_items/cloud_item.py +13 -12
  6. {q3dviewer-1.1.3 → q3dviewer-1.1.4}/q3dviewer/custom_items/frame_item.py +3 -7
  7. {q3dviewer-1.1.3 → q3dviewer-1.1.4}/q3dviewer/custom_items/grid_item.py +7 -9
  8. {q3dviewer-1.1.3 → q3dviewer-1.1.4}/q3dviewer/custom_items/line_item.py +14 -10
  9. {q3dviewer-1.1.3 → q3dviewer-1.1.4}/q3dviewer/custom_items/text_item.py +7 -5
  10. {q3dviewer-1.1.3 → q3dviewer-1.1.4}/q3dviewer/glwidget.py +8 -14
  11. {q3dviewer-1.1.3 → q3dviewer-1.1.4}/q3dviewer/tools/film_maker.py +0 -2
  12. {q3dviewer-1.1.3 → q3dviewer-1.1.4}/q3dviewer/utils/cloud_io.py +41 -26
  13. {q3dviewer-1.1.3 → q3dviewer-1.1.4}/q3dviewer/utils/maths.py +21 -19
  14. {q3dviewer-1.1.3 → q3dviewer-1.1.4/q3dviewer.egg-info}/PKG-INFO +27 -7
  15. {q3dviewer-1.1.3 → q3dviewer-1.1.4}/q3dviewer.egg-info/requires.txt +1 -0
  16. {q3dviewer-1.1.3 → q3dviewer-1.1.4}/setup.py +2 -1
  17. {q3dviewer-1.1.3 → q3dviewer-1.1.4}/LICENSE +0 -0
  18. {q3dviewer-1.1.3 → q3dviewer-1.1.4}/q3dviewer/__init__.py +0 -0
  19. {q3dviewer-1.1.3 → q3dviewer-1.1.4}/q3dviewer/base_glwidget.py +0 -0
  20. {q3dviewer-1.1.3 → q3dviewer-1.1.4}/q3dviewer/base_item.py +0 -0
  21. {q3dviewer-1.1.3 → q3dviewer-1.1.4}/q3dviewer/custom_items/__init__.py +0 -0
  22. {q3dviewer-1.1.3 → q3dviewer-1.1.4}/q3dviewer/custom_items/axis_item.py +0 -0
  23. {q3dviewer-1.1.3 → q3dviewer-1.1.4}/q3dviewer/custom_items/gaussian_item.py +0 -0
  24. {q3dviewer-1.1.3 → q3dviewer-1.1.4}/q3dviewer/custom_items/image_item.py +0 -0
  25. {q3dviewer-1.1.3 → q3dviewer-1.1.4}/q3dviewer/shaders/cloud_frag.glsl +0 -0
  26. {q3dviewer-1.1.3 → q3dviewer-1.1.4}/q3dviewer/shaders/cloud_vert.glsl +0 -0
  27. {q3dviewer-1.1.3 → q3dviewer-1.1.4}/q3dviewer/shaders/gau_frag.glsl +0 -0
  28. {q3dviewer-1.1.3 → q3dviewer-1.1.4}/q3dviewer/shaders/gau_prep.glsl +0 -0
  29. {q3dviewer-1.1.3 → q3dviewer-1.1.4}/q3dviewer/shaders/gau_vert.glsl +0 -0
  30. {q3dviewer-1.1.3 → q3dviewer-1.1.4}/q3dviewer/shaders/sort_by_key.glsl +0 -0
  31. {q3dviewer-1.1.3 → q3dviewer-1.1.4}/q3dviewer/tools/__init__.py +0 -0
  32. {q3dviewer-1.1.3 → q3dviewer-1.1.4}/q3dviewer/tools/cloud_viewer.py +0 -0
  33. {q3dviewer-1.1.3 → q3dviewer-1.1.4}/q3dviewer/tools/example_viewer.py +0 -0
  34. {q3dviewer-1.1.3 → q3dviewer-1.1.4}/q3dviewer/tools/gaussian_viewer.py +0 -0
  35. {q3dviewer-1.1.3 → q3dviewer-1.1.4}/q3dviewer/tools/lidar_calib.py +0 -0
  36. {q3dviewer-1.1.3 → q3dviewer-1.1.4}/q3dviewer/tools/lidar_cam_calib.py +0 -0
  37. {q3dviewer-1.1.3 → q3dviewer-1.1.4}/q3dviewer/tools/ros_viewer.py +0 -0
  38. {q3dviewer-1.1.3 → q3dviewer-1.1.4}/q3dviewer/utils/__init__.py +0 -0
  39. {q3dviewer-1.1.3 → q3dviewer-1.1.4}/q3dviewer/utils/convert_ros_msg.py +0 -0
  40. {q3dviewer-1.1.3 → q3dviewer-1.1.4}/q3dviewer/utils/gl_helper.py +0 -0
  41. {q3dviewer-1.1.3 → q3dviewer-1.1.4}/q3dviewer/utils/range_slider.py +0 -0
  42. {q3dviewer-1.1.3 → q3dviewer-1.1.4}/q3dviewer/viewer.py +0 -0
  43. {q3dviewer-1.1.3 → q3dviewer-1.1.4}/q3dviewer.egg-info/SOURCES.txt +0 -0
  44. {q3dviewer-1.1.3 → q3dviewer-1.1.4}/q3dviewer.egg-info/dependency_links.txt +0 -0
  45. {q3dviewer-1.1.3 → q3dviewer-1.1.4}/q3dviewer.egg-info/entry_points.txt +0 -0
  46. {q3dviewer-1.1.3 → q3dviewer-1.1.4}/q3dviewer.egg-info/top_level.txt +0 -0
  47. {q3dviewer-1.1.3 → q3dviewer-1.1.4}/setup.cfg +0 -0
@@ -1,14 +1,22 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: q3dviewer
3
- Version: 1.1.3
3
+ Version: 1.1.4
4
4
  Summary: A library designed for quickly deploying a 3D viewer.
5
5
  Home-page: https://github.com/scomup/q3dviewer
6
6
  Author: Liu Yang
7
7
  License: UNKNOWN
8
- Description: ## q3dviewer
8
+ Description:
9
+ ![q3dviewer Logo](imgs/logo.png)
10
+
11
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
12
+ [![PyPI version](https://badge.fury.io/py/q3dviewer.svg)](https://badge.fury.io/py/q3dviewer)
9
13
 
10
14
  `q3dviewer` is a library designed for quickly deploying a 3D viewer. It is based on Qt (PySide6) and provides efficient OpenGL items for displaying 3D objects (e.g., point clouds, cameras, and 3D Gaussians). You can use it to visualize your 3D data or set up an efficient viewer application. It is inspired by PyQtGraph but focuses more on efficient 3D rendering.
11
15
 
16
+
17
+ To show how to use `q3dviewer` as a library, we also provide some [very useful tools](#tools).
18
+
19
+
12
20
  ## Installation
13
21
 
14
22
  To install `q3dviewer`, execute the following command in your terminal on either Linux or Windows:
@@ -37,10 +45,10 @@ Description: ## q3dviewer
37
45
 
38
46
  ### 1. Cloud Viewer
39
47
 
40
- A tool for visualizing point cloud files. Launch it by executing the following command in your terminal:
48
+ A tool for visualizing point cloud files (LAS, PCD, PLY, E57). Launch it by executing the following command in your terminal:
41
49
 
42
50
  ```sh
43
- cloud_viewer # The viewer will be displayed
51
+ cloud_viewer
44
52
  ```
45
53
 
46
54
  *Alternatively*, if the path is not set (though it's not recommended):
@@ -49,7 +57,13 @@ Description: ## q3dviewer
49
57
  python3 -m q3dviewer.tools.cloud_viewer
50
58
  ```
51
59
 
52
- After the viewer launches, you can drag and drop files onto the window to display the point clouds. Multiple files can be dropped simultaneously to view them together. Supported formats include LAS, PCD, PLY, and E57.
60
+ **Basic Operations**
61
+ * Load files: Drag and drop point cloud files onto the window (multiple files are OK).
62
+ * `M` key: Display the visualization settings screen for point clouds, background color, etc.
63
+ * `Left mouse button` & `W, A, S, D` keys: Move the viewpoint on the horizontal plane.
64
+ * `Z, X` keys: Move in the direction the screen is facing.
65
+ * `Right mouse button` & `Arrow` keys: Rotate the viewpoint while keeping the screen center unchanged.
66
+ * `Shift` + `Right mouse button` & `Arrow` keys: Rotate the viewpoint while keeping the camera position unchanged.
53
67
 
54
68
  For example, you can download and view point clouds of Tokyo in LAS format from the following link:
55
69
 
@@ -75,15 +89,21 @@ Description: ## q3dviewer
75
89
  Would you like to create a video from point cloud data? With Film Maker, you can easily create videos with simple operations. Just edit keyframes using the user-friendly GUI, and the software will automatically interpolate the keyframes to generate the video.
76
90
 
77
91
  ```sh
78
- film_maker # drag and drop your cloud file to the window
92
+ film_maker
79
93
  ```
80
94
 
95
+ **Basic Operations**
96
+ * File loading & viewpoint movement: Same as Cloud_Viewer
81
97
  * Space key to add a keyframe.
82
98
  * Delete key to remove a keyframe.
99
+ * Play button: Automatically play the video (pressing again will stop playback)
100
+ * Record checkbox: When checked, actions will be automatically recorded during playback
83
101
 
84
102
  Film Maker GUI:
85
103
 
86
- ![Screenshot from 2025-02-02 18-20-51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149168/a1a6ad63-237c-482e-439d-e760223c59ca.png)
104
+ ![film_maker_demo.gif](imgs/film_maker_demo.gif)
105
+
106
+ The demo video demonstrating how to use Film Maker utilizes the [cloud data of Kyobashi Station Area](https://www.geospatial.jp/ckan/dataset/kyoubasiekisyuuhen_las) located in Osaka, Japan.
87
107
 
88
108
  ### 4. Gaussian Viewer
89
109
 
@@ -1,7 +1,15 @@
1
- ## q3dviewer
1
+
2
+ ![q3dviewer Logo](imgs/logo.png)
3
+
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+ [![PyPI version](https://badge.fury.io/py/q3dviewer.svg)](https://badge.fury.io/py/q3dviewer)
2
6
 
3
7
  `q3dviewer` is a library designed for quickly deploying a 3D viewer. It is based on Qt (PySide6) and provides efficient OpenGL items for displaying 3D objects (e.g., point clouds, cameras, and 3D Gaussians). You can use it to visualize your 3D data or set up an efficient viewer application. It is inspired by PyQtGraph but focuses more on efficient 3D rendering.
4
8
 
9
+
10
+ To show how to use `q3dviewer` as a library, we also provide some [very useful tools](#tools).
11
+
12
+
5
13
  ## Installation
6
14
 
7
15
  To install `q3dviewer`, execute the following command in your terminal on either Linux or Windows:
@@ -30,10 +38,10 @@ Once installed, you can directly use the following tools:
30
38
 
31
39
  ### 1. Cloud Viewer
32
40
 
33
- A tool for visualizing point cloud files. Launch it by executing the following command in your terminal:
41
+ A tool for visualizing point cloud files (LAS, PCD, PLY, E57). Launch it by executing the following command in your terminal:
34
42
 
35
43
  ```sh
36
- cloud_viewer # The viewer will be displayed
44
+ cloud_viewer
37
45
  ```
38
46
 
39
47
  *Alternatively*, if the path is not set (though it's not recommended):
@@ -42,7 +50,13 @@ cloud_viewer # The viewer will be displayed
42
50
  python3 -m q3dviewer.tools.cloud_viewer
43
51
  ```
44
52
 
45
- After the viewer launches, you can drag and drop files onto the window to display the point clouds. Multiple files can be dropped simultaneously to view them together. Supported formats include LAS, PCD, PLY, and E57.
53
+ **Basic Operations**
54
+ * Load files: Drag and drop point cloud files onto the window (multiple files are OK).
55
+ * `M` key: Display the visualization settings screen for point clouds, background color, etc.
56
+ * `Left mouse button` & `W, A, S, D` keys: Move the viewpoint on the horizontal plane.
57
+ * `Z, X` keys: Move in the direction the screen is facing.
58
+ * `Right mouse button` & `Arrow` keys: Rotate the viewpoint while keeping the screen center unchanged.
59
+ * `Shift` + `Right mouse button` & `Arrow` keys: Rotate the viewpoint while keeping the camera position unchanged.
46
60
 
47
61
  For example, you can download and view point clouds of Tokyo in LAS format from the following link:
48
62
 
@@ -68,15 +82,21 @@ ros_viewer
68
82
  Would you like to create a video from point cloud data? With Film Maker, you can easily create videos with simple operations. Just edit keyframes using the user-friendly GUI, and the software will automatically interpolate the keyframes to generate the video.
69
83
 
70
84
  ```sh
71
- film_maker # drag and drop your cloud file to the window
85
+ film_maker
72
86
  ```
73
87
 
88
+ **Basic Operations**
89
+ * File loading & viewpoint movement: Same as Cloud_Viewer
74
90
  * Space key to add a keyframe.
75
91
  * Delete key to remove a keyframe.
92
+ * Play button: Automatically play the video (pressing again will stop playback)
93
+ * Record checkbox: When checked, actions will be automatically recorded during playback
76
94
 
77
95
  Film Maker GUI:
78
96
 
79
- ![Screenshot from 2025-02-02 18-20-51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149168/a1a6ad63-237c-482e-439d-e760223c59ca.png)
97
+ ![film_maker_demo.gif](imgs/film_maker_demo.gif)
98
+
99
+ The demo video demonstrating how to use Film Maker utilizes the [cloud data of Kyobashi Station Area](https://www.geospatial.jp/ckan/dataset/kyoubasiekisyuuhen_las) located in Osaka, Japan.
80
100
 
81
101
  ### 4. Gaussian Viewer
82
102
 
@@ -14,6 +14,7 @@ pypcd4 = "^0.1"
14
14
  pye57 = "^0.1"
15
15
  laspy = "^2.0"
16
16
  imageio = "^2.9"
17
+ matplotlib = "^3.4"
17
18
 
18
19
  [tool.poetry.scripts]
19
20
  cloud_viewer = "q3dviewer.tools.cloud_viewer:main"
@@ -61,18 +61,28 @@ class CloudIOItem(CloudItem):
61
61
  def load(self, file, append=False):
62
62
  # print("Try to load %s ..." % file)
63
63
  if file.endswith(".pcd"):
64
- cloud, color_mode = load_pcd(file)
64
+ cloud = load_pcd(file)
65
65
  elif file.endswith(".ply"):
66
- cloud, color_mode = load_ply(file)
66
+ cloud = load_ply(file)
67
67
  elif file.endswith(".e57"):
68
- cloud, color_mode = load_e57(file)
68
+ cloud = load_e57(file)
69
69
  elif file.endswith(".las"):
70
- cloud, color_mode = load_las(file)
70
+ cloud = load_las(file)
71
71
  else:
72
72
  print("Not supported file type.")
73
73
  return
74
74
  self.set_data(data=cloud, append=append)
75
- self.set_color_mode(color_mode)
75
+
76
+ has_intensity = cloud['irgb'][0] & 0xff000000 > 0
77
+ has_rgb = cloud['irgb'][0] & 0x00ffffff > 0
78
+
79
+ if has_rgb:
80
+ self.set_color_mode('RGB')
81
+ elif has_intensity:
82
+ self.set_color_mode('I')
83
+ else:
84
+ self.set_color_mode('FLAT')
85
+
76
86
  return cloud
77
87
 
78
88
  def set_path(self, path):
@@ -14,15 +14,13 @@ from PySide6.QtWidgets import QLabel, QLineEdit, QDoubleSpinBox, \
14
14
  from OpenGL.GL import shaders
15
15
  from q3dviewer.utils import *
16
16
  from q3dviewer.utils.range_slider import RangeSlider
17
- from PySide6.QtCore import QRegularExpression
18
- from PySide6.QtGui import QRegularExpressionValidator
19
17
 
20
18
 
21
19
  # draw points with color (x, y, z, color)
22
20
  class CloudItem(BaseItem):
23
21
  def __init__(self, size, alpha,
24
22
  color_mode='I',
25
- color='#ffffff',
23
+ color='white',
26
24
  point_type='PIXEL',
27
25
  depth_test=False):
28
26
  super().__init__()
@@ -34,7 +32,12 @@ class CloudItem(BaseItem):
34
32
  self.point_type = point_type
35
33
  self.mutex = threading.Lock()
36
34
  self.data_type = [('xyz', '<f4', (3,)), ('irgb', '<u4')]
37
- self.flat_rgb = int(color[1:], 16)
35
+ self.color = color
36
+ try:
37
+ self.flat_rgb = text_to_rgba(color, flat=True)
38
+ except ValueError:
39
+ print(f"Invalid color: {color}, please use matplotlib color format")
40
+ exit(1)
38
41
  self.mode_table = {'FLAT': 0, 'I': 1, 'RGB': 2}
39
42
  self.point_type_table = {'PIXEL': 0, 'SQUARE': 1, 'SPHERE': 2}
40
43
  self.color_mode = self.mode_table[color_mode]
@@ -89,14 +92,12 @@ class CloudItem(BaseItem):
89
92
  layout.addWidget(self.combo_color)
90
93
 
91
94
  label_rgb = QLabel("Color:")
95
+ label_rgb.setToolTip("Use hex color, i.e. #FF4500, or named color, i.e. 'red'")
92
96
  layout.addWidget(label_rgb)
93
97
  self.edit_rgb = QLineEdit()
94
- self.edit_rgb.setToolTip("Hex number, i.e. #FF4500")
95
- self.edit_rgb.setText(f"#{self.flat_rgb:06x}")
98
+ self.edit_rgb.setToolTip("Use hex color, i.e. #FF4500, or named color, i.e. 'red'")
99
+ self.edit_rgb.setText(self.color)
96
100
  self.edit_rgb.textChanged.connect(self._on_color)
97
- regex = QRegularExpression(r"^#[0-9A-Fa-f]{6}$")
98
- validator = QRegularExpressionValidator(regex)
99
- self.edit_rgb.setValidator(validator)
100
101
  layout.addWidget(self.edit_rgb)
101
102
 
102
103
  self.slider_v = RangeSlider()
@@ -108,6 +109,7 @@ class CloudItem(BaseItem):
108
109
  "Show front points first (Depth Test)")
109
110
  self.checkbox_depth_test.setChecked(self.depth_test)
110
111
  self.checkbox_depth_test.stateChanged.connect(self.set_depthtest)
112
+ self._on_color_mode(self.color_mode)
111
113
  layout.addWidget(self.checkbox_depth_test)
112
114
 
113
115
  def _on_range(self, lower, upper):
@@ -164,11 +166,10 @@ class CloudItem(BaseItem):
164
166
 
165
167
  def _on_color(self, color):
166
168
  try:
167
- flat_rgb = int(color[1:], 16)
168
- self.flat_rgb = flat_rgb
169
+ self.flat_rgb = text_to_rgba(color, flat=True)
169
170
  self.need_update_setting = True
170
171
  except ValueError:
171
- pass
172
+ print(f"Invalid color: {color}, please use matplotlib color format")
172
173
 
173
174
  def set_size(self, size):
174
175
  self.size = size
@@ -162,13 +162,9 @@ class FrameItem(BaseItem):
162
162
  self.need_updating = False
163
163
 
164
164
  def set_color(self, color):
165
- if isinstance(color, str):
166
- self.rgba = hex_to_rgba(color)
167
- elif isinstance(color, list):
168
- self.rgba = color
169
- elif isinstance(color, tuple):
170
- self.rgba = list(color)
171
- else:
165
+ try:
166
+ self.rgba = text_to_rgba(color)
167
+ except ValueError:
172
168
  raise ValueError("Invalid color format")
173
169
 
174
170
  def set_line_width(self, width):
@@ -6,10 +6,8 @@ Distributed under MIT license. See LICENSE for more information.
6
6
  from q3dviewer.base_item import BaseItem
7
7
  from OpenGL.GL import *
8
8
  from PySide6.QtWidgets import QLabel, QDoubleSpinBox, QLineEdit
9
- from PySide6.QtCore import QRegularExpression
10
- from PySide6.QtGui import QRegularExpressionValidator
11
9
  import numpy as np
12
- from q3dviewer.utils.maths import hex_to_rgba
10
+ from q3dviewer.utils.maths import text_to_rgba
13
11
 
14
12
 
15
13
  class GridItem(BaseItem):
@@ -24,9 +22,9 @@ class GridItem(BaseItem):
24
22
 
25
23
  def set_color(self, color):
26
24
  try:
27
- self.rgba = hex_to_rgba(color)
25
+ self.rgba = text_to_rgba(color)
28
26
  except ValueError:
29
- pass
27
+ raise ValueError("Invalid color format. Use hex format like '#RRGGBB' or '#RRGGBBAA'.")
30
28
 
31
29
  def generate_grid_vertices(self):
32
30
  vertices = []
@@ -41,16 +39,13 @@ class GridItem(BaseItem):
41
39
  def initialize_gl(self):
42
40
  self.vao = glGenVertexArrays(1)
43
41
  vbo = glGenBuffers(1)
44
-
45
42
  glBindVertexArray(self.vao)
46
-
47
43
  glBindBuffer(GL_ARRAY_BUFFER, vbo)
48
44
  glBufferData(GL_ARRAY_BUFFER, self.vertices.nbytes, self.vertices, GL_STATIC_DRAW)
49
-
50
45
  glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, ctypes.c_void_p(0))
51
46
  glEnableVertexAttribArray(0)
52
-
53
47
  glBindVertexArray(0)
48
+ glBindBuffer(GL_ARRAY_BUFFER, 0)
54
49
 
55
50
  def add_setting(self, layout):
56
51
  spinbox_size = QDoubleSpinBox()
@@ -73,6 +68,7 @@ class GridItem(BaseItem):
73
68
  spinbox_offset_x.setPrefix("Offset X: ")
74
69
  spinbox_offset_x.setSingleStep(0.1)
75
70
  spinbox_offset_x.setValue(self.offset[0])
71
+ spinbox_offset_x.setRange(-1000, 1000)
76
72
  spinbox_offset_x.valueChanged.connect(self._on_offset_x)
77
73
  layout.addWidget(spinbox_offset_x)
78
74
 
@@ -80,6 +76,7 @@ class GridItem(BaseItem):
80
76
  spinbox_offset_y.setPrefix("Offset Y: ")
81
77
  spinbox_offset_y.setSingleStep(0.1)
82
78
  spinbox_offset_y.setValue(self.offset[1])
79
+ spinbox_offset_y.setRange(-1000, 1000)
83
80
  spinbox_offset_y.valueChanged.connect(self._on_offset_y)
84
81
  layout.addWidget(spinbox_offset_y)
85
82
 
@@ -87,6 +84,7 @@ class GridItem(BaseItem):
87
84
  spinbox_offset_z.setPrefix("Offset Z: ")
88
85
  spinbox_offset_z.setSingleStep(0.1)
89
86
  spinbox_offset_z.setValue(self.offset[2])
87
+ spinbox_offset_z.setRange(-1000, 1000)
90
88
  spinbox_offset_z.valueChanged.connect(self._on_offset_z)
91
89
  layout.addWidget(spinbox_offset_z)
92
90
 
@@ -8,13 +8,16 @@ from OpenGL.GL import *
8
8
  import numpy as np
9
9
  import threading
10
10
  from PySide6.QtWidgets import QLabel, QLineEdit, QDoubleSpinBox
11
- from PySide6.QtCore import QRegularExpression
12
- from PySide6.QtGui import QRegularExpressionValidator
13
- from q3dviewer.utils.maths import hex_to_rgba
11
+ from q3dviewer.utils.maths import text_to_rgba
14
12
 
15
13
 
16
14
  class LineItem(BaseItem):
17
15
  def __init__(self, width=1, color='#00ff00', line_type='LINE_STRIP'):
16
+ """
17
+ line_type: 'LINE_STRIP' or 'LINES'
18
+ LINE_STRIP: draw a connected line strip
19
+ LINES: draw a series of unconnected lines
20
+ """
18
21
  super(LineItem, self).__init__()
19
22
  self.width = width
20
23
  self.buff = np.empty((0, 3), np.float32)
@@ -23,19 +26,20 @@ class LineItem(BaseItem):
23
26
  self.capacity = 100000
24
27
  self.valid_buff_top = 0
25
28
  self.color = color
26
- self.rgb = hex_to_rgba(color)
29
+ try:
30
+ self.rgb = text_to_rgba(color)
31
+ except ValueError:
32
+ raise ValueError("Invalid color format. Use mathplotlib color format.")
33
+
27
34
  self.line_type = GL_LINE_STRIP if line_type == 'LINE_STRIP' else GL_LINES
28
35
 
29
36
  def add_setting(self, layout):
30
37
  label_color = QLabel("Color:")
31
38
  layout.addWidget(label_color)
32
39
  self.color_edit = QLineEdit()
33
- self.color_edit.setToolTip("Hex number, i.e. #FF4500")
40
+ self.color_edit.setToolTip("mathplotlib color format")
34
41
  self.color_edit.setText(self.color)
35
42
  self.color_edit.textChanged.connect(self._on_color)
36
- regex = QRegularExpression(r"^#[0-9A-Fa-f]{6}$")
37
- validator = QRegularExpressionValidator(regex)
38
- self.color_edit.setValidator(validator)
39
43
  layout.addWidget(self.color_edit)
40
44
 
41
45
  spinbox_width = QDoubleSpinBox()
@@ -48,10 +52,10 @@ class LineItem(BaseItem):
48
52
 
49
53
  def _on_color(self, color):
50
54
  try:
51
- self.rgb = hex_to_rgba(color)
55
+ self.rgb = text_to_rgba(color)
52
56
  self.color = color
53
57
  except ValueError:
54
- pass
58
+ print("Invalid color format. Use mathplotlib color format.")
55
59
 
56
60
  def set_color(self, color):
57
61
  self.color_edit.setText(color)
@@ -6,7 +6,7 @@ Distributed under MIT license. See LICENSE for more information.
6
6
  from PySide6 import QtCore, QtGui
7
7
  from q3dviewer.base_item import BaseItem
8
8
  from OpenGL.GL import *
9
- from q3dviewer.utils.maths import hex_to_rgba
9
+ from q3dviewer.utils.maths import text_to_rgba
10
10
 
11
11
 
12
12
  class Text2DItem(BaseItem):
@@ -16,8 +16,10 @@ class Text2DItem(BaseItem):
16
16
  """All keyword arguments are passed to set_data()"""
17
17
  BaseItem.__init__(self)
18
18
  self.pos = (20, 50)
19
- self.color = '#ffffff'
20
- self.rgb = hex_to_rgba(self.color)
19
+ try:
20
+ self.rgb = text_to_rgba(self.color)
21
+ except ValueError:
22
+ raise ValueError("Invalid color format. Use mathplotlib color format.")
21
23
  self.text = ''
22
24
  self.font = QtGui.QFont('Helvetica', 16)
23
25
  self.set_data(**kwds)
@@ -44,10 +46,10 @@ class Text2DItem(BaseItem):
44
46
 
45
47
  def set_color(self, color):
46
48
  try:
47
- self.rgb = hex_to_rgba(color)
49
+ self.rgb = text_to_rgba(color)
48
50
  self.color = color
49
51
  except ValueError:
50
- pass
52
+ print("Invalid color format. Use mathplotlib color format.")
51
53
 
52
54
  def paint(self):
53
55
  if len(self.text) < 1:
@@ -5,11 +5,11 @@ Distributed under MIT license. See LICENSE for more information.
5
5
 
6
6
  from PySide6 import QtCore
7
7
  from PySide6.QtWidgets import QWidget, QComboBox, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QCheckBox, QGroupBox
8
- from PySide6.QtGui import QKeyEvent, QVector3D, QRegularExpressionValidator
9
- from PySide6.QtCore import QRegularExpression
8
+ from PySide6.QtGui import QKeyEvent, QVector3D
10
9
  from OpenGL.GL import *
11
10
  import numpy as np
12
11
  from q3dviewer.base_glwidget import BaseGLWidget
12
+ from q3dviewer.utils.maths import text_to_rgba
13
13
 
14
14
  class SettingWindow(QWidget):
15
15
  def __init__(self):
@@ -52,7 +52,7 @@ class GLWidget(BaseGLWidget):
52
52
  def __init__(self):
53
53
  self.followed_name = 'none'
54
54
  self.named_items = {}
55
- self.color_str = '#000000'
55
+ self.color_str = 'black'
56
56
  self.followable_item_name = None
57
57
  self.setting_window = SettingWindow()
58
58
  self.enable_show_center = True
@@ -80,9 +80,6 @@ class GLWidget(BaseGLWidget):
80
80
  color_edit.setToolTip("'using hex color, i.e. #FF4500")
81
81
  color_edit.setText(self.color_str)
82
82
  color_edit.textChanged.connect(self.set_bg_color)
83
- regex = QRegularExpression(r"^#[0-9A-Fa-f]{6}$")
84
- validator = QRegularExpressionValidator(regex)
85
- color_edit.setValidator(validator)
86
83
  layout.addWidget(color_edit)
87
84
 
88
85
  label_focus = QLabel("Set Focus:")
@@ -108,16 +105,13 @@ class GLWidget(BaseGLWidget):
108
105
  if item.__class__.__name__ == 'AxisItem' and not item._disable_setting:
109
106
  self.followable_item_name.append(name)
110
107
 
111
- def set_bg_color(self, color_str):
108
+ def set_bg_color(self, color):
112
109
  try:
113
- color_flat = int(color_str[1:], 16)
114
- red = (color_flat >> 16) & 0xFF
115
- green = (color_flat >> 8) & 0xFF
116
- blue = color_flat & 0xFF
117
- self.color_str = color_str
118
- self.set_color([red, green, blue, 0])
110
+ self.color_str = color
111
+ red, green, blue, alpha = text_to_rgba(color)
112
+ self.set_color([red, green, blue, alpha])
119
113
  except ValueError:
120
- return
114
+ print("Invalid color format. Use mathplotlib color format.")
121
115
 
122
116
  def add_item_with_name(self, name, item):
123
117
  self.named_items.update({name: item})
@@ -151,7 +151,6 @@ class CMMViewer(q3d.Viewer):
151
151
  main_layout.addWidget(dock_widget)
152
152
  self.dock = dock_widget
153
153
 
154
-
155
154
  def update_video_path(self, path):
156
155
  self.video_path = path
157
156
 
@@ -235,7 +234,6 @@ class CMMViewer(q3d.Viewer):
235
234
  self.glwidget.set_cam_position(center=center,
236
235
  euler=euler)
237
236
 
238
-
239
237
  def create_frames(self):
240
238
  """
241
239
  Create the frames for playback by interpolating between key frames.
@@ -24,17 +24,14 @@ def load_ply(file):
24
24
  xyz = mesh.points
25
25
  rgb = np.zeros([xyz.shape[0]], dtype=np.uint32)
26
26
  intensity = np.zeros([xyz.shape[0]], dtype=np.uint32)
27
- color_mode = 'FLAT'
28
27
  if "intensity" in mesh.point_data:
29
28
  intensity = mesh.point_data["intensity"]
30
- color_mode = 'I'
31
29
  if "rgb" in mesh.point_data:
32
30
  rgb = mesh.point_data["rgb"]
33
- color_mode = 'RGB'
34
31
  irgb = (intensity << 24) | rgb
35
32
  dtype = [('xyz', '<f4', (3,)), ('irgb', '<u4')]
36
33
  cloud = np.rec.fromarrays([xyz, irgb], dtype=dtype)
37
- return cloud, color_mode
34
+ return cloud
38
35
 
39
36
 
40
37
  def save_pcd(cloud, save_path):
@@ -51,8 +48,35 @@ def save_pcd(cloud, save_path):
51
48
  i = (cloud['irgb'] & 0xFF000000) >> 24
52
49
  rgb = cloud['irgb'] & 0x00FFFFFF
53
50
 
54
- dtype = [('xyz', '<f4', (3,)), ('intensity', '<u4'), ('rgb', '<u4')]
55
- tmp = np.rec.fromarrays([cloud['xyz'], i, rgb], dtype=dtype)
51
+ # check no rgb value
52
+ if np.max(rgb) == 0:
53
+ fields = ('x', 'y', 'z', 'intensity')
54
+ metadata = MetaData.model_validate(
55
+ {
56
+ "fields": fields,
57
+ "size": [4, 4, 4, 4],
58
+ "type": ['F', 'F', 'F', 'U'],
59
+ "count": [1, 1, 1, 1],
60
+ "width": cloud.shape[0],
61
+ "points": cloud.shape[0],
62
+ })
63
+
64
+ dtype = [('xyz', '<f4', (3,)), ('intensity', '<u4')]
65
+ tmp = np.rec.fromarrays([cloud['xyz'], i], dtype=dtype)
66
+ PointCloud(metadata, tmp).save(save_path)
67
+ else:
68
+ fields = ('x', 'y', 'z', 'intensity', 'rgb')
69
+ metadata = MetaData.model_validate(
70
+ {
71
+ "fields": fields,
72
+ "size": [4, 4, 4, 4, 4],
73
+ "type": ['F', 'F', 'F', 'U', 'U'],
74
+ "count": [1, 1, 1, 1, 1],
75
+ "width": cloud.shape[0],
76
+ "points": cloud.shape[0],
77
+ })
78
+ dtype = [('xyz', '<f4', (3,)), ('intensity', '<u4'), ('rgb', '<u4')]
79
+ tmp = np.rec.fromarrays([cloud['xyz'], i, rgb], dtype=dtype)
56
80
 
57
81
  PointCloud(metadata, tmp).save(save_path)
58
82
 
@@ -62,17 +86,14 @@ def load_pcd(file):
62
86
  pc = PointCloud.from_path(file).pc_data
63
87
  rgb = np.zeros([pc.shape[0]], dtype=np.uint32)
64
88
  intensity = np.zeros([pc.shape[0]], dtype=np.uint32)
65
- color_mode = 'FLAT'
66
89
  if 'intensity' in pc.dtype.names:
67
90
  intensity = pc['intensity'].astype(np.uint32)
68
- color_mode = 'I'
69
91
  if 'rgb' in pc.dtype.names:
70
92
  rgb = pc['rgb'].astype(np.uint32)
71
- color_mode = 'RGB'
72
93
  irgb = (intensity << 24) | rgb
73
94
  xyz = np.stack([pc['x'], pc['y'], pc['z']], axis=1)
74
95
  cloud = np.rec.fromarrays([xyz, irgb], dtype=dtype)
75
- return cloud, color_mode
96
+ return cloud
76
97
 
77
98
 
78
99
  def save_e57(cloud, save_path):
@@ -100,23 +121,20 @@ def load_e57(file_path):
100
121
  z = scans["cartesianZ"]
101
122
  rgb = np.zeros([x.shape[0]], dtype=np.uint32)
102
123
  intensity = np.zeros([x.shape[0]], dtype=np.uint32)
103
- color_mode = 'FLAT'
104
124
  if "intensity" in scans:
105
125
  intensity = scans["intensity"].astype(np.uint32)
106
- color_mode = 'I'
107
126
  if all([x in scans for x in ["colorRed", "colorGreen", "colorBlue"]]):
108
127
  r = scans["colorRed"].astype(np.uint32)
109
128
  g = scans["colorGreen"].astype(np.uint32)
110
129
  b = scans["colorBlue"].astype(np.uint32)
111
130
  rgb = (r << 16) | (g << 8) | b
112
- color_mode = 'RGB'
113
131
  irgb = (intensity << 24) | rgb
114
132
  dtype = [('xyz', '<f4', (3,)), ('irgb', '<u4')]
115
133
  cloud = np.rec.fromarrays(
116
134
  [np.stack([x, y, z], axis=1), irgb],
117
135
  dtype=dtype)
118
136
  e57.close()
119
- return cloud, color_mode
137
+ return cloud
120
138
 
121
139
 
122
140
  def load_las(file):
@@ -124,27 +142,24 @@ def load_las(file):
124
142
  las = f.read()
125
143
  xyz = np.vstack((las.x, las.y, las.z)).transpose()
126
144
  dimensions = list(las.point_format.dimension_names)
127
- color_mode = 'FLAT'
128
145
  rgb = np.zeros([las.x.shape[0]], dtype=np.uint32)
129
146
  intensity = np.zeros([las.x.shape[0]], dtype=np.uint32)
130
147
  if 'intensity' in dimensions:
131
148
  intensity = las.intensity.astype(np.uint32)
132
- color_mode = 'I'
133
149
  if 'red' in dimensions and 'green' in dimensions and 'blue' in dimensions:
134
150
  red = las.red
135
151
  green = las.green
136
152
  blue = las.blue
137
153
  max_val = np.max([red, green, blue])
138
154
  if red.dtype == np.dtype('uint16') and max_val > 255:
139
- red = (red / 65535.0 * 255).astype(np.uint32)
140
- green = (green / 65535.0 * 255).astype(np.uint32)
141
- blue = (blue / 65535.0 * 255).astype(np.uint32)
155
+ red = (red / 255).astype(np.uint32)
156
+ green = (green / 255).astype(np.uint32)
157
+ blue = (blue / 255).astype(np.uint32)
142
158
  rgb = (red << 16) | (green << 8) | blue
143
- color_mode = 'RGB'
144
- color = (intensity << 24) | rgb
159
+ color = ((intensity / 255)<< 24) | rgb
145
160
  dtype = [('xyz', '<f4', (3,)), ('irgb', '<u4')]
146
161
  cloud = np.rec.fromarrays([xyz, color], dtype=dtype)
147
- return cloud, color_mode
162
+ return cloud
148
163
 
149
164
  def save_las(cloud, save_path):
150
165
  header = laspy.LasHeader(point_format=3, version="1.2")
@@ -152,10 +167,10 @@ def save_las(cloud, save_path):
152
167
  las.x = cloud['xyz'][:, 0]
153
168
  las.y = cloud['xyz'][:, 1]
154
169
  las.z = cloud['xyz'][:, 2]
155
- las.red = (cloud['irgb'] >> 16) & 0xFF
156
- las.green = (cloud['irgb'] >> 8) & 0xFF
157
- las.blue = cloud['irgb'] & 0xFF
158
- las.intensity = cloud['irgb'] >> 24
170
+ las.red = ((cloud['irgb'] >> 16) & 0xFF) * 255
171
+ las.green = ((cloud['irgb'] >> 8) & 0xFF) * 255
172
+ las.blue = (cloud['irgb'] & 0xFF)
173
+ las.intensity = (cloud['irgb'] >> 24) * 255
159
174
  las.write(save_path)
160
175
 
161
176
 
@@ -10,6 +10,7 @@ https://github.com/scomup/MathematicalRobotics.git
10
10
 
11
11
 
12
12
  import numpy as np
13
+ from matplotlib.colors import to_rgba
13
14
 
14
15
 
15
16
  _epsilon_ = 1e-5
@@ -292,26 +293,27 @@ def makeRt(T):
292
293
  t = T[0:3, 3]
293
294
  return R, t
294
295
 
295
- def hex_to_rgba(hex_color):
296
- if not hex_color.startswith("#"):
297
- print("Invalid hex color string.")
298
- return (1.0, 1.0, 1.0, 1.0)
299
- if len(hex_color) == 7:
300
- color_flat = int(hex_color[1:], 16)
301
- red = (color_flat >> 16) & 0xFF
302
- green = (color_flat >> 8) & 0xFF
303
- blue = color_flat & 0xFF
304
- return (red / 255.0, green / 255.0, blue / 255.0, 1.0)
305
- elif len(hex_color) == 9:
306
- color_flat = int(hex_color[1:], 16)
307
- red = (color_flat >> 24) & 0xFF
308
- green = (color_flat >> 16) & 0xFF
309
- blue = (color_flat >> 8) & 0xFF
310
- alpha = color_flat & 0xFF
311
- return (red / 255.0, green / 255.0, blue / 255.0, alpha / 255.0)
296
+
297
+ def text_to_rgba(color_text, flat=False):
298
+ """
299
+ Convert a color text to an RGBA tuple.
300
+
301
+ :param color_text: e.g. '#FF0000', '#FF0000FF', 'red', 'green', 'blue', 'yellow',
302
+ 'black', 'white', 'magenta', 'cyan', 'r', 'g', 'b', 'y', 'k', 'w', 'm', 'c'
303
+ :return: RGBA tuple, e.g. (1.0, 0.0, 0.0, 1.0)
304
+ """
305
+ rgba = to_rgba(color_text)
306
+ if flat:
307
+ r, g, b, _ = (np.array(rgba)*255).astype(np.uint32)
308
+ falt_rgb = ((r << 16) & 0xFF0000) | \
309
+ ((g << 8) & 0x00FF00) | \
310
+ ((b << 0) & 0x0000FF)
311
+ return falt_rgb
312
312
  else:
313
- print("Invalid hex color string.")
314
- return (1.0, 1.0, 1.0, 1.0)
313
+ return rgba
314
+ # except ValueError as e:
315
+ # raise ValueError(f"Invalid color text '{color_text}': {e}")
316
+
315
317
 
316
318
  # euler = np.array([1, 0.1, 0.1])
317
319
  # euler_angles = matrix_to_euler(euler_to_matrix(euler))
@@ -1,14 +1,22 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: q3dviewer
3
- Version: 1.1.3
3
+ Version: 1.1.4
4
4
  Summary: A library designed for quickly deploying a 3D viewer.
5
5
  Home-page: https://github.com/scomup/q3dviewer
6
6
  Author: Liu Yang
7
7
  License: UNKNOWN
8
- Description: ## q3dviewer
8
+ Description:
9
+ ![q3dviewer Logo](imgs/logo.png)
10
+
11
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
12
+ [![PyPI version](https://badge.fury.io/py/q3dviewer.svg)](https://badge.fury.io/py/q3dviewer)
9
13
 
10
14
  `q3dviewer` is a library designed for quickly deploying a 3D viewer. It is based on Qt (PySide6) and provides efficient OpenGL items for displaying 3D objects (e.g., point clouds, cameras, and 3D Gaussians). You can use it to visualize your 3D data or set up an efficient viewer application. It is inspired by PyQtGraph but focuses more on efficient 3D rendering.
11
15
 
16
+
17
+ To show how to use `q3dviewer` as a library, we also provide some [very useful tools](#tools).
18
+
19
+
12
20
  ## Installation
13
21
 
14
22
  To install `q3dviewer`, execute the following command in your terminal on either Linux or Windows:
@@ -37,10 +45,10 @@ Description: ## q3dviewer
37
45
 
38
46
  ### 1. Cloud Viewer
39
47
 
40
- A tool for visualizing point cloud files. Launch it by executing the following command in your terminal:
48
+ A tool for visualizing point cloud files (LAS, PCD, PLY, E57). Launch it by executing the following command in your terminal:
41
49
 
42
50
  ```sh
43
- cloud_viewer # The viewer will be displayed
51
+ cloud_viewer
44
52
  ```
45
53
 
46
54
  *Alternatively*, if the path is not set (though it's not recommended):
@@ -49,7 +57,13 @@ Description: ## q3dviewer
49
57
  python3 -m q3dviewer.tools.cloud_viewer
50
58
  ```
51
59
 
52
- After the viewer launches, you can drag and drop files onto the window to display the point clouds. Multiple files can be dropped simultaneously to view them together. Supported formats include LAS, PCD, PLY, and E57.
60
+ **Basic Operations**
61
+ * Load files: Drag and drop point cloud files onto the window (multiple files are OK).
62
+ * `M` key: Display the visualization settings screen for point clouds, background color, etc.
63
+ * `Left mouse button` & `W, A, S, D` keys: Move the viewpoint on the horizontal plane.
64
+ * `Z, X` keys: Move in the direction the screen is facing.
65
+ * `Right mouse button` & `Arrow` keys: Rotate the viewpoint while keeping the screen center unchanged.
66
+ * `Shift` + `Right mouse button` & `Arrow` keys: Rotate the viewpoint while keeping the camera position unchanged.
53
67
 
54
68
  For example, you can download and view point clouds of Tokyo in LAS format from the following link:
55
69
 
@@ -75,15 +89,21 @@ Description: ## q3dviewer
75
89
  Would you like to create a video from point cloud data? With Film Maker, you can easily create videos with simple operations. Just edit keyframes using the user-friendly GUI, and the software will automatically interpolate the keyframes to generate the video.
76
90
 
77
91
  ```sh
78
- film_maker # drag and drop your cloud file to the window
92
+ film_maker
79
93
  ```
80
94
 
95
+ **Basic Operations**
96
+ * File loading & viewpoint movement: Same as Cloud_Viewer
81
97
  * Space key to add a keyframe.
82
98
  * Delete key to remove a keyframe.
99
+ * Play button: Automatically play the video (pressing again will stop playback)
100
+ * Record checkbox: When checked, actions will be automatically recorded during playback
83
101
 
84
102
  Film Maker GUI:
85
103
 
86
- ![Screenshot from 2025-02-02 18-20-51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149168/a1a6ad63-237c-482e-439d-e760223c59ca.png)
104
+ ![film_maker_demo.gif](imgs/film_maker_demo.gif)
105
+
106
+ The demo video demonstrating how to use Film Maker utilizes the [cloud data of Kyobashi Station Area](https://www.geospatial.jp/ckan/dataset/kyoubasiekisyuuhen_las) located in Osaka, Japan.
87
107
 
88
108
  ### 4. Gaussian Viewer
89
109
 
@@ -2,6 +2,7 @@ PyOpenGL
2
2
  imageio
3
3
  imageio[ffmpeg]
4
4
  laspy
5
+ matplotlib
5
6
  meshio
6
7
  numpy
7
8
  pye57
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name='q3dviewer',
5
- version='1.1.3',
5
+ version='1.1.4',
6
6
  author="Liu Yang",
7
7
  description="A library designed for quickly deploying a 3D viewer.",
8
8
  long_description=open("README.md").read(),
@@ -28,6 +28,7 @@ setup(
28
28
  'laspy',
29
29
  'imageio',
30
30
  'imageio[ffmpeg]',
31
+ 'matplotlib',
31
32
  ],
32
33
  entry_points={
33
34
  'console_scripts': [
File without changes
File without changes
File without changes