Acadelab commited on
Commit
08a1f20
·
verified ·
1 Parent(s): 1b153fb

Add pipeline orchestrator

Browse files
Files changed (1) hide show
  1. run_pipeline.py +212 -0
run_pipeline.py ADDED
@@ -0,0 +1,212 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Red Team Pipeline Orchestrator - See README.md for full documentation.
4
+ Workflow: Input -> Protection (Paper X) -> Deepfake Generation -> Evaluation -> Report
5
+ """
6
+ import os, sys, json, argparse, time, logging, numpy as np
7
+ from pathlib import Path
8
+ from typing import List, Dict
9
+ from datetime import datetime
10
+ from dataclasses import dataclass, asdict
11
+ import torch
12
+ from PIL import Image
13
+
14
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s [%(levelname)s] %(message)s')
15
+ logger = logging.getLogger("RedTeamPipeline")
16
+
17
+ @dataclass
18
+ class PipelineConfig:
19
+ input_dir: str = "./data/test_images"
20
+ output_dir: str = "./results"
21
+ methods: List[str] = None
22
+ attacks: List[str] = None
23
+ fs_total_iter: int = 30
24
+ fs_noise_clamp: int = 12
25
+ fs_step_size: float = 1.0
26
+ fs_resize: int = 512
27
+ dg_iters: int = 800
28
+ dg_eps: float = 16.0/255.0
29
+ dg_step_size: float = 1.0/255.0
30
+ dg_batch_size: int = 1
31
+ dg_inference_steps: int = 4
32
+ vgm_steps: int = 1000
33
+ vgm_eps: float = 4.0/255.0
34
+ vgm_directed: bool = False
35
+ preprocessing_tests: List[str] = None
36
+ device: str = "cuda"
37
+ def __post_init__(self):
38
+ if self.methods is None: self.methods = ["faceshield", "diffusionguard", "vgmshield"]
39
+ if self.attacks is None: self.attacks = ["inpainting", "ip_adapter"]
40
+ if self.preprocessing_tests is None:
41
+ self.preprocessing_tests = ["jpeg_q75","jpeg_q50","jpeg_q25","blur_s1","blur_s2","resize_50","resize_75","noise_001","noise_005"]
42
+
43
+ def run_faceshield(image_path, output_path, config):
44
+ logger.info(f" [FaceShield] Protecting {os.path.basename(image_path)}")
45
+ try:
46
+ sys.path.insert(0, "/app/faceshield_src")
47
+ from modules.faceshield.protect import protect_image
48
+ return protect_image(image_path=image_path, output_path=output_path, device=config.device,
49
+ total_iter=config.fs_total_iter, noise_clamp=config.fs_noise_clamp, step_size=config.fs_step_size, resize_shape=config.fs_resize)
50
+ except Exception as e:
51
+ logger.error(f" [FaceShield] Failed: {e}")
52
+ return {"method": "faceshield", "status": "error", "error": str(e)}
53
+
54
+ def run_diffusionguard(image_path, mask_path, output_path, config):
55
+ logger.info(f" [DiffusionGuard] Protecting {os.path.basename(image_path)}")
56
+ try:
57
+ sys.path.insert(0, "/app/diffusionguard_src")
58
+ from modules.diffusionguard.protect import protect_image
59
+ return protect_image(image_path=image_path, mask_path=mask_path, output_path=output_path, device=config.device,
60
+ iters=config.dg_iters, eps=config.dg_eps, step_size=config.dg_step_size, batch_size=config.dg_batch_size, num_inference_steps=config.dg_inference_steps)
61
+ except Exception as e:
62
+ logger.error(f" [DiffusionGuard] Failed: {e}")
63
+ return {"method": "diffusionguard", "status": "error", "error": str(e)}
64
+
65
+ def run_vgmshield(image_path, output_path, config):
66
+ logger.info(f" [VGMShield] Protecting {os.path.basename(image_path)}")
67
+ try:
68
+ sys.path.insert(0, "/app/vgmshield_src")
69
+ from modules.vgmshield.protect import protect_image
70
+ return protect_image(input_path=image_path, output_path=output_path, directed=config.vgm_directed, eps=config.vgm_eps, steps=config.vgm_steps, device=config.device)
71
+ except Exception as e:
72
+ logger.error(f" [VGMShield] Failed: {e}")
73
+ return {"method": "vgmshield", "status": "error", "error": str(e)}
74
+
75
+ def run_inpainting_attack(image_path, mask_path, output_path, prompt="a photograph of a person", device="cuda"):
76
+ logger.info(f" [Attack:Inpainting] On {os.path.basename(image_path)}")
77
+ try:
78
+ from diffusers import StableDiffusionInpaintPipeline
79
+ pipe = StableDiffusionInpaintPipeline.from_pretrained("runwayml/stable-diffusion-inpainting", torch_dtype=torch.float16).to(device)
80
+ src = Image.open(image_path).convert("RGB").resize((512,512))
81
+ mask = Image.open(mask_path).convert("RGB").resize((512,512))
82
+ result = pipe(prompt=prompt, image=src, mask_image=mask, num_inference_steps=50).images[0]
83
+ os.makedirs(os.path.dirname(output_path), exist_ok=True)
84
+ result.save(output_path)
85
+ del pipe; torch.cuda.empty_cache()
86
+ return {"attack": "inpainting", "status": "success", "output": output_path}
87
+ except Exception as e:
88
+ logger.error(f" [Attack:Inpainting] Failed: {e}")
89
+ return {"attack": "inpainting", "status": "error", "error": str(e)}
90
+
91
+ def run_ip_adapter_attack(image_path, output_path, prompt="a photo of a person", device="cuda"):
92
+ logger.info(f" [Attack:IP-Adapter] On {os.path.basename(image_path)}")
93
+ try:
94
+ from diffusers import StableDiffusionPipeline
95
+ pipe = StableDiffusionPipeline.from_pretrained("runwayml/stable-diffusion-v1-5", torch_dtype=torch.float16).to(device)
96
+ pipe.load_ip_adapter("h94/IP-Adapter", subfolder="models", weight_name="ip-adapter_sd15.bin")
97
+ pipe.set_ip_adapter_scale(0.8)
98
+ src = Image.open(image_path).convert("RGB").resize((512,512))
99
+ result = pipe(prompt=prompt, ip_adapter_image=src, num_inference_steps=50).images[0]
100
+ os.makedirs(os.path.dirname(output_path), exist_ok=True)
101
+ result.save(output_path)
102
+ del pipe; torch.cuda.empty_cache()
103
+ return {"attack": "ip_adapter", "status": "success", "output": output_path}
104
+ except Exception as e:
105
+ logger.error(f" [Attack:IP-Adapter] Failed: {e}")
106
+ return {"attack": "ip_adapter", "status": "error", "error": str(e)}
107
+
108
+ def apply_preprocessing(image_path, attack_name, output_path):
109
+ from modules.evaluation.evaluate import PURIFICATION_ATTACKS
110
+ img = Image.open(image_path).convert("RGB")
111
+ processed = PURIFICATION_ATTACKS[attack_name](img)
112
+ os.makedirs(os.path.dirname(output_path), exist_ok=True)
113
+ processed.save(output_path)
114
+ return output_path
115
+
116
+ def generate_face_mask(image_path, output_path):
117
+ try:
118
+ import cv2
119
+ img = cv2.imread(image_path)
120
+ gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
121
+ face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')
122
+ faces = face_cascade.detectMultiScale(gray, 1.3, 5)
123
+ mask = np.zeros_like(img)
124
+ if len(faces) > 0:
125
+ for (x, y, w, h) in faces:
126
+ p = int(max(w, h) * 0.3)
127
+ mask[max(0,y-p):min(img.shape[0],y+h+p), max(0,x-p):min(img.shape[1],x+w+p)] = 255
128
+ else:
129
+ h, w = img.shape[:2]; mask[h//4:3*h//4, w//4:3*w//4] = 255
130
+ cv2.imwrite(output_path, mask)
131
+ except Exception:
132
+ img = Image.open(image_path); w, h = img.size
133
+ mask = Image.new("RGB", (w, h), "black")
134
+ from PIL import ImageDraw; draw = ImageDraw.Draw(mask)
135
+ m = min(w, h) // 4; draw.rectangle([m, m, w-m, h-m], fill="white")
136
+ mask.save(output_path)
137
+ return output_path
138
+
139
+ def run_pipeline(config):
140
+ start_time = time.time()
141
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
142
+ run_dir = os.path.join(config.output_dir, f"run_{timestamp}")
143
+ os.makedirs(run_dir, exist_ok=True)
144
+ with open(os.path.join(run_dir, "config.json"), 'w') as f: json.dump(asdict(config), f, indent=2, default=str)
145
+ input_dir = Path(config.input_dir)
146
+ test_images = sorted([str(p) for p in input_dir.iterdir() if p.suffix.lower() in {'.png','.jpg','.jpeg','.bmp','.webp'}])
147
+ if not test_images: logger.error(f"No images in {config.input_dir}"); return
148
+ logger.info(f"Found {len(test_images)} images | Methods: {config.methods} | Attacks: {config.attacks}")
149
+ all_results = []
150
+ for img_idx, image_path in enumerate(test_images):
151
+ img_name = Path(image_path).stem
152
+ logger.info(f"\n{'='*60}\nImage {img_idx+1}/{len(test_images)}: {img_name}\n{'='*60}")
153
+ mask_dir = os.path.join(run_dir, "masks"); os.makedirs(mask_dir, exist_ok=True)
154
+ mask_path = os.path.join(mask_dir, f"{img_name}_mask.png")
155
+ generate_face_mask(image_path, mask_path)
156
+ for method in config.methods:
157
+ method_dir = os.path.join(run_dir, method, img_name); os.makedirs(method_dir, exist_ok=True)
158
+ original_copy = os.path.join(method_dir, "original.png")
159
+ Image.open(image_path).convert("RGB").save(original_copy)
160
+ protected_path = os.path.join(method_dir, "protected.png")
161
+ logger.info(f"\n--- Method: {method} ---")
162
+ if method == "faceshield": protection_result = run_faceshield(image_path, protected_path, config)
163
+ elif method == "diffusionguard": protection_result = run_diffusionguard(image_path, mask_path, protected_path, config)
164
+ elif method == "vgmshield": protection_result = run_vgmshield(image_path, protected_path, config)
165
+ else: continue
166
+ with open(os.path.join(method_dir, "protection_result.json"), 'w') as f: json.dump(protection_result, f, indent=2, default=str)
167
+ if protection_result.get("status") != "success": logger.error(f"Protection failed for {method}"); continue
168
+ if config.preprocessing_tests:
169
+ pp_dir = os.path.join(method_dir, "preprocessed"); os.makedirs(pp_dir, exist_ok=True)
170
+ for pp in config.preprocessing_tests:
171
+ try: apply_preprocessing(protected_path, pp, os.path.join(pp_dir, f"{pp}.png")); logger.info(f" [Preprocess] {pp}")
172
+ except Exception as e: logger.error(f" [Preprocess] {pp} failed: {e}")
173
+ for attack in config.attacks:
174
+ attack_dir = os.path.join(method_dir, f"attack_{attack}"); os.makedirs(attack_dir, exist_ok=True)
175
+ df_clean = os.path.join(attack_dir, "deepfake_clean.png"); df_protected = os.path.join(attack_dir, "deepfake_protected.png")
176
+ if attack == "inpainting":
177
+ run_inpainting_attack(original_copy, mask_path, df_clean, device=config.device)
178
+ run_inpainting_attack(protected_path, mask_path, df_protected, device=config.device)
179
+ elif attack == "ip_adapter":
180
+ run_ip_adapter_attack(original_copy, df_clean, device=config.device)
181
+ run_ip_adapter_attack(protected_path, df_protected, device=config.device)
182
+ try:
183
+ from modules.evaluation.evaluate import evaluate_protection
184
+ for attack in config.attacks:
185
+ attack_dir = os.path.join(method_dir, f"attack_{attack}")
186
+ df_c = os.path.join(attack_dir, "deepfake_clean.png"); df_p = os.path.join(attack_dir, "deepfake_protected.png")
187
+ eval_result = evaluate_protection(original_copy, protected_path,
188
+ df_c if os.path.exists(df_c) else None, df_p if os.path.exists(df_p) else None, method_name=method, device=config.device)
189
+ ed = asdict(eval_result); ed["attack"] = attack; ed["image"] = img_name; all_results.append(ed)
190
+ with open(os.path.join(attack_dir, "evaluation.json"), 'w') as f: json.dump(ed, f, indent=2)
191
+ except Exception as e: logger.error(f" [Evaluation] Failed: {e}")
192
+ with open(os.path.join(run_dir, "report.json"), 'w') as f:
193
+ json.dump({"timestamp": timestamp, "config": asdict(config), "total_images": len(test_images), "methods": config.methods, "attacks": config.attacks, "time_s": time.time()-start_time, "results": all_results}, f, indent=2, default=str)
194
+ logger.info(f"\nPipeline complete! Results: {run_dir} | Time: {time.time()-start_time:.1f}s")
195
+ return all_results
196
+
197
+ def main():
198
+ p = argparse.ArgumentParser(description="Red Team Pipeline")
199
+ p.add_argument("--input_dir", default="./data/test_images"); p.add_argument("--output_dir", default="./results")
200
+ p.add_argument("--methods", nargs="+", default=["faceshield","diffusionguard","vgmshield"])
201
+ p.add_argument("--attacks", nargs="+", default=["inpainting","ip_adapter"]); p.add_argument("--device", default="cuda")
202
+ p.add_argument("--fs_iter", type=int, default=30); p.add_argument("--fs_noise", type=int, default=12)
203
+ p.add_argument("--dg_iter", type=int, default=800); p.add_argument("--dg_eps", type=float, default=16/255)
204
+ p.add_argument("--vgm_steps", type=int, default=1000); p.add_argument("--vgm_eps", type=float, default=4/255)
205
+ p.add_argument("--skip_robustness", action="store_true")
206
+ a = p.parse_args()
207
+ config = PipelineConfig(input_dir=a.input_dir, output_dir=a.output_dir, methods=a.methods, attacks=a.attacks, device=a.device,
208
+ fs_total_iter=a.fs_iter, fs_noise_clamp=a.fs_noise, dg_iters=a.dg_iter, dg_eps=a.dg_eps, vgm_steps=a.vgm_steps, vgm_eps=a.vgm_eps,
209
+ preprocessing_tests=[] if a.skip_robustness else None)
210
+ run_pipeline(config)
211
+
212
+ if __name__ == "__main__": main()