BOSlib 0.0.1__tar.gz → 0.0.2__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.
@@ -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