BOSlib 0.0.1__py3-none-any.whl → 0.0.4__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 CHANGED
@@ -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.4'
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
- 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()
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
- # 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
- )
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
- # 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
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
- 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
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(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
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
- 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
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,243 @@ 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_row : int = 0):
133
+ """
134
+ Compute a 1D BOS displacement field by estimating phase differences
135
+ between reference and experimental stripe signals.
136
+
137
+ This function first identifies the dominant stripe frequency via FFT
138
+ from a representative row (`freq_sample_area`) of `ref_array`. Then for
139
+ each column signal it:
140
+ 1. Bandpass-filters around the base frequency.
141
+ 2. Normalizes amplitude and applies a sine-based phase correction.
142
+ 3. Calculates the local phase difference via lowpass filtered
143
+ sine/cosine products.
144
+ 4. Converts phase shifts to physical displacement values.
145
+
146
+ Parameters
147
+ ----------
148
+ ref_array : np.ndarray, shape (M, N)
149
+ Reference image signals, with M samples (rows) and N separate
150
+ stripe‐signal columns.
151
+ exp_array : np.ndarray, shape (M, N)
152
+ Experimental image signals matching the dimensions of `ref_array`.
153
+ freq_sample_row : int
154
+ Row index in `ref_array` used to detect the dominant stripe frequency
155
+ for filtering and phase calculation.
156
+
157
+ Returns
158
+ -------
159
+ delta_h : np.ndarray, shape (M, N)
160
+ Displacement field (Δh) computed from the phase differences between
161
+ each column of `ref_array` and `exp_array`. Units are cycles/(2π·f),
162
+ where f is the dominant stripe frequency.
163
+ """
164
+ def freq_finder(sig):
165
+ """
166
+ Identify the dominant frequency in the signal using the FFT.
167
+
168
+ Parameters:
169
+ -----------
170
+ sig : np.ndarray
171
+ 1D numpy array representing a signal.
172
+
173
+ Returns:
174
+ --------
175
+ float
176
+ The dominant frequency (above 0.01 Hz) based on amplitude.
177
+ """
178
+ # Compute FFT frequencies
179
+ freq = np.fft.fftfreq(sig.shape[0])
180
+ # Compute FFT of the signal and normalize the amplitude
181
+ fk = np.fft.fft(sig)
182
+ fk = abs(fk / (sig.shape[0] / 2))
183
+ # Combine frequencies and amplitudes into a DataFrame
184
+ fk_df = pd.DataFrame(np.vstack([freq, fk]).T, columns=["freq", "amp"])
185
+ # Sort DataFrame by frequency and keep only non-negative frequencies
186
+ fk_df = fk_df.sort_values('freq')
187
+ fk_df = fk_df[fk_df["freq"] >= 0]
188
+ # Select frequencies above 0.01 Hz and sort by amplitude in descending order
189
+ freq_search = fk_df[fk_df["freq"] >= 0.01].sort_values('amp', ascending=False)
190
+ # Return the frequency corresponding to the highest amplitude
191
+ return freq_search.iloc[0, 0]
192
+
193
+ def bandpass(x, fa, fb):
194
+ """
195
+ Apply a bandpass Butterworth filter to the signal.
196
+
197
+ Parameters:
198
+ -----------
199
+ x : np.ndarray
200
+ Input signal.
201
+ fa : float
202
+ Lower cutoff frequency multiplier.
203
+ fb : float
204
+ Upper cutoff frequency multiplier.
205
+
206
+ Returns:
207
+ --------
208
+ np.ndarray
209
+ The bandpass-filtered signal.
210
+ """
211
+ gpass, gstop = 2, 60 # Passband and stopband gains (dB)
212
+ fp, fs = np.array([fa, fb]), np.array([fa / 2, fb * 2])
213
+ fn = 1 / 2 # Nyquist frequency (assuming a normalized sample rate)
214
+ wp, ws = fp / fn, fs / fn
215
+ # Determine the minimum filter order that meets the specifications
216
+ N, Wn = signal.buttord(wp, ws, gpass, gstop)
217
+ # Get the filter coefficients for a Butterworth filter
218
+ b, a = signal.butter(N, Wn, "band")
219
+ # Apply the filter forward and backward to avoid phase distortion
220
+ return signal.filtfilt(b, a, x)
221
+
222
+ def lowpass(x, lowcut):
223
+ """
224
+ Apply a lowpass Butterworth filter to the signal.
225
+
226
+ Parameters:
227
+ -----------
228
+ x : np.ndarray
229
+ Input signal.
230
+ lowcut : float
231
+ The low cutoff frequency.
232
+
233
+ Returns:
234
+ --------
235
+ np.ndarray
236
+ The lowpass-filtered signal.
237
+ """
238
+ order, nyq = 8, 0.5 * 1 # Order and Nyquist frequency (assuming sample rate = 1)
239
+ low = lowcut / nyq
240
+ # Get the filter coefficients for a lowpass Butterworth filter
241
+ b, a = signal.butter(order, low, btype='low')
242
+ # Apply the filter with zero-phase distortion
243
+ return signal.filtfilt(b, a, x)
244
+
245
+ def signal_separate(sig, f1):
246
+ """
247
+ Separate the signal into a constant (mean) component and a bandpass-filtered component.
248
+
249
+ Parameters:
250
+ -----------
251
+ sig : np.ndarray
252
+ Input signal.
253
+ f1 : float
254
+ Base frequency used to define the bandpass range.
255
+
256
+ Returns:
257
+ --------
258
+ np.ndarray
259
+ 2D array where the first column is the signal mean and the second column is the bandpass-filtered signal.
260
+ """
261
+ sig_f = np.zeros([sig.shape[0], 2])
262
+ # First column: constant value equal to the mean of the signal
263
+ sig_f[:, 0] = sig.mean()
264
+ # Second column: bandpass filtered signal using a frequency window around f1
265
+ sig_f[:, 1] = bandpass(sig, f1 * 0.7, f1 * 1.5)
266
+ return sig_f
267
+
268
+ def signal_scale_normalize(sig, f):
269
+ """
270
+ Normalize the signal based on a rolling maximum amplitude and add a sine correction.
271
+
272
+ Parameters:
273
+ -----------
274
+ sig : np.ndarray
275
+ Input signal.
276
+ f : float
277
+ Frequency used to calculate the sine correction.
278
+
279
+ Returns:
280
+ --------
281
+ np.ndarray
282
+ The normalized signal.
283
+ """
284
+ # Calculate the rolling maximum absolute value over a window of 0.5/f samples
285
+ sig_abs = np.array(pd.Series(abs(sig)).rolling(int(0.5 / f), center=True).max())
286
+ # Suppress parts of the signal where the amplitude is significantly below the mean
287
+ sig[sig_abs < np.nanmean(sig_abs) * 0.5] = 0
288
+ y = np.arange(0, sig.shape[0], 1)
289
+ # Generate a sine wave for phase correction
290
+ S = np.sin(2 * np.pi * f * y)
291
+ # Create a correction term based on the amplitude threshold
292
+ S1 = (1 - (sig_abs > np.nanmean(sig_abs * 0.5))) * S
293
+ # Add the correction term to the signal
294
+ sig = sig + S1
295
+ # Avoid division by very small numbers
296
+ sig_abs[sig_abs < np.nanmean(sig_abs * 0.5)] = 1
297
+ # Normalize the signal
298
+ sig_norm = sig / sig_abs
299
+ sig_norm[np.isnan(sig_norm)] = 0
300
+ return sig_norm
301
+
302
+ def phase_calculate(ref, exp, f1):
303
+ """
304
+ Calculate the phase difference between the reference and experimental signals.
305
+
306
+ Parameters:
307
+ -----------
308
+ ref : np.ndarray
309
+ Normalized reference signal.
310
+ exp : np.ndarray
311
+ Normalized experimental signal.
312
+ f1 : float
313
+ Base frequency.
314
+
315
+ Returns:
316
+ --------
317
+ np.ndarray
318
+ The phase difference calculated using lowpass filtered sine and cosine components.
319
+ """
320
+ # Compute sine and its gradient (approximation for cosine)
321
+ sin_ref = ref
322
+ cos_ref = np.gradient(ref) / (f1 * 2 * np.pi)
323
+ # Compute lowpass filtered products to obtain sine and cosine components of the phase difference
324
+ cos_phi = lowpass(sin_ref * exp, f1)
325
+ sin_phi = lowpass(cos_ref * exp, f1)
326
+ # Calculate the phase difference using arctan2 for correct quadrant determination
327
+ return np.arctan2(sin_phi, cos_phi)
328
+
329
+ def phase_1DBOS_process(sig_ref, sig_exp, f1):
330
+ """
331
+ Process a pair of reference and experimental signals to compute the phase difference.
332
+
333
+ Parameters:
334
+ -----------
335
+ sig_ref : np.ndarray
336
+ Single column from the reference signal array.
337
+ sig_exp : np.ndarray
338
+ Corresponding column from the experimental signal array.
339
+ f1 : float
340
+ Base frequency obtained from the reference array.
341
+
342
+ Returns:
343
+ --------
344
+ np.ndarray
345
+ The phase difference between the processed reference and experimental signals.
346
+ """
347
+ # Separate the signal into mean and bandpass-filtered components and normalize them
348
+ separate_sig_ref = signal_scale_normalize(signal_separate(sig_ref, f1)[:, 1], f1)
349
+ separate_sig_exp = signal_scale_normalize(signal_separate(sig_exp, f1)[:, 1], f1)
350
+ # Calculate the phase difference between the normalized signals
351
+ return phase_calculate(separate_sig_ref, separate_sig_exp, f1)
352
+
353
+ # Determine the dominant frequency from a representative column (column 100) of the reference array
354
+ f1 = freq_finder(ref_array[:,freq_sample_row])
355
+ # Initialize a 2D array to store phase differences for each column
356
+ phi_2D = np.zeros([ref_array.shape[0], ref_array.shape[1]]).astype("float64")
357
+
358
+ # Process each column of the reference and experimental arrays
359
+ for x in range(ref_array.shape[1]):
360
+ phi_2D[:, x] = phase_1DBOS_process(ref_array[:, x], exp_array[:, x], f1)
361
+
362
+ # Convert phase differences into displacement by dividing by (2*pi*f1)
363
+ delta_h = phi_2D / (2 * np.pi * f1)
364
+ 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.4
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
@@ -0,0 +1,13 @@
1
+ BOSlib/__init__.py,sha256=00N8-Ti4_R4lmLuqi08jwokhPvlvudBQXpHRZveEtds,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=fa0QxJXuBUELOmLLT2dB_uNaWMU5oxeTI0ZScZx5c5c,14877
7
+ BOSlib/shift_utils.py,sha256=XZ7cwKWV6gkPBcWDkhgU34fRNt1nxAFJNZqC42NIqbk,9397
8
+ BOSlib/utils.py,sha256=ceuHxQmvIsx6z2iudQC9j3SVpQ0m9myTBXuWf6C1eMs,6934
9
+ BOSlib-0.0.4.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
10
+ BOSlib-0.0.4.dist-info/METADATA,sha256=beniVbiXOpdcT5iQgI4Lh6l9Xe6wFH46-2hLDq6ffYI,3458
11
+ BOSlib-0.0.4.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
12
+ BOSlib-0.0.4.dist-info/top_level.txt,sha256=ZLqE8iduIk6Ng-LYGvz_faKu-yW4JJwjJpOsXDfBo14,7
13
+ BOSlib-0.0.4.dist-info/RECORD,,
@@ -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