修:draw-ui / oil-motion 原被当子模块指针收录 ⇒ 改为正常文件入库(两份内容原先对别人是空的)
一、问题(本轮实测)
`draw-ui` 与 `oil-motion` 目录里**各自带一个内嵌 `.git`** ⇒ 上一次提交把它们记成了 **gitlink(子模块指针)**
⇒ 仓库里只存了一个不属于任何远端的 commit id,**别人克隆下来这两份是空的** ✗(`git status` 显示 ` m draw-ui` / ` m oil-motion` = 子模块内容有改动)。
二、处置(可回退)
· 把两处的 `.git` **挪走**(⛔ 不是删除)⇒ `归档/内嵌git-20261008/{draw-ui,oil-motion}.git`;
· `git rm --cached` 掉那两个 gitlink,再 `git add` 两个目录 ⇒ **按正常文件入库**(内容才真的进仓库)。
三、副作用(如实记)
挪走 `.git` 后,这两个技能**不能再原地 `git pull` 取上游更新**(要更新得重新拉一份覆盖);
如需恢复其本地仓库,把 `归档/内嵌git-20261008/` 里的 `.git` 挪回原处即可。
This commit is contained in:
1 parent
e03465c398
commit
237a09a5b0
161 files changed
+19429
-2
No files matched your search
@@ -0,0 +1,101 @@
|
||||
#!/usr/bin/env python3
|
||||
"""按原始坐标比较截图,不缩放候选图,不把误差分数当成视觉验收。"""
|
||||
from __future__ import annotations
|
||||
import argparse
|
||||
import json
|
||||
import math
|
||||
from pathlib import Path
|
||||
import re
|
||||
from PIL import Image, ImageChops, ImageEnhance, ImageStat
|
||||
|
||||
|
||||
def safe_name(value):
|
||||
if not re.fullmatch(r'[\w.-]+', value) or '..' in value:
|
||||
raise ValueError('名称只能包含文字、数字、短横线、下划线或单个点')
|
||||
return value
|
||||
|
||||
|
||||
def rms(diff):
|
||||
stat = ImageStat.Stat(diff)
|
||||
return math.sqrt(sum(value * value for value in stat.rms) / len(stat.rms))
|
||||
|
||||
|
||||
def make_heatmap(diff):
|
||||
gray = ImageEnhance.Contrast(diff.convert('L')).enhance(2.2)
|
||||
gray = ImageEnhance.Brightness(gray).enhance(1.5)
|
||||
heat = Image.new('RGB', diff.size, (255, 255, 255))
|
||||
heat.paste(Image.new('RGB', diff.size, (255, 55, 55)), mask=gray)
|
||||
return heat
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--reference', type=Path, required=True)
|
||||
parser.add_argument('--candidate', type=Path, required=True)
|
||||
parser.add_argument('--out-dir', type=Path, required=True)
|
||||
parser.add_argument('--prefix', default='comparison')
|
||||
parser.add_argument('--clip', action='append', default=[], help='名称:x,y,w,h;可重复')
|
||||
parser.add_argument('--allow-height-difference', action='store_true', help='长页诊断:保留高度差,只比较共同坐标,不表示页面完整')
|
||||
args = parser.parse_args()
|
||||
reference = Image.open(args.reference).convert('RGB')
|
||||
candidate = Image.open(args.candidate).convert('RGB')
|
||||
if reference.width != candidate.width:
|
||||
parser.error('截图宽度不一致;请先校准视口、DPR及截图缩放方式。不会自动缩放。')
|
||||
if reference.height != candidate.height and not args.allow_height_difference:
|
||||
parser.error('截图高度不一致;先修正截图方式。长页诊断可显式使用 --allow-height-difference。')
|
||||
clips = []
|
||||
names = {'full', 'overlap', 'reference-tail', 'candidate-tail'}
|
||||
try:
|
||||
safe_name(args.prefix)
|
||||
for raw in args.clip:
|
||||
name, numbers = raw.split(':', 1)
|
||||
safe_name(name)
|
||||
x, y, w, h = map(int, numbers.split(','))
|
||||
if name in names or min(x, y) < 0 or min(w, h) <= 0 or x+w > reference.width or y+h > reference.height:
|
||||
raise ValueError('分区重名、尺寸无效或超出参考图')
|
||||
if y+h > candidate.height and not args.allow_height_difference:
|
||||
raise ValueError('分区超出候选图')
|
||||
names.add(name)
|
||||
clips.append((name, (x, y, x+w, y+h)))
|
||||
except ValueError as error:
|
||||
parser.error(str(error))
|
||||
args.out_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
def compare_region(name, ref, cand):
|
||||
diff = ImageChops.difference(ref, cand)
|
||||
paths = {suffix: args.out_dir / f'{args.prefix}-{name}-{suffix}.png' for suffix in ('candidate', 'diff', 'heatmap')}
|
||||
cand.save(paths['candidate'])
|
||||
diff.save(paths['diff'])
|
||||
make_heatmap(diff).save(paths['heatmap'])
|
||||
return {'name': name, 'size': ref.size, 'rms_diff': round(rms(diff), 3),
|
||||
'mean_abs_diff': round(sum(ImageStat.Stat(diff).mean)/3, 3),
|
||||
**{key: str(value) for key, value in paths.items()}}
|
||||
|
||||
height = min(reference.height, candidate.height)
|
||||
equal = reference.size == candidate.size
|
||||
box = (0, 0, reference.width, height)
|
||||
regions = [compare_region('full' if equal else 'overlap', reference.crop(box), candidate.crop(box))]
|
||||
tails = {}
|
||||
for label, image in [('reference', reference), ('candidate', candidate)]:
|
||||
if image.height > height:
|
||||
path = args.out_dir / f'{args.prefix}-{label}-tail.png'
|
||||
image.crop((0, height, image.width, image.height)).save(path)
|
||||
tails[label] = str(path)
|
||||
for name, box in clips:
|
||||
if box[3] > candidate.height:
|
||||
regions.append({'name': name, 'status': '候选截图缺少完整分区,未计分', 'box': box})
|
||||
else:
|
||||
regions.append(compare_region(name, reference.crop(box), candidate.crop(box)))
|
||||
metrics = {'reference': str(args.reference), 'candidate': str(args.candidate),
|
||||
'reference_size': reference.size, 'candidate_size_original': candidate.size,
|
||||
'candidate_size_compared': [candidate.width, height], 'resized': False,
|
||||
'geometry_match': equal, 'height_delta': candidate.height-reference.height,
|
||||
'unmatched_tails': tails, 'regions': regions,
|
||||
'note': '误差不等于还原度;字体、摄影和位置分别判断。尺寸一致也不代表视觉通过。'}
|
||||
(args.out_dir / f'{args.prefix}-metrics.json').write_text(json.dumps(metrics, ensure_ascii=False, indent=2), encoding='utf-8')
|
||||
print(json.dumps(metrics, ensure_ascii=False, indent=2))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
Reference in new issue
Block a user