taotoolkit 1.0.0__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.
@@ -0,0 +1,690 @@
1
+ #!/usr/bin/env python
2
+ # -*- encoding: utf-8 -*-
3
+ """VRF文件处理工具"""
4
+
5
+ import os
6
+ import re
7
+ import sys
8
+ import cv2
9
+ import argparse
10
+ import math
11
+ import numpy as np
12
+ from pathlib import Path
13
+ from .asrVrf import Vrf
14
+
15
+
16
+ from .tao_common import (
17
+ debugCtrl,
18
+ eExit,
19
+ loge,
20
+ logi,
21
+ logd,
22
+ normalize_args,
23
+ register_command,
24
+ )
25
+
26
+
27
+ def get_file_index(filename):
28
+ pattern = re.compile(r"req\[(\d+)\]")
29
+ res = pattern.findall(filename)
30
+ if len(res) != 1:
31
+ raise Exception("Cannot get file index!")
32
+ file_index = int(res[0])
33
+ return file_index
34
+
35
+
36
+ def get_meta_file_by_index(path, file_index):
37
+ file_list = os.listdir(path)
38
+ meta_fname = ""
39
+ for f in file_list:
40
+ if f.endswith("_%d.txt" % (file_index)):
41
+ meta_fname = f
42
+ break
43
+ meta_fname = path + "\\" + meta_fname
44
+ return meta_fname
45
+
46
+
47
+ def get_meta_file(path):
48
+ file_list = os.listdir(path)
49
+ meta_fname = ""
50
+ for f in file_list:
51
+ if f.endswith(".xml"):
52
+ meta_fname = f
53
+ break
54
+ meta_fname = path + "\\" + meta_fname
55
+ return meta_fname
56
+
57
+
58
+ def grep_info(tag, meta_txt):
59
+ pattern_str = "<%s>(.*?)</%s>" % (tag, tag)
60
+ pattern = re.compile(pattern_str)
61
+ return float(pattern.findall(meta_txt)[0])
62
+
63
+
64
+ def unpack_mipi_raw10(byte_buf):
65
+ data = np.frombuffer(byte_buf, dtype=np.uint8)
66
+ # 5 bytes contain 4 10-bit pixels (5x8 == 4x10)
67
+ b1, b2, b3, b4, b5 = np.reshape(data, (data.shape[0] // 5, 5)).astype(np.uint16).T
68
+ p1 = (b1 << 2) + ((b5) & 0x3)
69
+ p2 = (b2 << 2) + ((b5 >> 2) & 0x3)
70
+ p3 = (b3 << 2) + ((b5 >> 4) & 0x3)
71
+ p4 = (b4 << 2) + ((b5 >> 6) & 0x3)
72
+ unpacked = np.reshape(np.concatenate((p1[:, None], p2[:, None], p3[:, None], p4[:, None]), axis=1), 4 * p1.shape[0])
73
+ return unpacked
74
+
75
+
76
+ def unpack_mipi_raw12(byte_buf):
77
+ data = np.frombuffer(byte_buf, dtype=np.uint8)
78
+ # 3 bytes contain 2 12-bit pixels (3x8 == 2x12)
79
+ b1, b2, b3 = np.reshape(data, (data.shape[0] // 3, 3)).astype(np.uint16).T
80
+ p1 = (b1 << 4) + ((b3) & 0xF)
81
+ p2 = (b2 << 4) + ((b3 >> 4) & 0xF)
82
+ unpacked = np.reshape(np.concatenate((p1[:, None], p2[:, None]), axis=1), 2 * p1.shape[0])
83
+ return unpacked
84
+
85
+
86
+ def unpack_mipi_raw14(byte_buf):
87
+ data = np.frombuffer(byte_buf, dtype=np.uint8)
88
+ # 7 bytes contain 4 14-bit pixels (7x8 == 4x14)
89
+ b1, b2, b3, b4, b5, b6, b7 = np.reshape(data, (data.shape[0] // 7, 7)).astype(np.uint16).T
90
+ p1 = (b1 << 6) + (b5 & 0x3F)
91
+ p2 = (b2 << 6) + (b5 >> 6) + (b6 & 0xF)
92
+ p3 = (b3 << 6) + (b6 >> 4) + (b7 & 0x3)
93
+ p4 = (b4 << 6) + (b7 >> 2)
94
+ unpacked = np.reshape(np.concatenate((p1[:, None], p2[:, None], p3[:, None], p4[:, None]), axis=1), 4 * p1.shape[0])
95
+ return unpacked
96
+
97
+
98
+ def convertMipi2RawWithStrde(mipiFile, bitDeepth, width, height, stride):
99
+ mipiData = np.fromfile(mipiFile, dtype="uint8")
100
+
101
+ # print(mipiFile," file size:", mipiData.size)
102
+ mipiData.resize(height, stride)
103
+ # print(mipiData.shape)
104
+
105
+ if bitDeepth == 8 or bitDeepth == 16:
106
+ print("raw8 and raw16 no need to unpack")
107
+ return None
108
+ elif bitDeepth == 10:
109
+ byte2pixel = 5 / 4 # 5 bytes contain 4 10-bit pixels (5x8 == 4x10)
110
+ dataWithoutStride = np.ascontiguousarray(mipiData[::, : int(width * byte2pixel) :])
111
+ bayerData = unpack_mipi_raw10(dataWithoutStride)
112
+ elif bitDeepth == 12:
113
+ byte2pixel = 3 / 2 # 3 bytes contain 2 12-bit pixels (3x8 == 2x12)
114
+ dataWithoutStride = np.ascontiguousarray(mipiData[::, : int(width * byte2pixel) :])
115
+ bayerData = unpack_mipi_raw12(dataWithoutStride)
116
+ elif bitDeepth == 14:
117
+ byte2pixel = 7 / 4 # 7 bytes contain 4 14-bit pixels (7x8 == 4x14)
118
+ dataWithoutStride = np.ascontiguousarray(mipiData[::, : int(width * byte2pixel) :])
119
+ bayerData = unpack_mipi_raw14(dataWithoutStride)
120
+ else:
121
+ print("unsupport bayer bitDeepth:", bitDeepth)
122
+ return
123
+
124
+ # bayerData.tofile("tmp1920x1080_raw12.raw")
125
+ return bayerData
126
+
127
+
128
+ def convertMipi2Raw(mipiFile, bitDeepth, imgWidth, imgHeight, bayer_order=cv2.COLOR_BayerBG2BGR):
129
+ mipiData = np.fromfile(mipiFile, dtype="uint8")
130
+ print("mipiraw file size:", mipiData.size)
131
+ bayerData = None
132
+
133
+ if bitDeepth == 8 or bitDeepth == 16:
134
+ print("raw8 and raw16 no need to unpack")
135
+ return None
136
+ elif bitDeepth == 10:
137
+ # raw10
138
+ bayerData = unpack_mipi_raw10(mipiData[: imgWidth * imgHeight * 5 // 4])
139
+ img = bayerData >> 2
140
+ elif bitDeepth == 12:
141
+ # raw12
142
+ bayerData = unpack_mipi_raw12(mipiData[: imgWidth * imgHeight * 3 // 2])
143
+ img = bayerData >> 4
144
+ elif bitDeepth == 14:
145
+ # raw14
146
+ bayerData = unpack_mipi_raw14(mipiData[: imgWidth * imgHeight * 7 // 4])
147
+ img = bayerData >> 6
148
+ else:
149
+ print("unsupport bayer bitDeepth:", bitDeepth)
150
+
151
+ # bayerData.tofile(mipiFile[:-4]+'_unpack.raw')
152
+
153
+ # img = img.astype(np.uint8).reshape(imgHeight, imgWidth, 1)
154
+ # # print(bayer_order)
155
+ # rgbimg = cv2.cvtColor(img, bayer_order)
156
+ # cv2.imwrite(mipiFile[:-4]+'_unpack.jpg', rgbimg)
157
+
158
+ return bayerData
159
+
160
+
161
+ def judge_exist_xml(path):
162
+ files = os.listdir(path)
163
+ for k in range(len(files)):
164
+ files[k] = os.path.splitext(files[k])[1]
165
+
166
+ Str = ".xml"
167
+
168
+ if Str in files:
169
+ return True
170
+ else:
171
+ return False
172
+
173
+
174
+ """
175
+ 处理单个RAW文件,将其转换为VRF格式
176
+
177
+ 根据输入参数处理RAW图像数据,包括MIPI数据转换、元数据提取和VRF文件生成。
178
+
179
+ Args:
180
+ raw_name: RAW文件路径
181
+ width: 图像宽度
182
+ height: 图像高度
183
+ BLC10b: 黑电平校正值(10bit)
184
+ BayerOrder: Bayer格式顺序
185
+ BitDepth: 像素位深度
186
+ stride: MIPI数据步长,默认为0
187
+ isMipiRaw: 是否为MIPI RAW格式,默认为False
188
+ dgain: 数字增益值,默认为1
189
+
190
+ Returns:
191
+ 无返回值,处理结果保存为.vrf文件
192
+ """
193
+
194
+
195
+ def raw2vrf(
196
+ raw_name, width, height, BLC10b, BayerOrder, BitDepth, stride=0, isMipiRaw=False, dgain=1.0, WBGain=[1.0, 1.0, 1.0]
197
+ ):
198
+
199
+ path, filename = os.path.split(raw_name)
200
+
201
+ exist_xml = False
202
+
203
+ vrfObj = Vrf()
204
+ vrfObj.Width = np.uint16(width)
205
+ vrfObj.Height = np.uint16(height)
206
+
207
+ vrfObj.BLC10b = np.uint8(BLC10b)
208
+ vrfObj.BayerOrder = np.uint8(BayerOrder)
209
+ vrfObj.BitDepth = np.uint8(BitDepth)
210
+
211
+ if exist_xml:
212
+ # file_index = get_file_index(filename)
213
+ # meta_fname = get_meta_file_by_index(path, file_index)
214
+ meta_fname = get_meta_file(path)
215
+ f = open(meta_fname, "rt")
216
+ meta_txt = f.read()
217
+ f.close()
218
+
219
+ dgain = grep_info("digitalGain", meta_txt)
220
+ vrfObj.AWBGainB = np.uint16(128 * grep_info("gain_b", meta_txt))
221
+ vrfObj.AWBGainG = np.uint16(128 * grep_info("gain_g", meta_txt))
222
+ vrfObj.AWBGainR = np.uint16(128 * grep_info("gain_r", meta_txt))
223
+
224
+ vrfObj.AGain = np.uint16(16 * grep_info("shortGain", meta_txt))
225
+ vrfObj.TGain = np.uint16(vrfObj.AGain * dgain)
226
+ vrfObj.DrcGainDark = np.uint8(16 * grep_info("DrcGainDark", meta_txt))
227
+ vrfObj.DrcGain = np.uint8(16 * grep_info("DrcGain", meta_txt))
228
+ vrfObj.ExpoTime_us = np.uint32((grep_info("Short", meta_txt) / 1000))
229
+
230
+ else:
231
+ vrfObj.AWBGainB = np.uint16(128 * WBGain[2])
232
+ vrfObj.AWBGainG = np.uint16(128 * WBGain[1])
233
+ vrfObj.AWBGainR = np.uint16(128 * WBGain[0])
234
+
235
+ vrfObj.AGain = np.uint16(16)
236
+ vrfObj.TGain = np.uint16(vrfObj.AGain * dgain)
237
+ vrfObj.DrcGainDark = np.uint8(16)
238
+ vrfObj.DrcGain = np.uint8(16)
239
+
240
+ vrfObj.ExpoTime_us = np.uint32(65536)
241
+
242
+ vrfObj.PrintVrf()
243
+ if isMipiRaw:
244
+ if stride != 0:
245
+ bayerData = convertMipi2RawWithStrde(raw_name, vrfObj.BitDepth, width, height, stride)
246
+ else:
247
+ bayerData = convertMipi2Raw(raw_name, vrfObj.BitDepth, width, height)
248
+ else:
249
+ if BitDepth == 8:
250
+ bayerData = np.fromfile(raw_name, dtype="uint8")
251
+ else:
252
+ bayerData = np.fromfile(raw_name, dtype="uint16")
253
+
254
+ if bayerData != None:
255
+ vrfObj.unpackImage = bayerData.astype(np.uint16)
256
+ else:
257
+ eExit("get bayer data failed")
258
+
259
+ vrfName = os.path.join(path, os.path.splitext(filename)[0] + ".vrf")
260
+ vrfObj.SaveVrf(vrfName)
261
+ print(f"save {vrfName} successful")
262
+
263
+
264
+ def scaner_file(url):
265
+ file = os.listdir(url)
266
+ for f in file:
267
+ real_url = os.path.join(url, f)
268
+ if os.path.isfile(real_url):
269
+ print(os.path.abspath(real_url))
270
+ elif os.path.isdir(real_url):
271
+ scaner_file(real_url)
272
+ else:
273
+ print("other")
274
+ pass
275
+ print(real_url)
276
+
277
+
278
+ def raw2vrfBatch(
279
+ path,
280
+ width,
281
+ height,
282
+ BLC10b,
283
+ BayerOrder,
284
+ BitDepth,
285
+ stride,
286
+ isMipiRaw=False,
287
+ dgain=1.0,
288
+ WBGain=[1.0, 1.0, 1.0],
289
+ suffix="raw",
290
+ ):
291
+ file_list = os.listdir(path)
292
+ for f in file_list:
293
+ f_lower = f.lower()
294
+ if (
295
+ f_lower.endswith(".rawmipi")
296
+ or f_lower.endswith(".rawmipi10")
297
+ or f_lower.endswith(".raw")
298
+ or f_lower.endswith(".mipiraw")
299
+ or f_lower.endswith(f".{suffix}")
300
+ ):
301
+ raw_name = os.path.join(path, f)
302
+ raw2vrf(raw_name, width, height, BLC10b, BayerOrder, BitDepth, stride, isMipiRaw, dgain, WBGain)
303
+
304
+
305
+ def srgb_to_linear(c_srgb_8bit):
306
+ """
307
+ sRGB (0-255, gamma编码) -> 线性光强度 (0-1)
308
+ 标准 sRGB EOTF (IEC 61966-2-1)
309
+ """
310
+ c = np.asarray(c_srgb_8bit, dtype=np.float64) / 255.0
311
+ linear = np.where(c <= 0.04045, c / 12.92, ((c + 0.055) / 1.055) ** 2.4)
312
+ return linear
313
+
314
+
315
+ def map_colorchecker_to_bitdepth(raw_color_values_8bit, dataMin, dataMax, out_dtype=np.uint16):
316
+ """
317
+ 把 sRGB 8bit 色卡值动态映射到任意 bitDepth 对应的 RAW 数据范围
318
+
319
+ Args:
320
+ raw_color_values_8bit: (24, 3) 的 sRGB 8bit 色值列表
321
+ dataMin: 目标 bit depth 下的最小值 (通常是黑电平)
322
+ dataMax: 目标 bit depth 下的最大值 (通常是 2^bitDepth - 1)
323
+ out_dtype: 输出数据类型
324
+
325
+ Returns:
326
+ (24, 3) 的 ndarray,值域映射到 [dataMin, dataMax]
327
+ """
328
+ linear_values = srgb_to_linear(raw_color_values_8bit) # [0,1]
329
+ mapped = linear_values * (dataMax - dataMin) + dataMin
330
+ return mapped.astype(out_dtype)
331
+
332
+
333
+ def get_bayer_pattern(bayer_order):
334
+ """返回 2x2 pattern,索引方式 pattern[row_phase][col_phase] -> 'R'/'G'/'B'"""
335
+ patterns = {
336
+ 3: [["R", "G"], ["G", "B"]], # RGGB
337
+ 0: [["B", "G"], ["G", "R"]], # BGGR
338
+ 1: [["G", "B"], ["R", "G"]], # GBRG
339
+ 2: [["G", "R"], ["B", "G"]], # GRBG
340
+ }
341
+ return patterns.get(bayer_order, patterns[3]) # 默认 RGGB
342
+
343
+
344
+ def fill_bayer_region(arr, y_start, y_end, x_start, x_end, r, g, b, pattern):
345
+ """
346
+ 按 Bayer CFA 相位,把 arr 的 [y_start:y_end, x_start:x_end] 区域
347
+ 填充为给定的 r/g/b 值。相位由区域在全图中的绝对起始坐标决定。
348
+ """
349
+ h = y_end - y_start
350
+ w = x_end - x_start
351
+ if h <= 0 or w <= 0:
352
+ return
353
+ region = np.zeros((h, w), dtype=arr.dtype)
354
+ color_map = {"R": r, "G": g, "B": b}
355
+ for dy in range(2):
356
+ for dx in range(2):
357
+ row_phase = (y_start + dy) % 2
358
+ col_phase = (x_start + dx) % 2
359
+ region[dy::2, dx::2] = color_map[pattern[row_phase][col_phase]]
360
+ arr[y_start:y_end, x_start:x_end] = region
361
+
362
+
363
+ def generateVrf(outPath, width, height, bitDepth=10):
364
+ """
365
+ 生成带中心标准 24 色卡的随机噪声 VRF 文件
366
+
367
+ Args:
368
+ outPath: 输出路径
369
+ width: 图像宽度
370
+ height: 图像高度
371
+ bitDepth: 位深 (默认 10)
372
+ """
373
+ if not os.path.exists(outPath):
374
+ os.makedirs(outPath)
375
+
376
+ vrfObj = Vrf()
377
+ vrfObj.BitDepth = np.uint8(bitDepth)
378
+ vrfObj.Width = np.uint16(width)
379
+ vrfObj.Height = np.uint16(height)
380
+ vrfObj.BLC10b = np.uint8(0)
381
+
382
+ dataMax = int(math.pow(2, bitDepth)) - 1
383
+ dataMin = int(vrfObj.BLC10b * (vrfObj.BitDepth - 9))
384
+
385
+ logi(f"bitDepth:{bitDepth} wxh: {width,height} dataRange: {dataMin,dataMax}")
386
+
387
+ # 生成完整随机噪声
388
+ random_noise = np.random.randint(dataMin, dataMax, (height, width), dtype=np.uint16)
389
+
390
+ # ========== 标准 24 色卡 (sRGB, 8-bit) ==========
391
+ # 24 个色块,按行排列 (4 行 x 6 列)
392
+ color_checker_rows = 4
393
+ color_checker_cols = 6
394
+ # ========== 标准 24 色卡 (sRGB, 8-bit, gamma编码) ==========
395
+ raw_color_values_8bit = [
396
+ [115, 82, 68], # 暗肤色
397
+ [194, 150, 130], # 亮肤色
398
+ [98, 122, 157], # 蓝天
399
+ [87, 108, 67], # 叶片绿
400
+ [133, 128, 177], # 蓝花
401
+ [103, 189, 170], # 蓝绿
402
+ [214, 126, 44], # 橙色
403
+ [80, 91, 166], # 紫蓝花
404
+ [193, 90, 99], # moderate red
405
+ [94, 60, 108], # 紫色
406
+ [157, 188, 64], # 黄绿
407
+ [224, 163, 46], # 橙黄
408
+ [56, 61, 150], # 蓝
409
+ [70, 148, 73], # 绿
410
+ [175, 54, 60], # 红
411
+ [231, 199, 31], # 黄
412
+ [187, 86, 149], # 品红
413
+ [8, 133, 161], # 青
414
+ [243, 243, 242], # 白
415
+ [200, 200, 200], # 浅灰 1
416
+ [160, 160, 160], # 浅灰 2
417
+ [122, 122, 121], # 浅灰 3
418
+ [85, 85, 85], # 浅灰 4
419
+ [52, 52, 52], # 黑
420
+ ]
421
+
422
+ # 映射到 sensor RAW 的实际数据范围 [dataMin, dataMax]
423
+ raw_color_values = map_colorchecker_to_bitdepth(raw_color_values_8bit, dataMin, dataMax)
424
+
425
+ # ========== 计算中心区域 ==========
426
+ left = (width // 3) // 2 * 2
427
+ top = (height // 3) // 2 * 2
428
+ right = width - left
429
+ bottom = height - top
430
+
431
+ cell_w = ((right - left) // color_checker_cols) // 2 * 2
432
+ cell_h = ((bottom - top) // color_checker_rows) // 2 * 2
433
+
434
+ logd(f"色卡区域: left={left}, right={right}, top={top}, bottom={bottom}")
435
+ logd(f"每个色块大小: {cell_w} x {cell_h}")
436
+
437
+ border_px = 2
438
+ black_level = dataMin # 用 sensor 黑电平作为"纯黑"边框值
439
+ # ========== 绘制 24 色卡 ==========
440
+ for row in range(color_checker_rows):
441
+ for col in range(color_checker_cols):
442
+ y_start = top + row * cell_h
443
+ y_end = top + (row + 1) * cell_h
444
+ x_start = left + col * cell_w
445
+ x_end = left + (col + 1) * cell_w
446
+
447
+ idx = row * color_checker_cols + col
448
+ r, g, b = raw_color_values[idx]
449
+
450
+ pattern = get_bayer_pattern(vrfObj.BayerOrder)
451
+
452
+ # 1) 先把整个格子填成黑色边框底色
453
+ fill_bayer_region(
454
+ random_noise, y_start, y_end, x_start, x_end, black_level, black_level, black_level, pattern
455
+ )
456
+
457
+ # 2) 再在内缩 border_px 后的区域填实际色块颜色
458
+ inner_y_start = y_start + border_px
459
+ inner_y_end = y_end - border_px
460
+ inner_x_start = x_start + border_px
461
+ inner_x_end = x_end - border_px
462
+
463
+ if inner_y_end > inner_y_start and inner_x_end > inner_x_start:
464
+ fill_bayer_region(
465
+ random_noise, inner_y_start, inner_y_end, inner_x_start, inner_x_end, r, g, b, pattern
466
+ )
467
+ else:
468
+ logi(f"警告: 色块 idx={idx} 尺寸太小,border_px={border_px} 后无法容纳色块内容,已跳过内部填充")
469
+
470
+ vrfObj.unpackImage = random_noise
471
+ vrfName = os.path.join(outPath, f"random_noise{bitDepth}.vrf")
472
+ vrfObj.SaveVrf(vrfName)
473
+ logi(f"VRF 文件已生成: {vrfName}")
474
+
475
+ # return vrfObj
476
+
477
+
478
+ def generateRandomVrf(outPath, width, height, bitDepth=10):
479
+ if not os.path.exists(outPath):
480
+ os.makedirs(outPath)
481
+
482
+ vrfObj = Vrf()
483
+ vrfObj.BitDepth = np.uint8(bitDepth)
484
+ vrfObj.Width = np.uint16(width)
485
+ vrfObj.Height = np.uint16(height)
486
+
487
+ dataMax = int(math.pow(2, bitDepth)) - 1
488
+ dataMin = int(vrfObj.BLC10b * (vrfObj.BitDepth - 9))
489
+ # 生成10位随机噪声数据(0-1023)
490
+ logi(f"bitDepth:{bitDepth} wxh: {width,height} dataRange: {dataMin,dataMax}")
491
+ random_noise = np.random.randint(dataMin, dataMax, (height, width), dtype=np.uint16) # 10位数据范围是0-1023
492
+
493
+ vrfObj.unpackImage = random_noise
494
+
495
+ vrfName = os.path.join(outPath, f"random_noise{bitDepth}.vrf")
496
+ vrfObj.SaveVrf(vrfName)
497
+
498
+
499
+ def makeNoiseToVrf(vrfFile: str, noiseLevel: float):
500
+ path = Path(vrfFile)
501
+ if not path.is_file():
502
+ raise FileNotFoundError(f"{vrfFile} 不是一个有效的文件")
503
+
504
+ outPath, _, nameWithoutSuffix, _ = path.parent, path.name, path.stem, path.suffix
505
+
506
+ vrfObj = Vrf()
507
+ vrfObj.Read(vrfFile)
508
+ bitDepth = vrfObj.BitDepth
509
+
510
+ dataMax = int(math.pow(2, bitDepth)) - 1
511
+ dataMin = vrfObj.BLC10b * (vrfObj.BitDepth - 9)
512
+
513
+ # 生成随机噪声, 噪声范围可以根据需要调整
514
+ noiseMax = int(noiseLevel * dataMax)
515
+ noiseMin = -noiseMax
516
+ print(f"bitDepth:{bitDepth} noiseRange: {noiseMin,noiseMax}")
517
+ random_noise = np.random.randint(noiseMin, noiseMax, (vrfObj.Height, vrfObj.Width), dtype=np.int16)
518
+
519
+ # 将噪声添加到原始数据中
520
+ noisy_data = vrfObj.unpackImage + random_noise
521
+
522
+ # 确保数据在bitDepth位范围内
523
+ print(f"dataMin: {dataMin}")
524
+ noisy_data = np.clip(noisy_data, dataMin, dataMax).astype(np.uint16)
525
+
526
+ vrfObj.unpackImage = noisy_data
527
+
528
+ vrfName = os.path.join(outPath, nameWithoutSuffix + f"_random_noise_{noiseLevel}.vrf")
529
+ vrfObj.SaveVrf(vrfName)
530
+
531
+
532
+ def CLI_raw2vrf(argsList):
533
+ parser = argparse.ArgumentParser(prog="raw2vrf", description="mipiraw/raw文件转换成vrf文件")
534
+ parser.add_argument(
535
+ "-p",
536
+ "--path",
537
+ type=str,
538
+ required=True,
539
+ default="",
540
+ help="RAW文件路径或文件名,如果是路径则会处理该路径下的所有raw/mipiraw文件",
541
+ )
542
+ parser.add_argument(
543
+ "-f", "--format", type=str, required=True, choices=["raw", "mipiraw"], help="raw格式,可选值: raw, mipiraw"
544
+ )
545
+ parser.add_argument("--width", type=int, required=True, help="图像宽度")
546
+ parser.add_argument("--height", type=int, required=True, help="图像高度")
547
+ parser.add_argument("-d", "--depth", type=int, required=True, choices=[8, 10, 12, 14, 16], help="像素位深度")
548
+ parser.add_argument("-g", "--gain", type=float, default=1, help="数字增益值,float类型")
549
+ parser.add_argument("-s", "--stride", type=int, default=0, help="raw stride,默认为0表示无stride")
550
+ parser.add_argument("-o", "--ob10b", type=int, default=0, help="黑电平校正值(10bit)")
551
+ parser.add_argument(
552
+ "-b",
553
+ "--bayerorder",
554
+ type=int,
555
+ default=0,
556
+ choices=[0, 1, 2, 3],
557
+ help="Bayer格式顺序,0:BGGR, 1:GBRG, 2:GRBG, 3:RGGB",
558
+ )
559
+ parser.add_argument("--wbgain", type=float, default=[1.0, 1.0, 1.0], help="wb gain,float类型, 顺序为R G B", nargs=3)
560
+ parser.add_argument(
561
+ "--suffix",
562
+ type=str,
563
+ required=False,
564
+ default="raw",
565
+ help="文件后缀,默认为raw/rawmipi/mipiraw,如果是路径则会处理该路径下的所有指定后缀的文件",
566
+ )
567
+
568
+ args = parser.parse_args(argsList)
569
+
570
+ path = args.path
571
+ isMipiRaw = True if args.format == "mipiraw" else False
572
+ width = int(args.width)
573
+ height = int(args.height)
574
+ dgain = float(args.gain)
575
+ stride = int(args.stride)
576
+ BLC10b = np.uint16(int(args.ob10b))
577
+ BayerOrder = np.uint16(int(args.bayerorder))
578
+ BitDepth = np.uint16(int(args.depth))
579
+ WBGain = [float(gain) for gain in args.wbgain]
580
+ suffix = args.suffix
581
+
582
+ if os.path.isdir(path):
583
+ raw2vrfBatch(path, width, height, BLC10b, BayerOrder, BitDepth, stride, isMipiRaw, dgain, WBGain, suffix)
584
+ else:
585
+ raw2vrf(path, width, height, BLC10b, BayerOrder, BitDepth, stride, isMipiRaw, dgain, WBGain)
586
+
587
+
588
+ def rotVrf(input, output, mode):
589
+ vrfObj = Vrf()
590
+ vrfObj.Read(input)
591
+ vrfObj.rot(mode)
592
+ vrfObj.SaveVrf(output)
593
+
594
+
595
+ def rotVrfBatch(path, mode, tag):
596
+ file_list = os.listdir(path)
597
+ for f in file_list:
598
+ f_lower = f.lower()
599
+ if f_lower.endswith(".vrf") and f.find(tag) == -1:
600
+ input = os.path.join(path, f)
601
+ output = os.path.join(path, os.path.splitext(f)[0] + f"{tag}.vrf")
602
+ rotVrf(input, output, mode)
603
+
604
+
605
+ def CLI_rotVrf(argsList):
606
+ parser = argparse.ArgumentParser(prog="rotVrf", description="vrf旋转")
607
+ parser.add_argument(
608
+ "-p", "--path", type=str, required=True, default="", help="RAW文件路径或文件名,路径则处理所有raw/mipiraw文件"
609
+ )
610
+ parser.add_argument("-o", "--orientation", type=str, required=True, choices=["mirror", "flip", "mirror_flip"])
611
+ parser.add_argument("-s", "--suffix", type=str, required=False, default="_rot", help="输出文件添加后缀")
612
+ args = parser.parse_args(argsList)
613
+
614
+ if os.path.isdir(args.path):
615
+ rotVrfBatch(args.path, args.orientation, args.suffix)
616
+ else:
617
+ rotVrf(args.path, os.path.splitext(args.path)[0] + f"{args.suffix}.vrf", args.orientation)
618
+
619
+
620
+ def CLI_generateVrf(argsList):
621
+ parser = argparse.ArgumentParser(prog="generateVrf", description="生成VRF")
622
+ parser.add_argument("-p", "--path", type=str, required=True, default="", help="输出VRF路径")
623
+ parser.add_argument("--width", type=int, required=True, help="vrf宽度")
624
+ parser.add_argument("--height", type=int, required=True, help="vrf高度")
625
+ parser.add_argument("-d", "--depth", type=int, required=True, choices=[8, 10, 12, 14, 16], help="像素位深度")
626
+ args = parser.parse_args(argsList)
627
+
628
+ generateVrf(args.path, args.width, args.height, args.depth)
629
+
630
+
631
+ def float_range(min_val: float, max_val: float):
632
+ def _check(value):
633
+ try:
634
+ f = float(value)
635
+ except ValueError:
636
+ raise argparse.ArgumentTypeError(f"无效的浮点数: {value}")
637
+ if f < min_val or f > max_val:
638
+ raise argparse.ArgumentTypeError(f"值必须在 {min_val} 到 {max_val} 之间")
639
+ return f
640
+
641
+ return _check
642
+
643
+
644
+ def CLI_addNoise2Vrf(argsList):
645
+ parser = argparse.ArgumentParser(prog="addNoise2Vrf", description="在VRF中添加随机噪声")
646
+ parser.add_argument(
647
+ "-p", "--path", type=str, required=True, default="", help="RAW文件路径或文件名,路径则处理所有raw/mipiraw文件"
648
+ )
649
+ parser.add_argument("-l", "--level", type=float_range(0.0, 1.0), required=True, help="噪声等级,范围0-1.0")
650
+ args = parser.parse_args(argsList)
651
+
652
+ makeNoiseToVrf(args.path, args.level)
653
+
654
+
655
+ _COMMAND_REGISTRY = {}
656
+
657
+
658
+ def printHelp():
659
+ print("[vrfProcess]")
660
+ print("raw2vrf -h : raw/mipiraw to vrf")
661
+ print("rotVrf -h : 旋转 vrf")
662
+ print("gvrf -h : 生成随机噪声 vrf")
663
+ print("avrf -h : 增加随机噪声到 vrf")
664
+
665
+
666
+ CLI_raw2vrf = register_command("raw2vrf", registry=_COMMAND_REGISTRY)(CLI_raw2vrf)
667
+ CLI_rotVrf = register_command("rotvrf", registry=_COMMAND_REGISTRY)(CLI_rotVrf)
668
+ CLI_generateVrf = register_command("gvrf", registry=_COMMAND_REGISTRY)(CLI_generateVrf)
669
+ CLI_addNoise2Vrf = register_command("avrf", registry=_COMMAND_REGISTRY)(CLI_addNoise2Vrf)
670
+
671
+
672
+ def vrfToolMain(args):
673
+ debugCtrl(1) if "debug" in args else debugCtrl(0)
674
+ args.remove("debug") if "debug" in args else args
675
+ args = normalize_args(args)
676
+ if len(args) < 1 or args[0] in {"help", "-h", "--help"}:
677
+ printHelp()
678
+ return
679
+ cmd = args[0]
680
+ handler = _COMMAND_REGISTRY.get(cmd)
681
+ if handler is None:
682
+ loge(f"unsupport command {cmd}")
683
+ return
684
+ handler(args[1:])
685
+
686
+
687
+ if __name__ == "__main__":
688
+ debugCtrl(1) if "debug" in sys.argv else debugCtrl(0)
689
+ sys.argv.remove("debug") if "debug" in sys.argv else sys.argv
690
+ vrfToolMain(sys.argv)