BOSlib 0.0.1__py3-none-any.whl → 0.0.3__py3-none-any.whl
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.
- BOSlib/__init__.py +1 -1
- BOSlib/evaluation.py +331 -331
- BOSlib/shift.py +233 -8
- {BOSlib-0.0.1.dist-info → BOSlib-0.0.3.dist-info}/METADATA +1 -1
- BOSlib-0.0.3.dist-info/RECORD +13 -0
- BOSlib-0.0.1.dist-info/RECORD +0 -13
- {BOSlib-0.0.1.dist-info → BOSlib-0.0.3.dist-info}/LICENSE +0 -0
- {BOSlib-0.0.1.dist-info → BOSlib-0.0.3.dist-info}/WHEEL +0 -0
- {BOSlib-0.0.1.dist-info → BOSlib-0.0.3.dist-info}/top_level.txt +0 -0
BOSlib/__init__.py
CHANGED
BOSlib/evaluation.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
import open3d as o3d
|
1
|
+
#import open3d as o3d
|
2
2
|
import numpy as np
|
3
3
|
from tqdm import tqdm
|
4
4
|
import matplotlib.pyplot as plt
|
@@ -7,338 +7,338 @@ import matplotlib.colors as mcolors
|
|
7
7
|
import statistics
|
8
8
|
import mpl_toolkits.axes_grid1
|
9
9
|
|
10
|
-
class CalculateDiff:
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
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
64
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
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
85
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
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
123
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
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
139
|
|
140
|
-
def _normalize(
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
140
|
+
# def _normalize(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
148
|
|
149
|
-
def viewer(
|
150
|
-
|
151
|
-
|
152
|
-
) -> None:
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
class array2pointcloud:
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
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
|
BOSlib/shift.py
CHANGED
@@ -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,230 @@ 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,freq_sample_area):
|
133
|
+
"""
|
134
|
+
Compute the phase difference and corresponding displacement (delta_h)
|
135
|
+
between reference and experimental signal arrays using a 1D Background
|
136
|
+
Oriented Schlieren (BOS) process.
|
137
|
+
|
138
|
+
Parameters:
|
139
|
+
-----------
|
140
|
+
ref_array : np.ndarray
|
141
|
+
2D numpy array containing reference signals, where each column is a distinct signal.
|
142
|
+
exp_array : np.ndarray
|
143
|
+
2D numpy array containing experimental signals, matching the dimensions of ref_array.
|
144
|
+
|
145
|
+
Returns:
|
146
|
+
--------
|
147
|
+
delta_h : np.ndarray
|
148
|
+
2D numpy array representing the displacement computed from the phase differences.
|
149
|
+
"""
|
150
|
+
|
151
|
+
def freq_finder(sig):
|
152
|
+
"""
|
153
|
+
Identify the dominant frequency in the signal using the FFT.
|
154
|
+
|
155
|
+
Parameters:
|
156
|
+
-----------
|
157
|
+
sig : np.ndarray
|
158
|
+
1D numpy array representing a signal.
|
159
|
+
|
160
|
+
Returns:
|
161
|
+
--------
|
162
|
+
float
|
163
|
+
The dominant frequency (above 0.01 Hz) based on amplitude.
|
164
|
+
"""
|
165
|
+
# Compute FFT frequencies
|
166
|
+
freq = np.fft.fftfreq(sig.shape[0])
|
167
|
+
# Compute FFT of the signal and normalize the amplitude
|
168
|
+
fk = np.fft.fft(sig)
|
169
|
+
fk = abs(fk / (sig.shape[0] / 2))
|
170
|
+
# Combine frequencies and amplitudes into a DataFrame
|
171
|
+
fk_df = pd.DataFrame(np.vstack([freq, fk]).T, columns=["freq", "amp"])
|
172
|
+
# Sort DataFrame by frequency and keep only non-negative frequencies
|
173
|
+
fk_df = fk_df.sort_values('freq')
|
174
|
+
fk_df = fk_df[fk_df["freq"] >= 0]
|
175
|
+
# Select frequencies above 0.01 Hz and sort by amplitude in descending order
|
176
|
+
freq_search = fk_df[fk_df["freq"] >= 0.01].sort_values('amp', ascending=False)
|
177
|
+
# Return the frequency corresponding to the highest amplitude
|
178
|
+
return freq_search.iloc[0, 0]
|
179
|
+
|
180
|
+
def bandpass(x, fa, fb):
|
181
|
+
"""
|
182
|
+
Apply a bandpass Butterworth filter to the signal.
|
183
|
+
|
184
|
+
Parameters:
|
185
|
+
-----------
|
186
|
+
x : np.ndarray
|
187
|
+
Input signal.
|
188
|
+
fa : float
|
189
|
+
Lower cutoff frequency multiplier.
|
190
|
+
fb : float
|
191
|
+
Upper cutoff frequency multiplier.
|
192
|
+
|
193
|
+
Returns:
|
194
|
+
--------
|
195
|
+
np.ndarray
|
196
|
+
The bandpass-filtered signal.
|
197
|
+
"""
|
198
|
+
gpass, gstop = 2, 60 # Passband and stopband gains (dB)
|
199
|
+
fp, fs = np.array([fa, fb]), np.array([fa / 2, fb * 2])
|
200
|
+
fn = 1 / 2 # Nyquist frequency (assuming a normalized sample rate)
|
201
|
+
wp, ws = fp / fn, fs / fn
|
202
|
+
# Determine the minimum filter order that meets the specifications
|
203
|
+
N, Wn = signal.buttord(wp, ws, gpass, gstop)
|
204
|
+
# Get the filter coefficients for a Butterworth filter
|
205
|
+
b, a = signal.butter(N, Wn, "band")
|
206
|
+
# Apply the filter forward and backward to avoid phase distortion
|
207
|
+
return signal.filtfilt(b, a, x)
|
208
|
+
|
209
|
+
def lowpass(x, lowcut):
|
210
|
+
"""
|
211
|
+
Apply a lowpass Butterworth filter to the signal.
|
212
|
+
|
213
|
+
Parameters:
|
214
|
+
-----------
|
215
|
+
x : np.ndarray
|
216
|
+
Input signal.
|
217
|
+
lowcut : float
|
218
|
+
The low cutoff frequency.
|
219
|
+
|
220
|
+
Returns:
|
221
|
+
--------
|
222
|
+
np.ndarray
|
223
|
+
The lowpass-filtered signal.
|
224
|
+
"""
|
225
|
+
order, nyq = 8, 0.5 * 1 # Order and Nyquist frequency (assuming sample rate = 1)
|
226
|
+
low = lowcut / nyq
|
227
|
+
# Get the filter coefficients for a lowpass Butterworth filter
|
228
|
+
b, a = signal.butter(order, low, btype='low')
|
229
|
+
# Apply the filter with zero-phase distortion
|
230
|
+
return signal.filtfilt(b, a, x)
|
231
|
+
|
232
|
+
def signal_separate(sig, f1):
|
233
|
+
"""
|
234
|
+
Separate the signal into a constant (mean) component and a bandpass-filtered component.
|
235
|
+
|
236
|
+
Parameters:
|
237
|
+
-----------
|
238
|
+
sig : np.ndarray
|
239
|
+
Input signal.
|
240
|
+
f1 : float
|
241
|
+
Base frequency used to define the bandpass range.
|
242
|
+
|
243
|
+
Returns:
|
244
|
+
--------
|
245
|
+
np.ndarray
|
246
|
+
2D array where the first column is the signal mean and the second column is the bandpass-filtered signal.
|
247
|
+
"""
|
248
|
+
sig_f = np.zeros([sig.shape[0], 2])
|
249
|
+
# First column: constant value equal to the mean of the signal
|
250
|
+
sig_f[:, 0] = sig.mean()
|
251
|
+
# Second column: bandpass filtered signal using a frequency window around f1
|
252
|
+
sig_f[:, 1] = bandpass(sig, f1 * 0.7, f1 * 1.5)
|
253
|
+
return sig_f
|
254
|
+
|
255
|
+
def signal_scale_normalize(sig, f):
|
256
|
+
"""
|
257
|
+
Normalize the signal based on a rolling maximum amplitude and add a sine correction.
|
258
|
+
|
259
|
+
Parameters:
|
260
|
+
-----------
|
261
|
+
sig : np.ndarray
|
262
|
+
Input signal.
|
263
|
+
f : float
|
264
|
+
Frequency used to calculate the sine correction.
|
265
|
+
|
266
|
+
Returns:
|
267
|
+
--------
|
268
|
+
np.ndarray
|
269
|
+
The normalized signal.
|
270
|
+
"""
|
271
|
+
# Calculate the rolling maximum absolute value over a window of 0.5/f samples
|
272
|
+
sig_abs = np.array(pd.Series(abs(sig)).rolling(int(0.5 / f), center=True).max())
|
273
|
+
# Suppress parts of the signal where the amplitude is significantly below the mean
|
274
|
+
sig[sig_abs < np.nanmean(sig_abs) * 0.5] = 0
|
275
|
+
y = np.arange(0, sig.shape[0], 1)
|
276
|
+
# Generate a sine wave for phase correction
|
277
|
+
S = np.sin(2 * np.pi * f * y)
|
278
|
+
# Create a correction term based on the amplitude threshold
|
279
|
+
S1 = (1 - (sig_abs > np.nanmean(sig_abs * 0.5))) * S
|
280
|
+
# Add the correction term to the signal
|
281
|
+
sig = sig + S1
|
282
|
+
# Avoid division by very small numbers
|
283
|
+
sig_abs[sig_abs < np.nanmean(sig_abs * 0.5)] = 1
|
284
|
+
# Normalize the signal
|
285
|
+
sig_norm = sig / sig_abs
|
286
|
+
sig_norm[np.isnan(sig_norm)] = 0
|
287
|
+
return sig_norm
|
288
|
+
|
289
|
+
def phase_calculate(ref, exp, f1):
|
290
|
+
"""
|
291
|
+
Calculate the phase difference between the reference and experimental signals.
|
292
|
+
|
293
|
+
Parameters:
|
294
|
+
-----------
|
295
|
+
ref : np.ndarray
|
296
|
+
Normalized reference signal.
|
297
|
+
exp : np.ndarray
|
298
|
+
Normalized experimental signal.
|
299
|
+
f1 : float
|
300
|
+
Base frequency.
|
301
|
+
|
302
|
+
Returns:
|
303
|
+
--------
|
304
|
+
np.ndarray
|
305
|
+
The phase difference calculated using lowpass filtered sine and cosine components.
|
306
|
+
"""
|
307
|
+
# Compute sine and its gradient (approximation for cosine)
|
308
|
+
sin_ref = ref
|
309
|
+
cos_ref = np.gradient(ref) / (f1 * 2 * np.pi)
|
310
|
+
# Compute lowpass filtered products to obtain sine and cosine components of the phase difference
|
311
|
+
cos_phi = lowpass(sin_ref * exp, f1)
|
312
|
+
sin_phi = lowpass(cos_ref * exp, f1)
|
313
|
+
# Calculate the phase difference using arctan2 for correct quadrant determination
|
314
|
+
return np.arctan2(sin_phi, cos_phi)
|
315
|
+
|
316
|
+
def phase_1DBOS_process(sig_ref, sig_exp, f1):
|
317
|
+
"""
|
318
|
+
Process a pair of reference and experimental signals to compute the phase difference.
|
319
|
+
|
320
|
+
Parameters:
|
321
|
+
-----------
|
322
|
+
sig_ref : np.ndarray
|
323
|
+
Single column from the reference signal array.
|
324
|
+
sig_exp : np.ndarray
|
325
|
+
Corresponding column from the experimental signal array.
|
326
|
+
f1 : float
|
327
|
+
Base frequency obtained from the reference array.
|
328
|
+
|
329
|
+
Returns:
|
330
|
+
--------
|
331
|
+
np.ndarray
|
332
|
+
The phase difference between the processed reference and experimental signals.
|
333
|
+
"""
|
334
|
+
# Separate the signal into mean and bandpass-filtered components and normalize them
|
335
|
+
separate_sig_ref = signal_scale_normalize(signal_separate(sig_ref, f1)[:, 1], f1)
|
336
|
+
separate_sig_exp = signal_scale_normalize(signal_separate(sig_exp, f1)[:, 1], f1)
|
337
|
+
# Calculate the phase difference between the normalized signals
|
338
|
+
return phase_calculate(separate_sig_ref, separate_sig_exp, f1)
|
339
|
+
|
340
|
+
# Determine the dominant frequency from a representative column (column 100) of the reference array
|
341
|
+
f1 = freq_finder(ref_array[freq_sample_area])
|
342
|
+
# Initialize a 2D array to store phase differences for each column
|
343
|
+
phi_2D = np.zeros([ref_array.shape[0], ref_array.shape[1]]).astype("float64")
|
344
|
+
|
345
|
+
# Process each column of the reference and experimental arrays
|
346
|
+
for x in range(ref_array.shape[1]):
|
347
|
+
phi_2D[:, x] = phase_1DBOS_process(ref_array[:, x], exp_array[:, x], f1)
|
348
|
+
|
349
|
+
# Convert phase differences into displacement by dividing by (2*pi*f1)
|
350
|
+
delta_h = phi_2D / (2 * np.pi * f1)
|
351
|
+
return delta_h
|
@@ -0,0 +1,13 @@
|
|
1
|
+
BOSlib/__init__.py,sha256=hAIN1H3r752cYHCyBxi1Ttl0ucNgzuSfjnPSeoHIc34,225
|
2
|
+
BOSlib/culculate_refractiveindex.py,sha256=v-yIZEtsJchOjO7RFIR3XU4mCYdmbGxKjar03OC5MsA,3421
|
3
|
+
BOSlib/evaluation.py,sha256=Ipf5TUXFRuVlwILucJRyqZRazS02Wb7QQ6okXw-P4Vs,12809
|
4
|
+
BOSlib/reconstruction.py,sha256=qBxid-wJfsyTfKK1EWa6NejxJ7lHA1HEeIRV6743k98,6545
|
5
|
+
BOSlib/reconstruction_utils.py,sha256=hoJQ9crumwdLvmOkIpWasADi2H3-3272P3QPhTrcWu8,1809
|
6
|
+
BOSlib/shift.py,sha256=SZj_yUMdjy9M8L_NZvu9JWvRuxpmRBRSEn8qoI8Yl_M,14174
|
7
|
+
BOSlib/shift_utils.py,sha256=XZ7cwKWV6gkPBcWDkhgU34fRNt1nxAFJNZqC42NIqbk,9397
|
8
|
+
BOSlib/utils.py,sha256=ceuHxQmvIsx6z2iudQC9j3SVpQ0m9myTBXuWf6C1eMs,6934
|
9
|
+
BOSlib-0.0.3.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
10
|
+
BOSlib-0.0.3.dist-info/METADATA,sha256=Fl1RW0mepo2P4DoQFa3T3QexAQB8QKrYJXJI_fOJG9o,3458
|
11
|
+
BOSlib-0.0.3.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
|
12
|
+
BOSlib-0.0.3.dist-info/top_level.txt,sha256=ZLqE8iduIk6Ng-LYGvz_faKu-yW4JJwjJpOsXDfBo14,7
|
13
|
+
BOSlib-0.0.3.dist-info/RECORD,,
|
BOSlib-0.0.1.dist-info/RECORD
DELETED
@@ -1,13 +0,0 @@
|
|
1
|
-
BOSlib/__init__.py,sha256=kb_Iy46y664fgo_rYX3A3DnWzsLsMYXPRD3X5SW4-ik,225
|
2
|
-
BOSlib/culculate_refractiveindex.py,sha256=v-yIZEtsJchOjO7RFIR3XU4mCYdmbGxKjar03OC5MsA,3421
|
3
|
-
BOSlib/evaluation.py,sha256=c0AtR9jz3ShYI-C4PAsHanZAXIsX1d-Le1wdO-fVh0c,12241
|
4
|
-
BOSlib/reconstruction.py,sha256=qBxid-wJfsyTfKK1EWa6NejxJ7lHA1HEeIRV6743k98,6545
|
5
|
-
BOSlib/reconstruction_utils.py,sha256=hoJQ9crumwdLvmOkIpWasADi2H3-3272P3QPhTrcWu8,1809
|
6
|
-
BOSlib/shift.py,sha256=tKBFo-rEPj9BX4070LGdYYRUttRZs1yhJBDwQyWIb0g,5631
|
7
|
-
BOSlib/shift_utils.py,sha256=XZ7cwKWV6gkPBcWDkhgU34fRNt1nxAFJNZqC42NIqbk,9397
|
8
|
-
BOSlib/utils.py,sha256=ceuHxQmvIsx6z2iudQC9j3SVpQ0m9myTBXuWf6C1eMs,6934
|
9
|
-
BOSlib-0.0.1.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
10
|
-
BOSlib-0.0.1.dist-info/METADATA,sha256=KdnGZNLmiH4x80Uc_sUz6EWzEPP7QCY_7VCk0PacoqY,3458
|
11
|
-
BOSlib-0.0.1.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
|
12
|
-
BOSlib-0.0.1.dist-info/top_level.txt,sha256=ZLqE8iduIk6Ng-LYGvz_faKu-yW4JJwjJpOsXDfBo14,7
|
13
|
-
BOSlib-0.0.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|