JohnChiu commited on
Commit
14e0055
·
1 Parent(s): b5ac1c2

add utils and its demo

Browse files
Files changed (3) hide show
  1. .gitignore +3 -0
  2. main_validate_findpeaks.ipynb +0 -0
  3. utils.py +297 -0
.gitignore ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+
main_validate_findpeaks.ipynb ADDED
The diff for this file is too large to render. See raw diff
 
utils.py ADDED
@@ -0,0 +1,297 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ from scipy.ndimage import convolve
3
+ import os
4
+ from numpy.lib.stride_tricks import sliding_window_view
5
+ from numba import njit
6
+ import cv2
7
+
8
+ def mipi_raw10_to_raw8_scaled(raw10_data):
9
+ raw10_data = np.frombuffer(raw10_data, dtype=np.uint8)
10
+ n_blocks = len(raw10_data) // 5
11
+ raw10_data = raw10_data[:n_blocks * 5].reshape(-1, 5)
12
+
13
+ p0 = (raw10_data[:, 0].astype(np.uint16) << 2) | ((raw10_data[:, 4] >> 0) & 0x03)
14
+ p1 = (raw10_data[:, 1].astype(np.uint16) << 2) | ((raw10_data[:, 4] >> 2) & 0x03)
15
+ p2 = (raw10_data[:, 2].astype(np.uint16) << 2) | ((raw10_data[:, 4] >> 4) & 0x03)
16
+ p3 = (raw10_data[:, 3].astype(np.uint16) << 2) | ((raw10_data[:, 4] >> 6) & 0x03)
17
+
18
+ raw8_data = np.empty((n_blocks * 4 * 2,), dtype=np.uint8)
19
+ raw8_data[0::8] = p0 & 0xFF
20
+ raw8_data[1::8] = p0 >> 8
21
+ raw8_data[2::8] = p1 & 0xFF
22
+ raw8_data[3::8] = p1 >> 8
23
+ raw8_data[4::8] = p2 & 0xFF
24
+ raw8_data[5::8] = p2 >> 8
25
+ raw8_data[6::8] = p3 & 0xFF
26
+ raw8_data[7::8] = p3 >> 8
27
+
28
+ return raw8_data.tobytes()
29
+
30
+ def readRAW(path):
31
+ filesize = os.path.getsize(path)
32
+
33
+ with open(path, "rb") as f:
34
+ raw_data = f.read()
35
+
36
+ # Case 1: 如果是 MIPI RAW10 格式,大小为 7,372,800 字节
37
+ if filesize == 7372800:
38
+ raw_data = mipi_raw10_to_raw8_scaled(raw_data)
39
+
40
+ # 转换为 int16 并 reshape
41
+ arr = np.frombuffer(raw_data, dtype=np.int16).reshape(96, 240, 256)
42
+
43
+ # Byte Swap: [x,y,256] → [x,y,128,2] → swap last dim → [x,y,256]
44
+ reshaped = arr.reshape(*arr.shape[:-1], -1, 2)
45
+ swapped = reshaped[..., ::-1]
46
+ histogram_data = swapped.reshape(arr.shape)
47
+
48
+ # Line remapping (每组8行:0,4,1,5,...)
49
+ mapping = [0, 4, 1, 5, 2, 6, 3, 7]
50
+ group_size = 8
51
+ num_groups = 12 # 96 / 8
52
+ output = np.empty_like(histogram_data)
53
+
54
+ for g in range(num_groups):
55
+ start = g * group_size
56
+ end = start + group_size
57
+ output[start:end, :, :] = histogram_data[start:end, :, :][mapping, :, :]
58
+
59
+ return output.astype(np.int16)
60
+
61
+ def binning_2x2_stride2(data):
62
+ """
63
+ data: numpy array (96, 240, 256)
64
+ return: numpy array (48, 120, 256)
65
+ """
66
+ # 先 reshape 再求和,效率高
67
+ return data.reshape(48, 2, 120, 2, 256).sum(axis=(1, 3))
68
+
69
+ def binning_2x2_stride1(data):
70
+ """
71
+ data: numpy array (96, 240, 256)
72
+ return: numpy array (95, 239, 256) # 因为stride=1,边界少1行1列
73
+ """
74
+ # 直接用切片叠加四个偏移
75
+ binned = (data[:-1, :-1] + data[1:, :-1] +
76
+ data[:-1, 1:] + data[1:, 1:])
77
+ # binned = np.pad(binned, ((0,1),(0,1),(0,0)), mode='constant')
78
+ return binned
79
+
80
+
81
+ def ma_vectorized(data, kernel=[-2, -2, 1, 2, 2, 3, -1, -1]):
82
+ kernel = np.array(kernel, dtype=np.float32)
83
+ k = kernel.size
84
+ kernel_sum = kernel.sum()
85
+
86
+ # 确保 data 是 numpy array
87
+ data = np.asarray(data, dtype=np.float32)
88
+
89
+ # 取滑动窗口视图,shape: (96, 240, 256 - k + 1, k)
90
+ windows = np.lib.stride_tricks.sliding_window_view(data, window_shape=k, axis=2)
91
+
92
+ # 直接点乘kernel,然后求和,得到平滑结果 (96,240,256-k+1)
93
+ smoothed = np.tensordot(windows, kernel, axes=([3],[0])) / kernel_sum
94
+ smoothed[smoothed<0] = 0
95
+
96
+ # 为了保持和输入长度一致,可以两边补0或其他策略,这里简单在尾部补零
97
+ pad_width = ((0,0), (0,0), (0,k-1))
98
+ smoothed = np.pad(smoothed, pad_width, mode='constant', constant_values=0)
99
+ return smoothed
100
+
101
+
102
+ def ma_vectorized_fast(data, kernel=[-2, -2, 1, 2, 2, 3, -1, -1]):
103
+ kernel = np.array(kernel, dtype=np.float32)
104
+ k = kernel.size
105
+ kernel_sum = kernel.sum()
106
+
107
+ if kernel_sum == 0:
108
+ kernel_sum = 1 # 避免除 0
109
+
110
+ # padding 边界,保持中心对齐
111
+ pad = k // 2
112
+ data_padded = np.pad(data, ((0,0),(0,0),(pad,pad)), mode='edge')
113
+
114
+ # 利用 np.convolve 沿最后一个轴计算
115
+ def conv_1d(x):
116
+ return np.convolve(x, kernel[::-1], mode='valid') / kernel_sum
117
+
118
+ # 按最后一维应用
119
+ smoothed = np.apply_along_axis(conv_1d, 2, data_padded)
120
+
121
+ smoothed = np.maximum(smoothed, 0) # 负数置零
122
+ return smoothed
123
+
124
+ BIN_SIZE = 180
125
+ MAX_PEAKS = 2 # 峰值个数
126
+
127
+ @njit
128
+ def sum_hist(hist, length):
129
+ return np.sum(hist[:length])
130
+ @njit
131
+ def max_hist(hist, length):
132
+ return np.max(hist[:length])
133
+ @njit
134
+ def compute_centroid(hist, start_bin, end_bin):
135
+ bins = np.arange(start_bin, end_bin + 1)
136
+ values = hist[start_bin:end_bin + 1]
137
+ total = np.sum(values)
138
+ if total == 0:
139
+ return (start_bin + end_bin) / 2.0
140
+ return np.sum(bins * values) / total
141
+
142
+ @njit
143
+ def find_peaks_hw(histograms, histograms_ma):
144
+ """
145
+ 输入:
146
+ histograms: (H, W, 256) 原始直方图
147
+ histograms_ma: (H, W, 256) 平滑直方图
148
+ 输出:
149
+ tof_data: (H, W, MAX_PEAKS) 质心位置
150
+ peak_data: (H, W, MAX_PEAKS) 峰值强度
151
+ noise_data: (H, W) 噪声值
152
+ multishot_data: (H, W) 多拍信息
153
+ totalcount: (H, W) 总计数
154
+ nt_count_n: (H, W, MAX_PEAKS) NT计数
155
+ """
156
+
157
+ H, W, _ = histograms.shape
158
+
159
+ # 输出初始化
160
+ tof_data = np.zeros((H, W, MAX_PEAKS), dtype=np.float32)
161
+ peak_data = np.zeros((H, W, MAX_PEAKS), dtype=np.int32)
162
+ noise_data = np.zeros((H, W), dtype=np.int16)
163
+ multishot_data = np.zeros((H, W), dtype=np.int16)
164
+ totalcount = np.zeros((H, W), dtype=np.int32)
165
+ nt_count_n = np.zeros((H, W, MAX_PEAKS), dtype=np.int32)
166
+
167
+ for i in range(H):
168
+ for j in range(W):
169
+ hist_raw = histograms[i, j]
170
+ hist = histograms_ma[i, j]
171
+ count = 0
172
+ bin_idx = 1
173
+
174
+ # 多拍信息
175
+ multishot = (int(hist_raw[254]) << 8) + (int(hist_raw[255]) >> 2)
176
+ multishot_data[i, j] = multishot
177
+
178
+ totalcount[i, j] = int(sum_hist(hist_raw, BIN_SIZE) << 13) // multishot
179
+
180
+ # 噪声估计
181
+ noise_data[i, j] = int(sum_hist(hist, 8) + 4) >> 3
182
+
183
+ noise_i = max_hist(hist, 8)
184
+ # max_th = np.max(hist) * 0.01 + 25
185
+ max_th = 50
186
+ noise_i = max_th if noise_i < max_th else noise_i * 3
187
+ th = 2 * noise_i - 24 / (2e4 * 4) * noise_i * noise_i
188
+
189
+ # 峰值检测
190
+ while bin_idx < BIN_SIZE - 1 and count < MAX_PEAKS:
191
+ # 找到第一个 >= TH 的 bin
192
+ while bin_idx < BIN_SIZE - 1 and hist[bin_idx] < th:
193
+ bin_idx += 1
194
+
195
+ bin_idx -= 1
196
+ start_bin = bin_idx
197
+ start_peak = hist[start_bin]
198
+
199
+ # 找最大值 bin
200
+ while bin_idx + 1 < BIN_SIZE and (
201
+ hist[bin_idx] < hist[bin_idx - 1] or hist[bin_idx] < hist[bin_idx + 1]
202
+ ):
203
+ bin_idx += 1
204
+ max_bin = bin_idx
205
+ max_peak = hist[max_bin]
206
+
207
+ # 找 end_bin
208
+ while bin_idx + 1 < BIN_SIZE and (
209
+ hist[bin_idx] > th or hist[bin_idx] > start_peak or hist[bin_idx] > hist[bin_idx + 1]
210
+ ):
211
+ bin_idx += 1
212
+ end_bin = bin_idx
213
+
214
+ if (
215
+ start_bin == end_bin
216
+ or start_bin == max_bin
217
+ or max_bin == end_bin
218
+ or (max_peak - start_peak) < 50
219
+ ):
220
+ bin_idx += 1
221
+ continue
222
+
223
+ # 质心
224
+ centroid = compute_centroid(hist, start_bin, end_bin)
225
+ tof_data[i, j, count] = centroid
226
+ peak_data[i, j, count] = (int(hist[max_bin]) << 13) // multishot
227
+
228
+ # NT count
229
+ nt_end_bin = max_bin - 10
230
+ nt_start_bin = nt_end_bin - 48
231
+ nt_start_bin = 0 if nt_start_bin < 0 else nt_start_bin
232
+ nt_num = nt_end_bin - nt_start_bin
233
+ est_nt = 48 * noise_data[i, j] if nt_num < 48 else 0
234
+ nt_count_n[i, j, count] = np.sum(hist[nt_start_bin:nt_start_bin + nt_num]) + est_nt
235
+
236
+ count += 1
237
+
238
+ return tof_data, peak_data, noise_data, multishot_data, totalcount, nt_count_n
239
+
240
+
241
+ def local_threshold(img, window_size=15, C=2):
242
+ h, w = img.shape
243
+ out = np.zeros_like(img, dtype=np.uint8)
244
+ pad = window_size // 2
245
+ padded = cv2.copyMakeBorder(img, pad, pad, pad, pad, cv2.BORDER_REFLECT)
246
+
247
+ for i in range(h):
248
+ for j in range(w):
249
+ local_region = padded[i:i+window_size, j:j+window_size]
250
+ # thresh = np.mean(local_region) - C
251
+ thresh = np.mean(local_region,axis=(0,1)) - 0.1
252
+ out[i,j] = img[i,j] if img[i,j] > thresh else 0
253
+
254
+ return out
255
+
256
+ def select_peaks_hw(tof_data, peak_data):
257
+ """
258
+ 输入:
259
+ tof_data: (H, W, MAX_PEAKS) 质心位置
260
+ peak_data: (H, W, MAX_PEAKS) 峰值强度
261
+ 输出:
262
+ tof: (H, W)
263
+ peak: (H, W)
264
+
265
+ """
266
+ peak_data = peak_data *256/48000
267
+ ref_set = np.zeros_like(peak_data)
268
+ for i in range(MAX_PEAKS):
269
+ ref_set[...,i] = peak_data[...,i]*tof_data[...,i]*tof_data[...,i] /1200 * 6
270
+ ref = np.log2(ref_set[...,i])
271
+ ref[ref<0] = 0
272
+ _, otsu_binary = cv2.threshold(ref.astype(np.uint8), 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
273
+ # dynamic_ref = np.max(ref,axis=(0,1)) * 0.8
274
+ # ref[ref>100] = 100
275
+ ref[ref<3] = 0
276
+ # ref = local_threshold(ref,5,0)
277
+ ref_set[...,i] =(ref)
278
+ ref_set[...,1]*= 3
279
+ ref = np.max(ref_set,axis=2)
280
+
281
+ frame1 = ref_set[...,0]
282
+ frame2 = ref_set[...,1]
283
+ # 两个 mask 初始化为 0
284
+ mask1 = np.zeros_like(frame1, dtype=np.uint8)
285
+ mask2 = np.zeros_like(frame2, dtype=np.uint8)
286
+
287
+ # 逐像素比较:frame1 > frame2
288
+ mask1[(frame1 > frame2) & (frame1 > 0)] = 1
289
+ mask2[(frame2 > frame1) & (frame2 > 0)] = 1
290
+
291
+ # 如果相等且非零,可以任选一帧,这里给 mask1
292
+ mask1[(frame1 == frame2) & (frame1 > 0)] = 1
293
+
294
+ tof = tof_data[...,0] * mask1 + tof_data[...,1] * mask2
295
+ peak = peak_data[...,0] * mask1 + peak_data[...,1] * mask2
296
+
297
+ return tof,peak,ref,ref_set