"""카메라 모드 장 ('카메라 센서 종류 — RGB · 깊이 · …') 바로 뒤에 라이다 · 레이더 센서 데이터 장을 넣는다. 같은 틀 · 같은 배치 (왼쪽 2×2 그림 + 오른쪽 설명 상자 4개) — 상자는 카메라 모드 장의 것을 복사해 글만 바꾼다. 그림: figures/out/sensors_data.jpg (figures/sensors/ 의 sdump · run.py · viz.py). 실행: python docs/presentation/add_sensor_data_slide.py [입력.pptx] [출력.pptx] (기본: vkgs_sim_merged.pptx 를 제자리에서) """ import copy import sys from pathlib import Path from PIL import Image from pptx import Presentation from pptx.oxml.ns import qn HERE = Path(__file__).resolve().parent SRC = Path(sys.argv[1]) if len(sys.argv) > 1 else HERE / "vkgs_sim_merged.pptx" OUT = Path(sys.argv[2]) if len(sys.argv) > 2 else SRC FIG = HERE / "figures/out/sensors_data.jpg" ANCHOR = "카메라 센서 종류" HEADING = "라이다 · 레이더 센서 데이터 — 같은 순간의 4가지 보기" BULLET = "scene-0402 기록 주행의 한 순간 (11.9 m/s) — 라이다 한 바퀴 3.4만 점 · 레이더 5대 검출 605개, 다른 차량은 nuScenes 박스" BOXES = [("라이다 → 카메라 투영", "같은 순간의 실제 nuScenes 정면 사진에 시뮬 라이다 점을 투영 — 노면 링 · 길가 나무 · 앞 차량 자리가 사진과 겹침"), ("거리 영상 · BEV", "HDL-32E 근사: 32빔 (+10.7° ~ −30.7°) × 0.33° 방위, 70 m, 20 Hz — 복셀 월드 (0.2 m) 에 광선을 쏨"), ("레이더 5대", "ARS 408 근사: 시야 120° · 120 m · 13 Hz · 대당 최대 125점, 노면 반사 제거 — 정지물 도플러 vr = −v·cosθ"), ("출력 형식", "O 키 · closed-loop (--serve) 관측에 함께 저장 — LIDAR_TOP.bin (nuScenes .pcd.bin) · RADAR_*.txt (vr · 보정 속도 · RCS)")] NOTE = ("그림 1: 실제 사진 위 투영 — 시뮬 라이다 기하가 실제 장면과 맞는지 보는 그림. 그림 2: 라이다 원시 데이터 형식 (빔 × 방위 거리 영상). " "그림 3: 위에서 본 한 바퀴, 색은 지면 위 높이. 그림 4: 레이더 5대 시야와 검출, 색은 도플러 — 자차가 움직이므로 정지물도 앞은 다가옴, 뒤는 멀어짐. " "다른 차량은 nuScenes 어노테이션 박스로 넣었고 정지로 본다 (속도 · 도플러는 교통 참가자 작업 뒤). 만드는 법: figures/sensors/README.txt.") def heading(s): """소제목 막대 (그룹 도형) 의 글.""" return "".join(c.text_frame.text for sh in s.shapes if sh.shape_type == 6 for c in sh.shapes if c.has_text_frame) prs = Presentation(SRC) slides = list(prs.slides) if any(HEADING in heading(s) for s in slides): sys.exit(f"이미 들어 있다: {SRC}") src = next(s for s in slides if ANCHOR in heading(s)) sl = prs.slides.add_slide(src.slide_layout) tree = sl.shapes._spTree for sh in list(sl.shapes): tree.remove(sh._element) pic_box = None for sh in src.shapes: if sh.shape_type == 13: # 그림 자리만 기억하고 그림은 새로 넣는다 pic_box = (sh.left, sh.top, sh.width, sh.height) continue el = copy.deepcopy(sh._element) for node in el.iter(): for attr in (qn("r:embed"), qn("r:link"), qn("r:id")): rid = node.get(attr) if rid: node.set(attr, sl.part.relate_to(src.part.related_part(rid), src.part.rels[rid].reltype)) tree.append(el) def set_text(p, text): rs = p.runs rs[0].text = text for r in rs[1:]: r._r.getparent().remove(r._r) boxes = [sh for sh in sl.shapes if sh.name.startswith("Rounded Rectangle")] assert len(boxes) == len(BOXES), len(boxes) for sh, (t, d) in zip(sorted(boxes, key=lambda s: s.top), BOXES): ps = sh.text_frame.paragraphs set_text(ps[0], t) set_text(ps[1], d) for sh in sl.shapes: if sh.shape_type == 6: for c in sh.shapes: if c.has_text_frame and c.text_frame.text.strip(): set_text(c.text_frame.paragraphs[0], HEADING) elif sh.name == "TextBox 9": tf = sh.text_frame for p in tf.paragraphs[1:]: p._p.getparent().remove(p._p) set_text(tf.paragraphs[0], BULLET) x, y, w, h = pic_box im = Image.open(FIG) s_ = min(w / im.width, h / im.height) sl.shapes.add_picture(str(FIG), x + int((w - im.width * s_) / 2), y, int(im.width * s_), int(im.height * s_)) sl.notes_slide.notes_text_frame.text = NOTE lst = prs.slides._sldIdLst new = list(lst)[-1] lst.remove(new) list(lst)[slides.index(src)].addnext(new) prs.save(OUT) print("saved", OUT, len(prs.slides), "slides — 새 장", slides.index(src) + 2, "쪽")