BOSlib 0.0.1__tar.gz → 0.0.2__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
@@ -6,4 +6,4 @@ from .reconstruction_utils import *
6
6
  from .culculate_refractiveindex import *
7
7
  from .evaluation import *
8
8
 
9
- __version__ = '0.0.1'
9
+ __version__ = '0.0.2'
@@ -0,0 +1,344 @@
1
+ #import open3d as o3d
2
+ import numpy as np
3
+ from tqdm import tqdm
4
+ import matplotlib.pyplot as plt
5
+ from matplotlib.colors import Normalize
6
+ import matplotlib.colors as mcolors
7
+ import statistics
8
+ import mpl_toolkits.axes_grid1
9
+
10
+ # class CalculateDiff:
11
+ # """
12
+ # A class to calculate the difference between two point clouds in terms of neighbor densities.
13
+
14
+ # Attributes
15
+ # ----------
16
+ # output_path : str
17
+ # Path where the resulting point cloud with differences will be saved.
18
+ # r : float
19
+ # Radius for neighbor point sampling in KDTree.
20
+
21
+ # Methods
22
+ # -------
23
+ # diff(pcl1, pcl2)
24
+ # Calculates and visualizes the relative density differences between two point clouds.
25
+ # ectraction_neighborpoints(pointcloud, target_positions)
26
+ # Extracts the density of neighboring points around specified target positions.
27
+ # """
28
+ # def __init__(self, output_path: str, r: float) -> None:
29
+ # """
30
+ # Initializes the CalculateDiff class.
31
+
32
+ # Parameters
33
+ # ----------
34
+ # output_path : str
35
+ # Path to save the output point cloud.
36
+ # r : float
37
+ # Radius for neighbor point sampling in KDTree.
38
+ # """
39
+ # self.outputpath = output_path
40
+ # self.r = r
41
+
42
+ # def diff(self, pcl1, pcl2):
43
+ # """
44
+ # Calculates the relative density difference between two point clouds.
45
+
46
+ # The method computes the difference in neighbor densities for points in two point clouds.
47
+ # It normalizes the differences and clips them for visualization, then creates a new point
48
+ # cloud to save the results.
49
+
50
+ # Parameters
51
+ # ----------
52
+ # pcl1 : open3d.geometry.PointCloud
53
+ # The first point cloud.
54
+ # pcl2 : open3d.geometry.PointCloud
55
+ # The second point cloud.
56
+
57
+ # Returns
58
+ # -------
59
+ # open3d.geometry.PointCloud
60
+ # The point cloud representing the relative density differences.
61
+ # """
62
+ # # Initialize the output point cloud
63
+ # diff_pointcloud = o3d.geometry.PointCloud()
64
+
65
+ # # Extract point positions from the input point clouds
66
+ # positions_pcl1 = np.array(pcl1.points)
67
+ # positions_pcl2 = np.array(pcl2.points)
68
+
69
+ # # Use the sparser point cloud for density calculation
70
+ # if positions_pcl1.shape[0] < positions_pcl2.shape[0]:
71
+ # ground_position = positions_pcl1
72
+ # else:
73
+ # ground_position = positions_pcl2
74
+
75
+ # # Compute neighbor densities for each point cloud
76
+ # density_pcl1 = self.ectraction_neighborpoints(pcl1, ground_position)
77
+ # density_pcl2 = self.ectraction_neighborpoints(pcl2, ground_position)
78
+ # density_diff = density_pcl1 - density_pcl2
79
+
80
+ # # Convert to relative error
81
+ # density_diff_relative = 100 * np.divide(
82
+ # np.abs(density_diff),
83
+ # np.array(density_pcl1)
84
+ # )
85
+
86
+ # # Clip relative differences to a maximum of 100
87
+ # density_diff_relative = np.clip(density_diff_relative, 0, 100)
88
+
89
+ # # Apply the differences to the output point cloud
90
+ # diff_pointcloud.normals = o3d.utility.Vector3dVector(density_diff_relative)
91
+ # diff_pointcloud.points = o3d.utility.Vector3dVector(ground_position)
92
+
93
+ # # Normalize density differences and map them to RGB values
94
+ # RGB, minval, maxval = _normalize(density_diff)
95
+ # diff_pointcloud.colors = o3d.utility.Vector3dVector(np.array(RGB))
96
+
97
+ # # Save the resulting point cloud
98
+ # o3d.io.write_point_cloud(self.outputpath, diff_pointcloud, format='pts', compressed=True)
99
+
100
+ # return diff_pointcloud
101
+
102
+ # def ectraction_neighborpoints(self, pointcloud, target_positions):
103
+ # """
104
+ # Extracts the density of neighbor points for given target positions in a point cloud.
105
+
106
+ # This function uses KDTree for efficient neighbor search.
107
+
108
+ # Parameters
109
+ # ----------
110
+ # pointcloud : open3d.geometry.PointCloud
111
+ # The input point cloud.
112
+ # target_positions : numpy.ndarray
113
+ # Array of positions to search for neighbors.
114
+
115
+ # Returns
116
+ # -------
117
+ # numpy.ndarray
118
+ # Array of densities (number of neighbor points) for each target position.
119
+ # """
120
+ # # Create a KDTree for neighbor point search
121
+ # kdtree = o3d.geometry.KDTreeFlann(pointcloud)
122
+ # radius = self.r # Radius for neighbor search
123
+
124
+ # all_indices = [] # List to store indices of neighbors
125
+ # for pos in tqdm(target_positions, desc="Extracting neighbor points"):
126
+ # [k, idx, _] = kdtree.search_radius_vector_3d(pos, radius)
127
+ # if np.asarray(idx).shape[0] == 0:
128
+ # index = [0]
129
+ # elif np.asarray(idx).shape[0] == 1:
130
+ # index = idx
131
+ # else:
132
+ # index = [np.asarray(idx)[0]]
133
+ # all_indices.extend([index])
134
+
135
+ # # Extract neighbor densities
136
+ # neighbor_density = np.asarray(pointcloud.normals)[all_indices, :][:, 0]
137
+ # neighbor_density_array = np.asarray(neighbor_density)
138
+ # return neighbor_density_array
139
+
140
+ # def _normalize(self,data):
141
+ # """
142
+ # Min-Maxスケーリングを使用してデータを正規化します。
143
+ # """
144
+ # min_val = np.min(data)
145
+ # max_val = np.max(data)
146
+ # normalized_data = [(x - min_val) / (max_val - min_val) for x in data]
147
+ # return normalized_data, min_val, max_val
148
+
149
+ # def viewer(
150
+ # pointcloud_path: str, vmax: float, vcentre: float, vmin: float,
151
+ # color: str, unit_colorbar: str, unit_xy: str, rho: float
152
+ # ) -> None:
153
+ # """
154
+ # Visualizes a point cloud with color-coded density values as a scatter plot.
155
+
156
+ # Parameters
157
+ # ----------
158
+ # pointcloud_path : str
159
+ # Path to the point cloud file to be loaded.
160
+ # vmax : float
161
+ # Maximum value for the color scale. If None, it is set to the maximum of the normalized density.
162
+ # vcentre : float
163
+ # Center value for the color scale.
164
+ # vmin : float
165
+ # Minimum value for the color scale.
166
+ # color : str
167
+ # Colormap to use for visualization.
168
+ # unit_colorbar : str
169
+ # Label for the colorbar indicating the unit of the density values.
170
+ # unit_xy : str
171
+ # Label for the x and y axes indicating the unit of the coordinates.
172
+ # rho : float
173
+ # Normalization factor for density values.
174
+
175
+ # Returns
176
+ # -------
177
+ # None
178
+ # Displays a scatter plot of the point cloud with density visualized as colors.
179
+
180
+ # Notes
181
+ # -----
182
+ # The density values are normalized by `rho`, and their statistics (max, min, mean, median)
183
+ # are printed to the console. The point cloud's x and y coordinates are used for the scatter plot.
184
+ # """
185
+ # # Load the point cloud
186
+ # pointcloud = o3d.io.read_point_cloud(pointcloud_path)
187
+
188
+ # # Extract coordinates and density values
189
+ # x = np.asarray(pointcloud.points)[:, 0]
190
+ # y = np.asarray(pointcloud.points)[:, 1]
191
+ # density = np.asarray(pointcloud.normals)[:, 0]
192
+ # density_nondim = density / rho # Normalize density by rho
193
+
194
+ # # Configure color normalization for the scatter plot
195
+ # if vmax is None:
196
+ # norm = Normalize(vmin=density_nondim.min(), vmax=density_nondim.max())
197
+ # else:
198
+ # # Use a TwoSlopeNorm for customized vmin, vcenter, and vmax
199
+ # norm = mcolors.TwoSlopeNorm(vmin=vmin, vcenter=vcentre, vmax=vmax)
200
+
201
+ # # Create figure and axes for the scatter plot
202
+ # fig = plt.figure(figsize=(9, 6))
203
+ # ax = fig.add_subplot(111)
204
+
205
+ # # Plot the scatter plot
206
+ # sc = ax.scatter(x, y, c=density_nondim, cmap=color, s=1, norm=norm)
207
+ # ax.set_aspect('equal', adjustable='box') # Set equal aspect ratio
208
+
209
+ # # Add a colorbar to the plot
210
+ # divider = mpl_toolkits.axes_grid1.make_axes_locatable(ax)
211
+ # cax = divider.append_axes('right', '5%', pad=0.1)
212
+ # cbar = plt.colorbar(sc, ax=ax, cax=cax, orientation='vertical')
213
+ # cbar.set_label(unit_colorbar) # Set colorbar label
214
+
215
+ # # Set axis labels and titles
216
+ # ax.set_xlabel(unit_xy)
217
+ # ax.set_ylabel(unit_xy)
218
+
219
+ # # Display the plot
220
+ # plt.show()
221
+
222
+ # class array2pointcloud:
223
+ # """
224
+ # Converts a 2D array into a 3D point cloud with associated density and color information.
225
+
226
+ # Parameters
227
+ # ----------
228
+ # px2mm_y : float
229
+ # Conversion factor from pixels to millimeters along the y-axis.
230
+ # px2mm_x : float
231
+ # Conversion factor from pixels to millimeters along the x-axis.
232
+ # array : np.array
233
+ # Input 2D array representing pixel data.
234
+ # ox : int
235
+ # Origin offset in pixels along the x-axis.
236
+ # oy : int
237
+ # Origin offset in pixels along the y-axis.
238
+ # outpath : str
239
+ # Path where the resulting point cloud file will be saved.
240
+ # Flip : bool
241
+ # Whether to flip the array horizontally.
242
+
243
+ # Attributes
244
+ # ----------
245
+ # data_px : np.array
246
+ # The original or flipped array data in pixel units.
247
+ # data_mm : np.array
248
+ # Transformed data with coordinates in millimeter units and density as the fourth column.
249
+ # points : np.array
250
+ # 3D points representing the x, y, and z coordinates.
251
+ # density : np.array
252
+ # Density values expanded for storing as normals.
253
+ # RGB : np.array
254
+ # RGB color values derived from the density data.
255
+
256
+ # Methods
257
+ # -------
258
+ # __call__()
259
+ # Executes the conversion process and saves the resulting point cloud.
260
+ # px2mm_method()
261
+ # Converts the pixel coordinates and density values to millimeter units.
262
+ # reshaper()
263
+ # Extracts and reshapes the 3D points, density, and RGB values.
264
+ # set_array()
265
+ # Assembles the data into an Open3D PointCloud object.
266
+ # """
267
+ # def __init__(self, px2mm_y: float, px2mm_x: float, array: np.array,
268
+ # ox: int, oy: int, outpath: str, Flip: bool) -> None:
269
+ # self.px2mm_x = px2mm_x
270
+ # self.px2mm_y = px2mm_y
271
+ # self.data_px = array
272
+ # self.ox = ox
273
+ # self.oy = oy
274
+ # self.output_path = outpath
275
+ # self.flip = Flip
276
+
277
+ # # Initialize placeholders for processed data
278
+ # self.data_mm = None
279
+ # self.points = None
280
+ # self.density = None
281
+ # self.RGB = None
282
+
283
+ # def __call__(self):
284
+ # """
285
+ # Executes the full conversion pipeline and saves the result as a point cloud.
286
+ # """
287
+ # self.px2mm_method()
288
+ # self.reshaper()
289
+ # pcd = self.set_array()
290
+ # o3d.io.write_point_cloud(self.output_path, pcd, format='pts', compressed=True)
291
+
292
+ # def px2mm_method(self):
293
+ # """
294
+ # Converts pixel-based coordinates to millimeter-based coordinates.
295
+
296
+ # Notes
297
+ # -----
298
+ # If `self.flip` is True, the input array is flipped horizontally. The resulting
299
+ # millimeter-based coordinates and density values are stored in `self.data_mm`.
300
+ # """
301
+ # if self.flip:
302
+ # self.data_px = np.fliplr(self.data_px)
303
+
304
+ # data_list = []
305
+ # for i in range(self.data_px.shape[0]):
306
+ # for j in range(self.data_px.shape[1]):
307
+ # # Calculate millimeter coordinates and append density value
308
+ # point = [float(self.px2mm_x * (j - self.ox)),
309
+ # float(self.px2mm_y * (i - self.oy)),
310
+ # 0.0, # z-coordinate is 0
311
+ # float(self.data_px[i, j])]
312
+ # data_list.append(point)
313
+
314
+ # self.data_mm = np.array(data_list)
315
+
316
+ # def reshaper(self):
317
+ # """
318
+ # Reshapes the transformed data into points, density, and RGB values.
319
+
320
+ # Notes
321
+ # -----
322
+ # Density values are tiled to create normals for the point cloud.
323
+ # The `nm` function is used to normalize density values into RGB colors.
324
+ # """
325
+ # self.points = self.data_mm[:, :3] # Extract 3D coordinates
326
+ # self.density = np.tile(self.data_mm[:, 3], (3, 1)).T # Expand density values
327
+ # colors, _, _ = _normalize(self.density) # Normalize density to RGB
328
+ # self.RGB = np.array(colors)
329
+
330
+ # def set_array(self):
331
+ # """
332
+ # Creates an Open3D PointCloud object from the processed data.
333
+
334
+ # Returns
335
+ # -------
336
+ # pcd : o3d.geometry.PointCloud
337
+ # The resulting point cloud object with points, colors, and normals.
338
+ # """
339
+ # pcd = o3d.geometry.PointCloud()
340
+ # pcd.points = o3d.utility.Vector3dVector(self.points)
341
+ # pcd.colors = o3d.utility.Vector3dVector(self.RGB)
342
+ # pcd.normals = o3d.utility.Vector3dVector(self.density)
343
+
344
+ # return pcd
@@ -1,8 +1,12 @@
1
1
  from skimage.metrics import structural_similarity as ssm
2
2
  import numpy as np
3
3
  from PIL import Image
4
+ from scipy import signal
5
+ import pandas as pd
6
+
4
7
  import BOSlib.shift_utils as ib
5
8
 
9
+
6
10
  def SSIM(ref_array : np.ndarray, exp_array : np.ndarray):
7
11
  """
8
12
  Compute the inverted Structural Similarity Index (SSIM) difference matrix between two grayscale images.
@@ -80,11 +84,11 @@ def SP_BOS(ref_array : np.ndarray, exp_array : np.ndarray, binarization : str ="
80
84
  bin_ref = ib._biner_thresh(ar_ref, thresh)
81
85
  bin_exp = ib._biner_thresh(ar_exp, thresh)
82
86
 
83
- print("Binarization",bin_ref.shape,bin_exp.shape)
87
+ #print("Binarization",bin_ref.shape,bin_exp.shape)
84
88
  elif binarization =="HPfilter":
85
89
  bin_ref=ib._biner_HP(ar_ref, freq)
86
90
  bin_exp=ib._biner_HP(ar_exp, freq)
87
- print("Binarization",bin_ref.shape,bin_exp.shape)
91
+ #print("Binarization",bin_ref.shape,bin_exp.shape)
88
92
  else:
89
93
  raise ValueError("Binarization is thresh or HPfilter")
90
94
 
@@ -92,25 +96,25 @@ def SP_BOS(ref_array : np.ndarray, exp_array : np.ndarray, binarization : str ="
92
96
  ref_u, ref_d = ib._bin_indexer(bin_ref)
93
97
  ref_u = np.nan_to_num(ref_u)
94
98
  ref_d = np.nan_to_num(ref_d)
95
- print("bin_indexer_ref",ref_u.shape,ref_d.shape)
99
+ #print("bin_indexer_ref",ref_u.shape,ref_d.shape)
96
100
  # Detect the coordinates of the color boundaries in the binarized experimental image
97
101
  # u represents the upper boundary of the white stripe, d represents the lower boundary
98
102
  exp_u, exp_d = ib._bin_indexer(bin_exp)
99
103
  exp_u = np.nan_to_num(exp_u)
100
104
  exp_d = np.nan_to_num(exp_d)
101
- print("bin_indexer_exp",exp_u.shape,exp_d.shape)
105
+ #print("bin_indexer_exp",exp_u.shape,exp_d.shape)
102
106
 
103
107
  # Remove data with abnormally large displacements as noise
104
108
  ref_u, exp_u = ib._noize_reducer_2(ref_u, exp_u, 10)
105
109
  ref_d, exp_d = ib._noize_reducer_2(ref_d, exp_d, 10)
106
- print("noize_reducer_2",exp_u.shape,exp_d.shape)
107
- print("noize_reducer_2",ref_u.shape,ref_d.shape)
110
+ #print("noize_reducer_2",exp_u.shape,exp_d.shape)
111
+ #print("noize_reducer_2",ref_u.shape,ref_d.shape)
108
112
 
109
113
  # Combine the upper and lower boundary data to calculate the center of the stripe
110
114
  ref = ib._mixing(ref_u, ref_d)
111
115
  exp = ib._mixing(exp_u, exp_d)
112
116
 
113
- print("mixing",ref.shape,exp.shape)
117
+ #print("mixing",ref.shape,exp.shape)
114
118
 
115
119
  # Calculate displacement (upward displacement is positive)
116
120
  diff = -(exp - ref)
@@ -118,9 +122,72 @@ def SP_BOS(ref_array : np.ndarray, exp_array : np.ndarray, binarization : str ="
118
122
  # Rearrange the displacement values into the correct positions and interpolate gaps
119
123
  diff_comp = ib._complementer(ref, diff)
120
124
 
121
- print("complementer",diff_comp.shape)
125
+ #print("complementer",diff_comp.shape)
122
126
 
123
127
  # Subtract the overall background movement by dividing by the mean displacement
124
128
  diff_comp = diff_comp - np.nanmean(diff_comp[0:1000, 10:100])
125
129
 
126
130
  return diff_comp
131
+
132
+ def S_BOS(ref_array : np.ndarray, exp_array : np.ndarray):
133
+ def freq_finder(sig):
134
+ freq = np.fft.fftfreq(sig.shape[0])
135
+ fk = np.fft.fft(sig)
136
+ fk = abs(fk / (sig.shape[0] / 2))
137
+ fk_df = pd.DataFrame(np.vstack([freq, fk]).T, columns=["freq", "amp"])
138
+ fk_df = fk_df.sort_values('freq')
139
+ fk_df = fk_df[fk_df["freq"] >= 0]
140
+ freq_search = fk_df[fk_df["freq"] >= 0.01].sort_values('amp', ascending=False)
141
+ return freq_search.iloc[0, 0]
142
+
143
+ def bandpass(x, fa, fb):
144
+ gpass, gstop = 2, 60
145
+ fp, fs = np.array([fa, fb]), np.array([fa / 2, fb * 2])
146
+ fn = 1 / 2
147
+ wp, ws = fp / fn, fs / fn
148
+ N, Wn = signal.buttord(wp, ws, gpass, gstop)
149
+ b, a = signal.butter(N, Wn, "band")
150
+ return signal.filtfilt(b, a, x)
151
+
152
+ def lowpass(x, lowcut):
153
+ order, nyq = 8, 0.5 * 1
154
+ low = lowcut / nyq
155
+ b, a = signal.butter(order, low, btype='low')
156
+ return signal.filtfilt(b, a, x)
157
+
158
+ def signal_separate(sig, f1):
159
+ sig_f = np.zeros([sig.shape[0], 2])
160
+ sig_f[:, 0] = sig.mean()
161
+ sig_f[:, 1] = bandpass(sig, f1 * 0.7, f1 * 1.5)
162
+ return sig_f
163
+
164
+ def signal_scale_normalize(sig, f):
165
+ sig_abs = np.array(pd.Series(abs(sig)).rolling(int(0.5 / f), center=True).max())
166
+ sig[sig_abs < np.nanmean(sig_abs) * 0.5] = 0
167
+ y = np.arange(0, sig.shape[0], 1)
168
+ S = np.sin(2 * np.pi * f * y)
169
+ S1 = (1 - (sig_abs > np.nanmean(sig_abs * 0.5))) * S
170
+ sig = sig + S1
171
+ sig_abs[sig_abs < np.nanmean(sig_abs * 0.5)] = 1
172
+ sig_norm = sig / sig_abs
173
+ sig_norm[np.isnan(sig_norm)] = 0
174
+ return sig_norm
175
+
176
+ def phase_calculate(ref, exp, f1):
177
+ sin_ref, cos_ref = ref, np.gradient(ref) / (f1 * 2 * np.pi)
178
+ cos_phi, sin_phi = lowpass(sin_ref * exp, f1), lowpass(cos_ref * exp, f1)
179
+ return np.arctan2(sin_phi, cos_phi)
180
+
181
+ def phase_1DBOS_process(sig_ref, sig_exp, f1):
182
+ separate_sig_ref = signal_scale_normalize(signal_separate(sig_ref, f1)[:, 1], f1)
183
+ separate_sig_exp = signal_scale_normalize(signal_separate(sig_exp, f1)[:, 1], f1)
184
+ return phase_calculate(separate_sig_ref, separate_sig_exp, f1)
185
+
186
+ f1 = freq_finder(ref_array[:, 100])
187
+ phi_2D = np.zeros([ref_array.shape[0], ref_array.shape[1]]).astype("float64")
188
+
189
+ for x in range(ref_array.shape[1]):
190
+ phi_2D[:, x] = phase_1DBOS_process(ref_array[:, x], exp_array[:, x], f1)
191
+
192
+ delta_h = phi_2D / (2 * np.pi * f1)
193
+ return delta_h
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: BOSlib
3
- Version: 0.0.1
3
+ Version: 0.0.2
4
4
  Summary: the library of Background Oriented Schlieren
5
5
  Home-page: https://github.com/ogayuuki0202/BOSlib
6
6
  Download-URL: https://github.com/ogayuuki0202/BOSlib
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: BOSlib
3
- Version: 0.0.1
3
+ Version: 0.0.2
4
4
  Summary: the library of Background Oriented Schlieren
5
5
  Home-page: https://github.com/ogayuuki0202/BOSlib
6
6
  Download-URL: https://github.com/ogayuuki0202/BOSlib
@@ -1,344 +0,0 @@
1
- import open3d as o3d
2
- import numpy as np
3
- from tqdm import tqdm
4
- import matplotlib.pyplot as plt
5
- from matplotlib.colors import Normalize
6
- import matplotlib.colors as mcolors
7
- import statistics
8
- import mpl_toolkits.axes_grid1
9
-
10
- class CalculateDiff:
11
- """
12
- A class to calculate the difference between two point clouds in terms of neighbor densities.
13
-
14
- Attributes
15
- ----------
16
- output_path : str
17
- Path where the resulting point cloud with differences will be saved.
18
- r : float
19
- Radius for neighbor point sampling in KDTree.
20
-
21
- Methods
22
- -------
23
- diff(pcl1, pcl2)
24
- Calculates and visualizes the relative density differences between two point clouds.
25
- ectraction_neighborpoints(pointcloud, target_positions)
26
- Extracts the density of neighboring points around specified target positions.
27
- """
28
- def __init__(self, output_path: str, r: float) -> None:
29
- """
30
- Initializes the CalculateDiff class.
31
-
32
- Parameters
33
- ----------
34
- output_path : str
35
- Path to save the output point cloud.
36
- r : float
37
- Radius for neighbor point sampling in KDTree.
38
- """
39
- self.outputpath = output_path
40
- self.r = r
41
-
42
- def diff(self, pcl1, pcl2):
43
- """
44
- Calculates the relative density difference between two point clouds.
45
-
46
- The method computes the difference in neighbor densities for points in two point clouds.
47
- It normalizes the differences and clips them for visualization, then creates a new point
48
- cloud to save the results.
49
-
50
- Parameters
51
- ----------
52
- pcl1 : open3d.geometry.PointCloud
53
- The first point cloud.
54
- pcl2 : open3d.geometry.PointCloud
55
- The second point cloud.
56
-
57
- Returns
58
- -------
59
- open3d.geometry.PointCloud
60
- The point cloud representing the relative density differences.
61
- """
62
- # Initialize the output point cloud
63
- diff_pointcloud = o3d.geometry.PointCloud()
64
-
65
- # Extract point positions from the input point clouds
66
- positions_pcl1 = np.array(pcl1.points)
67
- positions_pcl2 = np.array(pcl2.points)
68
-
69
- # Use the sparser point cloud for density calculation
70
- if positions_pcl1.shape[0] < positions_pcl2.shape[0]:
71
- ground_position = positions_pcl1
72
- else:
73
- ground_position = positions_pcl2
74
-
75
- # Compute neighbor densities for each point cloud
76
- density_pcl1 = self.ectraction_neighborpoints(pcl1, ground_position)
77
- density_pcl2 = self.ectraction_neighborpoints(pcl2, ground_position)
78
- density_diff = density_pcl1 - density_pcl2
79
-
80
- # Convert to relative error
81
- density_diff_relative = 100 * np.divide(
82
- np.abs(density_diff),
83
- np.array(density_pcl1)
84
- )
85
-
86
- # Clip relative differences to a maximum of 100
87
- density_diff_relative = np.clip(density_diff_relative, 0, 100)
88
-
89
- # Apply the differences to the output point cloud
90
- diff_pointcloud.normals = o3d.utility.Vector3dVector(density_diff_relative)
91
- diff_pointcloud.points = o3d.utility.Vector3dVector(ground_position)
92
-
93
- # Normalize density differences and map them to RGB values
94
- RGB, minval, maxval = _normalize(density_diff)
95
- diff_pointcloud.colors = o3d.utility.Vector3dVector(np.array(RGB))
96
-
97
- # Save the resulting point cloud
98
- o3d.io.write_point_cloud(self.outputpath, diff_pointcloud, format='pts', compressed=True)
99
-
100
- return diff_pointcloud
101
-
102
- def ectraction_neighborpoints(self, pointcloud, target_positions):
103
- """
104
- Extracts the density of neighbor points for given target positions in a point cloud.
105
-
106
- This function uses KDTree for efficient neighbor search.
107
-
108
- Parameters
109
- ----------
110
- pointcloud : open3d.geometry.PointCloud
111
- The input point cloud.
112
- target_positions : numpy.ndarray
113
- Array of positions to search for neighbors.
114
-
115
- Returns
116
- -------
117
- numpy.ndarray
118
- Array of densities (number of neighbor points) for each target position.
119
- """
120
- # Create a KDTree for neighbor point search
121
- kdtree = o3d.geometry.KDTreeFlann(pointcloud)
122
- radius = self.r # Radius for neighbor search
123
-
124
- all_indices = [] # List to store indices of neighbors
125
- for pos in tqdm(target_positions, desc="Extracting neighbor points"):
126
- [k, idx, _] = kdtree.search_radius_vector_3d(pos, radius)
127
- if np.asarray(idx).shape[0] == 0:
128
- index = [0]
129
- elif np.asarray(idx).shape[0] == 1:
130
- index = idx
131
- else:
132
- index = [np.asarray(idx)[0]]
133
- all_indices.extend([index])
134
-
135
- # Extract neighbor densities
136
- neighbor_density = np.asarray(pointcloud.normals)[all_indices, :][:, 0]
137
- neighbor_density_array = np.asarray(neighbor_density)
138
- return neighbor_density_array
139
-
140
- def _normalize(self,data):
141
- """
142
- Min-Maxスケーリングを使用してデータを正規化します。
143
- """
144
- min_val = np.min(data)
145
- max_val = np.max(data)
146
- normalized_data = [(x - min_val) / (max_val - min_val) for x in data]
147
- return normalized_data, min_val, max_val
148
-
149
- def viewer(
150
- pointcloud_path: str, vmax: float, vcentre: float, vmin: float,
151
- color: str, unit_colorbar: str, unit_xy: str, rho: float
152
- ) -> None:
153
- """
154
- Visualizes a point cloud with color-coded density values as a scatter plot.
155
-
156
- Parameters
157
- ----------
158
- pointcloud_path : str
159
- Path to the point cloud file to be loaded.
160
- vmax : float
161
- Maximum value for the color scale. If None, it is set to the maximum of the normalized density.
162
- vcentre : float
163
- Center value for the color scale.
164
- vmin : float
165
- Minimum value for the color scale.
166
- color : str
167
- Colormap to use for visualization.
168
- unit_colorbar : str
169
- Label for the colorbar indicating the unit of the density values.
170
- unit_xy : str
171
- Label for the x and y axes indicating the unit of the coordinates.
172
- rho : float
173
- Normalization factor for density values.
174
-
175
- Returns
176
- -------
177
- None
178
- Displays a scatter plot of the point cloud with density visualized as colors.
179
-
180
- Notes
181
- -----
182
- The density values are normalized by `rho`, and their statistics (max, min, mean, median)
183
- are printed to the console. The point cloud's x and y coordinates are used for the scatter plot.
184
- """
185
- # Load the point cloud
186
- pointcloud = o3d.io.read_point_cloud(pointcloud_path)
187
-
188
- # Extract coordinates and density values
189
- x = np.asarray(pointcloud.points)[:, 0]
190
- y = np.asarray(pointcloud.points)[:, 1]
191
- density = np.asarray(pointcloud.normals)[:, 0]
192
- density_nondim = density / rho # Normalize density by rho
193
-
194
- # Configure color normalization for the scatter plot
195
- if vmax is None:
196
- norm = Normalize(vmin=density_nondim.min(), vmax=density_nondim.max())
197
- else:
198
- # Use a TwoSlopeNorm for customized vmin, vcenter, and vmax
199
- norm = mcolors.TwoSlopeNorm(vmin=vmin, vcenter=vcentre, vmax=vmax)
200
-
201
- # Create figure and axes for the scatter plot
202
- fig = plt.figure(figsize=(9, 6))
203
- ax = fig.add_subplot(111)
204
-
205
- # Plot the scatter plot
206
- sc = ax.scatter(x, y, c=density_nondim, cmap=color, s=1, norm=norm)
207
- ax.set_aspect('equal', adjustable='box') # Set equal aspect ratio
208
-
209
- # Add a colorbar to the plot
210
- divider = mpl_toolkits.axes_grid1.make_axes_locatable(ax)
211
- cax = divider.append_axes('right', '5%', pad=0.1)
212
- cbar = plt.colorbar(sc, ax=ax, cax=cax, orientation='vertical')
213
- cbar.set_label(unit_colorbar) # Set colorbar label
214
-
215
- # Set axis labels and titles
216
- ax.set_xlabel(unit_xy)
217
- ax.set_ylabel(unit_xy)
218
-
219
- # Display the plot
220
- plt.show()
221
-
222
- class array2pointcloud:
223
- """
224
- Converts a 2D array into a 3D point cloud with associated density and color information.
225
-
226
- Parameters
227
- ----------
228
- px2mm_y : float
229
- Conversion factor from pixels to millimeters along the y-axis.
230
- px2mm_x : float
231
- Conversion factor from pixels to millimeters along the x-axis.
232
- array : np.array
233
- Input 2D array representing pixel data.
234
- ox : int
235
- Origin offset in pixels along the x-axis.
236
- oy : int
237
- Origin offset in pixels along the y-axis.
238
- outpath : str
239
- Path where the resulting point cloud file will be saved.
240
- Flip : bool
241
- Whether to flip the array horizontally.
242
-
243
- Attributes
244
- ----------
245
- data_px : np.array
246
- The original or flipped array data in pixel units.
247
- data_mm : np.array
248
- Transformed data with coordinates in millimeter units and density as the fourth column.
249
- points : np.array
250
- 3D points representing the x, y, and z coordinates.
251
- density : np.array
252
- Density values expanded for storing as normals.
253
- RGB : np.array
254
- RGB color values derived from the density data.
255
-
256
- Methods
257
- -------
258
- __call__()
259
- Executes the conversion process and saves the resulting point cloud.
260
- px2mm_method()
261
- Converts the pixel coordinates and density values to millimeter units.
262
- reshaper()
263
- Extracts and reshapes the 3D points, density, and RGB values.
264
- set_array()
265
- Assembles the data into an Open3D PointCloud object.
266
- """
267
- def __init__(self, px2mm_y: float, px2mm_x: float, array: np.array,
268
- ox: int, oy: int, outpath: str, Flip: bool) -> None:
269
- self.px2mm_x = px2mm_x
270
- self.px2mm_y = px2mm_y
271
- self.data_px = array
272
- self.ox = ox
273
- self.oy = oy
274
- self.output_path = outpath
275
- self.flip = Flip
276
-
277
- # Initialize placeholders for processed data
278
- self.data_mm = None
279
- self.points = None
280
- self.density = None
281
- self.RGB = None
282
-
283
- def __call__(self):
284
- """
285
- Executes the full conversion pipeline and saves the result as a point cloud.
286
- """
287
- self.px2mm_method()
288
- self.reshaper()
289
- pcd = self.set_array()
290
- o3d.io.write_point_cloud(self.output_path, pcd, format='pts', compressed=True)
291
-
292
- def px2mm_method(self):
293
- """
294
- Converts pixel-based coordinates to millimeter-based coordinates.
295
-
296
- Notes
297
- -----
298
- If `self.flip` is True, the input array is flipped horizontally. The resulting
299
- millimeter-based coordinates and density values are stored in `self.data_mm`.
300
- """
301
- if self.flip:
302
- self.data_px = np.fliplr(self.data_px)
303
-
304
- data_list = []
305
- for i in range(self.data_px.shape[0]):
306
- for j in range(self.data_px.shape[1]):
307
- # Calculate millimeter coordinates and append density value
308
- point = [float(self.px2mm_x * (j - self.ox)),
309
- float(self.px2mm_y * (i - self.oy)),
310
- 0.0, # z-coordinate is 0
311
- float(self.data_px[i, j])]
312
- data_list.append(point)
313
-
314
- self.data_mm = np.array(data_list)
315
-
316
- def reshaper(self):
317
- """
318
- Reshapes the transformed data into points, density, and RGB values.
319
-
320
- Notes
321
- -----
322
- Density values are tiled to create normals for the point cloud.
323
- The `nm` function is used to normalize density values into RGB colors.
324
- """
325
- self.points = self.data_mm[:, :3] # Extract 3D coordinates
326
- self.density = np.tile(self.data_mm[:, 3], (3, 1)).T # Expand density values
327
- colors, _, _ = _normalize(self.density) # Normalize density to RGB
328
- self.RGB = np.array(colors)
329
-
330
- def set_array(self):
331
- """
332
- Creates an Open3D PointCloud object from the processed data.
333
-
334
- Returns
335
- -------
336
- pcd : o3d.geometry.PointCloud
337
- The resulting point cloud object with points, colors, and normals.
338
- """
339
- pcd = o3d.geometry.PointCloud()
340
- pcd.points = o3d.utility.Vector3dVector(self.points)
341
- pcd.colors = o3d.utility.Vector3dVector(self.RGB)
342
- pcd.normals = o3d.utility.Vector3dVector(self.density)
343
-
344
- return pcd
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes