From e0b250e77f510c2233df0a67678da58414ff499c Mon Sep 17 00:00:00 2001 From: Jiao77 Date: Wed, 30 Jul 2025 12:05:29 +0800 Subject: [PATCH] add tool of layout --- TRAINING_STRATEGY_ANALYSIS.md | 100 --------------------- pyproject.toml | 2 + tools/klayoutconvertor.py | 159 ++++++++++++++++++++++++++++++++++ tools/layout2png.py | 88 ------------------- uv.lock | 35 ++++++++ 5 files changed, 196 insertions(+), 188 deletions(-) delete mode 100644 TRAINING_STRATEGY_ANALYSIS.md create mode 100644 tools/klayoutconvertor.py delete mode 100644 tools/layout2png.py diff --git a/TRAINING_STRATEGY_ANALYSIS.md b/TRAINING_STRATEGY_ANALYSIS.md deleted file mode 100644 index bc5635c..0000000 --- a/TRAINING_STRATEGY_ANALYSIS.md +++ /dev/null @@ -1,100 +0,0 @@ -# RoRD 训练策略分析与改进 - -## 原始问题分析 - -### 1. 技术错误 -- **PIL.Image.LANCZOS 错误**: 使用了已弃用的 `Image.LANCZOS`,应改为 `Image.Resampling.LANCZOS` -- **模型架构不匹配**: 检测头和描述子头使用了不同尺寸的特征图,导致训练不稳定 - -### 2. 训练策略问题 - -#### 2.1 损失函数设计 -- **检测损失**: 使用 MSE 损失不适合二分类问题,应使用 BCE 损失 -- **描述子损失**: Triplet Loss 采样策略不够有效,随机采样产生大量简单负样本 - -#### 2.2 数据增强策略 -- **尺度抖动范围过大**: `(0.7, 1.5)` 可能导致训练不稳定 -- **几何变换过于简单**: 只考虑8个离散方向,缺乏连续性 -- **缺少其他增强**: 没有亮度、对比度、噪声等增强 - -#### 2.3 训练配置 -- **批次大小过小**: 只有4,对于现代GPU效率低 -- **学习率可能过高**: 1e-4 可能导致训练不稳定 -- **缺少验证机制**: 没有验证集和早停 -- **缺少监控**: 没有详细的训练日志和损失分解 - -## 改进方案 - -### 1. 技术修复 -✅ **已修复**: PIL.Image.LANCZOS → Image.Resampling.LANCZOS -✅ **已修复**: 统一检测头和描述子头的特征图尺寸 - -### 2. 损失函数改进 -✅ **检测损失**: -- 使用 BCE 损失替代 MSE -- 添加平滑 L1 损失作为辅助 - -✅ **描述子损失**: -- 增加采样点数量 (100 → 200) -- 使用网格采样替代随机采样 -- 实现困难负样本挖掘 - -### 3. 数据增强优化 -✅ **尺度抖动**: 缩小范围到 `(0.8, 1.2)` -✅ **额外增强**: -- 亮度调整 (0.8-1.2倍) -- 对比度调整 (0.8-1.2倍) -- 高斯噪声 (σ=5) - -### 4. 训练配置优化 -✅ **批次大小**: 4 → 8 -✅ **学习率**: 1e-4 → 5e-5 -✅ **训练轮数**: 20 → 50 -✅ **添加权重衰减**: 1e-4 - -### 5. 训练流程改进 -✅ **验证集**: 80/20 分割 -✅ **学习率调度**: ReduceLROnPlateau -✅ **早停机制**: 10个epoch无改善则停止 -✅ **梯度裁剪**: max_norm=1.0 -✅ **详细日志**: 训练和验证损失分解 - -## 预期效果 - -### 1. 训练稳定性 -- 更稳定的损失下降曲线 -- 减少过拟合风险 -- 更好的泛化能力 - -### 2. 模型性能 -- 更准确的检测结果 -- 更鲁棒的描述子 -- 更好的几何不变性 - -### 3. 训练效率 -- 更快的收敛速度 -- 更好的资源利用率 -- 更完善的监控机制 - -## 使用建议 - -### 1. 训练前准备 -```bash -# 确保数据路径正确 -python train.py --data_dir /path/to/layouts --save_dir /path/to/save -``` - -### 2. 监控训练 -- 查看日志文件了解详细训练过程 -- 关注验证损失变化趋势 -- 监控学习率自动调整 - -### 3. 模型选择 -- 使用 `rord_model_best.pth` 作为最终模型 -- 该模型在验证集上表现最佳 - -### 4. 进一步优化建议 -- 考虑使用预训练权重初始化 -- 实验不同的数据增强组合 -- 尝试其他损失函数权重平衡 -- 考虑使用混合精度训练加速 \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index d4a1026..fe3f458 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,9 @@ readme = "README.md" requires-python = ">=3.12" dependencies = [ "cairosvg>=2.8.2", + "gdspy>=1.6.13", "gdstk>=0.9.60", + "klayout>=0.30.2", "numpy>=2.3.0", "opencv-python>=4.11.0.86", "pillow>=11.2.1", diff --git a/tools/klayoutconvertor.py b/tools/klayoutconvertor.py new file mode 100644 index 0000000..6ca5747 --- /dev/null +++ b/tools/klayoutconvertor.py @@ -0,0 +1,159 @@ +# tools/klayoutconvertor.py +#!/usr/bin/env python3 +""" +KLayout GDS to PNG Converter + +This script uses KLayout's Python API to convert GDS files to PNG images. +It accepts command-line arguments for input parameters. + +Requirements: + pip install klayout + +Usage: + python klayoutconvertor.py input.gds output.png [options] +""" + +import klayout.db as pya +import klayout.lay as lay +from PIL import Image +import os +import argparse +import sys + +Image.MAX_IMAGE_PIXELS = None + + +def export_gds_as_image( + gds_path: str, + output_path: str, + layers: list = [1, 2], + center_um: tuple = (0, 0), + view_size_um: float = 100.0, + resolution: int = 2048, + binarize: bool = True +) -> None: + """ + Export GDS file as PNG image using KLayout. + + Args: + gds_path: Input GDS file path + output_path: Output PNG file path + layers: List of layer numbers to include + center_um: Center coordinates in micrometers (x, y) + view_size_um: View size in micrometers + resolution: Output image resolution + binarize: Whether to convert to black and white + """ + if not os.path.exists(gds_path): + raise FileNotFoundError(f"Input file not found: {gds_path}") + + # Ensure output directory exists + output_dir = os.path.dirname(output_path) + if output_dir: + os.makedirs(output_dir, exist_ok=True) + + layout = pya.Layout() + layout.read(gds_path) + top = layout.top_cell() + + # Create layout view + view = lay.LayoutView() + view.set_config("background-color", "#ffffff") + view.set_config("grid-visible", "false") + + # Load layout into view correctly + view.load_layout(gds_path) + + # Add all layers + view.add_missing_layers() + + # Configure view to show entire layout with reasonable resolution + if view_size_um > 0: + # Use specified view size + box = pya.DBox( + center_um[0] - view_size_um / 2, + center_um[1] - view_size_um / 2, + center_um[0] + view_size_um / 2, + center_um[1] + view_size_um / 2 + ) + else: + # Use full layout bounds with size limit + bbox = top.bbox() + if bbox: + # Convert to micrometers (KLayout uses database units) + dbu = layout.dbu + box = pya.DBox( + bbox.left * dbu, + bbox.bottom * dbu, + bbox.right * dbu, + bbox.top * dbu + ) + + else: + # Fallback to 100x100 um if empty layout + box = pya.DBox(-50, -50, 50, 50) + + view.max_hier() + view.zoom_box(box) + + # Save to temporary file first, then load with PIL + import tempfile + temp_path = tempfile.NamedTemporaryFile(suffix='.png', delete=False).name + + try: + view.save_image(temp_path, resolution, resolution) + img = Image.open(temp_path) + + if binarize: + # Convert to grayscale and binarize + img = img.convert("L") + img = img.point(lambda x: 255 if x > 128 else 0, '1') + else: + # Convert to grayscale + img = img.convert("L") + + img.save(output_path) + finally: + # Clean up temp file + if os.path.exists(temp_path): + os.unlink(temp_path) + + +def main(): + """Main CLI entry point.""" + parser = argparse.ArgumentParser(description='Convert GDS to PNG using KLayout') + parser.add_argument('input', help='Input GDS file') + parser.add_argument('output', help='Output PNG file') + parser.add_argument('--layers', nargs='+', type=int, default=[1, 2], + help='Layers to include (default: 1 2)') + parser.add_argument('--center-x', type=float, default=0, + help='Center X coordinate in micrometers (default: 0)') + parser.add_argument('--center-y', type=float, default=0, + help='Center Y coordinate in micrometers (default: 0)') + parser.add_argument('--size', type=float, default=0, + help='View size in micrometers (default: 0 = full layout)') + parser.add_argument('--resolution', type=int, default=2048, + help='Output image resolution (default: 2048)') + parser.add_argument('--no-binarize', action='store_true', + help='Disable binarization (keep grayscale)') + + args = parser.parse_args() + + try: + export_gds_as_image( + gds_path=args.input, + output_path=args.output, + layers=args.layers, + center_um=(args.center_x, args.center_y), + view_size_um=args.size, + resolution=args.resolution, + binarize=not args.no_binarize + ) + print("Conversion completed successfully!") + except Exception as e: + print(f"Error: {e}") + sys.exit(1) + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/tools/layout2png.py b/tools/layout2png.py deleted file mode 100644 index 25af7e7..0000000 --- a/tools/layout2png.py +++ /dev/null @@ -1,88 +0,0 @@ -import gdstk -import cairosvg -import argparse -import os - -def convert_layout_to_png_via_svg(layout_path, png_path, cell_name=None, pixels_per_unit=10): - """ - 通过先生成 SVG 再转换为 PNG 的方式,将 GDSII 或 OASIS 文件光栅化。 - 此版本修正了 write_svg 的参数错误,兼容性更强。 - - 参数: - layout_path (str): 输入的版图文件路径(.gds 或 .oas)。 - png_path (str): 输出的 PNG 文件路径。 - cell_name (str, optional): 需要转换的单元名称。如果为 None,则使用顶层单元。 - pixels_per_unit (int, optional): 版图数据库单位到像素的转换比例,控制图像分辨率。 - """ - print(f"正在从 '{layout_path}' 读取版图文件...") - - # 1. 加载版图文件 - _, extension = os.path.splitext(layout_path) - extension = extension.lower() - - if extension == '.gds': - lib = gdstk.read_gds(layout_path) - elif extension == '.oas': - lib = gdstk.read_oas(layout_path) - else: - raise ValueError(f"不支持的文件类型: '{extension}'。请输入 .gds 或 .oas 文件。") - - if cell_name: - cell = lib.cells[cell_name] - else: - top_cells = lib.top_level() - if not top_cells: - raise ValueError("错误:版图文件中没有找到顶层单元。") - cell = top_cells[0] - print(f"未指定单元名称,自动选择顶层单元: '{cell.name}'") - - # 2. 将版图单元写入临时的 SVG 文件 (已移除无效的 padding 参数) - temp_svg_path = png_path + ".temp.svg" - print(f"步骤 1/2: 正在将单元 '{cell.name}' 转换为临时 SVG 文件...") - cell.write_svg( - temp_svg_path # 隐藏默认字体,避免影响边界 - ) - - # 3. 使用 cairosvg 将 SVG 文件转换为 PNG - print(f"步骤 2/2: 正在将 SVG 转换为 PNG...") - # 获取单元的精确边界框 - bb = cell.bb() - if bb is None: - raise ValueError(f"单元 '{cell.name}' 为空或无法获取其边界框。") - - # 根据边界框和分辨率计算输出图像的宽度 - width, height = bb[1] - bb[0] - output_width = width * pixels_per_unit - - cairosvg.svg2png(url=temp_svg_path, write_to=png_path, output_width=output_width) - - # 4. 清理临时的 SVG 文件 - os.remove(temp_svg_path) - - print(f"成功!图像已保存至: '{png_path}'") - -if __name__ == "__main__": - parser = argparse.ArgumentParser( - description="将 GDSII (.gds) 或 OASIS (.oas) 版图文件转换为 PNG 图像 (通过SVG)。", - epilog="示例: python rasterize.py -i my_chip.oas -o my_chip.png -ppu 20" - ) - parser.add_argument('-i', '--input', type=str, required=True, help="输入的版图文件路径 (.gds 或 .oas)。") - parser.add_argument('-o', '--output', type=str, help="输出的 PNG 文件路径。如果未提供,将使用输入文件名并替换扩展名为 .png。") - parser.add_argument('-c', '--cell', type=str, default=None, help="要转换的特定单元的名称。默认为顶层单元。") - parser.add_argument('-ppu', '--pixels_per_unit', type=int, default=10, help="每微米(um)的像素数,用于控制输出图像的分辨率。") - args = parser.parse_args() - - if not args.output: - base_name = os.path.splitext(os.path.basename(args.input))[0] - args.output = f"{base_name}.png" - print(f"未指定输出路径,将自动保存为: '{args.output}'") - - try: - convert_layout_to_png_via_svg( - layout_path=args.input, - png_path=args.output, - cell_name=args.cell, - pixels_per_unit=args.pixels_per_unit - ) - except Exception as e: - print(f"\n处理失败: {e}") \ No newline at end of file diff --git a/uv.lock b/uv.lock index a9981b2..bfa7e86 100644 --- a/uv.lock +++ b/uv.lock @@ -108,6 +108,15 @@ wheels = [ { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bb/61/78c7b3851add1481b048b5fdc29067397a1784e2910592bc81bb3f608635/fsspec-2025.5.1-py3-none-any.whl", hash = "sha256:24d3a2e663d5fc735ab256263c4075f374a174c3410c0b25e5bd1970bceaa462", size = 199052, upload-time = "2025-05-24T12:03:21.66Z" }, ] +[[package]] +name = "gdspy" +version = "1.6.13" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7a/c5/01a4b160bc9ac9b0f8621dd6f90e964596e60a369ff2d076ebb4ce52c402/gdspy-1.6.13.zip", hash = "sha256:38c61a7267f90767d90b8fcdda96c7a629df26e06f7153084c773f3d6363f4f0", size = 157902, upload-time = "2023-04-26T12:21:35.91Z" } + [[package]] name = "gdstk" version = "0.9.60" @@ -147,6 +156,28 @@ wheels = [ { url = "https://pypi.tuna.tsinghua.edu.cn/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, ] +[[package]] +name = "klayout" +version = "0.30.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0c/31/e3a3b3413d81fbc31e7176182410d0fb8bda73ae4f380ca4030661e62ea7/klayout-0.30.2.tar.gz", hash = "sha256:1d1b919f02b24d579c8c063407352e39a86b74c3149572d4880a0fae83634ba5", size = 3846876, upload-time = "2025-05-29T22:14:55.661Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/54/ff/d87f7a258562aa51d781b27c85e360f035d3978320550a07222b839d5db6/klayout-0.30.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:508047cf3dac2f78e8ba4d41f00b4e63db1e071b767c072e1311baddf0671004", size = 21080501, upload-time = "2025-05-29T22:14:04.029Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6b/f0/4cae68285f5a1d7b8195b7a67a1de5539f56071f3fea475df9864153ff0e/klayout-0.30.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:988afdf5236b403a362a4aece786b09497d886604ec366313e0e73d0ff2f0045", size = 19642508, upload-time = "2025-05-29T22:14:06.536Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6d/ec/fcb1838d46342beddeba0bfff64a64b8ad1652628cd78d66a26382f311d8/klayout-0.30.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9202f98ccf6e4d65930f2b6c16f779946b445dabd6e6eb0dcacd4edf8748dba", size = 23441152, upload-time = "2025-05-29T22:14:08.657Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/99/9b/9ed15b304af88bd393cad463360bfaf5e311d55fe5ced8b8d227dc0797dc/klayout-0.30.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a48691ba05e005726610f5e88803260d251a95b13b45dcaffa65e648a680e30d", size = 25191179, upload-time = "2025-05-29T22:14:11.731Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fb/0b/80efdb75a78c0c31f49266440c7b543ccec7bb98a34d24c49dea70262ac1/klayout-0.30.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5d71a49e6a81064b677320d44c17a88201aba115d844ab4695913c5a4b7da5d7", size = 26968714, upload-time = "2025-05-29T22:14:14.336Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/91/fa/5ff012942a88f2d71a72ac892e2697e5cf8f34ccd9a6abf26195004622d5/klayout-0.30.2-cp312-cp312-win32.whl", hash = "sha256:a7395a4de62160b1844ac1775231a41f1a227dd74cef2c898dc0fea9aeca41a2", size = 11511576, upload-time = "2025-05-29T22:51:16.639Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6f/e8/9f883666ce969a07a26892ab7a6d2b24d7504e84c4880723924836639be6/klayout-0.30.2-cp312-cp312-win_amd64.whl", hash = "sha256:3d0776ec9d53a2f3451694c68df2d28159b3708aaa16bfbd432921dcec71608a", size = 13190391, upload-time = "2025-05-29T22:51:19.026Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b8/1b/788488ac14c11169d794a2d5bcb86392f422cff9a34887b5e0bb36a9ec83/klayout-0.30.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c7e289b8000aa1e4563fb2f46f73e9ead9ed8f123eceab954b5f9319f82f8868", size = 21080506, upload-time = "2025-05-29T22:14:16.996Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f4/84/8e0a17f9acd6c40d2a149b028f8e3e95c86030385396777d3ad7eb2c1720/klayout-0.30.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6fb118949f5ae05e85a8ff00db0b3741eb6b010fa0c10a970189819bc646b441", size = 19642487, upload-time = "2025-05-29T22:14:19.287Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3e/31/7b82974d2091dbe4c32c72a5dbd9c0454cb69da6a2c5e828ad55ec154de3/klayout-0.30.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:459044166d4068f9e866680f402ffcad08b0fc346ee282fcfbc975cf3776b3bc", size = 23441135, upload-time = "2025-05-29T22:14:21.807Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c9/4a/697a3211ce128cb08e09fd9a4633f665f4e9de77d324b7ef89744f7df939/klayout-0.30.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc7e937f9a3caed8e14b2febcfd2b4d916d1cbc18d0b52f8a019413f9c50f826", size = 25191185, upload-time = "2025-05-29T22:14:24.01Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2e/c5/5bb5d8f95338e65d92fbe005bb09dc0fa57d2b037f695d332b0d570dea3f/klayout-0.30.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:966354bc59ba132804832441f4f545c0336a94b284f3b64e62baac4918be52da", size = 26968753, upload-time = "2025-05-29T22:14:26.42Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b6/09/6e0a606bcc24d9a985fc1c8623cbfe5ef649bda107e6c54c2d77d18e8bc2/klayout-0.30.2-cp313-cp313-win32.whl", hash = "sha256:478a673b125e3c81551652ef93fb69fd56e9cf16e020b889592016ad5046623a", size = 11511690, upload-time = "2025-05-29T22:51:21.302Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e0/0e/89dd819f642d2a0d306905dac27a7d82ba75d2e887753c5a432ad7cbd5c4/klayout-0.30.2-cp313-cp313-win_amd64.whl", hash = "sha256:c27601cfd8f39ff55f63b795abc9c043ec46319127c86084b12b7c5b187135f6", size = 13190664, upload-time = "2025-05-29T22:51:23.621Z" }, +] + [[package]] name = "markupsafe" version = "3.0.2" @@ -450,7 +481,9 @@ version = "0.1.0" source = { virtual = "." } dependencies = [ { name = "cairosvg" }, + { name = "gdspy" }, { name = "gdstk" }, + { name = "klayout" }, { name = "numpy" }, { name = "opencv-python" }, { name = "pillow" }, @@ -461,7 +494,9 @@ dependencies = [ [package.metadata] requires-dist = [ { name = "cairosvg", specifier = ">=2.8.2" }, + { name = "gdspy", specifier = ">=1.6.13" }, { name = "gdstk", specifier = ">=0.9.60" }, + { name = "klayout", specifier = ">=0.30.2" }, { name = "numpy", specifier = ">=2.3.0" }, { name = "opencv-python", specifier = ">=4.11.0.86" }, { name = "pillow", specifier = ">=11.2.1" },