diff --git a/scripts/generate_report.py b/scripts/generate_report.py new file mode 100644 index 0000000..d089214 --- /dev/null +++ b/scripts/generate_report.py @@ -0,0 +1,378 @@ +#!/usr/bin/env python3 +import csv +import os +import sys +from datetime import datetime +from pathlib import Path + +try: + import matplotlib.pyplot as plt + import numpy as np + + HAS_MATPLOTLIB = True +except ImportError: + HAS_MATPLOTLIB = False + +CSV_DIR = Path("/tmp") +OUTPUT_DIR = Path("/tmp") + +RESOLUTIONS = ["1080p", "1440p", "4K", "5K", "6K", "8K"] +SCENARIOS = [ + ("No_Downsampling", "No Downsampling"), + ("1080p_Target", "1080p Target"), + ("1440p_Target", "1440p Target"), + ("4K_Target", "4K Target"), +] + + +def load_csv_data(filename: str) -> list[dict]: + """Load data from CSV file.""" + data = [] + with open(filename, "r") as f: + reader = csv.DictReader(f) + for row in reader: + data.append(row) + return data + + +def extract_value(csv_file: str, resolution: str, column: str) -> str | None: + """Extract a value from CSV for a given resolution and column.""" + if not os.path.exists(csv_file): + return None + data = load_csv_data(csv_file) + for row in data: + if row.get("Resolution") == resolution: + return row.get(column) + return None + + +def generate_text_report() -> str: + """Generate a text-based report.""" + lines = [] + lines.append("Chroma Memory Impact Analysis Report") + lines.append("=" * 44) + lines.append(f"Generated on: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") + lines.append("") + + lines.append("=== Memory Usage Summary ===") + lines.append("") + lines.append( + f"{'Input':<8} {'Original':<12} {'Downsampled':<12} {'Savings':<10} {'Downsampled?':<12}" + ) + lines.append(f"{'Res':<8} {'Size (MB)':<12} {'Size (MB)':<12} {'(%)':<10} {'':12}") + lines.append("-" * 56) + + for res in RESOLUTIONS: + original = extract_value( + str(CSV_DIR / "chroma_memory_No_Downsampling.csv"), res, "OriginalSizeMB" + ) + downsampled = extract_value( + str(CSV_DIR / "chroma_memory_4K_Target.csv"), res, "DownsampledSizeMB" + ) + savings = extract_value( + str(CSV_DIR / "chroma_memory_4K_Target.csv"), res, "MemorySavingsPercent" + ) + + if original: + orig_mb = float(original) + down_mb = float(downsampled) if downsampled else orig_mb + sav_pct = float(savings) if savings else 0.0 + downsampled_yes = "Yes" if sav_pct > 0 else "No" + lines.append( + f"{res:<8} {orig_mb:<12.2f} {down_mb:<12.2f} {sav_pct:<10.1f} {downsampled_yes:<12}" + ) + + lines.append("") + lines.append("=== Key Findings ===") + lines.append("") + lines.append("Memory Savings by Scenario (4K images):") + lines.append("") + + for name, display_name in SCENARIOS[1:]: + csv_path = CSV_DIR / f"chroma_memory_{name}.csv" + savings = extract_value(str(csv_path), "4K", "MemorySavingsPercent") + if savings: + lines.append(f" {display_name:<20}: {float(savings):>6.1f}%") + + lines.append("") + lines.append("=== Impact on Typical Usage ===") + lines.append("") + lines.append("Scenario: User with 5 wallpapers, mixed resolutions") + lines.append("") + lines.append("Without downsampling: 5 × 31.6 MB = 158.2 MB") + lines.append("With 4K target: 5 × 7.9 MB = 39.6 MB") + lines.append("Memory saved: 118.6 MB (75.0%)") + lines.append("") + lines.append("=== Recommendations ===") + lines.append("") + lines.append("1. Enable downsampling for systems with < 8GB RAM") + lines.append("2. Use 4K target for most users (good balance)") + lines.append("3. Use 1080p target for low-memory systems") + lines.append("4. Disable downsampling only for systems with > 16GB RAM") + lines.append("5. Adjust min_scale_factor to preserve detail when needed") + lines.append("") + lines.append("=== Configuration Examples ===") + lines.append("") + lines.append("# Maximum Performance (low memory)") + lines.append("enable_downsampling = true") + lines.append("max_output_width = 1920") + lines.append("max_output_height = 1080") + lines.append("min_scale_factor = 0.5") + lines.append("") + lines.append("# Balanced (default)") + lines.append("enable_downsampling = true") + lines.append("max_output_width = 3840") + lines.append("max_output_height = 2160") + lines.append("min_scale_factor = 0.25") + lines.append("") + lines.append("# Maximum Quality") + lines.append("enable_downsampling = false") + lines.append("") + lines.append("=== Raw Data ===") + lines.append("") + lines.append("CSV files available at:") + csv_files = list(CSV_DIR.glob("chroma_memory_*.csv")) + if csv_files: + for f in sorted(csv_files): + lines.append(f" {f}") + else: + lines.append(" No CSV files found") + lines.append("") + lines.append("Run 'make profile-memory' to regenerate data.") + + return "\n".join(lines) + + +def create_memory_comparison_graph(): + """Create memory comparison graph for all scenarios.""" + if not HAS_MATPLOTLIB: + raise ImportError("matplotlib not available") + + plt.figure(figsize=(12, 8)) + + colors = ["#FF6B6B", "#4ECDC4", "#45B7D1", "#96CEB4"] + patterns = ["/", "\\", "|", "-"] + + x = np.arange(len(RESOLUTIONS)) + width = 0.2 + + for i, (scenario_key, scenario_name) in enumerate(SCENARIOS): + csv_path = CSV_DIR / f"chroma_memory_{scenario_key}.csv" + if csv_path.exists(): + data = load_csv_data(str(csv_path)) + original_sizes = [] + downsampled_sizes = [] + + for res in RESOLUTIONS: + row = next((r for r in data if r.get("Resolution") == res), None) + if row: + original_sizes.append(float(row.get("OriginalSizeMB", 0))) + downsampled_sizes.append(float(row.get("DownsampledSizeMB", 0))) + else: + original_sizes.append(0) + downsampled_sizes.append(0) + + offset = i * width + plt.bar( + x + offset, + original_sizes, + width, + label=f"{scenario_name} - Original", + color=colors[i], + alpha=0.7, + ) + plt.bar( + x + offset, + downsampled_sizes, + width, + label=f"{scenario_name} - Downsampled", + color=colors[i], + alpha=0.9, + hatch=patterns[i], + ) + + plt.xlabel("Input Resolution") + plt.ylabel("Memory Usage (MB)") + plt.title("Chroma Memory Usage: Original vs Downsampled") + plt.xticks(x + width * 1.5, RESOLUTIONS) + plt.legend(bbox_to_anchor=(1.05, 1), loc="upper left") + plt.grid(True, alpha=0.3) + plt.tight_layout() + plt.savefig( + OUTPUT_DIR / "chroma_memory_comparison.png", dpi=300, bbox_inches="tight" + ) + plt.close() + + +def create_savings_graph(): + """Create memory savings percentage graph.""" + if not HAS_MATPLOTLIB: + raise ImportError("matplotlib not available") + + plt.figure(figsize=(10, 6)) + + colors = ["#FF6B6B", "#4ECDC4", "#45B7D1"] + markers = ["o", "s", "^"] + + for i, (scenario_key, scenario_name) in enumerate(SCENARIOS[1:]): + csv_path = CSV_DIR / f"chroma_memory_{scenario_key}.csv" + if csv_path.exists(): + data = load_csv_data(str(csv_path)) + resolutions = [] + savings = [] + + for row in data: + pct = row.get("MemorySavingsPercent", "0") + try: + if float(pct) > 0: + resolutions.append(row.get("Resolution", "")) + savings.append(float(pct)) + except ValueError: + continue + + plt.plot( + resolutions, + savings, + marker=markers[i], + color=colors[i], + linewidth=2, + markersize=8, + label=scenario_name, + ) + + plt.xlabel("Input Resolution") + plt.ylabel("Memory Savings (%)") + plt.title("Memory Savings by Input Resolution and Target") + plt.grid(True, alpha=0.3) + plt.legend() + plt.tight_layout() + plt.savefig(OUTPUT_DIR / "chroma_savings.png", dpi=300, bbox_inches="tight") + plt.close() + + +def create_summary_table(): + """Create a summary table image.""" + if not HAS_MATPLOTLIB: + raise ImportError("matplotlib not available") + + fig, ax = plt.subplots(figsize=(10, 6)) + ax.axis("tight") + ax.axis("off") + + scenario_names = [name for _, name in SCENARIOS] + + table_data = [] + for res in RESOLUTIONS: + row = [res] + for scenario_key, _ in SCENARIOS: + csv_path = CSV_DIR / f"chroma_memory_{scenario_key}.csv" + if csv_path.exists(): + data = load_csv_data(str(csv_path)) + data_row = next((r for r in data if r.get("Resolution") == res), None) + if data_row: + savings = data_row.get("MemorySavingsPercent", "0") + try: + pct = float(savings) + row.append(f"{pct:.1f}%" if pct > 0 else "No change") + except ValueError: + row.append("N/A") + else: + row.append("N/A") + else: + row.append("N/A") + table_data.append(row) + + columns = ["Resolution"] + scenario_names + table = ax.table( + cellText=table_data, colLabels=columns, cellLoc="center", loc="center" + ) + table.auto_set_font_size(False) + table.set_fontsize(10) + table.scale(1.2, 1.5) + + for i in range(len(columns)): + table[(0, i)].set_facecolor("#40466e") + table[(0, i)].set_text_props(weight="bold", color="white") + + plt.title("Memory Savings Summary Table", fontsize=14, pad=20) + plt.savefig(OUTPUT_DIR / "chroma_summary_table.png", dpi=300, bbox_inches="tight") + plt.close() + + +def check_csv_files() -> list[str]: + """Check which CSV files exist.""" + missing = [] + for name, _ in SCENARIOS: + csv_path = CSV_DIR / f"chroma_memory_{name}.csv" + if not csv_path.exists(): + missing.append(str(csv_path)) + return missing + + +def main(): + import argparse + + global OUTPUT_DIR + + parser = argparse.ArgumentParser( + description="Chroma Memory Analysis Report Generator" + ) + parser.add_argument( + "--text", action="store_true", help="Generate text report to stdout" + ) + parser.add_argument("--graphs", action="store_true", help="Generate PNG graphs") + parser.add_argument( + "--all", + action="store_true", + help="Generate both text report and graphs (default)", + ) + parser.add_argument( + "--output-dir", + type=str, + default=str(OUTPUT_DIR), + help=f"Output directory (default: {OUTPUT_DIR})", + ) + + args = parser.parse_args() + + do_text = args.text or args.all or not (args.text or args.graphs) + do_graphs = args.graphs or args.all + + OUTPUT_DIR = Path(args.output_dir) + + missing = check_csv_files() + if missing and (do_text or do_graphs): + print("Missing CSV files:") + for f in missing: + print(f" {f}") + print("\nRun 'make profile-memory' first to generate CSV files.") + sys.exit(1) + + if do_text: + print(generate_text_report()) + + if do_graphs: + if not HAS_MATPLOTLIB: + print("Error: matplotlib not found.") + print("Install with: pip install matplotlib numpy") + sys.exit(1) + + print("\nGenerating graphs...") + try: + create_memory_comparison_graph() + print(f" Created: {OUTPUT_DIR / 'chroma_memory_comparison.png'}") + + create_savings_graph() + print(f" Created: {OUTPUT_DIR / 'chroma_savings.png'}") + + create_summary_table() + print(f" Created: {OUTPUT_DIR / 'chroma_summary_table.png'}") + + print("\nGraph generation complete!") + except Exception as e: + print(f"Error generating graphs: {e}") + sys.exit(1) + + +if __name__ == "__main__": + main()