add LayoutGMN apply - gds prser and graph constructor

This commit is contained in:
Jiao77
2025-08-29 22:35:51 +08:00
parent 7d648aaa7c
commit b79d54dccb
4 changed files with 217 additions and 29 deletions

View File

@@ -39,30 +39,76 @@ class GDSParser:
return patches
def extract_geometries_from_patch(self, patch_bbox: Tuple[float, float, float, float]) -> List[Dict]:
"""从给定的区块中提取所有几何对象。
"""从给定的区块中提取所有几何对象,并记录全局与区块内(裁剪后)的信息
说明:
- 为了处理跨越多个区块的多边形,本函数会计算多边形与区块边界框的布尔相交,
得到位于该区块内的裁剪多边形,并同时记录原始(全局)多边形信息。
Args:
patch_bbox: 区块的边界框 (x_min, y_min, x_max, y_max)。
Returns:
一个字典列表,每个字典代表一个几何对象及其属性(多边形、层、边界框)。
一个字典列表,每个字典代表一个几何对象及其属性
- global_points: 原始多边形顶点Nx2 ndarray
- global_bbox: 原始多边形边界框
- global_area: 原始多边形面积
- clipped_points: 与区块相交后的裁剪多边形顶点Mx2 ndarray可能为空
- clipped_area: 裁剪后面积(可能为 0
- area_ratio: 裁剪面积 / 原始面积(用于衡量跨区块比例)
- is_partial: 是否为跨区块(裁剪面积 < 原始面积)
- layer: 层映射到的整数索引
- patch_bbox: 当前区块边界框
"""
x_min, y_min, x_max, y_max = patch_bbox
# 获取单元内的所有多边形
rect = gdstk.rectangle(x_min, y_min, x_max, y_max)
polygons = self.top_cell.get_polygons(by_spec=True)
geometries = []
# 遍历所有多边形
geometries: List[Dict] = []
for (layer, datatype), poly_list in polygons.items():
layer_str = f"{layer}/{datatype}"
# 只处理在 layer_mapping 中定义的层
if layer_str in self.layer_mapping:
for poly in poly_list:
# 简单的边界框相交检查
p_xmin, p_ymin, p_xmax, p_ymax = poly.bb()
if not (p_xmax < x_min or p_xmin > x_max or p_ymax < y_min or p_ymin > y_max):
geometries.append({
"polygon": poly,
"layer": self.layer_mapping[layer_str],
"bbox": (p_xmin, p_ymin, p_xmax, p_ymax)
})
if layer_str not in self.layer_mapping:
continue
layer_idx = self.layer_mapping[layer_str]
for poly in poly_list:
p_xmin, p_ymin, p_xmax, p_ymax = poly.bb()
# 快速边界框测试(若无相交则跳过)
if p_xmax < x_min or p_xmin > x_max or p_ymax < y_min or p_ymin > y_max:
continue
# 全局多边形点与面积
global_points = np.array(poly.points, dtype=float)
global_area = abs(gdstk.Polygon(global_points).area())
# 与区块矩形做相交,可能返回多个多边形
clipped = gdstk.boolean([poly], [rect], "and", precision=1e-3, layer=layer, datatype=datatype)
clipped_points_list: List[np.ndarray] = []
clipped_area = 0.0
if clipped:
for cpoly in clipped:
pts = np.array(cpoly.points, dtype=float)
if pts.size == 0:
continue
area = abs(gdstk.Polygon(pts).area())
if area <= 0:
continue
clipped_points_list.append(pts)
clipped_area += area
area_ratio = (clipped_area / global_area) if global_area > 0 else 0.0
is_partial = area_ratio < 0.999 # 允许微小数值误差
geometries.append({
"global_points": global_points,
"global_bbox": (p_xmin, p_ymin, p_xmax, p_ymax),
"global_area": float(global_area),
"clipped_points_list": clipped_points_list, # 可能包含多个裁剪片段
"clipped_area": float(clipped_area),
"area_ratio": float(area_ratio),
"is_partial": bool(is_partial),
"layer": layer_idx,
"patch_bbox": (x_min, y_min, x_max, y_max),
})
return geometries

View File

@@ -1,4 +1,4 @@
from typing import List, Dict
from typing import List, Dict, Tuple
import torch
from torch_geometric.data import Data
from scipy.spatial import cKDTree
@@ -32,21 +32,64 @@ class GraphConstructor:
if not geometries:
return None
node_features = []
node_positions = []
# 提取每个几何图形的特征
for geo in geometries:
x_min, y_min, x_max, y_max = geo["bbox"]
width = x_max - x_min
height = y_max - y_min
area = width * height
centroid_x = x_min + width / 2
centroid_y = y_min + height / 2
node_features: List[List[float]] = []
node_positions: List[List[float]] = []
node_layers: List[int] = []
node_meta: List[Dict] = []
# 提取每个几何图形的特征(优先使用裁剪后片段的质心;若无裁剪片段,则使用全局质心)
for geo in geometries:
layer_idx: int = int(geo["layer"]) if "layer" in geo else 0
global_bbox = geo.get("global_bbox", None)
global_points = geo.get("global_points", None)
clipped_points_list = geo.get("clipped_points_list", []) or []
clipped_area = float(geo.get("clipped_area", 0.0))
global_area = float(geo.get("global_area", 0.0))
area_ratio = float(geo.get("area_ratio", 0.0))
is_partial = bool(geo.get("is_partial", False))
# 选择用于节点位置与宽高的几何:若存在裁剪片段,聚合其外接框,否则用全局框
if clipped_points_list:
# 合并所有裁剪片段点,计算整体外接框与质心
all_pts = np.vstack(clipped_points_list)
elif global_points is not None:
all_pts = np.array(global_points, dtype=float)
else:
# 回退到 bbox 信息(兼容旧格式)
x_min, y_min, x_max, y_max = geo["bbox"]
all_pts = np.array([[x_min, y_min], [x_max, y_max]], dtype=float)
x_min, y_min = np.min(all_pts, axis=0)
x_max, y_max = np.max(all_pts, axis=0)
width = float(x_max - x_min)
height = float(y_max - y_min)
centroid_x = float(x_min + width / 2.0)
centroid_y = float(y_min + height / 2.0)
# 节点特征:质心、宽、高、裁剪面积、全局面积占比、层索引(数值化)
features = [
centroid_x,
centroid_y,
width,
height,
clipped_area,
(clipped_area / global_area) if global_area > 0 else 0.0,
float(layer_idx),
1.0 if is_partial else 0.0,
]
# 特征包括:中心点坐标、宽度、高度、面积
features = [centroid_x, centroid_y, width, height, area]
node_features.append(features)
node_positions.append([centroid_x, centroid_y])
node_layers.append(layer_idx)
# 将原始与裁剪的必要元信息保存在 Data 中(以便后续可视化与调试)
node_meta.append({
"layer": layer_idx,
"global_bbox": tuple(global_bbox) if global_bbox is not None else None,
"global_area": global_area,
"clipped_area": clipped_area,
"area_ratio": area_ratio,
"is_partial": is_partial,
})
# 将特征和位置转换为 PyTorch 张量
x = torch.tensor(node_features, dtype=torch.float)
@@ -57,6 +100,9 @@ class GraphConstructor:
# 创建图数据对象
data = Data(x=x, edge_index=edge_index, pos=pos, y=torch.tensor([label], dtype=torch.float))
# 附加层索引与元信息(元信息以对象列表形式保存,供上层使用;不会参与张量运算)
data.layer = torch.tensor(node_layers, dtype=torch.long)
data.node_meta = node_meta
return data
def _create_edges(self, node_positions: torch.Tensor) -> torch.Tensor: