mcp-relight-harmonize 1.0.2 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -67,6 +67,23 @@ To inspect or discover previously generated relight variations or composite outp
67
67
  1. Call tool `list_cached_variations({ cache_dir?: "<path>" })`.
68
68
  2. Inspect `files` list containing filename, full path, byte size, and last modified timestamp for all output artifacts.
69
69
 
70
+ ### Workflow F: Dynamic 6-Layer Decomposition & Interactive Modification Loop
71
+ For comprehensive optical intelligence and guided generation:
72
+ 1. Call tool `analyze_optical_profile({ image_path: "<path>", extract_layers: true, user_intent?: "<intent>" })` or execute Python script:
73
+ `python scripts/extract_layers.py --image <path> [--intent "<intent>"]`
74
+ 2. The engine generates 6 visual analytical layers into `Layers/`:
75
+ - `01_highlights.png` (Highlights / Specular Zones)
76
+ - `02_shadows.png` (Shadows / Low-Key Zones)
77
+ - `03_ambient_occlusion.png` (Deep Ambient Occlusion & Contact Umbra)
78
+ - `04_edges.png` (Sobel High-Frequency Contours)
79
+ - `05_depth_normals.png` (3D Surface Normal Gradient Field)
80
+ - `06_chroma_saturation.png` (Chrominance & Saturation Distribution)
81
+ 3. Examine each layer and retain optical findings in memory / CoT.
82
+ 4. Present the dynamic `Layer.md` and address the user with the mandatory decision prompt:
83
+ **"ماذا تريد من تعديل؟"**
84
+ presenting dynamically generated options tailored specifically to the image's detected optical profile.
85
+ 5. Upon the user's decision, immediately formulate the target prompt for **GPT Image** or **Nano Banana**. If the client/agent has an embedded image generator (`generate_image`), trigger generation immediately!
86
+
70
87
  ---
71
88
 
72
89
  ## 3. Reference Documentation
@@ -0,0 +1,397 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ MarwanDevSpace - Dynamic 6-Layer Optical Decomposition & Intelligence Engine (Agent Skill Edition)
4
+ Extracts 6 analytical visual layers into Layers/ directory:
5
+ 1. 01_highlights.png - طبقة الألوان الفاتحة (Highlights / Specular Zones)
6
+ 2. 02_shadows.png - طبقة الألوان الغامقة (Shadows / Low-Key Zones)
7
+ 3. 03_ambient_occlusion.png - طبقة الظل العالي (Deep Ambient Occlusion & Ground Contact)
8
+ 4. 04_edges.png - طبقة الحواف (Sobel High-Frequency Contours)
9
+ 5. 05_depth_normals.png - طبقة العمق (3D Surface Normal Gradient Field)
10
+ 6. 06_chroma_saturation.png - طبقة الألوان والتشبع والبكسلات (Chroma & Saturation Distribution)
11
+
12
+ Generates dynamic, on-demand Layer.md reports and physical generative prompts
13
+ specifically targeting GPT Image and Nano Banana without static boilerplate.
14
+ """
15
+
16
+ import os
17
+ import sys
18
+ import argparse
19
+ import json
20
+ from typing import Optional, Dict, Any, List
21
+ import numpy as np
22
+ from PIL import Image
23
+
24
+ def calculate_cct(r_mean: float, g_mean: float, b_mean: float) -> float:
25
+ """Calculates Correlated Color Temperature (Kelvin) using CIE 1931 xy and McCamy formulation."""
26
+ r_norm = min(max(r_mean / 255.0, 0.0), 1.0)
27
+ g_norm = min(max(g_mean / 255.0, 0.0), 1.0)
28
+ b_norm = min(max(b_mean / 255.0, 0.0), 1.0)
29
+
30
+ # Gamma linearization (sRGB -> linear RGB)
31
+ r_lin = ((r_norm + 0.055) / 1.055) ** 2.4 if r_norm > 0.04045 else r_norm / 12.92
32
+ g_lin = ((g_norm + 0.055) / 1.055) ** 2.4 if g_norm > 0.04045 else g_norm / 12.92
33
+ b_lin = ((b_norm + 0.055) / 1.055) ** 2.4 if b_norm > 0.04045 else b_norm / 12.92
34
+
35
+ # CIE XYZ matrix transformation
36
+ X = 0.4124564 * r_lin + 0.3575761 * g_lin + 0.1804375 * b_lin
37
+ Y = 0.2126729 * r_lin + 0.7151522 * g_lin + 0.0721750 * b_lin
38
+ Z = 0.0193339 * r_lin + 0.1191920 * g_lin + 0.9503041 * b_lin
39
+
40
+ total = X + Y + Z
41
+ if total <= 1e-7:
42
+ return 6500.0
43
+
44
+ x = X / total
45
+ y = Y / total
46
+ denom = 0.1858 - y
47
+ if abs(denom) < 1e-6:
48
+ denom = 1e-6 if denom >= 0 else -1e-6
49
+
50
+ n = (x - 0.3320) / denom
51
+ cct = 449.0 * (n ** 3) + 3525.0 * (n ** 2) + 6823.3 * n + 5520.33
52
+ return float(np.clip(cct, 1500.0, 20000.0))
53
+
54
+ def build_dynamic_diagnostics(
55
+ cct_kelvin: float,
56
+ azimuth_deg: float,
57
+ elevation_deg: float,
58
+ hl_coverage_pct: float,
59
+ shadow_coverage_pct: float,
60
+ ao_coverage_pct: float,
61
+ mean_gradient: float,
62
+ mean_saturation: float,
63
+ user_intent: Optional[str] = None
64
+ ) -> Dict[str, Any]:
65
+ """Dynamically analyzes layer metrics to produce targeted findings, customized suggestions, and diffusion prompts."""
66
+ findings: List[str] = []
67
+ tailored_options: List[str] = []
68
+
69
+ # 1. Thermal & Chromatic Diagnostics
70
+ if cct_kelvin < 3800:
71
+ thermal_desc = f"طيف لوني دافئ جداً ({round(cct_kelvin)}K - Tungsten/Golden Hour)"
72
+ findings.append(f"🔥 **انحياز طيفي دافئ**: الصورة مسجلة بحرارة `{round(cct_kelvin)}K` مع غلبة للأطياف البرتقالية والصفراء في القنوات اللونية.")
73
+ tailored_options.append(f"موازنة حرارة الألوان من `{round(cct_kelvin)}K` إلى 5500K نهارية محايدة لإزالة المسحة الكهرمانية.")
74
+ elif cct_kelvin > 6500:
75
+ thermal_desc = f"طيف لوني بارد ({round(cct_kelvin)}K - Cool Atmospheric Skylight)"
76
+ findings.append(f"❄️ **انحياز طيفي بارد**: الصورة مسجلة بحرارة `{round(cct_kelvin)}K` مع غلبة للزرقة والارتداد السماوي البارد.")
77
+ tailored_options.append(f"تدفئة المشهد الضوئي برفع حرارة الألوان إلى 3200K (Mood Tungsten) أو 4500K سينمائي.")
78
+ else:
79
+ thermal_desc = f"طيف نهاري متوازن ({round(cct_kelvin)}K - Balanced Daylight)"
80
+ findings.append(f"☀️ **حرارة ألوان متوازنة**: حرارة الألوان عند `{round(cct_kelvin)}K` تماثل الإضاءة الاستوديوية المحايدة.")
81
+
82
+ # 2. Lighting Angles & Dynamic Contrast
83
+ light_dir = "يمين" if 0 <= azimuth_deg < 90 or 270 <= azimuth_deg <= 360 else "يسار"
84
+ light_vert = "علوية" if elevation_deg > 50 else "أفقية جانبية"
85
+ findings.append(f"💡 **توجيه الإضاءة الرئيسية**: زاوية السمت `{round(azimuth_deg)}°` (إضاءة من جهة ال{light_dir}) مع زاوية ارتفاع `{round(elevation_deg)}°` ({light_vert}).")
86
+
87
+ if shadow_coverage_pct > 35.0 and hl_coverage_pct > 15.0:
88
+ findings.append(f"⚡ **تباين درامي عالي (Chiaroscuro)**: كثافة الظلال `{round(shadow_coverage_pct, 1)}%` مع سعة مناطق ساطعة `{round(hl_coverage_pct, 1)}%` تدل على تباين قوي بين المفتاح الضوئي والملء.")
89
+ tailored_options.append("تنعيم التباين ورفع تفاصيل الظلال المغلقة عبر وضع الإضاءة المحيطية (Ambient Fill +0.8 EV).")
90
+ elif shadow_coverage_pct < 12.0 and hl_coverage_pct < 12.0:
91
+ findings.append("🌫️ **إضاءة منبسطة ناعمة (Flat Diffuse)**: تباين منخفض وغياب للمناطق الساطعة الحادة، ما يعطي مظهراً هادئاً.")
92
+ tailored_options.append("إضافة عمق درامي وزيادة التباين بنمط Chiaroscuro عالي التحديد مع إضاءة اتجاهية بارزة.")
93
+
94
+ # 3. Grounding & Ambient Occlusion
95
+ if ao_coverage_pct < 2.0:
96
+ findings.append(f"⚠️ **فقدان الارتكاز الأرضي (Lack of Grounding)**: نسبة الظلال التلامسية العميقة `{round(ao_coverage_pct, 2)}%` تكاد تنعدم، مما قد يظهر العنصر كأنه طافٍ في الفراغ.")
97
+ tailored_options.append(f"بناء وتثبيت ظل تلامسي أرضي فيزيائي (Contact Shadow Footprint) أسفل أدنى نقطة ارتكاز.")
98
+ else:
99
+ findings.append(f"⚓ **ارتكاز أرضي متماسك**: نسبة ظلال التلامس والانغلاق الموضعي `{round(ao_coverage_pct, 1)}%` تؤمن التصاقاً بصرياً طبيعياً بالأرضية.")
100
+
101
+ # 4. Surface Micro-textures & Edges
102
+ if mean_gradient > 0.03:
103
+ findings.append(f"🔍 **تفاصيل سطحية دقيقة وحواف حادة**: متوسط تباين سوبل `{round(mean_gradient, 4)}` يشير إلى وفرة في التفاصيل المجهرية والأنسجة السطحية الواضحة.")
104
+ else:
105
+ findings.append(f"🎨 **حواف ناعمة وسطح انسيابي**: متوسط تدرج سوبل `{round(mean_gradient, 4)}` يشير إلى مساحات ناعمة متصلة ومناسبة للتنعيم وإعادة التوزيع.")
106
+
107
+ # 5. Chroma / Saturation
108
+ if mean_saturation > 0.40:
109
+ findings.append(f"🌈 **تشبع لوني مكثف**: متوسط التشبع `{round(mean_saturation, 3)}` يظهر كثافة لونية حيوية في البكسلات.")
110
+ else:
111
+ findings.append(f"🔘 **لوحة لونية هادئة أو معتدلة**: متوسط التشبع `{round(mean_saturation, 3)}` يعكس تدرجات رصينة غير مبالغ بها.")
112
+
113
+ if user_intent:
114
+ tailored_options.insert(0, f"تطبيق طلبك المخصص فوراً: \"{user_intent}\" بتطابق فيزيائي دقيق مع بيانات الطبقات الست.")
115
+ else:
116
+ tailored_options.append("إعادة إضاءة بنمط هالة الحواف (Rim Light Halo +1.2 EV) لإبراز حدود المجسم وعزله عن الخلفية.")
117
+ tailored_options.append("دمج العنصر في خلفية جديدة مع مطابقة إحصائيات الألوان ودرجة الحرارة وظلال الارتكاز.")
118
+
119
+ intent_clause = f", {user_intent}" if user_intent else ""
120
+ gpt_prompt = (
121
+ f"A master-quality studio photograph{intent_clause}, calibrated optical lighting at {round(azimuth_deg)}° azimuth "
122
+ f"and {round(elevation_deg)}° elevation, authentic {round(cct_kelvin)}K color temperature balance, "
123
+ f"physically-grounded ambient occlusion contact shadows firmly anchoring the base plane, "
124
+ f"crisp micro-surface geometry (Sobel roughness index {round(mean_gradient, 3)}), "
125
+ f"smooth luminance falloff and authentic subsurface scattering, 85mm prime lens f/2.0."
126
+ )
127
+
128
+ nano_banana_prompt = (
129
+ f"optics relight, cct {round(cct_kelvin)}K, light vector azimuth {round(azimuth_deg)} deg elevation {round(elevation_deg)} deg, "
130
+ f"surface roughness {round(mean_gradient, 4)}, saturation index {round(mean_saturation, 3)}, "
131
+ f"ground contact occlusion caster, volumetric photon bounce, high-key rim highlight accents, "
132
+ f"denoising strength 0.38{intent_clause}"
133
+ )
134
+
135
+ return {
136
+ "thermalDescription": thermal_desc,
137
+ "findings": findings,
138
+ "tailoredOptions": tailored_options,
139
+ "gptImagePrompt": gpt_prompt,
140
+ "nanoBananaPrompt": nano_banana_prompt,
141
+ }
142
+
143
+ def extract_layers(image_path: str, output_dir: str = "Layers", user_intent: Optional[str] = None) -> Dict[str, Any]:
144
+ abs_image_path = os.path.abspath(image_path)
145
+ if not os.path.exists(abs_image_path):
146
+ raise FileNotFoundError(f"Source image not found: {abs_image_path}")
147
+
148
+ os.makedirs(output_dir, exist_ok=True)
149
+ abs_output_dir = os.path.abspath(output_dir)
150
+
151
+ img = Image.open(abs_image_path).convert("RGB")
152
+ rgb = np.array(img, dtype=np.float32)
153
+ height, width, _ = rgb.shape
154
+ num_pixels = width * height
155
+
156
+ lum = 0.2126 * rgb[:, :, 0] + 0.7152 * rgb[:, :, 1] + 0.0722 * rgb[:, :, 2]
157
+ norm_lum = lum / 255.0
158
+
159
+ hl_mask = np.clip((lum - 170.0) / 75.0, 0.0, 1.0)
160
+ hl_layer = rgb * hl_mask[:, :, np.newaxis]
161
+ hl_coverage_pct = float((np.sum(lum > 170.0) / num_pixels) * 100.0)
162
+ hl_path = os.path.join(abs_output_dir, "01_highlights.png")
163
+ Image.fromarray(np.uint8(np.clip(hl_layer, 0, 255))).save(hl_path)
164
+
165
+ shadow_mask = np.clip((95.0 - lum) / 80.0, 0.0, 1.0)
166
+ shadow_layer = rgb * shadow_mask[:, :, np.newaxis]
167
+ shadow_coverage_pct = float((np.sum(lum < 85.0) / num_pixels) * 100.0)
168
+ shadow_path = os.path.join(abs_output_dir, "02_shadows.png")
169
+ Image.fromarray(np.uint8(np.clip(shadow_layer, 0, 255))).save(shadow_path)
170
+
171
+ ao_mask = np.clip((40.0 - lum) / 40.0, 0.0, 1.0)
172
+ ao_visual = np.zeros((height, width, 3), dtype=np.float32)
173
+ ao_visual[:, :, 0] = (1.0 - ao_mask) * 230.0
174
+ ao_visual[:, :, 1] = (1.0 - ao_mask) * 240.0 + (ao_mask * 15.0)
175
+ ao_visual[:, :, 2] = (1.0 - ao_mask) * 255.0 + (ao_mask * 30.0)
176
+ ao_coverage_pct = float((np.sum(lum < 35.0) / num_pixels) * 100.0)
177
+ ao_path = os.path.join(abs_output_dir, "03_ambient_occlusion.png")
178
+ Image.fromarray(np.uint8(np.clip(ao_visual, 0, 255))).save(ao_path)
179
+
180
+ sobel_x = np.array([[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]], dtype=np.float32)
181
+ sobel_y = np.array([[-1, -2, -1], [0, 0, 0], [1, 2, 1]], dtype=np.float32)
182
+ padded_lum = np.pad(norm_lum, 1, mode="edge")
183
+ gx = np.zeros((height, width), dtype=np.float32)
184
+ gy = np.zeros((height, width), dtype=np.float32)
185
+ for i in range(3):
186
+ for j in range(3):
187
+ gx += sobel_x[i, j] * padded_lum[i : i + height, j : j + width]
188
+ gy += sobel_y[i, j] * padded_lum[i : i + height, j : j + width]
189
+
190
+ edge_mag = np.sqrt(gx ** 2 + gy ** 2)
191
+ edge_vis = np.clip(edge_mag * 4.0 * 255.0, 0, 255)
192
+ mean_gradient = float(np.mean(edge_mag))
193
+ edge_path = os.path.join(abs_output_dir, "04_edges.png")
194
+ Image.fromarray(np.uint8(edge_vis)).save(edge_path)
195
+
196
+ scale = 6.0
197
+ nx = -gx * scale
198
+ ny = -gy * scale
199
+ nz = np.ones((height, width), dtype=np.float32)
200
+ mag = np.sqrt(nx ** 2 + ny ** 2 + nz ** 2) + 1e-7
201
+ nx_norm = nx / mag
202
+ ny_norm = ny / mag
203
+ nz_norm = nz / mag
204
+
205
+ normal_rgb = np.zeros((height, width, 3), dtype=np.float32)
206
+ normal_rgb[:, :, 0] = (nx_norm * 0.5 + 0.5) * 255.0
207
+ normal_rgb[:, :, 1] = (ny_norm * 0.5 + 0.5) * 255.0
208
+ normal_rgb[:, :, 2] = (nz_norm * 0.5 + 0.5) * 255.0
209
+ depth_path = os.path.join(abs_output_dir, "05_depth_normals.png")
210
+ Image.fromarray(np.uint8(np.clip(normal_rgb, 0, 255))).save(depth_path)
211
+
212
+ lum_excess = np.maximum(lum - np.mean(lum), 0.0) ** 2
213
+ sum_w = np.sum(lum_excess) + 1e-7
214
+ avg_lx = np.sum(nx_norm * lum_excess) / sum_w
215
+ avg_ly = np.sum(ny_norm * lum_excess) / sum_w
216
+ avg_lz = np.sum(nz_norm * lum_excess) / sum_w
217
+ l_mag = np.sqrt(avg_lx**2 + avg_ly**2 + avg_lz**2) + 1e-7
218
+ avg_lx, avg_ly, avg_lz = avg_lx / l_mag, avg_ly / l_mag, avg_lz / l_mag
219
+ azimuth_deg = (np.degrees(np.arctan2(-avg_ly, avg_lx)) + 360.0) % 360.0
220
+ elevation_deg = np.clip(np.degrees(np.arcsin(np.clip(avg_lz, -1.0, 1.0))), 0.0, 90.0)
221
+
222
+ max_c = np.maximum(np.maximum(rgb[:, :, 0], rgb[:, :, 1]), rgb[:, :, 2])
223
+ min_c = np.minimum(np.minimum(rgb[:, :, 0], rgb[:, :, 1]), rgb[:, :, 2])
224
+ chroma = max_c - min_c
225
+ sat = np.zeros_like(chroma)
226
+ non_zero = max_c > 1e-4
227
+ sat[non_zero] = chroma[non_zero] / max_c[non_zero]
228
+
229
+ sat_vis = np.zeros((height, width, 3), dtype=np.float32)
230
+ sat_vis[:, :, 0] = np.clip(sat * 255.0 * 1.5, 0, 255)
231
+ sat_vis[:, :, 1] = np.clip((1.0 - np.abs(sat - 0.5) * 2.0) * 220.0, 0, 255)
232
+ sat_vis[:, :, 2] = np.clip((1.0 - sat) * 200.0, 0, 255)
233
+ mean_saturation = float(np.mean(sat))
234
+ chroma_path = os.path.join(abs_output_dir, "06_chroma_saturation.png")
235
+ Image.fromarray(np.uint8(sat_vis)).save(chroma_path)
236
+
237
+ mean_r = float(np.mean(rgb[:, :, 0]))
238
+ mean_g = float(np.mean(rgb[:, :, 1]))
239
+ mean_b = float(np.mean(rgb[:, :, 2]))
240
+ cct_kelvin = calculate_cct(mean_r, mean_g, mean_b)
241
+
242
+ diag = build_dynamic_diagnostics(
243
+ cct_kelvin=cct_kelvin,
244
+ azimuth_deg=azimuth_deg,
245
+ elevation_deg=elevation_deg,
246
+ hl_coverage_pct=hl_coverage_pct,
247
+ shadow_coverage_pct=shadow_coverage_pct,
248
+ ao_coverage_pct=ao_coverage_pct,
249
+ mean_gradient=mean_gradient,
250
+ mean_saturation=mean_saturation,
251
+ user_intent=user_intent
252
+ )
253
+
254
+ report = {
255
+ "sourceImage": abs_image_path,
256
+ "layersDirectory": abs_output_dir,
257
+ "dimensions": [width, height],
258
+ "opticalMetrics": {
259
+ "cctKelvin": round(cct_kelvin, 1),
260
+ "dominantLightVector": [round(float(avg_lx), 4), round(float(avg_ly), 4), round(float(avg_lz), 4)],
261
+ "lightingAngles": {
262
+ "azimuthDeg": round(float(azimuth_deg), 1),
263
+ "elevationDeg": round(float(elevation_deg), 1),
264
+ },
265
+ "meanLuminance": round(float(np.mean(lum)), 2),
266
+ "meanSaturation": round(mean_saturation, 3),
267
+ "edgeRoughness": round(mean_gradient, 4),
268
+ },
269
+ "diagnostics": diag,
270
+ "layers": {
271
+ "01_highlights": {
272
+ "file": "01_highlights.png",
273
+ "path": hl_path,
274
+ "description": "طبقة الألوان الفاتحة - High-key Luminance & Specular Zones",
275
+ "coveragePct": round(hl_coverage_pct, 2),
276
+ },
277
+ "02_shadows": {
278
+ "file": "02_shadows.png",
279
+ "path": shadow_path,
280
+ "description": "طبقة الألوان الغامقة - Low-key & Shadow Zones",
281
+ "coveragePct": round(shadow_coverage_pct, 2),
282
+ },
283
+ "03_ambient_occlusion": {
284
+ "file": "03_ambient_occlusion.png",
285
+ "path": ao_path,
286
+ "description": "طبقة الظل العالي - High Contact Shadows & Ground Occlusion",
287
+ "coveragePct": round(ao_coverage_pct, 2),
288
+ },
289
+ "04_edges": {
290
+ "file": "04_edges.png",
291
+ "path": edge_path,
292
+ "description": "طبقة الحواف - Sobel High-Frequency Gradient Contours",
293
+ "meanGradient": round(mean_gradient, 4),
294
+ },
295
+ "05_depth_normals": {
296
+ "file": "05_depth_normals.png",
297
+ "path": depth_path,
298
+ "description": "طبقة العمق - 3D Surface Normal Gradient Field (RGB Tangent Map)",
299
+ "dominantAzimuth": round(float(azimuth_deg), 1),
300
+ "dominantElevation": round(float(elevation_deg), 1),
301
+ },
302
+ "06_chroma_saturation": {
303
+ "file": "06_chroma_saturation.png",
304
+ "path": chroma_path,
305
+ "description": "طبقة الألوان والتشبع والبكسلات - Chrominance & Saturation Distribution",
306
+ "meanSaturation": round(mean_saturation, 3),
307
+ },
308
+ },
309
+ }
310
+
311
+ findings_rendered = "\n\n".join([f"{item}" for item in diag["findings"]])
312
+ options_rendered = "\n".join([f"- **خيار {idx + 1}**: {opt}" for idx, opt in enumerate(diag["tailoredOptions"])])
313
+ user_intent_header = f"\n**الهدف المخصص المطلوب**: `{user_intent}`\n" if user_intent else ""
314
+
315
+ layer_md_path = os.path.join(abs_output_dir, "Layer.md")
316
+ layer_md_content = f"""# تقرير الطبقات التحليلية الست (Layer.md)
317
+ **مصدر الصورة**: `{abs_image_path}`
318
+ **أبعاد الصورة**: `{width}x{height}` بكسل
319
+ **التصنيف الضوئي المكتشف**: `{diag['thermalDescription']}`
320
+ **زاوية الإضاءة المسجلة**: سمت `{round(azimuth_deg)}°` | ارتفاع `{round(elevation_deg)}°`{user_intent_header}
321
+
322
+ ---
323
+
324
+ ## 1. فهرس الطبقات التحليلية الست (The 6 Extracted Layers)
325
+
326
+ | # | اسم الطبقة | الملف المولد | القراءة الفيزيائية المكتشفة |
327
+ |---|---|---|---|
328
+ | **1** | **طبقة الألوان الفاتحة (Highlights)** | [`01_highlights.png`](file:///{hl_path.replace(os.sep, '/')}) | نسبة تغطية الأضواء الساطعة: `{round(hl_coverage_pct, 2)}%` |
329
+ | **2** | **طبقة الألوان الغامقة (Shadows)** | [`02_shadows.png`](file:///{shadow_path.replace(os.sep, '/')}) | مساحة الظلال والمناطق الداكنة: `{round(shadow_coverage_pct, 2)}%` |
330
+ | **3** | **طبقة الظل العالي (Ambient Occlusion)** | [`03_ambient_occlusion.png`](file:///{ao_path.replace(os.sep, '/')}) | كثافة الارتكاز الأرضي وظلال التلامس: `{round(ao_coverage_pct, 2)}%` |
331
+ | **4** | **طبقة الحواف (Edges & Contours)** | [`04_edges.png`](file:///{edge_path.replace(os.sep, '/')}) | تباين الحواف المجهرية لسوبل: `{round(mean_gradient, 4)}` |
332
+ | **5** | **طبقة العمق (3D Depth Normals)** | [`05_depth_normals.png`](file:///{depth_path.replace(os.sep, '/')}) | توجيه التنسور المتجهي ثلاثي الأبعاد: سمت `{round(azimuth_deg)}°` |
333
+ | **6** | **طبقة الألوان والتشبع (Chroma / Saturation)** | [`06_chroma_saturation.png`](file:///{chroma_path.replace(os.sep, '/')}) | متوسط تشبع وتوزيع نقاء الألوان: `{round(mean_saturation, 3)}` |
334
+
335
+ ---
336
+
337
+ ## 2. النتائج البصرية الديناميكية المستخلصة من الصورة (Dynamic Findings)
338
+
339
+ {findings_rendered}
340
+
341
+ ---
342
+
343
+ ## 3. التوجيه التوليدي المخصص (Calibrated Prompts for Generative Engines)
344
+
345
+ ### أ. موجه GPT Image (DALL-E 3 / GPT-4o Vision):
346
+ > "{diag['gptImagePrompt']}"
347
+
348
+ ### ب. موجه Nano Banana (High-Density Optical Tokenizer):
349
+ > "{diag['nanoBananaPrompt']}"
350
+
351
+ ---
352
+
353
+ ## 4. الخطوة التفاعلية الموجهة بالبيانات (Dynamic Interactive Decision)
354
+
355
+ **ماذا تريد من تعديل؟**
356
+ بناءً على المعطيات الضوئية المكتشفة في هذه الصورة تحديداً، إليك خيارات التعديل المقترحة:
357
+
358
+ {options_rendered}
359
+
360
+ 💬 *يمكنك أيضاً طلب أي تعديل مخصص بحرية تامة، وسيتم فوراً توليد برومبت دقيق للغاية أو إنشاء الصورة المعدلة بالكامل!*
361
+ """
362
+
363
+ with open(layer_md_path, "w", encoding="utf-8") as f:
364
+ f.write(layer_md_content)
365
+
366
+ report["layerMarkdownPath"] = layer_md_path
367
+ return report
368
+
369
+ def main():
370
+ parser = argparse.ArgumentParser(description="MarwanDevSpace Dynamic 6-Layer Optical Decomposition")
371
+ parser.add_argument("--image", "-i", required=True, help="Path to input image")
372
+ parser.add_argument("--output-dir", "-o", default="Layers", help="Output directory for the 6 layers (default: Layers)")
373
+ parser.add_argument("--intent", "-t", default=None, help="Optional user modification intent to tailor report and prompts")
374
+ parser.add_argument("--json", action="store_true", help="Print JSON report to stdout")
375
+ args = parser.parse_args()
376
+
377
+ try:
378
+ report = extract_layers(args.image, args.output_dir, user_intent=args.intent)
379
+ if args.json:
380
+ print(json.dumps(report, indent=2))
381
+ else:
382
+ diag = report["diagnostics"]
383
+ print(f"[MarwanDevSpace] 6 Analytical layers successfully extracted to: {report['layersDirectory']}")
384
+ print(f"[MarwanDevSpace] Dynamic Layer.md report created: {report['layerMarkdownPath']}")
385
+ print(f"\nOptical Profile: {diag['thermalDescription']} | Azimuth: {report['opticalMetrics']['lightingAngles']['azimuthDeg']}°")
386
+ print("\nKey Dynamic Findings:")
387
+ for f in diag["findings"][:3]:
388
+ print(f" • {f}")
389
+ print("\n[Interactive Decision Point]: ماذا تريد من تعديل؟")
390
+ for opt in diag["tailoredOptions"]:
391
+ print(f" - {opt}")
392
+ except Exception as e:
393
+ print(f"[Error] Failed to extract layers: {e}", file=sys.stderr)
394
+ sys.exit(1)
395
+
396
+ if __name__ == "__main__":
397
+ main()