# -*- coding: utf-8 -*- """ Full generation script for OPS01.ipynb and OPS01_student.ipynb """ import os import re import json def clean_html(text): if not text: return "" text = re.sub(r'
', '', text) text = re.sub(r'
', '\n\n', text) text = re.sub(r'', '`', text)
text = re.sub(r'', '`', text)
text = re.sub(r'', '> ', text) text = re.sub(r'', '\n', text) return text.strip() def create_student_code(m_num, full_code): """Create student exercise code with TODO blanks and clear hints""" if m_num == 1: # Blank out check and validate target = '''def check(name, value, rule): """값 하나를 규칙(type, enum, min, max, length, pattern)으로 검사해 오류 목록을 돌려준다.""" if not isinstance(value, rule["type"]) or isinstance(value, bool): return [f"{name}: 자료형 오류({type(value).__name__})"] errs = [] if "enum" in rule and value not in rule["enum"]: errs.append(f"{name}: 허용되지 않은 값 '{value}'") if "min" in rule and value < rule["min"]: errs.append(f"{name}: 최소값 {rule['min']} 미만") if "max" in rule and value > rule["max"]: errs.append(f"{name}: 최대값 {rule['max']} 초과") if "length" in rule and len(value) != rule["length"]: errs.append(f"{name}: 길이 {rule['length']} 필요") if "pattern" in rule and not re.match(rule["pattern"], value): errs.append(f"{name}: 형식 불일치 '{value}'") return errs''' replacement = '''def check(name, value, rule): """값 하나를 규칙(type, enum, min, max, length, pattern)으로 검사해 오류 목록을 돌려준다.""" # [TODO : 학생 실습 1-1] 자료형(type) 일치 여부 확인 (bool 예외 처리 포함) if not isinstance(value, rule["type"]) or isinstance(value, bool): return [f"{name}: 자료형 오류({type(value).__name__})"] errs = [] # [TODO : 학생 실습 1-2] rule의 enum, min, max, length, pattern 검증 로직을 작성하세요. # 힌트: # 1) 'enum' in rule and value not in rule['enum'] -> f"{name}: 허용되지 않은 값 '{value}'" # 2) 'min' in rule and value < rule['min'] -> f"{name}: 최소값 {rule['min']} 미만" # 3) 'max' in rule and value > rule['max'] -> f"{name}: 최대값 {rule['max']} 초과" # 4) 'length' in rule and len(value) != rule['length'] -> f"{name}: 길이 {rule['length']} 필요" # 5) 'pattern' in rule and not re.match(rule['pattern'], value) -> f"{name}: 형식 불일치 '{value}'" # --- 코드 작성 영역 --- if "enum" in rule and value not in rule["enum"]: errs.append(f"{name}: 허용되지 않은 값 '{value}'") if "min" in rule and value < rule["min"]: errs.append(f"{name}: 최소값 {rule['min']} 미만") if "max" in rule and value > rule["max"]: errs.append(f"{name}: 최대값 {rule['max']} 초과") if "length" in rule and len(value) != rule["length"]: errs.append(f"{name}: 길이 {rule['length']} 필요") if "pattern" in rule and not re.match(rule["pattern"], value): errs.append(f"{name}: 형식 불일치 '{value}'") # ---------------------- return errs''' # Let's replace only the inside logic for student student_replacement = '''def check(name, value, rule): """값 하나를 규칙(type, enum, min, max, length, pattern)으로 검사해 오류 목록을 돌려준다.""" # [TODO : 학생 실습 1-1] 자료형(type) 일치 여부 확인 (bool 예외 처리 포함) if not isinstance(value, rule["type"]) or isinstance(value, bool): return [f"{name}: 자료형 오류({type(value).__name__})"] errs = [] # [TODO : 학생 실습 1-2] rule의 enum, min, max, length, pattern 규칙을 검사하여 errs 리스트에 오류 메시지를 추가하세요. # 힌트: # 1) 'enum': value가 rule['enum'] 리스트에 없는 경우 -> f"{name}: 허용되지 않은 값 '{value}'" # 2) 'min': value가 rule['min'] 미만인 경우 -> f"{name}: 최소값 {rule['min']} 미만" # 3) 'max': value가 rule['max'] 초과인 경우 -> f"{name}: 최대값 {rule['max']} 초과" # 4) 'length': len(value)가 rule['length']와 다른 경우 -> f"{name}: 길이 {rule['length']} 필요" # 5) 'pattern': re.match(rule['pattern'], value)가 False인 경우 -> f"{name}: 형식 불일치 '{value}'" # <<< 여기에 코드를 작성하세요 >>> pass # 작성 후 pass를 제거하고 완성하세요. return errs''' if target in full_code: return full_code.replace(target, student_replacement) return full_code elif m_num == 2: # Preprocessing pipeline target = '''def preprocess(raw, weather): """전처리 파이프라인: 기상에 따라 노이즈 제거와 대비 향상을 적용한다.""" img = raw.astype(float) if weather == "fog": # 안개: 저주파 배경을 빼서 대비를 높이고 가우시안 필터로 매끄럽게 만든다. bg = ndimage.gaussian_filter(img, sigma=8.0) img = img - bg img = np.clip(img * 2.5 + 128, 0, 255) img = ndimage.gaussian_filter(img, sigma=0.8) elif weather == "night": # 야간: 센서 노이즈가 심하므로 미디언 필터로 점 잡음을 없앤다. img = ndimage.median_filter(img, size=3) img = np.clip((img - img.min()) / (img.max() - img.min() + 1e-6) * 255, 0, 255) elif weather == "rain": # 비: 줄무늬 잡음을 가우시안 필터로 줄인다. img = ndimage.gaussian_filter(img, sigma=1.2) img = np.clip(img * 1.5, 0, 255) elif weather == "smoke": # 연막: 산란 잡음을 미디언 필터와 국소 대비 향상으로 완화한다. img = ndimage.median_filter(img, size=3) bg = ndimage.gaussian_filter(img, sigma=4.0) img = np.clip((img - bg) * 2.0 + 128, 0, 255) else: # 맑음: 최소한의 스케일링만 적용한다. img = np.clip(img, 0, 255) return img.astype(np.uint8)''' student_replacement = '''def preprocess(raw, weather): """전처리 파이프라인: 기상에 따라 노이즈 제거와 대비 향상을 적용한다.""" img = raw.astype(float) # [TODO : 학생 실습 2] 기상 조건(fog, night, rain, smoke, clear)에 따른 적응형 영상 전처리 로직을 완성하세요. # 힌트: # - "fog" (안개): 저주파 배경(ndimage.gaussian_filter sigma=8.0) 감산 후 대비 증폭(np.clip(img*2.5 + 128, 0, 255)) # - "night" (야간): 점잡음 제거를 위해 ndimage.median_filter(img, size=3) 적용 및 Min-Max 정규화 # - "rain" (강우): 가우시안 필터(sigma=1.2) 및 스케일링 # - "smoke" (연막): 미디언 필터(size=3) 후 국소 배경 감산(sigma=4.0) # - "clear" (맑음): np.clip(img, 0, 255) # <<< 여기에 코드를 작성하세요 >>> pass # 작성 후 pass를 제거하고 완성하세요. return img.astype(np.uint8)''' if target in full_code: return full_code.replace(target, student_replacement) return full_code elif m_num == 3: # Target iou and detect target = '''def iou(box_a, box_b): """두 바운딩 박스(x, y, w, h)의 IoU(교집합/합집합)를 계산한다.""" ax1, ay1, ax2, ay2 = box_a[0], box_a[1], box_a[0] + box_a[2], box_a[1] + box_a[3] bx1, by1, bx2, by2 = box_b[0], box_b[1], box_b[0] + box_b[2], box_b[1] + box_b[3] ix1, iy1 = max(ax1, bx1), max(ay1, by1) ix2, iy2 = min(ax2, bx2), min(ay2, by2) iw, ih = max(0, ix2 - ix1), max(0, iy2 - iy1) inter = iw * ih union = box_a[2] * box_a[3] + box_b[2] * box_b[3] - inter return inter / union if union > 0 else 0.0''' student_replacement = '''def iou(box_a, box_b): """두 바운딩 박스(x, y, w, h)의 IoU(교집합/합집합)를 계산한다.""" # [TODO : 학생 실습 3] 바운딩 박스 간의 IoU(Intersection over Union) 계산 수식을 완성하세요. # 힌트: # 1) box_a = (x, y, w, h) -> (ax1, ay1, ax2, ay2) # 2) 교집합 영역 (ix1, iy1), (ix2, iy2) 구하기: ix1=max(ax1, bx1), ix2=min(ax2, bx2)... # 3) iw = max(0, ix2 - ix1), ih = max(0, iy2 - iy1), inter = iw * ih # 4) union = area_a + area_b - inter # 5) inter / union (union > 0일 때) 반환 # <<< 여기에 코드를 작성하세요 >>> return 0.0 # 작성 후 완성하세요.''' if target in full_code: return full_code.replace(target, student_replacement) return full_code elif m_num == 4: # Pattern classification target = '''def classify_motion(track): """항적의 위치·속도 변화를 분석해 비행 패턴을 판정한다.""" pts = track["points"] if len(pts) < 3: return "too_short" dx = np.diff([p[0] for p in pts]) dy = np.diff([p[1] for p in pts]) angles = np.arctan2(dy, dx) angle_changes = np.abs(np.diff(angles)) # 각도 변화가 180도를 넘어가면 반대 방향으로 회전한 것이므로 보정 angle_changes = np.where(angle_changes > np.pi, 2 * np.pi - angle_changes, angle_changes) mean_turn = float(np.mean(angle_changes)) speed = float(track["speed"]) # 1) 급격한 방향 전환(지그재그) -> 조류 오경보 if mean_turn > 0.8: return "bird_erratic" # 2) 고속 + 직선 비행 -> 순항미사일 if speed >= 25.0 and mean_turn < 0.2: return "cruise_missile_straight" # 3) 완만한 회전 비행 -> 정찰 드론 if 0.2 <= mean_turn <= 0.8: return "recon_drone_loitering" # 4) 중간 속도 직선 비행 -> 자폭 드론 if speed >= 10.0 and mean_turn < 0.2: return "attack_drone_straight" return "unknown"''' student_replacement = '''def classify_motion(track): """항적의 위치·속도 변화를 분석해 비행 패턴을 판정한다.""" pts = track["points"] if len(pts) < 3: return "too_short" dx = np.diff([p[0] for p in pts]) dy = np.diff([p[1] for p in pts]) angles = np.arctan2(dy, dx) angle_changes = np.abs(np.diff(angles)) angle_changes = np.where(angle_changes > np.pi, 2 * np.pi - angle_changes, angle_changes) mean_turn = float(np.mean(angle_changes)) speed = float(track["speed"]) # [TODO : 학생 실습 4] 비행 궤적의 회전각(mean_turn)과 비행속도(speed)를 바탕으로 비행 패턴을 분류하세요. # 1) 조류 (bird_erratic): mean_turn > 0.8 (급격한 지그재그 회전) # 2) 순항미사일 (cruise_missile_straight): speed >= 25.0 및 mean_turn < 0.2 (초고속 직선) # 3) 정찰드론 (recon_drone_loitering): 0.2 <= mean_turn <= 0.8 (선회 비행) # 4) 자폭드론 (attack_drone_straight): speed >= 10.0 및 mean_turn < 0.2 (중속 직선) # <<< 여기에 코드를 작성하세요 >>> return "unknown" # 작성 후 완성하세요.''' if target in full_code: return full_code.replace(target, student_replacement) return full_code elif m_num == 5: # Confidence score features and grades target = '''def extract_features(raw, bboxes): """각 바운딩 박스에서 크기, 밝기 통계, 주변 대비를 추출한다.""" feats = [] for b in bboxes: x, y, w, h = b crop = raw[y:y+h, x:x+w] if crop.size == 0: feats.append([0, 0, 0, 0, 0]) continue area = float(w * h) mean_val = float(crop.mean()) max_val = float(crop.max()) std_val = float(crop.std()) # 주변 영역(1화소 확장)과의 대비 y0, y1 = max(0, y - 2), min(raw.shape[0], y + h + 2) x0, x1 = max(0, x - 2), min(raw.shape[1], x + w + 2) surr = raw[y0:y1, x0:x1] contrast = float(mean_val - surr.mean()) feats.append([area, mean_val, max_val, std_val, contrast]) return np.array(feats)''' student_replacement = '''def extract_features(raw, bboxes): """각 바운딩 박스에서 크기, 밝기 통계, 주변 대비를 추출한다.""" feats = [] # [TODO : 학생 실습 5] 바운딩 박스 패치로부터 특징 벡터 [면적, 평균밝기, 최대밝기, 표준편차, 주변대비]를 추출하세요. # 힌트: # - crop = raw[y:y+h, x:x+w] # - area = w * h, mean_val = crop.mean(), max_val = crop.max(), std_val = crop.std() # - contrast = mean_val - surround_mean # <<< 여기에 코드를 작성하세요 >>> for b in bboxes: feats.append([0.0, 0.0, 0.0, 0.0, 0.0]) # 작성 후 실제 추출 로직으로 대체 return np.array(feats)''' if target in full_code: return full_code.replace(target, student_replacement) return full_code elif m_num == 6: # Extrapolate impact target = '''# 예상 탄착점(3초 뒤 위치): 속도가 유지된다고 가정하고 외삽 t_impact = 3.0 tracks["impact_x"] = tracks["x"] + tracks["vx"] * t_impact tracks["impact_y"] = tracks["y"] + tracks["vy"] * t_impact # 방어구역 침범 여부: 반경 2km 이내로 들어오는지 계산 def check_breach(row): dist_now = np.sqrt(row["x"] ** 2 + row["y"] ** 2) dist_future = np.sqrt(row["impact_x"] ** 2 + row["impact_y"] ** 2) return "침범 경보" if (dist_now < 2.0 or dist_future < 2.0) else "정상 비행"''' student_replacement = '''# [TODO : 학생 실습 6-1] 3초 뒤 예상 탄착점(impact_x, impact_y)을 선형 외삽 공식으로 계산하세요. # 힌트: 미래 위치 = 현재 위치 + 속도 * 시간(t_impact) t_impact = 3.0 # <<< 코드 작성 >>> tracks["impact_x"] = tracks["x"] + tracks["vx"] * t_impact tracks["impact_y"] = tracks["y"] + tracks["vy"] * t_impact # [TODO : 학생 실습 6-2] 현재 위치 또는 예상 탄착점이 방어구역(반경 2.0km) 이내인지 검사하는 함수를 완성하세요. def check_breach(row): # <<< 여기에 코드를 작성하세요 >>> dist_now = np.sqrt(row["x"] ** 2 + row["y"] ** 2) dist_future = np.sqrt(row["impact_x"] ** 2 + row["impact_y"] ** 2) return "침범 경보" if (dist_now < 2.0 or dist_future < 2.0) else "정상 비행"''' if target in full_code: return full_code.replace(target, student_replacement) return full_code elif m_num == 7: # HITL workflow target = '''def decide(item, operator_id): """운용관의 판정에 따라 서명을 만들고 감사 로그 레코드를 돌려준다.""" conf = item["confidence"] grade = item["grade"] # 고신뢰도 위협은 즉시 승인 권고, 저신뢰도는 반려 권고 if grade == "High": action = "APPROVE" reason = f"고신뢰도({conf:.2f}) 위협, 방어구역 진입 확인" elif grade == "Medium": action = "HOLD" reason = f"중간신뢰도({conf:.2f}), 추가 관측(3프레임) 필요" else: action = "REJECT" reason = f"저신뢰도({conf:.2f}), 오경보(조류/반사체)로 판정" sig = make_signature(item["track_id"], operator_id, action) return { "track_id": item["track_id"], "class": item["class"], "confidence": conf, "grade": grade, "operator": operator_id, "action": action, "action_desc": ACTIONS[action], "reason": reason, "signature": sig, "timestamp": time.time(), }''' student_replacement = '''def decide(item, operator_id): """운용관의 판정에 따라 서명을 만들고 감사 로그 레코드를 돌려준다.""" conf = item["confidence"] grade = item["grade"] # [TODO : 학생 실습 7] 위협 등급(grade)에 따른 HITL 의사결정 규칙을 완성하세요. # 1) High -> action: 'APPROVE', reason: '고신뢰도({conf:.2f}) 위협, 방어구역 진입 확인' # 2) Medium -> action: 'HOLD', reason: '중간신뢰도({conf:.2f}), 추가 관측(3프레임) 필요' # 3) Low (기타) -> action: 'REJECT', reason: '저신뢰도({conf:.2f}), 오경보(조류/반사체)로 판정' # <<< 여기에 코드를 작성하세요 >>> action = "HOLD" reason = "검토 중" sig = make_signature(item["track_id"], operator_id, action) return { "track_id": item["track_id"], "class": item["class"], "confidence": conf, "grade": grade, "operator": operator_id, "action": action, "action_desc": ACTIONS.get(action, ""), "reason": reason, "signature": sig, "timestamp": time.time(), }''' if target in full_code: return full_code.replace(target, student_replacement) return full_code elif m_num == 8: # Stress test failsafe target = '''# 3) Fail-safe 복원력 시험: 과부하(120건) 시 우선순위(중요도) 기반 처리 # 지휘관 지침: 자폭드론/순항미사일 우선, 조류/반사체 후순위, 고신뢰도 우선 overload_n = 120 rng_test = np.random.default_rng(99) targets = [{"id": f"T{i:03d}", "class": rng_test.choice(["recon_drone", "attack_drone", "cruise_missile", "bird", "clutter"]), "conf": float(rng_test.uniform(0.3, 0.99)), "dist_km": float(rng_test.uniform(0.5, 5.0))} for i in range(overload_n)] # 우선순위 점수: 위협 클래스 가중치 + 신뢰도 + 거리 역수 weight = {"cruise_missile": 3.0, "attack_drone": 2.5, "recon_drone": 1.5, "bird": 0.1, "clutter": 0.0} for t in targets: t["priority"] = weight[t["class"]] * 0.5 + t["conf"] * 0.3 + (5.0 - t["dist_km"]) / 5.0 * 0.2 # 처리 용량이 30건으로 제한된 경우 최우선 30건 선별 capacity = 30 selected = sorted(targets, key=lambda x: x["priority"], reverse=True)[:capacity]''' student_replacement = '''# [TODO : 학생 실습 8] 과부하(120건 동시 유입) 상황에서 우선순위 가중치(위협도+신뢰도+거리)를 계산하고 상위 30건을 선별하세요. # 힌트: # 1) 위협 클래스 가중치: cruise_missile(3.0), attack_drone(2.5), recon_drone(1.5), bird(0.1), clutter(0.0) # 2) 우선순위 수식 = weight[class] * 0.5 + conf * 0.3 + (5.0 - dist_km)/5.0 * 0.2 # 3) sorted(targets, key=lambda x: x['priority'], reverse=True)[:capacity] # <<< 여기에 코드를 작성하세요 >>> overload_n = 120 rng_test = np.random.default_rng(99) targets = [{"id": f"T{i:03d}", "class": rng_test.choice(["recon_drone", "attack_drone", "cruise_missile", "bird", "clutter"]), "conf": float(rng_test.uniform(0.3, 0.99)), "dist_km": float(rng_test.uniform(0.5, 5.0))} for i in range(overload_n)] capacity = 30 selected = [] # 작성 후 완성하세요.''' if target in full_code: return full_code.replace(target, student_replacement) return full_code elif m_num == 9: # AAR Threshold Retuning target = '''# 3) 파라미터 재보정: k를 2.0에서 4.5까지 0.5 간격으로 바꾸며 최적값 탐색 # 지휘관 기준: 실제 위협(드론) 탐지율 90% 이상 유지하면서, 오경보 건수를 최소화 k_candidates = np.arange(2.0, 5.0, 0.5) retune_rows = [] for k in k_candidates: rec = run_pipeline(k=k, p_high=0.75) retune_rows.append({"임계값(k)": k, "실제위협(건)": rec["tp"] + rec["fn"], "탐지성공(TP)": rec["tp"], "미탐(FN)": rec["fn"], "오경보(FP)": rec["fp"], "위협 탐지율(%)": round(rec["tp"] / (rec["tp"] + rec["fn"]) * 100, 1), "오경보율(%)": round(rec["fp"] / (rec["fp"] + rec["tn"]) * 100, 1), "지연(ms)": round(rec["latency_ms"], 2)})''' student_replacement = '''# [TODO : 학생 실습 9] 임계값(k) 후보군(2.0 ~ 4.5)에 대해 파이프라인 성능(TP, FN, FP, 탐지율, 오경보율)을 평가하고 최적 파라미터를 도출하세요. # 힌트: # - 탐지율(Recall) = TP / (TP + FN) * 100 # - 오경보율(FAR) = FP / (FP + TN) * 100 k_candidates = np.arange(2.0, 5.0, 0.5) retune_rows = [] # <<< 여기에 코드를 작성하세요 >>> for k in k_candidates: pass # 작성 후 완성하세요.''' if target in full_code: return full_code.replace(target, student_replacement) return full_code elif m_num == 10: # Mission 10 integration target = '''# 파이프라인 단계별 함수 def step_preprocess(raw, weather): img = raw.astype(float) if weather in ("fog", "smoke"): bg = ndimage.gaussian_filter(img, sigma=6.0) img = np.clip((img - bg) * 2.0 + 128, 0, 255) elif weather == "night": img = ndimage.median_filter(img, size=3) return img.astype(np.uint8)''' student_replacement = '''# [TODO : 학생 실습 10] 엔드투엔드 파이프라인의 전처리 및 통합 추론 흐름을 확인하고 실행하세요. def step_preprocess(raw, weather): img = raw.astype(float) if weather in ("fog", "smoke"): bg = ndimage.gaussian_filter(img, sigma=6.0) img = np.clip((img - bg) * 2.0 + 128, 0, 255) elif weather == "night": img = ndimage.median_filter(img, size=3) return img.astype(np.uint8)''' if target in full_code: return full_code.replace(target, student_replacement) return full_code return full_code