CoolFace
Apppublic

Propelis/QC_Rules

sourceHugging Faceupdated 1y agoView on Hugging Face
0likes
compare_app.py620 linesDownload Raw Back to root
1import streamlit as st2import tempfile3import os4import pandas as pd5from src.extract_text.google_document_api import GoogleDocumentAPI6from pdf2image import convert_from_path7from PIL import Image, ImageDraw, ImageFont8from src.utils.image_utils import ImageUtils9import base6410from io import BytesIO11from src.utils.barcode import Barcode12import anthropic13import json14 15def load_client_artwork_files():16    """Load all artwork PDF files from client directory"""17    base_path = "requirements_library/client-requirements"18    artwork_files = []19    20    if not os.path.exists(base_path):21        return artwork_files22    23    # Walk through all subdirectories24    for root, dirs, files in os.walk(base_path):25        for file in files:26            file_path = os.path.join(root, file)27            relative_path = os.path.relpath(file_path, base_path)28            29            if file.lower().endswith('.pdf'):30                artwork_files.append({31                    'name': f"{relative_path}",32                    'path': file_path,33                    'type': 'artwork'34                })35    36    return artwork_files37 38def load_artwork_content(file_info):39    """Load artwork content as bytes"""40    try:41        with open(file_info['path'], 'rb') as f:42            return f.read()43    except Exception as e:44        st.error(f"Error loading artwork file {file_info['name']}: {str(e)}")45        return None46 47def extract_pdf_data(pdf_file, file_name):48    """Extract text, bounding boxes, images, and barcodes from PDF"""49    try:50        # Create a temporary file to process the PDF51        with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as tmp_file:52            pdf_file.seek(0)53            tmp_file.write(pdf_file.read())54            tmp_pdf_path = tmp_file.name55        56        # Extract text and bounding boxes using Google Document API57        google_document_api = GoogleDocumentAPI(credentials_path="src/extract_text/photon-services-f0d3ec1417d0.json")58        document = google_document_api.process_document(tmp_pdf_path)59        text_content = google_document_api.extract_text_with_markdown_table(document)60        bounding_boxes = google_document_api.extract_text_with_bounding_boxes(document)61        62        # Convert PDF to image63        try:64            images = convert_from_path(tmp_pdf_path)65            if not images:66                raise ValueError("No pages found in PDF")67            page_image = images[0]  # Assuming single page for now68        except Exception as e:69            st.error(f"Error converting PDF to image: {str(e)}")70            # Create a placeholder image71            page_image = Image.new('RGB', (800, 600), color='white')72            draw = ImageDraw.Draw(page_image)73            draw.text((400, 300), "PDF conversion failed", fill='black', anchor='mm')74        75        # Process image for comparison: standardize size and optimize quality76        processed_image, quality, file_size = ImageUtils.process_image_for_comparison(77            page_image, 78            target_size=(1200, 1600),  # Standard size for comparison79            max_size_bytes=1024 * 1024  # 1MB limit80        )81        82        # Convert processed image to base64 for API83        image_base64 = ImageUtils.image_to_base64_optimized(84            page_image,85            target_size=(1200, 1600),86            max_size_bytes=1024 * 102487        )88        89        # Scan for barcodes90        barcode = Barcode()91        barcode_results = barcode.scan_and_validate(page_image)92        93        # Clean up temporary file94        if os.path.exists(tmp_pdf_path):95            os.unlink(tmp_pdf_path)96        97        return {98            'text_content': text_content,99            'bounding_boxes': bounding_boxes,100            'image': processed_image,  # Use the processed image101            'original_image': page_image,  # Keep original for reference102            'image_base64': image_base64,103            'barcode_results': barcode_results,104            'file_name': file_name,105            'image_quality': quality,106            'image_size_bytes': file_size107        }108        109    except Exception as e:110        st.error(f"Error processing PDF {file_name}: {str(e)}")111        return None112 113def compare_artworks_with_claude(artwork1_data, artwork2_data, model="claude-sonnet-4-20250514"):114    """Compare two artworks using Claude API"""115    116    # Prepare the comparison prompt117    prompt = f"""118You are an expert packaging compliance analyzer. Compare these two artwork PDFs and provide a detailed analysis of their differences and similarities.119 120## Artwork 1: {artwork1_data['file_name']}121**Text Content:**122{artwork1_data['text_content']}123 124**Bounding Box Data:**125{json.dumps(artwork1_data['bounding_boxes'][:10], indent=2) if artwork1_data['bounding_boxes'] else "No text elements detected"}126 127**Barcode Data:**128{json.dumps(artwork1_data['barcode_results'], indent=2) if artwork1_data['barcode_results'] else "No barcodes detected"}129 130## Artwork 2: {artwork2_data['file_name']}131**Text Content:**132{artwork2_data['text_content']}133 134**Bounding Box Data:**135{json.dumps(artwork2_data['bounding_boxes'][:10], indent=2) if artwork2_data['bounding_boxes'] else "No text elements detected"}136 137**Barcode Data:**138{json.dumps(artwork2_data['barcode_results'], indent=2) if artwork2_data['barcode_results'] else "No barcodes detected"}139 140Please provide a comprehensive comparison analysis in the following JSON format:141 142{{143    "overall_similarity": 0.85,144    "comparison_summary": "Brief overview of the comparison results",145    "text_differences": [146        {{147            "category": "Missing Text",148            "artwork1_content": "Text found only in artwork 1",149            "artwork2_content": "Text found only in artwork 2",150            "significance": "HIGH/MEDIUM/LOW",151            "description": "Detailed explanation of the difference"152        }}153    ],154    "layout_differences": [155        {{156            "category": "Position Changes",157            "element": "Element that moved",158            "artwork1_position": "Description of position in artwork 1",159            "artwork2_position": "Description of position in artwork 2",160            "significance": "HIGH/MEDIUM/LOW",161            "description": "Impact of this change"162        }}163    ],164    "barcode_differences": [165        {{166            "category": "Barcode Changes",167            "artwork1_barcodes": "Description of barcodes in artwork 1",168            "artwork2_barcodes": "Description of barcodes in artwork 2",169            "significance": "HIGH/MEDIUM/LOW",170            "description": "Analysis of barcode differences"171        }}172    ],173    "visual_differences": [174        {{175            "category": "Visual Elements",176            "description": "Description of visual differences observed in the images",177            "significance": "HIGH/MEDIUM/LOW",178            "recommendation": "Suggested action or consideration"179        }}180    ],181    "compliance_impact": [182        {{183            "area": "Regulatory compliance area affected",184            "impact": "Description of potential compliance impact",185            "risk_level": "HIGH/MEDIUM/LOW",186            "recommendation": "Recommended action"187        }}188    ],189    "recommendations": [190        "List of actionable recommendations based on the comparison"191    ]192}}193 194Analyze both the textual content and visual elements. Pay special attention to:1951. Missing or changed text elements1962. Repositioned elements that might affect readability1973. Barcode differences that could impact functionality1984. Visual changes that might affect brand consistency or compliance1995. Any changes that could impact regulatory compliance200 201Provide specific, actionable insights that would be valuable for quality control and compliance verification.202"""203 204    try:205        # Initialize Anthropic client206        client = anthropic.Anthropic(api_key=os.getenv('CLAUDE_API_KEY'))207        208        # Create message with both images209        message = client.messages.create(210            model=model,211            max_tokens=4000,212            messages=[213                {214                    "role": "user",215                    "content": [216                        {217                            "type": "text",218                            "text": prompt219                        },220                        {221                            "type": "image",222                            "source": {223                                "type": "base64",224                                "media_type": "image/png",225                                "data": artwork1_data['image_base64']226                            }227                        },228                        {229                            "type": "image",230                            "source": {231                                "type": "base64",232                                "media_type": "image/png",233                                "data": artwork2_data['image_base64']234                            }235                        }236                    ]237                }238            ]239        )240        241        # Parse the response242        response_text = ""243        for content_block in message.content:244            if hasattr(content_block, 'type') and content_block.type == 'text':245                response_text += content_block.text246        247        # Try to extract JSON from the response248        try:249            # Find JSON in the response250            start_idx = response_text.find('{')251            end_idx = response_text.rfind('}') + 1252            253            if start_idx != -1 and end_idx != -1:254                json_str = response_text[start_idx:end_idx]255                comparison_results = json.loads(json_str)256            else:257                # Fallback: create a basic structure with the raw response258                comparison_results = {259                    "overall_similarity": 0.5,260                    "comparison_summary": "Analysis completed but JSON parsing failed",261                    "raw_response": response_text,262                    "text_differences": [],263                    "layout_differences": [],264                    "barcode_differences": [],265                    "visual_differences": [],266                    "compliance_impact": [],267                    "recommendations": ["Review the raw analysis output for detailed insights"]268                }269        except json.JSONDecodeError:270            # Fallback for JSON parsing errors271            comparison_results = {272                "overall_similarity": 0.5,273                "comparison_summary": "Analysis completed but structured parsing failed",274                "raw_response": response_text,275                "text_differences": [],276                "layout_differences": [],277                "barcode_differences": [],278                "visual_differences": [],279                "compliance_impact": [],280                "recommendations": ["Review the raw analysis output for detailed insights"]281            }282        283        return comparison_results284        285    except Exception as e:286        st.error(f"Error calling Claude API: {str(e)}")287        return None288 289def display_comparison_results(results, artwork1_data, artwork2_data):290    """Display the comparison results in a structured format"""291    292    if not results:293        st.error("No comparison results to display")294        return295    296    # Overall Summary297    st.markdown("## ๐Ÿ“Š Comparison Summary")298    299    col1, col2, col3 = st.columns(3)300    with col1:301        similarity = results.get('overall_similarity', 0.5)302        st.metric("Overall Similarity", f"{similarity:.1%}")303    304    with col2:305        total_differences = (306            len(results.get('text_differences', [])) +307            len(results.get('layout_differences', [])) +308            len(results.get('barcode_differences', [])) +309            len(results.get('visual_differences', []))310        )311        st.metric("Total Differences", total_differences)312    313    with col3:314        compliance_impacts = len(results.get('compliance_impact', []))315        st.metric("Compliance Impacts", compliance_impacts)316    317    # Summary description318    if 'comparison_summary' in results:319        st.markdown(f"**Summary:** {results['comparison_summary']}")320    321    # Create tabs for different types of differences322    tabs = st.tabs(["๐Ÿ“ Text Differences", "๐Ÿ“ Layout Changes", "๐Ÿ“ฑ Barcode Changes", "๐ŸŽจ Visual Differences", "โš–๏ธ Compliance Impact", "๐Ÿ’ก Recommendations"])323    324    with tabs[0]:  # Text Differences325        st.markdown("### Text Content Differences")326        text_diffs = results.get('text_differences', [])327        if text_diffs:328            for i, diff in enumerate(text_diffs):329                significance_color = {"HIGH": "๐Ÿ”ด", "MEDIUM": "๐ŸŸก", "LOW": "๐ŸŸข"}.get(diff.get('significance', 'MEDIUM'), "๐ŸŸก")330                331                with st.expander(f"{significance_color} {diff.get('category', 'Text Difference')} - {diff.get('significance', 'MEDIUM')} Impact"):332                    col1, col2 = st.columns(2)333                    with col1:334                        st.markdown(f"**{artwork1_data['file_name']}:**")335                        st.text(diff.get('artwork1_content', 'N/A'))336                    with col2:337                        st.markdown(f"**{artwork2_data['file_name']}:**")338                        st.text(diff.get('artwork2_content', 'N/A'))339                    340                    st.markdown(f"**Description:** {diff.get('description', 'No description available')}")341        else:342            st.info("No significant text differences found")343    344    with tabs[1]:  # Layout Changes345        st.markdown("### Layout and Positioning Changes")346        layout_diffs = results.get('layout_differences', [])347        if layout_diffs:348            for diff in layout_diffs:349                significance_color = {"HIGH": "๐Ÿ”ด", "MEDIUM": "๐ŸŸก", "LOW": "๐ŸŸข"}.get(diff.get('significance', 'MEDIUM'), "๐ŸŸก")350                351                with st.expander(f"{significance_color} {diff.get('category', 'Layout Change')} - {diff.get('significance', 'MEDIUM')} Impact"):352                    st.markdown(f"**Element:** {diff.get('element', 'Unknown element')}")353                    354                    col1, col2 = st.columns(2)355                    with col1:356                        st.markdown(f"**Position in {artwork1_data['file_name']}:**")357                        st.text(diff.get('artwork1_position', 'N/A'))358                    with col2:359                        st.markdown(f"**Position in {artwork2_data['file_name']}:**")360                        st.text(diff.get('artwork2_position', 'N/A'))361                    362                    st.markdown(f"**Impact:** {diff.get('description', 'No description available')}")363        else:364            st.info("No significant layout differences found")365    366    with tabs[2]:  # Barcode Changes367        st.markdown("### Barcode Differences")368        barcode_diffs = results.get('barcode_differences', [])369        if barcode_diffs:370            for diff in barcode_diffs:371                significance_color = {"HIGH": "๐Ÿ”ด", "MEDIUM": "๐ŸŸก", "LOW": "๐ŸŸข"}.get(diff.get('significance', 'MEDIUM'), "๐ŸŸก")372                373                with st.expander(f"{significance_color} {diff.get('category', 'Barcode Change')} - {diff.get('significance', 'MEDIUM')} Impact"):374                    col1, col2 = st.columns(2)375                    with col1:376                        st.markdown(f"**{artwork1_data['file_name']} Barcodes:**")377                        st.text(diff.get('artwork1_barcodes', 'N/A'))378                    with col2:379                        st.markdown(f"**{artwork2_data['file_name']} Barcodes:**")380                        st.text(diff.get('artwork2_barcodes', 'N/A'))381                    382                    st.markdown(f"**Analysis:** {diff.get('description', 'No description available')}")383        else:384            st.info("No significant barcode differences found")385    386    with tabs[3]:  # Visual Differences387        st.markdown("### Visual and Design Differences")388        visual_diffs = results.get('visual_differences', [])389        if visual_diffs:390            for diff in visual_diffs:391                significance_color = {"HIGH": "๐Ÿ”ด", "MEDIUM": "๐ŸŸก", "LOW": "๐ŸŸข"}.get(diff.get('significance', 'MEDIUM'), "๐ŸŸก")392                393                with st.expander(f"{significance_color} {diff.get('category', 'Visual Change')} - {diff.get('significance', 'MEDIUM')} Impact"):394                    st.markdown(f"**Description:** {diff.get('description', 'No description available')}")395                    if 'recommendation' in diff:396                        st.markdown(f"**Recommendation:** {diff['recommendation']}")397        else:398            st.info("No significant visual differences found")399    400    with tabs[4]:  # Compliance Impact401        st.markdown("### Compliance and Regulatory Impact")402        compliance_impacts = results.get('compliance_impact', [])403        if compliance_impacts:404            for impact in compliance_impacts:405                risk_color = {"HIGH": "๐Ÿ”ด", "MEDIUM": "๐ŸŸก", "LOW": "๐ŸŸข"}.get(impact.get('risk_level', 'MEDIUM'), "๐ŸŸก")406                407                with st.expander(f"{risk_color} {impact.get('area', 'Compliance Area')} - {impact.get('risk_level', 'MEDIUM')} Risk"):408                    st.markdown(f"**Impact:** {impact.get('impact', 'No description available')}")409                    st.markdown(f"**Recommendation:** {impact.get('recommendation', 'No recommendation provided')}")410        else:411            st.success("No compliance impacts identified")412    413    with tabs[5]:  # Recommendations414        st.markdown("### Action Items and Recommendations")415        recommendations = results.get('recommendations', [])416        if recommendations:417            for i, rec in enumerate(recommendations, 1):418                st.markdown(f"{i}. {rec}")419        else:420            st.info("No specific recommendations provided")421    422    # Raw response section (collapsible)423    if 'raw_response' in results:424        with st.expander("๐Ÿ” Raw Analysis Output"):425            st.text(results['raw_response'])426 427def display_side_by_side_images(artwork1_data, artwork2_data):428    """Display the two artwork images side by side"""429    st.markdown("## ๐Ÿ–ผ๏ธ Side-by-Side Comparison")430    431    col1, col2 = st.columns(2)432    433    with col1:434        st.markdown(f"### {artwork1_data['file_name']}")435        st.image(ImageUtils.crop_image(artwork1_data['image']), caption=artwork1_data['file_name'], use_container_width=True)436        437        # Display image processing info438        if 'image_quality' in artwork1_data and 'image_size_bytes' in artwork1_data:439            quality = artwork1_data['image_quality']440            size_mb = artwork1_data['image_size_bytes'] / (1024 * 1024)441            st.info(f"๐Ÿ“Š Image Quality: {quality}% | Size: {size_mb:.2f}MB")442        443        # Display extracted data summary444        with st.expander("๐Ÿ“Š Extracted Data Summary"):445            text_elements = len(artwork1_data['bounding_boxes']) if artwork1_data['bounding_boxes'] else 0446            barcodes = len(artwork1_data['barcode_results']) if artwork1_data['barcode_results'] else 0447            st.metric("Text Elements", text_elements)448            st.metric("Barcodes", barcodes)449    450    with col2:451        st.markdown(f"### {artwork2_data['file_name']}")452        st.image(ImageUtils.crop_image(artwork2_data['image']), caption=artwork2_data['file_name'], use_container_width=True)453        454        # Display image processing info455        if 'image_quality' in artwork2_data and 'image_size_bytes' in artwork2_data:456            quality = artwork2_data['image_quality']457            size_mb = artwork2_data['image_size_bytes'] / (1024 * 1024)458            st.info(f"๐Ÿ“Š Image Quality: {quality}% | Size: {size_mb:.2f}MB")459        460        # Display extracted data summary461        with st.expander("๐Ÿ“Š Extracted Data Summary"):462            text_elements = len(artwork2_data['bounding_boxes']) if artwork2_data['bounding_boxes'] else 0463            barcodes = len(artwork2_data['barcode_results']) if artwork2_data['barcode_results'] else 0464            st.metric("Text Elements", text_elements)465            st.metric("Barcodes", barcodes)466 467def main():468    st.set_page_config(layout="wide", page_title="Artwork Comparison Tool")469    470    # Load client artwork files471    client_artwork_files = load_client_artwork_files()472    473    # Initialize session state474    if "artwork1_data" not in st.session_state:475        st.session_state.artwork1_data = None476    if "artwork2_data" not in st.session_state:477        st.session_state.artwork2_data = None478    if "comparison_results" not in st.session_state:479        st.session_state.comparison_results = None480    481    st.title("๐ŸŽจ Artwork Comparison Tool")482    st.write("Compare two packaging artwork PDFs to identify differences in text, layout, barcodes, and visual elements.")483    484    # File selection section485    st.markdown("## ๐Ÿ“ Select Artworks to Compare")486    487    col1, col2 = st.columns(2)488    489    with col1:490        st.markdown("### ๐ŸŽจ Artwork 1")491        492        # Create tabs for client files vs upload493        art1_tab1, art1_tab2 = st.tabs(["๐Ÿ“ Client Files", "๐Ÿ“ค Upload New"])494        495        with art1_tab1:496            if client_artwork_files:497                art1_options = ["Select artwork 1..."] + [f["name"] for f in client_artwork_files]498                selected_art1_file = st.selectbox("Choose artwork 1:", art1_options, key="art1_select")499                500                if selected_art1_file != "Select artwork 1...":501                    # Find and load the selected file502                    for file_info in client_artwork_files:503                        if file_info["name"] == selected_art1_file:504                            file_content = load_artwork_content(file_info)505                            if file_content:506                                import io507                                temp_file = io.BytesIO(file_content)508                                temp_file.name = file_info["name"]509                                510                                # Extract data from the artwork511                                with st.spinner("Processing artwork 1..."):512                                    st.session_state.artwork1_data = extract_pdf_data(temp_file, file_info["name"])513                                514                                if st.session_state.artwork1_data:515                                    st.success(f"โœ… Loaded artwork 1: {selected_art1_file}")516                            break517            else:518                st.info("No client artwork files found")519        520        with art1_tab2:521            artwork1_file = st.file_uploader("Upload Artwork 1 (PDF)", type=["pdf"], key="art1_upload")522            523            if artwork1_file:524                with st.spinner("Processing artwork 1..."):525                    st.session_state.artwork1_data = extract_pdf_data(artwork1_file, artwork1_file.name)526                527                if st.session_state.artwork1_data:528                    st.success(f"โœ… Uploaded artwork 1: {artwork1_file.name}")529    530    with col2:531        st.markdown("### ๐ŸŽจ Artwork 2")532        533        # Create tabs for client files vs upload534        art2_tab1, art2_tab2 = st.tabs(["๐Ÿ“ Client Files", "๐Ÿ“ค Upload New"])535        536        with art2_tab1:537            if client_artwork_files:538                art2_options = ["Select artwork 2..."] + [f["name"] for f in client_artwork_files]539                selected_art2_file = st.selectbox("Choose artwork 2:", art2_options, key="art2_select")540                541                if selected_art2_file != "Select artwork 2...":542                    # Find and load the selected file543                    for file_info in client_artwork_files:544                        if file_info["name"] == selected_art2_file:545                            file_content = load_artwork_content(file_info)546                            if file_content:547                                import io548                                temp_file = io.BytesIO(file_content)549                                temp_file.name = file_info["name"]550                                551                                # Extract data from the artwork552                                with st.spinner("Processing artwork 2..."):553                                    st.session_state.artwork2_data = extract_pdf_data(temp_file, file_info["name"])554                                555                                if st.session_state.artwork2_data:556                                    st.success(f"โœ… Loaded artwork 2: {selected_art2_file}")557                            break558            else:559                st.info("No client artwork files found")560        561        with art2_tab2:562            artwork2_file = st.file_uploader("Upload Artwork 2 (PDF)", type=["pdf"], key="art2_upload")563            564            if artwork2_file:565                with st.spinner("Processing artwork 2..."):566                    st.session_state.artwork2_data = extract_pdf_data(artwork2_file, artwork2_file.name)567                568                if st.session_state.artwork2_data:569                    st.success(f"โœ… Uploaded artwork 2: {artwork2_file.name}")570    571    # Display images side by side if both are loaded572    if st.session_state.artwork1_data and st.session_state.artwork2_data:573        display_side_by_side_images(st.session_state.artwork1_data, st.session_state.artwork2_data)574    575    # Model selection576    model_option = "claude-sonnet-4-20250514"577    578    # Comparison button579    if st.button("๐Ÿ” Compare Artworks", type="primary"):580        if st.session_state.artwork1_data and st.session_state.artwork2_data:581            with st.spinner("Analyzing artworks with Claude..."):582                st.session_state.comparison_results = compare_artworks_with_claude(583                    st.session_state.artwork1_data,584                    st.session_state.artwork2_data,585                    model=model_option586                )587            588            if st.session_state.comparison_results:589                st.success("โœ… Comparison analysis complete!")590            else:591                st.error("โŒ Comparison analysis failed")592        else:593            st.warning("โš ๏ธ Please select or upload both artworks before comparing")594    595    # Display comparison results596    if st.session_state.comparison_results:597        display_comparison_results(598            st.session_state.comparison_results,599            st.session_state.artwork1_data,600            st.session_state.artwork2_data601        )602    603    # Add helpful information604    st.markdown("---")605    st.markdown("""606    ### ๐Ÿ› ๏ธ How It Works607    1. **Extract Content**: The tool extracts text, bounding boxes, images, and barcodes from both PDFs608    2. **AI Analysis**: Claude analyzes the extracted data and visual elements to identify differences609    3. **Structured Results**: Differences are categorized by type (text, layout, barcode, visual) and significance610    4. **Compliance Assessment**: Potential compliance impacts are identified with risk levels and recommendations611    612    ### ๐ŸŽฏ Use Cases613    - **Quality Control**: Verify artwork changes between versions614    - **Brand Consistency**: Ensure visual elements remain consistent615    - **Compliance Review**: Identify changes that might affect regulatory compliance616    - **Change Documentation**: Track and document artwork modifications617    """)618 619if __name__ == "__main__":620    main()