', unsafe_allow_html=True)
-
- # Handle sample document recreation if needed
- if process_button and st.session_state.processed_document_active and st.session_state.original_sample_bytes is not None:
- # Recreate the uploaded file from stored bytes
- from io import BytesIO
- import mimetypes
-
- # Determine mime type based on file extension
- file_ext = os.path.splitext(st.session_state.original_sample_name)[1].lower()
- if file_ext == '.pdf':
- mime_type = 'application/pdf'
- elif file_ext in ['.jpg', '.jpeg']:
- mime_type = 'image/jpeg'
- elif file_ext == '.png':
- mime_type = 'image/png'
- else:
- mime_type = mimetypes.guess_type(st.session_state.original_sample_name)[0] or 'application/octet-stream'
+ # Apply preprocessing if needed
+ if any(preprocessing_options.values()) and file_type == "image":
+ status_text.text("Applying image preprocessing...")
+ processed_bytes = preprocess_image(uploaded_file.getvalue(), preprocessing_options)
- # Create a synthetic file-like object with the same interface as UploadedFile
- uploaded_file = type('obj', (object,), {
- 'name': st.session_state.original_sample_name,
- 'getvalue': lambda: st.session_state.original_sample_bytes,
- 'read': lambda: st.session_state.original_sample_bytes,
- 'seek': lambda x: None,
- 'type': mime_type
- })
+ # Save processed image to temp file
+ with tempfile.NamedTemporaryFile(delete=False, suffix=Path(uploaded_file.name).suffix) as proc_tmp:
+ proc_tmp.write(processed_bytes)
+ temp_path = proc_tmp.name
- # Empty container for progress indicators - will be filled during processing
- # Positioned right after the process button for better visibility
- progress_placeholder = st.empty()
+ # Get file size in MB
+ file_size_mb = os.path.getsize(temp_path) / (1024 * 1024)
- # Image preprocessing preview - show if image file and preprocessing options are set
- # Remove the document active check to show preview immediately after selection
- if (any(sidebar_options["preprocessing_options"].values()) and
- uploaded_file.type.startswith('image/')):
-
- st.markdown("**Preprocessed Preview**")
- try:
- # Create a container for the preview
- with st.container():
- processed_bytes = preprocess_image(uploaded_file.getvalue(), sidebar_options["preprocessing_options"])
- # Convert image to base64 and display as HTML to avoid fullscreen button
- img_data = base64.b64encode(processed_bytes).decode()
- img_html = f''
- st.markdown(img_html, unsafe_allow_html=True)
-
- # Show preprocessing metadata in a well-formatted caption
- meta_items = []
- # Only include document type in the list if actual preprocessing is applied
- has_active_preprocessing = (
- sidebar_options["preprocessing_options"].get("grayscale", False) or
- sidebar_options["preprocessing_options"].get("denoise", False) or
- sidebar_options["preprocessing_options"].get("contrast", 0) != 0 or
- sidebar_options["preprocessing_options"].get("rotation", 0) != 0
- )
-
- # Only show document type if there's actual preprocessing being applied
- if has_active_preprocessing and sidebar_options["preprocessing_options"].get("document_type", "standard") != "standard":
- meta_items.append(f"Document type ({sidebar_options['preprocessing_options']['document_type']})")
- if sidebar_options["preprocessing_options"].get("grayscale", False):
- meta_items.append("Grayscale")
- if sidebar_options["preprocessing_options"].get("denoise", False):
- meta_items.append("Denoise")
- if sidebar_options["preprocessing_options"].get("contrast", 0) != 0:
- meta_items.append(f"Contrast ({sidebar_options['preprocessing_options']['contrast']})")
- if sidebar_options["preprocessing_options"].get("rotation", 0) != 0:
- meta_items.append(f"Rotation ({sidebar_options['preprocessing_options']['rotation']}°)")
-
- # Only show "Applied:" if there are actual preprocessing steps
- if meta_items:
- meta_text = "Applied: " + ", ".join(meta_items)
- st.caption(meta_text)
- except Exception as e:
- st.error(f"Error in preprocessing: {str(e)}")
- st.info("Try using grayscale preprocessing for PNG images with transparency")
+ # Check if file exceeds API limits (50 MB)
+ if file_size_mb > 50:
+ st.error(f"File too large ({file_size_mb:.1f} MB). Maximum file size allowed by Mistral API is 50MB.")
+ return {
+ "file_name": uploaded_file.name,
+ "topics": ["Document"],
+ "languages": ["English"],
+ "confidence_score": 0.0,
+ "error": f"File size {file_size_mb:.2f} MB exceeds Mistral API limit of 50 MB",
+ "ocr_contents": {
+ "error": f"Failed to process file: File size {file_size_mb:.2f} MB exceeds Mistral API limit of 50 MB",
+ "partial_text": "Document could not be processed due to size limitations."
+ }
+ }
- # Container for success message (will be filled after processing)
- metadata_placeholder = st.empty()
-
- # Check if this is an auto-processing situation
- auto_processing = st.session_state.auto_process_sample and not st.session_state.processed_document_active
+ # Update progress
+ progress_bar.progress(40)
+ status_text.text("Processing document with OCR...")
- # Show a message if auto-processing is happening
- auto_processing_message = st.empty()
- if auto_processing:
- auto_processing_message.info("Automatically processing sample document...")
+ # Process the file with file size information for automatic page limiting
+ # Make sure we're using the latest mistral-ocr model
+ # See https://docs.mistral.ai/capabilities/document/ for more info
+ result = processor.process_file(temp_path, file_type=file_type, use_vision=use_vision, file_size_mb=file_size_mb)
- # Determine if we should process the document
- # Either process button was clicked OR auto-processing is happening
- should_process = process_button or auto_processing
-
- if should_process:
- # Reset auto-process flag to avoid processing on next rerun
- if st.session_state.auto_process_sample:
- st.session_state.auto_process_sample = False
-
- # Move the progress indicator reference to just below the button
- progress_reporter = ProgressReporter(progress_placeholder).setup()
+ # Complete progress
+ progress_bar.progress(100)
+ status_text.empty()
- try:
- # Process the document, capturing both result and temp file paths
- # Modified to pass existing temp_file_paths to avoid resource leaks
- existing_temp_paths = []
- if 'temp_file_paths' in st.session_state:
- existing_temp_paths = st.session_state.temp_file_paths
-
- result = process_file(
- uploaded_file=uploaded_file,
- use_vision=sidebar_options["use_vision"],
- preprocessing_options=sidebar_options["preprocessing_options"],
- progress_reporter=progress_reporter,
- pdf_dpi=sidebar_options.get("pdf_dpi", 150),
- max_pages=sidebar_options.get("max_pages", 3),
- pdf_rotation=sidebar_options.get("pdf_rotation", 0),
- custom_prompt=sidebar_options.get("custom_prompt", ""),
- perf_mode=sidebar_options.get("perf_mode", "Quality"),
- use_segmentation=sidebar_options.get("use_segmentation", False)
- )
-
- # Ensure temp_file_paths in session state is updated with any new paths
- # This is critical for proper resource cleanup when document is closed
- if 'has_images' in result and result['has_images']:
- logger.info("Document has images, ensuring temp files are tracked")
- if 'temp_file_paths' not in st.session_state:
- st.session_state.temp_file_paths = []
-
- # Handle text-only OCR results (like the Milgram flier)
- if ('ocr_contents' in result and
- 'raw_text' in result['ocr_contents'] and
- len(result['ocr_contents']) <= 2 and # Only raw_text and possibly one other field
- 'has_images' not in result):
- logger.info("Text-only OCR detected, handling as special case")
- # Ensure raw_text is properly formatted as markdown
- raw_text = result['ocr_contents']['raw_text']
- # If we don't have other structured content, set a placeholder title
- if 'title' not in result['ocr_contents']:
- result['ocr_contents']['title'] = "Document Text"
-
- # Display success message at the top of results, before any previews
- with left_col:
- # First show the success message (full width)
- st.success("**Document processed successfully**")
-
- # Then show the close button (also full width, positioned to left)
- st.button("Close Document",
- key="close_document_btn",
- type="secondary",
- on_click=close_document)
-
- # Add a small spacer
- st.markdown("", unsafe_allow_html=True)
-
- # Display results
- display_results(result, right_col, sidebar_options.get("custom_prompt", ""))
-
- # Set processed_document_active to True when a new document is processed
- st.session_state.processed_document_active = True
-
- # Clear the auto-processing message
- auto_processing_message.empty()
-
- # Store information about this processed file to track when new files are uploaded
- if uploaded_file is not None:
- st.session_state.last_processed_file = current_file_identifier
-
- # Store the result in the previous results list
- # Add timestamp to result for history tracking
- result_copy = result.copy()
- result_copy['timestamp'] = format_timestamp()
-
- # Store if this was a sample document
- if 'is_sample_document' in st.session_state and st.session_state.is_sample_document:
- result_copy['sample_document'] = True
-
- # Add to session state, keeping the most recent 20 results
- st.session_state.previous_results.insert(0, result_copy)
- if len(st.session_state.previous_results) > 20:
- st.session_state.previous_results = st.session_state.previous_results[:20]
-
- except Exception as e:
- st.error(f"Error processing document: {str(e)}")
-
- # Log the error
- import logging
- logging.error(f"Document processing error: {str(e)}", exc_info=True)
+ return result
+ except Exception as e:
+ progress_bar.progress(100)
+ status_text.empty()
+ st.error(f"Error during processing: {str(e)}")
+ raise
+ finally:
+ # Clean up the temporary file
+ if os.path.exists(temp_path):
+ os.unlink(temp_path)
+
+# App title and description
+st.title("Historical Document OCR")
+st.subheader("Powered by Mistral AI")
+
+# Create main layout with tabs and columns
+main_tab1, main_tab2 = st.tabs(["Document Processing", "About"])
-def main():
- """Main application function"""
- # Initialize session state
- init_session_state()
+with main_tab1:
+ # Create a two-column layout for file upload and preview
+ upload_col, preview_col = st.columns([1, 1])
- # Handle any required cleanup at the start of execution
- # CRITICAL: This two-phase state cleanup pattern is essential for Streamlit's execution model.
- # When close_clicked is True, we need to restart the app's execution with a clean slate.
- # DO NOT REMOVE OR MODIFY this pattern as it ensures proper UI cleanup.
- if st.session_state.get('close_clicked', False):
- # Reset the flag - cleanup has been handled
- st.session_state.close_clicked = False
- # Don't do anything else in this run - force a clean restart
- st.rerun()
+ # File uploader in the left column
+ with upload_col:
+ st.markdown("""
+ Upload an image or PDF file to get started.
- # Initialize new flag for redirecting to processing tab
- if 'redirect_to_processing' not in st.session_state:
- st.session_state.redirect_to_processing = False
+ Using the latest `mistral-ocr-latest` model for advanced document understanding.
+ """)
+ uploaded_file = st.file_uploader("Choose a file", type=["pdf", "png", "jpg", "jpeg"], help="Limit 50MB per file")
+
+# Sidebar with options
+with st.sidebar:
+ st.header("Options")
+
+ # Model options
+ st.subheader("Model Settings")
+ use_vision = st.checkbox("Use Vision Model", value=True,
+ help="For image files, use the vision model for improved analysis (may be slower)")
- # Apply custom CSS
- from ui.layout import load_css
- load_css()
+ # Image preprocessing options (collapsible)
+ st.subheader("Image Preprocessing")
+ with st.expander("Preprocessing Options"):
+ preprocessing_options = {}
+ preprocessing_options["grayscale"] = st.checkbox("Convert to Grayscale",
+ help="Convert image to grayscale before OCR")
+ preprocessing_options["threshold"] = st.checkbox("Apply Thresholding",
+ help="Apply adaptive thresholding to enhance text")
+ preprocessing_options["denoise"] = st.checkbox("Denoise Image",
+ help="Remove noise from the image")
+ preprocessing_options["contrast"] = st.slider("Adjust Contrast", -5, 5, 0,
+ help="Adjust image contrast (-5 to +5)")
- # Create sidebar options
- sidebar_options = create_sidebar_options()
+ # PDF options (collapsible)
+ st.subheader("PDF Options")
+ with st.expander("PDF Settings"):
+ pdf_dpi = st.slider("PDF Resolution (DPI)", 72, 300, 150,
+ help="Higher DPI gives better quality but slower processing")
+ max_pages = st.number_input("Maximum Pages to Process", 1, 20, 5,
+ help="Limit number of pages to process")
+
+# About tab content
+with main_tab2:
+ st.markdown("""
+ ### About This Application
- # Create main layout with tabs - simpler, more compact approach
- tab_names = ["Document Processing", "Sample Documents", "Learn More"]
- main_tab1, main_tab2, main_tab3 = st.tabs(tab_names)
+ This app uses [Mistral AI's Document OCR](https://docs.mistral.ai/capabilities/document/) to extract text and images from historical documents.
- with main_tab1:
- # Create a two-column layout for file upload and results with minimal padding
- st.markdown('', unsafe_allow_html=True)
- # Using a 2:3 column ratio gives more space to the results column
- left_col, right_col = st.columns([2, 3])
+ It can process:
+ - Image files (jpg, png, etc.)
+ - PDF documents (multi-page support)
+
+ The extracted content is processed into structured data based on the document type, combining:
+ - Text extraction with `mistral-ocr-latest`
+ - Analysis with language models
+ - Layout preservation with images
+
+ View results in three formats:
+ - Structured HTML view
+ - Raw JSON (for developers)
+ - Markdown with images (preserves document layout)
+
+ **New Features:**
+ - Image preprocessing for better OCR quality
+ - PDF resolution and page controls
+ - Progress tracking during processing
+ """)
+
+with main_tab1:
+ if uploaded_file is not None:
+ # Check file size (cap at 50MB)
+ file_size_mb = len(uploaded_file.getvalue()) / (1024 * 1024)
- with left_col:
- # Create file uploader
- uploaded_file = create_file_uploader()
-
- # If a real file is uploaded, clear any sample document
- if uploaded_file is not None and 'sample_document' in st.session_state:
- st.session_state.sample_document = None
- st.session_state.is_sample_document = False
+ if file_size_mb > 50:
+ with upload_col:
+ st.error(f"File too large ({file_size_mb:.1f} MB). Maximum file size is 50MB.")
+ st.stop()
+
+ file_ext = Path(uploaded_file.name).suffix.lower()
+
+ # Display document preview in preview column
+ with preview_col:
+ st.subheader("Document Preview")
+ if file_ext == ".pdf":
+ try:
+ # Convert first page of PDF to image for preview
+ pdf_bytes = uploaded_file.getvalue()
+ images = convert_from_bytes(pdf_bytes, first_page=1, last_page=1, dpi=150)
+
+ if images:
+ # Convert PIL image to bytes for Streamlit
+ first_page = images[0]
+ img_bytes = io.BytesIO()
+ first_page.save(img_bytes, format='JPEG')
+ img_bytes.seek(0)
+
+ # Display the PDF preview
+ st.image(img_bytes, caption=f"PDF Preview: {uploaded_file.name}", use_container_width=True)
+ else:
+ st.info(f"PDF uploaded: {uploaded_file.name}")
+ except Exception:
+ # Simply show the file name without an error message
+ st.info(f"PDF uploaded: {uploaded_file.name}")
+ st.info("Click 'Process Document' to analyze the content.")
+ else:
+ st.image(uploaded_file, use_container_width=True)
+
+ # Add image preprocessing preview in a collapsible section if needed
+ if any(preprocessing_options.values()) and uploaded_file.type.startswith('image/'):
+ with st.expander("Image Preprocessing Preview"):
+ preview_cols = st.columns(2)
- # Check if we have a sample document loaded (only if no real file uploaded)
- elif ('sample_document' in st.session_state and
- st.session_state.sample_document is not None):
+ with preview_cols[0]:
+ st.markdown("**Original Image**")
+ st.image(uploaded_file, use_container_width=True)
- # Use the sample document instead of the uploaded file
- uploaded_file = st.session_state.sample_document
+ with preview_cols[1]:
+ st.markdown("**Preprocessed Image**")
+ try:
+ processed_bytes = preprocess_image(uploaded_file.getvalue(), preprocessing_options)
+ st.image(io.BytesIO(processed_bytes), use_container_width=True)
+ except Exception as e:
+ st.error(f"Error in preprocessing: {str(e)}")
+
+ # Process button - flush left with similar padding as file browser
+ with upload_col:
+ process_button = st.button("Process Document", use_container_width=True)
+
+ # Results section
+ if process_button:
+ try:
+ # Get max_pages or default if not available
+ max_pages_value = max_pages if 'max_pages' in locals() else None
- # Just reset the sample document loading flags after it's been used
- if st.session_state.sample_just_loaded:
- st.session_state.sample_just_loaded = False
- st.session_state.sample_document_processed = True
- st.session_state.auto_process_sample = True
-
- # Only process document if available
- if uploaded_file is not None:
- process_document(uploaded_file, left_col, right_col, sidebar_options)
-
- with main_tab2:
- # Sample Documents tab
+ # Call process_file with all options
+ result = process_file(uploaded_file, use_vision, preprocessing_options)
+
+ # Create results tabs for better organization
+ results_tab1, results_tab2 = st.tabs(["Document Analysis", "Technical Details"])
+
+ with results_tab1:
+ # Create two columns for metadata and content
+ meta_col, content_col = st.columns([1, 2])
+
+ with meta_col:
+ st.subheader("Document Metadata")
+ st.success("**Document processed successfully**")
+
+ # Display file info
+ st.write(f"**File Name:** {result.get('file_name', uploaded_file.name)}")
+
+ # Display info if only limited pages were processed
+ if 'limited_pages' in result:
+ st.info(f"Processed {result['limited_pages']['processed']} of {result['limited_pages']['total']} pages")
+
+ # Display languages if available
+ if 'languages' in result:
+ languages = [lang for lang in result['languages'] if lang is not None]
+ if languages:
+ st.write(f"**Languages:** {', '.join(languages)}")
+
+ # Confidence score if available
+ if 'confidence_score' in result:
+ confidence = result['confidence_score']
+ st.write(f"**OCR Confidence:** {confidence:.1%}")
+
+ # Display topics if available
+ if 'topics' in result and result['topics']:
+ st.write(f"**Topics:** {', '.join(result['topics'])}")
+
+ with content_col:
+ st.subheader("Document Contents")
+ if 'ocr_contents' in result:
+ # Check if there are images in the OCR result
+ has_images = False
+ if 'raw_response' in result:
+ try:
+ has_images = any(page.images for page in result['raw_response'].pages)
+ except Exception:
+ has_images = False
+
+ # Create tabs for different views
+ if has_images:
+ view_tab1, view_tab2, view_tab3 = st.tabs(["Structured View", "Raw JSON", "With Images"])
+ else:
+ view_tab1, view_tab2 = st.tabs(["Structured View", "Raw JSON"])
+
+ with view_tab1:
+ # Display in a more user-friendly format based on the content structure
+ html_content = ""
+ if isinstance(result['ocr_contents'], dict):
+ for section, content in result['ocr_contents'].items():
+ if content: # Only display non-empty sections
+ section_title = f"
"
+ st.markdown(f"#### {section.replace('_', ' ').title()}")
+ for k, v in content.items():
+ html_dict += f"
{k}
{v}
"
+ st.markdown(f"**{k}:** {v}")
+ html_dict += "
"
+ html_content += html_dict
+
+ # Add download button in a smaller section
+ with st.expander("Export Content"):
+ # Alternative download button
+ html_bytes = html_content.encode()
+ st.download_button(
+ label="Download as HTML",
+ data=html_bytes,
+ file_name="document_content.html",
+ mime="text/html"
+ )
+
+ with view_tab2:
+ # Show the raw JSON for developers
+ st.json(result)
+
+ if has_images:
+ with view_tab3:
+ # Show loading indicator while preparing images
+ with st.spinner("Preparing document with embedded images..."):
+ try:
+ # Import function
+ try:
+ from ocr_utils import get_combined_markdown
+ except ImportError:
+ st.error("Required module ocr_utils not found.")
+ st.stop()
+
+ # Check if raw_response is available
+ if 'raw_response' not in result:
+ st.warning("Raw OCR response not available. Cannot display images.")
+ st.stop()
+
+ # Validate the raw_response structure before processing
+ if not hasattr(result['raw_response'], 'pages'):
+ st.warning("Invalid OCR response format. Cannot display images.")
+ st.stop()
+
+ # Get the combined markdown with images
+ # Set a flag to compress images if needed
+ compress_images = True
+ max_image_width = 800 # Maximum width for images
+
+ try:
+ # First try to get combined markdown with compressed images
+ if compress_images and hasattr(result['raw_response'], 'pages'):
+ from ocr_utils import get_combined_markdown_compressed
+ combined_markdown = get_combined_markdown_compressed(
+ result['raw_response'],
+ max_width=max_image_width,
+ quality=85
+ )
+ else:
+ # Fall back to regular method if compression not available
+ combined_markdown = get_combined_markdown(result['raw_response'])
+ except (ImportError, AttributeError):
+ # Fall back to regular method
+ combined_markdown = get_combined_markdown(result['raw_response'])
+
+ if not combined_markdown or combined_markdown.strip() == "":
+ st.warning("No image content found in the document.")
+ st.stop()
+
+ # Check if there are many images that might cause loading issues
+ image_count = sum(len(page.images) for page in result['raw_response'].pages if hasattr(page, 'images'))
+
+ # Add warning for image-heavy documents
+ if image_count > 10:
+ st.warning(f"This document contains {image_count} images. Rendering may take longer than usual.")
+
+ # Add CSS to ensure proper spacing and handling of text and images
+ st.markdown("""
+
+ """, unsafe_allow_html=True)
+
+ # For very image-heavy documents, show images in a paginated way
+ if image_count > 20:
+ # Show image content in a paginated way
+ st.write("Document contains many images. Showing in a paginated format:")
+
+ # Split the combined markdown by page separators
+ pages = combined_markdown.split("---")
+
+ # Create a page selector
+ page_num = st.selectbox("Select page to view:",
+ options=list(range(1, len(pages)+1)),
+ index=0)
+
+ # Display only the selected page
+ st.markdown(f"""
+
+ {pages[page_num-1]}
+
+ """, unsafe_allow_html=True)
+
+ # Add note about pagination
+ st.info(f"Showing page {page_num} of {len(pages)}. Select a different page from the dropdown above.")
+ else:
+ # Wrap the markdown in a div with the class for styling
+ st.markdown(f"""
+
+ {combined_markdown}
+
+ """, unsafe_allow_html=True)
+
+ # Add a download button for the combined content
+ st.download_button(
+ label="Download with Images (HTML)",
+ data=f"""
+
+
+
+
+
+ {combined_markdown}
+
+
+ """,
+ file_name="document_with_images.html",
+ mime="text/html"
+ )
+
+ except Exception as e:
+ st.error(f"Could not display document with images: {str(e)}")
+ st.info("Try refreshing or processing the document again.")
+ else:
+ st.error("No OCR content was extracted from the document.")
+
+ with results_tab2:
+ st.subheader("Raw Processing Results")
+ st.json(result)
+
+ except Exception as e:
+ st.error(f"Error processing document: {str(e)}")
+ else:
+ # Display sample images in the main area when no file is uploaded
+ st.info("Upload a document to get started using the file uploader above.")
+
+ # Show example images in a grid
+ st.subheader("Example Documents")
- # Show redirect message if a sample was just loaded
- if st.session_state.get('redirect_to_processing', False):
- st.success("**Sample document loaded!** Please switch to the **Document Processing** tab to view and process it.")
- # Clear the flag after showing the message
- st.session_state.redirect_to_processing = False
+ # Add a sample images container
+ with st.container():
+ # Find sample images from the input directory to display
+ input_dir = Path(__file__).parent / "input"
+ sample_images = []
+ if input_dir.exists():
+ # Find valid jpg files (with size > 50KB to avoid placeholders)
+ sample_images = [
+ path for path in input_dir.glob("*.jpg")
+ if path.stat().st_size > 50000
+ ][:3] # Limit to 3 samples
- show_example_documents()
-
- # Previous results tab temporarily removed
-
- with main_tab3:
- # About tab
- display_about_tab()
-
-# Run the application
-if __name__ == "__main__":
- main()
+ if sample_images:
+ columns = st.columns(3)
+ for i, img_path in enumerate(sample_images):
+ with columns[i % 3]:
+ try:
+ st.image(str(img_path), caption=img_path.name, use_container_width=True)
+ except Exception as e:
+ st.error(f"Error loading image {img_path.name}: {str(e)}")
\ No newline at end of file
diff --git a/backup/app.py b/backup/app.py
new file mode 100644
index 0000000000000000000000000000000000000000..d895b2e927b0769b27073abf1adb99e4261a7795
--- /dev/null
+++ b/backup/app.py
@@ -0,0 +1,535 @@
+import os
+import streamlit as st
+import json
+import sys
+import time
+from pathlib import Path
+import tempfile
+import io
+from pdf2image import convert_from_bytes
+from PIL import Image, ImageEnhance, ImageFilter
+import cv2
+import numpy as np
+
+# Import the StructuredOCR class and config from the local files
+from structured_ocr import StructuredOCR
+from config import MISTRAL_API_KEY
+
+# Set page configuration
+st.set_page_config(
+ page_title="Historical OCR",
+ page_icon="🚀",
+ layout="wide",
+ initial_sidebar_state="expanded"
+)
+
+# Enable caching for expensive operations
+@st.cache_data(ttl=3600, show_spinner=False)
+def convert_pdf_to_images(pdf_bytes, dpi=150):
+ """Convert PDF bytes to a list of images with caching"""
+ try:
+ return convert_from_bytes(pdf_bytes, dpi=dpi)
+ except Exception as e:
+ st.error(f"Error converting PDF: {str(e)}")
+ return []
+
+@st.cache_data(ttl=3600, show_spinner=False)
+def preprocess_image(image_bytes, preprocessing_options):
+ """Preprocess image with selected options"""
+ # Convert bytes to OpenCV format
+ image = Image.open(io.BytesIO(image_bytes))
+ img_array = np.array(image)
+
+ # Apply preprocessing based on selected options
+ if preprocessing_options.get("grayscale", False):
+ img_array = cv2.cvtColor(img_array, cv2.COLOR_RGB2GRAY)
+ img_array = cv2.cvtColor(img_array, cv2.COLOR_GRAY2RGB)
+
+ if preprocessing_options.get("contrast", 0) != 0:
+ contrast_factor = 1 + (preprocessing_options.get("contrast", 0) / 10)
+ image = Image.fromarray(img_array)
+ enhancer = ImageEnhance.Contrast(image)
+ image = enhancer.enhance(contrast_factor)
+ img_array = np.array(image)
+
+ if preprocessing_options.get("denoise", False):
+ img_array = cv2.fastNlMeansDenoisingColored(img_array, None, 10, 10, 7, 21)
+
+ if preprocessing_options.get("threshold", False):
+ # Convert to grayscale if not already
+ if len(img_array.shape) == 3:
+ gray = cv2.cvtColor(img_array, cv2.COLOR_RGB2GRAY)
+ else:
+ gray = img_array
+ # Apply adaptive threshold
+ binary = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
+ cv2.THRESH_BINARY, 11, 2)
+ # Convert back to RGB
+ img_array = cv2.cvtColor(binary, cv2.COLOR_GRAY2RGB)
+
+ # Convert back to PIL Image
+ processed_image = Image.fromarray(img_array)
+
+ # Convert to bytes
+ byte_io = io.BytesIO()
+ processed_image.save(byte_io, format='PNG')
+ byte_io.seek(0)
+
+ return byte_io.getvalue()
+
+# Define functions
+def process_file(uploaded_file, use_vision=True, preprocessing_options=None):
+ """Process the uploaded file and return the OCR results
+
+ Args:
+ uploaded_file: The uploaded file to process
+ use_vision: Whether to use vision model
+ preprocessing_options: Dictionary of preprocessing options
+ """
+ if preprocessing_options is None:
+ preprocessing_options = {}
+
+ # Show progress indicator
+ progress_bar = st.progress(0)
+ status_text = st.empty()
+ status_text.text("Preparing file for processing...")
+
+ # Save the uploaded file to a temporary file
+ with tempfile.NamedTemporaryFile(delete=False, suffix=Path(uploaded_file.name).suffix) as tmp:
+ tmp.write(uploaded_file.getvalue())
+ temp_path = tmp.name
+
+ try:
+ # Check if API key is available
+ if not MISTRAL_API_KEY:
+ # Return dummy data if no API key
+ progress_bar.progress(100)
+ status_text.empty()
+ return {
+ "file_name": uploaded_file.name,
+ "topics": ["Sample Document"],
+ "languages": ["English"],
+ "ocr_contents": {
+ "title": "Sample Document",
+ "content": "This is sample content. To process real documents, please set the MISTRAL_API_KEY environment variable."
+ }
+ }
+
+ # Update progress
+ progress_bar.progress(20)
+ status_text.text("Initializing OCR processor...")
+
+ # Initialize OCR processor
+ processor = StructuredOCR()
+
+ # Determine file type from extension
+ file_ext = Path(uploaded_file.name).suffix.lower()
+ file_type = "pdf" if file_ext == ".pdf" else "image"
+
+ # Apply preprocessing if needed
+ if any(preprocessing_options.values()) and file_type == "image":
+ status_text.text("Applying image preprocessing...")
+ processed_bytes = preprocess_image(uploaded_file.getvalue(), preprocessing_options)
+
+ # Save processed image to temp file
+ with tempfile.NamedTemporaryFile(delete=False, suffix=Path(uploaded_file.name).suffix) as proc_tmp:
+ proc_tmp.write(processed_bytes)
+ temp_path = proc_tmp.name
+
+ # Get file size in MB
+ file_size_mb = os.path.getsize(temp_path) / (1024 * 1024)
+
+ # Check if file exceeds API limits (50 MB)
+ if file_size_mb > 50:
+ st.error(f"File too large ({file_size_mb:.1f} MB). Maximum file size allowed by Mistral API is 50MB.")
+ return {
+ "file_name": uploaded_file.name,
+ "topics": ["Document"],
+ "languages": ["English"],
+ "confidence_score": 0.0,
+ "error": f"File size {file_size_mb:.2f} MB exceeds Mistral API limit of 50 MB",
+ "ocr_contents": {
+ "error": f"Failed to process file: File size {file_size_mb:.2f} MB exceeds Mistral API limit of 50 MB",
+ "partial_text": "Document could not be processed due to size limitations."
+ }
+ }
+
+ # Update progress
+ progress_bar.progress(40)
+ status_text.text("Processing document with OCR...")
+
+ # Process the file with file size information for automatic page limiting
+ # Make sure we're using the latest mistral-ocr model
+ # See https://docs.mistral.ai/capabilities/document/ for more info
+ result = processor.process_file(temp_path, file_type=file_type, use_vision=use_vision, file_size_mb=file_size_mb)
+
+ # Complete progress
+ progress_bar.progress(100)
+ status_text.empty()
+
+ return result
+ except Exception as e:
+ progress_bar.progress(100)
+ status_text.empty()
+ st.error(f"Error during processing: {str(e)}")
+ raise
+ finally:
+ # Clean up the temporary file
+ if os.path.exists(temp_path):
+ os.unlink(temp_path)
+
+# App title and description
+st.title("Historical Document OCR")
+st.subheader("Powered by Mistral AI")
+
+# Create main layout with tabs and columns
+main_tab1, main_tab2 = st.tabs(["Document Processing", "About"])
+
+with main_tab1:
+ # Create a two-column layout for file upload and preview
+ upload_col, preview_col = st.columns([1, 1])
+
+ # File uploader in the left column
+ with upload_col:
+ st.markdown("""
+ Upload an image or PDF file to get started.
+
+ Using the latest `mistral-ocr-latest` model for advanced document understanding.
+ """)
+ uploaded_file = st.file_uploader("Choose a file", type=["pdf", "png", "jpg", "jpeg"])
+
+# Sidebar with options
+with st.sidebar:
+ st.header("Options")
+
+ # Model options
+ st.subheader("Model Settings")
+ use_vision = st.checkbox("Use Vision Model", value=True,
+ help="For image files, use the vision model for improved analysis (may be slower)")
+
+ # Image preprocessing options (collapsible)
+ st.subheader("Image Preprocessing")
+ with st.expander("Preprocessing Options"):
+ preprocessing_options = {}
+ preprocessing_options["grayscale"] = st.checkbox("Convert to Grayscale",
+ help="Convert image to grayscale before OCR")
+ preprocessing_options["threshold"] = st.checkbox("Apply Thresholding",
+ help="Apply adaptive thresholding to enhance text")
+ preprocessing_options["denoise"] = st.checkbox("Denoise Image",
+ help="Remove noise from the image")
+ preprocessing_options["contrast"] = st.slider("Adjust Contrast", -5, 5, 0,
+ help="Adjust image contrast (-5 to +5)")
+
+ # PDF options (collapsible)
+ st.subheader("PDF Options")
+ with st.expander("PDF Settings"):
+ pdf_dpi = st.slider("PDF Resolution (DPI)", 72, 300, 150,
+ help="Higher DPI gives better quality but slower processing")
+ max_pages = st.number_input("Maximum Pages to Process", 1, 20, 5,
+ help="Limit number of pages to process")
+
+# About tab content
+with main_tab2:
+ st.markdown("""
+ ### About This Application
+
+ This app uses [Mistral AI's Document OCR](https://docs.mistral.ai/capabilities/document/) to extract text and images from historical documents.
+
+ It can process:
+ - Image files (jpg, png, etc.)
+ - PDF documents (multi-page support)
+
+ The extracted content is processed into structured data based on the document type, combining:
+ - Text extraction with `mistral-ocr-latest`
+ - Analysis with language models
+ - Layout preservation with images
+
+ View results in three formats:
+ - Structured HTML view
+ - Raw JSON (for developers)
+ - Markdown with images (preserves document layout)
+
+ **New Features:**
+ - Image preprocessing for better OCR quality
+ - PDF resolution and page controls
+ - Progress tracking during processing
+ """)
+
+with main_tab1:
+ if uploaded_file is not None:
+ # Check file size (cap at 50MB)
+ file_size_mb = len(uploaded_file.getvalue()) / (1024 * 1024)
+
+ if file_size_mb > 50:
+ with upload_col:
+ st.error(f"File too large ({file_size_mb:.1f} MB). Maximum file size is 50MB.")
+ st.stop()
+
+ file_ext = Path(uploaded_file.name).suffix.lower()
+
+ # Display document preview in preview column
+ with preview_col:
+ st.subheader("Document Preview")
+ if file_ext == ".pdf":
+ try:
+ # Convert first page of PDF to image for preview
+ pdf_bytes = uploaded_file.getvalue()
+ images = convert_from_bytes(pdf_bytes, first_page=1, last_page=1, dpi=150)
+
+ if images:
+ # Convert PIL image to bytes for Streamlit
+ first_page = images[0]
+ img_bytes = io.BytesIO()
+ first_page.save(img_bytes, format='JPEG')
+ img_bytes.seek(0)
+
+ # Display the PDF preview
+ st.image(img_bytes, caption=f"PDF Preview: {uploaded_file.name}", use_container_width=True)
+ else:
+ st.info(f"PDF uploaded: {uploaded_file.name}")
+ except Exception:
+ # Simply show the file name without an error message
+ st.info(f"PDF uploaded: {uploaded_file.name}")
+ st.info("Click 'Process Document' to analyze the content.")
+ else:
+ st.image(uploaded_file, use_container_width=True)
+
+ # Add image preprocessing preview in a collapsible section if needed
+ if any(preprocessing_options.values()) and uploaded_file.type.startswith('image/'):
+ with st.expander("Image Preprocessing Preview"):
+ preview_cols = st.columns(2)
+
+ with preview_cols[0]:
+ st.markdown("**Original Image**")
+ st.image(uploaded_file, use_container_width=True)
+
+ with preview_cols[1]:
+ st.markdown("**Preprocessed Image**")
+ try:
+ processed_bytes = preprocess_image(uploaded_file.getvalue(), preprocessing_options)
+ st.image(io.BytesIO(processed_bytes), use_container_width=True)
+ except Exception as e:
+ st.error(f"Error in preprocessing: {str(e)}")
+
+ # Process button - flush left with similar padding as file browser
+ with upload_col:
+ process_button = st.button("Process Document", use_container_width=True)
+
+ # Results section
+ if process_button:
+ try:
+ # Get max_pages or default if not available
+ max_pages_value = max_pages if 'max_pages' in locals() else None
+
+ # Call process_file with all options
+ result = process_file(uploaded_file, use_vision, preprocessing_options)
+
+ # Create results tabs for better organization
+ results_tab1, results_tab2 = st.tabs(["Document Analysis", "Technical Details"])
+
+ with results_tab1:
+ # Create two columns for metadata and content
+ meta_col, content_col = st.columns([1, 2])
+
+ with meta_col:
+ st.subheader("Document Metadata")
+ st.success("**Document processed successfully**")
+
+ # Display file info
+ st.write(f"**File Name:** {result.get('file_name', uploaded_file.name)}")
+
+ # Display info if only limited pages were processed
+ if 'limited_pages' in result:
+ st.info(f"Processed {result['limited_pages']['processed']} of {result['limited_pages']['total']} pages")
+
+ # Display languages if available
+ if 'languages' in result:
+ languages = [lang for lang in result['languages'] if lang is not None]
+ if languages:
+ st.write(f"**Languages:** {', '.join(languages)}")
+
+ # Confidence score if available
+ if 'confidence_score' in result:
+ confidence = result['confidence_score']
+ st.write(f"**OCR Confidence:** {confidence:.1%}")
+
+ # Display topics if available
+ if 'topics' in result and result['topics']:
+ st.write(f"**Topics:** {', '.join(result['topics'])}")
+
+ with content_col:
+ st.subheader("Document Contents")
+ if 'ocr_contents' in result:
+ # Check if there are images in the OCR result
+ has_images = False
+ if 'raw_response' in result:
+ try:
+ has_images = any(page.images for page in result['raw_response'].pages)
+ except Exception:
+ has_images = False
+
+ # Create tabs for different views
+ if has_images:
+ view_tab1, view_tab2, view_tab3 = st.tabs(["Structured View", "Raw JSON", "With Images"])
+ else:
+ view_tab1, view_tab2 = st.tabs(["Structured View", "Raw JSON"])
+
+ with view_tab1:
+ # Display in a more user-friendly format based on the content structure
+ html_content = ""
+ if isinstance(result['ocr_contents'], dict):
+ for section, content in result['ocr_contents'].items():
+ if content: # Only display non-empty sections
+ section_title = f"
"
+ st.markdown(f"#### {section.replace('_', ' ').title()}")
+ for k, v in content.items():
+ html_dict += f"
{k}
{v}
"
+ st.markdown(f"**{k}:** {v}")
+ html_dict += "
"
+ html_content += html_dict
+
+ # Add download button in a smaller section
+ with st.expander("Export Content"):
+ # Alternative download button
+ html_bytes = html_content.encode()
+ st.download_button(
+ label="Download as HTML",
+ data=html_bytes,
+ file_name="document_content.html",
+ mime="text/html"
+ )
+
+ with view_tab2:
+ # Show the raw JSON for developers
+ st.json(result)
+
+ if has_images:
+ with view_tab3:
+ # Show loading indicator while preparing images
+ with st.spinner("Preparing document with embedded images..."):
+ try:
+ # Import function
+ try:
+ from ocr_utils import get_combined_markdown
+ except ImportError:
+ st.error("Required module ocr_utils not found.")
+ st.stop()
+
+ # Check if raw_response is available
+ if 'raw_response' not in result:
+ st.warning("Raw OCR response not available. Cannot display images.")
+ st.stop()
+
+ # Validate the raw_response structure before processing
+ if not hasattr(result['raw_response'], 'pages'):
+ st.warning("Invalid OCR response format. Cannot display images.")
+ st.stop()
+
+ # Get the combined markdown with images
+ combined_markdown = get_combined_markdown(result['raw_response'])
+
+ if not combined_markdown or combined_markdown.strip() == "":
+ st.warning("No image content found in the document.")
+ st.stop()
+
+ # Add CSS to ensure proper spacing and handling of text and images
+ st.markdown("""
+
+ """, unsafe_allow_html=True)
+
+ # Wrap the markdown in a div with the class for styling
+ st.markdown(f"""
+
+ {combined_markdown}
+
+ """, unsafe_allow_html=True)
+
+ # Add a download button for the combined content
+ st.download_button(
+ label="Download with Images (HTML)",
+ data=f"""
+
+
+
+
+
+ {combined_markdown}
+
+
+ """,
+ file_name="document_with_images.html",
+ mime="text/html"
+ )
+
+ except Exception as e:
+ st.error(f"Could not display document with images: {str(e)}")
+ st.info("Try refreshing or processing the document again.")
+ else:
+ st.error("No OCR content was extracted from the document.")
+
+ with results_tab2:
+ st.subheader("Raw Processing Results")
+ st.json(result)
+
+ except Exception as e:
+ st.error(f"Error processing document: {str(e)}")
+ else:
+ # Display sample images in the main area when no file is uploaded
+ st.info("Upload a document to get started using the file uploader above.")
+
+ # Show example images in a grid
+ st.subheader("Example Documents")
+
+ # Add a sample images container
+ with st.container():
+ # Find sample images from the input directory to display
+ input_dir = Path(__file__).parent / "input"
+ sample_images = []
+ if input_dir.exists():
+ sample_images = list(input_dir.glob("*.jpg"))[:3] # Limit to 3 samples
+
+ if sample_images:
+ columns = st.columns(3)
+ for i, img_path in enumerate(sample_images):
+ with columns[i % 3]:
+ st.image(str(img_path), caption=img_path.name, use_container_width=True)
\ No newline at end of file
diff --git a/backup/config.py b/backup/config.py
new file mode 100644
index 0000000000000000000000000000000000000000..f999828a8664fad82e03b043f59bfff44e0b0b06
--- /dev/null
+++ b/backup/config.py
@@ -0,0 +1,17 @@
+# config.py
+"""
+Configuration file for Mistral OCR processing.
+Contains API key and other settings.
+"""
+import os
+
+# Your Mistral API key - get from Hugging Face secrets or environment variable
+# The priority order is: HF_SPACES environment var > regular environment var > empty string
+# Note: No default API key is provided for security reasons
+MISTRAL_API_KEY = os.environ.get("HF_MISTRAL_API_KEY", # First check HF-specific env var
+ os.environ.get("MISTRAL_API_KEY", "")) # Then check regular env var
+
+# Model settings
+OCR_MODEL = "mistral-ocr-latest"
+TEXT_MODEL = "ministral-8b-latest"
+VISION_MODEL = "pixtral-12b-latest"
\ No newline at end of file
diff --git a/input/magician-or-bottle-cungerer.jpg b/backup/input/The Magician, or Bottle Cungerer.jpeg
similarity index 100%
rename from input/magician-or-bottle-cungerer.jpg
rename to backup/input/The Magician, or Bottle Cungerer.jpeg
diff --git a/input/baldwin-15th-north.jpg b/backup/input/baldwin-letter-1.jpg
similarity index 100%
rename from input/baldwin-15th-north.jpg
rename to backup/input/baldwin-letter-1.jpg
diff --git a/backup/input/baldwin-letter-2.jpg b/backup/input/baldwin-letter-2.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..b19ec5985abd7d36aef556b7427d2524c54d5d13
--- /dev/null
+++ b/backup/input/baldwin-letter-2.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:8b605a6eabd466da265e9e1aa3576160c4dbee06643ece5a18cdb1e45f3f683a
+size 114136
diff --git a/backup/input/flier.png b/backup/input/flier.png
new file mode 100644
index 0000000000000000000000000000000000000000..a02e7743490614e3f9884a88aa9ad15214609a34
Binary files /dev/null and b/backup/input/flier.png differ
diff --git a/backup/input/letter-1.jpg b/backup/input/letter-1.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..4e6b833fc8ab797f608dfe7c4e92642ca8b773d3
--- /dev/null
+++ b/backup/input/letter-1.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a30d7d9f224c777a1697507200a87e41be5fd590efbe8271fa41dbd8bd8a158d
+size 135244
diff --git a/backup/input/letter-2.jpg b/backup/input/letter-2.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..b19ec5985abd7d36aef556b7427d2524c54d5d13
--- /dev/null
+++ b/backup/input/letter-2.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:8b605a6eabd466da265e9e1aa3576160c4dbee06643ece5a18cdb1e45f3f683a
+size 114136
diff --git a/backup/input/letter-3.jpg b/backup/input/letter-3.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..fa0cc25dd4affd353900e34e9e986d6fa435ee8e
--- /dev/null
+++ b/backup/input/letter-3.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:7fe2d81bb4e8bef7cdbf87c58a8cc180c49c313e5099de167ae37bbbfb895e88
+size 230837
diff --git a/backup/input/magellan-travels.jpg b/backup/input/magellan-travels.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..82e136cd1601c2c3acad8c7599339f84f335f469
--- /dev/null
+++ b/backup/input/magellan-travels.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:ae3e860789e2c3c8032499e5326864294dbc1b01059169fd08203c980577010b
+size 283156
diff --git a/backup/input/menu.pdf b/backup/input/menu.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..276af61cf2f4ac361eaa86b3c430c527b1e59230
--- /dev/null
+++ b/backup/input/menu.pdf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:42d96008f374f5be8046b569c868e33f4e5a0e5e166c245d324b44140c7e6c2e
+size 2554815
diff --git a/backup/input/recipe.jpg b/backup/input/recipe.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..1701f0ba9840b4b978a4bae4d14c2780516c5f26
Binary files /dev/null and b/backup/input/recipe.jpg differ
diff --git a/backup/ocr_utils.py b/backup/ocr_utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..5c8a006402aa612fa1929a60c3754c5d8ee69c43
--- /dev/null
+++ b/backup/ocr_utils.py
@@ -0,0 +1,136 @@
+"""
+Utility functions for OCR processing with Mistral AI.
+Contains helper functions for working with OCR responses and image handling.
+"""
+
+import json
+import base64
+from pathlib import Path
+from typing import Dict, List, Optional, Union
+
+from mistralai import DocumentURLChunk, ImageURLChunk, TextChunk
+
+def replace_images_in_markdown(markdown_str: str, images_dict: dict) -> str:
+ """
+ Replace image placeholders in markdown with base64-encoded images.
+
+ Args:
+ markdown_str: Markdown text containing image placeholders
+ images_dict: Dictionary mapping image IDs to base64 strings
+
+ Returns:
+ Markdown text with images replaced by base64 data
+ """
+ for img_name, base64_str in images_dict.items():
+ markdown_str = markdown_str.replace(
+ f"", f""
+ )
+ return markdown_str
+
+def get_combined_markdown(ocr_response) -> str:
+ """
+ Combine OCR text and images into a single markdown document.
+ Ensures proper spacing between text and images.
+
+ Args:
+ ocr_response: Response from OCR processing containing text and images
+ See https://docs.mistral.ai/capabilities/document/ for API reference
+
+ Returns:
+ Combined markdown string with embedded images
+ """
+ markdowns: list[str] = []
+ # Extract images from page
+ for page in ocr_response.pages:
+ image_data = {}
+ for img in page.images:
+ image_data[img.id] = img.image_base64
+
+ # Replace image placeholders with actual images
+ page_markdown = replace_images_in_markdown(page.markdown, image_data)
+
+ # Ensure proper spacing between paragraphs and images
+ # Add extra newlines between paragraphs to improve rendering
+ page_markdown = page_markdown.replace("\n", "\n\n")
+
+ # Add page separator for multi-page documents
+ markdowns.append(page_markdown)
+
+ # Join pages with clear separators for multi-page documents
+ return "\n\n---\n\n".join(markdowns)
+
+def encode_image_for_api(image_path: Union[str, Path]) -> str:
+ """
+ Encode an image as base64 for API use.
+
+ Args:
+ image_path: Path to the image file
+
+ Returns:
+ Base64 data URL for the image
+ """
+ # Convert to Path object if string
+ image_file = Path(image_path) if isinstance(image_path, str) else image_path
+
+ # Verify image exists
+ if not image_file.is_file():
+ raise FileNotFoundError(f"Image file not found: {image_file}")
+
+ # Encode image as base64
+ encoded = base64.b64encode(image_file.read_bytes()).decode()
+ return f"data:image/jpeg;base64,{encoded}"
+
+def process_image_with_ocr(client, image_path: Union[str, Path], model: str = "mistral-ocr-latest"):
+ """
+ Process an image with OCR and return the response.
+
+ Args:
+ client: Mistral AI client
+ image_path: Path to the image file
+ model: OCR model to use
+
+ Returns:
+ OCR response object
+ """
+ # Encode image as base64
+ base64_data_url = encode_image_for_api(image_path)
+
+ # Process image with OCR
+ image_response = client.ocr.process(
+ document=ImageURLChunk(image_url=base64_data_url),
+ model=model
+ )
+
+ return image_response
+
+def ocr_response_to_json(ocr_response, indent: int = 4) -> str:
+ """
+ Convert OCR response to a formatted JSON string.
+
+ Args:
+ ocr_response: OCR response object
+ indent: Indentation level for JSON formatting
+
+ Returns:
+ Formatted JSON string
+ """
+ # Convert response to JSON
+ response_dict = json.loads(ocr_response.model_dump_json())
+ return json.dumps(response_dict, indent=indent)
+
+# For display in notebooks
+try:
+ from IPython.display import Markdown, display
+
+ def display_ocr_with_images(ocr_response):
+ """
+ Display OCR response with embedded images in IPython environments.
+
+ Args:
+ ocr_response: OCR response object
+ """
+ combined_markdown = get_combined_markdown(ocr_response)
+ display(Markdown(combined_markdown))
+except ImportError:
+ # IPython not available
+ pass
\ No newline at end of file
diff --git a/backup/pdf_ocr.py b/backup/pdf_ocr.py
new file mode 100644
index 0000000000000000000000000000000000000000..44ac2dec6ee5da63f03e3a7264a0c44125617acb
--- /dev/null
+++ b/backup/pdf_ocr.py
@@ -0,0 +1,76 @@
+#!/usr/bin/env python3
+"""
+PDFOCR - Module for processing PDF files with OCR and extracting structured data.
+"""
+
+import json
+from pathlib import Path
+from structured_ocr import StructuredOCR
+
+class PDFOCR:
+ """Class for processing PDF files with OCR and extracting structured data."""
+
+ def __init__(self, api_key=None):
+ """Initialize the PDF OCR processor."""
+ self.processor = StructuredOCR(api_key=api_key)
+
+ def process_pdf(self, pdf_path, use_vision=True):
+ """
+ Process a PDF file with OCR and extract structured data.
+
+ Args:
+ pdf_path: Path to the PDF file
+ use_vision: Whether to use vision model for improved analysis
+
+ Returns:
+ Dictionary with structured OCR results
+ """
+ pdf_path = Path(pdf_path)
+ if not pdf_path.exists():
+ raise FileNotFoundError(f"PDF file not found: {pdf_path}")
+
+ return self.processor.process_file(pdf_path, file_type="pdf", use_vision=use_vision)
+
+ def save_json_output(self, pdf_path, output_path, use_vision=True):
+ """
+ Process a PDF file and save the structured output as JSON.
+
+ Args:
+ pdf_path: Path to the PDF file
+ output_path: Path where to save the JSON output
+ use_vision: Whether to use vision model for improved analysis
+
+ Returns:
+ Path to the saved JSON file
+ """
+ # Process the PDF
+ result = self.process_pdf(pdf_path, use_vision=use_vision)
+
+ # Save the result to JSON
+ output_path = Path(output_path)
+ output_path.parent.mkdir(parents=True, exist_ok=True)
+
+ with open(output_path, 'w') as f:
+ json.dump(result, f, indent=2)
+
+ return output_path
+
+# For testing directly
+if __name__ == "__main__":
+ import sys
+
+ if len(sys.argv) < 2:
+ print("Usage: python pdf_ocr.py [output_path]")
+ sys.exit(1)
+
+ pdf_path = sys.argv[1]
+ output_path = sys.argv[2] if len(sys.argv) > 2 else None
+
+ processor = PDFOCR()
+
+ if output_path:
+ result_path = processor.save_json_output(pdf_path, output_path)
+ print(f"Results saved to: {result_path}")
+ else:
+ result = processor.process_pdf(pdf_path)
+ print(json.dumps(result, indent=2))
\ No newline at end of file
diff --git a/backup/requirements.txt b/backup/requirements.txt
new file mode 100644
index 0000000000000000000000000000000000000000..cd8bfec2f8a8e2662446f6ae690ed53227bf7b47
--- /dev/null
+++ b/backup/requirements.txt
@@ -0,0 +1,10 @@
+streamlit>=1.43.2
+mistralai>=0.0.7
+pydantic>=2.0.0
+pycountry>=23.12.11
+pillow>=10.0.0
+python-multipart>=0.0.6
+pdf2image>=1.17.0
+pytesseract>=0.3.10
+opencv-python-headless>=4.6.0
+numpy>=1.23.5
\ No newline at end of file
diff --git a/backup/structured_ocr.py b/backup/structured_ocr.py
new file mode 100644
index 0000000000000000000000000000000000000000..ff80ba6010cd863225a93486561ee5dd12742c51
--- /dev/null
+++ b/backup/structured_ocr.py
@@ -0,0 +1,414 @@
+import os
+import sys
+import time
+from enum import Enum
+from pathlib import Path
+import json
+import base64
+import pycountry
+import logging
+from pydantic import BaseModel
+from mistralai import Mistral
+from mistralai import DocumentURLChunk, ImageURLChunk, TextChunk
+
+# Configure logging
+logging.basicConfig(level=logging.INFO,
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
+
+# Import utilities for OCR processing
+try:
+ from ocr_utils import replace_images_in_markdown, get_combined_markdown
+except ImportError:
+ # Define fallback functions if module not found
+ def replace_images_in_markdown(markdown_str, images_dict):
+ for img_name, base64_str in images_dict.items():
+ markdown_str = markdown_str.replace(
+ f"", f""
+ )
+ return markdown_str
+
+ def get_combined_markdown(ocr_response):
+ markdowns = []
+ for page in ocr_response.pages:
+ image_data = {}
+ for img in page.images:
+ image_data[img.id] = img.image_base64
+ markdowns.append(replace_images_in_markdown(page.markdown, image_data))
+ return "\n\n".join(markdowns)
+
+# Import config directly (now local to historical-ocr)
+from config import MISTRAL_API_KEY, OCR_MODEL, TEXT_MODEL, VISION_MODEL
+
+# Create language enum for structured output
+languages = {lang.alpha_2: lang.name for lang in pycountry.languages if hasattr(lang, 'alpha_2')}
+
+class LanguageMeta(Enum.__class__):
+ def __new__(metacls, cls, bases, classdict):
+ for code, name in languages.items():
+ classdict[name.upper().replace(' ', '_')] = name
+ return super().__new__(metacls, cls, bases, classdict)
+
+class Language(Enum, metaclass=LanguageMeta):
+ pass
+
+class StructuredOCRModel(BaseModel):
+ file_name: str
+ topics: list[str]
+ languages: list[Language]
+ ocr_contents: dict
+
+class StructuredOCR:
+ def __init__(self, api_key=None):
+ """Initialize the OCR processor with API key"""
+ self.api_key = api_key or MISTRAL_API_KEY
+ self.client = Mistral(api_key=self.api_key)
+
+ def process_file(self, file_path, file_type=None, use_vision=True, max_pages=None, file_size_mb=None, custom_pages=None):
+ """Process a file and return structured OCR results
+
+ Args:
+ file_path: Path to the file to process
+ file_type: 'pdf' or 'image' (will be auto-detected if None)
+ use_vision: Whether to use vision model for improved analysis
+ max_pages: Optional limit on number of pages to process
+ file_size_mb: Optional file size in MB (used for automatic page limiting)
+ custom_pages: Optional list of specific page numbers to process
+
+ Returns:
+ Dictionary with structured OCR results
+ """
+ # Convert file_path to Path object if it's a string
+ file_path = Path(file_path)
+
+ # Auto-detect file type if not provided
+ if file_type is None:
+ suffix = file_path.suffix.lower()
+ file_type = "pdf" if suffix == ".pdf" else "image"
+
+ # Get file size if not provided
+ if file_size_mb is None and file_path.exists():
+ file_size_mb = file_path.stat().st_size / (1024 * 1024) # Convert bytes to MB
+
+ # Check if file exceeds API limits (50 MB)
+ if file_size_mb and file_size_mb > 50:
+ logging.warning(f"File size {file_size_mb:.2f} MB exceeds Mistral API limit of 50 MB")
+ return {
+ "file_name": file_path.name,
+ "topics": ["Document"],
+ "languages": ["English"],
+ "confidence_score": 0.0,
+ "error": f"File size {file_size_mb:.2f} MB exceeds API limit of 50 MB",
+ "ocr_contents": {
+ "error": f"Failed to process file: File size {file_size_mb:.2f} MB exceeds Mistral API limit of 50 MB",
+ "partial_text": "Document could not be processed due to size limitations."
+ }
+ }
+
+ # For PDF files, limit pages based on file size if no explicit limit is given
+ if file_type == "pdf" and file_size_mb and max_pages is None and custom_pages is None:
+ if file_size_mb > 100: # Very large files
+ max_pages = 3
+ elif file_size_mb > 50: # Large files
+ max_pages = 5
+ elif file_size_mb > 20: # Medium files
+ max_pages = 10
+ else: # Small files
+ max_pages = None # Process all pages
+
+ # Start processing timer
+ start_time = time.time()
+
+ # Read and process the file
+ if file_type == "pdf":
+ result = self._process_pdf(file_path, use_vision, max_pages, custom_pages)
+ else:
+ result = self._process_image(file_path, use_vision)
+
+ # Add processing time information
+ processing_time = time.time() - start_time
+ result['processing_time'] = processing_time
+
+ # Add a default confidence score if not present
+ if 'confidence_score' not in result:
+ result['confidence_score'] = 0.85 # Default confidence
+
+ return result
+
+ def _process_pdf(self, file_path, use_vision=True, max_pages=None, custom_pages=None):
+ """Process a PDF file with OCR
+
+ Args:
+ file_path: Path to the PDF file
+ use_vision: Whether to use vision model
+ max_pages: Optional limit on the number of pages to process
+ custom_pages: Optional list of specific page numbers to process
+ """
+ logger = logging.getLogger("pdf_processor")
+ logger.info(f"Processing PDF: {file_path}")
+
+ try:
+ # Upload the PDF file
+ logger.info("Uploading PDF file to Mistral API")
+ uploaded_file = self.client.files.upload(
+ file={
+ "file_name": file_path.stem,
+ "content": file_path.read_bytes(),
+ },
+ purpose="ocr",
+ )
+
+ # Get a signed URL for the uploaded file
+ signed_url = self.client.files.get_signed_url(file_id=uploaded_file.id, expiry=1)
+
+ # Process the PDF with OCR
+ logger.info(f"Processing PDF with OCR using {OCR_MODEL}")
+ pdf_response = self.client.ocr.process(
+ document=DocumentURLChunk(document_url=signed_url.url),
+ model=OCR_MODEL,
+ include_image_base64=True
+ )
+
+ # Limit pages if requested
+ pages_to_process = pdf_response.pages
+ total_pages = len(pdf_response.pages)
+ limited_pages = False
+
+ logger.info(f"PDF has {total_pages} total pages")
+
+ # Handle custom page selection if provided
+ if custom_pages:
+ # Convert to 0-based indexing and filter valid page numbers
+ valid_indices = [i-1 for i in custom_pages if 0 < i <= total_pages]
+ if valid_indices:
+ pages_to_process = [pdf_response.pages[i] for i in valid_indices]
+ limited_pages = True
+ logger.info(f"Processing {len(valid_indices)} custom-selected pages")
+ # Otherwise handle max_pages limit
+ elif max_pages and total_pages > max_pages:
+ pages_to_process = pages_to_process[:max_pages]
+ limited_pages = True
+ logger.info(f"Processing only first {max_pages} pages out of {total_pages} total pages")
+
+ # Calculate average confidence score based on OCR response if available
+ confidence_score = 0.0
+ try:
+ # Some OCR APIs provide confidence scores
+ confidence_values = []
+ for page in pages_to_process:
+ if hasattr(page, 'confidence'):
+ confidence_values.append(page.confidence)
+
+ if confidence_values:
+ confidence_score = sum(confidence_values) / len(confidence_values)
+ else:
+ confidence_score = 0.85 # Default if no confidence scores available
+ except:
+ confidence_score = 0.85 # Default fallback
+
+ # Combine pages' markdown into a single string
+ all_markdown = "\n\n".join([page.markdown for page in pages_to_process])
+
+ # Extract structured data using the appropriate model
+ if use_vision:
+ # Get base64 of first page for vision model
+ first_page_image = None
+ if pages_to_process and pages_to_process[0].images:
+ first_page_image = pages_to_process[0].images[0].image_base64
+
+ if first_page_image:
+ # Use vision model
+ logger.info(f"Using vision model: {VISION_MODEL}")
+ result = self._extract_structured_data_with_vision(first_page_image, all_markdown, file_path.name)
+ else:
+ # Fall back to text-only model if no image available
+ logger.info(f"No images in PDF, falling back to text model: {TEXT_MODEL}")
+ result = self._extract_structured_data_text_only(all_markdown, file_path.name)
+ else:
+ # Use text-only model
+ logger.info(f"Using text-only model: {TEXT_MODEL}")
+ result = self._extract_structured_data_text_only(all_markdown, file_path.name)
+
+ # Add page limit info to result if needed
+ if limited_pages:
+ result['limited_pages'] = {
+ 'processed': len(pages_to_process),
+ 'total': total_pages
+ }
+
+ # Add confidence score
+ result['confidence_score'] = confidence_score
+
+ # Store the raw OCR response for image rendering
+ result['raw_response'] = pdf_response
+
+ logger.info(f"PDF processing completed successfully")
+ return result
+
+ except Exception as e:
+ logger.error(f"Error processing PDF: {str(e)}")
+ # Return basic result on error
+ return {
+ "file_name": file_path.name,
+ "topics": ["Document"],
+ "languages": ["English"],
+ "confidence_score": 0.0,
+ "error": str(e),
+ "ocr_contents": {
+ "error": f"Failed to process PDF: {str(e)}",
+ "partial_text": "Document could not be fully processed."
+ }
+ }
+
+ def _process_image(self, file_path, use_vision=True):
+ """Process an image file with OCR"""
+ logger = logging.getLogger("image_processor")
+ logger.info(f"Processing image: {file_path}")
+
+ try:
+ # Read and encode the image file
+ logger.info("Encoding image for API")
+ encoded_image = base64.b64encode(file_path.read_bytes()).decode()
+ base64_data_url = f"data:image/jpeg;base64,{encoded_image}"
+
+ # Process the image with OCR
+ logger.info(f"Processing image with OCR using {OCR_MODEL}")
+ image_response = self.client.ocr.process(
+ document=ImageURLChunk(image_url=base64_data_url),
+ model=OCR_MODEL,
+ include_image_base64=True
+ )
+
+ # Get the OCR markdown from the first page
+ image_ocr_markdown = image_response.pages[0].markdown if image_response.pages else ""
+
+ # Calculate confidence score if available
+ confidence_score = 0.85 # Default value
+ try:
+ if hasattr(image_response.pages[0], 'confidence'):
+ confidence_score = image_response.pages[0].confidence
+ except:
+ pass
+
+ # Extract structured data using the appropriate model
+ if use_vision:
+ logger.info(f"Using vision model: {VISION_MODEL}")
+ result = self._extract_structured_data_with_vision(base64_data_url, image_ocr_markdown, file_path.name)
+ else:
+ logger.info(f"Using text-only model: {TEXT_MODEL}")
+ result = self._extract_structured_data_text_only(image_ocr_markdown, file_path.name)
+
+ # Add confidence score
+ result['confidence_score'] = confidence_score
+
+ # Store the raw OCR response for image rendering
+ result['raw_response'] = image_response
+
+ logger.info("Image processing completed successfully")
+ return result
+
+ except Exception as e:
+ logger.error(f"Error processing image: {str(e)}")
+ # Return basic result on error
+ return {
+ "file_name": file_path.name,
+ "topics": ["Document"],
+ "languages": ["English"],
+ "confidence_score": 0.0,
+ "error": str(e),
+ "ocr_contents": {
+ "error": f"Failed to process image: {str(e)}",
+ "partial_text": "Image could not be processed."
+ }
+ }
+
+ def _extract_structured_data_with_vision(self, image_base64, ocr_markdown, filename):
+ """Extract structured data using vision model"""
+ try:
+ # Parse with vision model with a timeout
+ chat_response = self.client.chat.parse(
+ model=VISION_MODEL,
+ messages=[
+ {
+ "role": "user",
+ "content": [
+ ImageURLChunk(image_url=image_base64),
+ TextChunk(text=(
+ f"This is a historical document's OCR in markdown:\n"
+ f"\n{ocr_markdown}\n.\n"
+ f"Convert this into a structured JSON response with the OCR contents in a sensible dictionary. "
+ f"Extract topics, languages, and organize the content logically."
+ ))
+ ],
+ },
+ ],
+ response_format=StructuredOCRModel,
+ temperature=0
+ )
+
+ # Convert the response to a dictionary
+ result = json.loads(chat_response.choices[0].message.parsed.json())
+
+ # Ensure languages is a list of strings, not Language enum objects
+ if 'languages' in result:
+ result['languages'] = [str(lang) for lang in result.get('languages', [])]
+
+ except Exception as e:
+ # Fall back to text-only model if vision model fails
+ print(f"Vision model failed: {str(e)}. Falling back to text-only model.")
+ result = self._extract_structured_data_text_only(ocr_markdown, filename)
+
+ return result
+
+ def _extract_structured_data_text_only(self, ocr_markdown, filename):
+ """Extract structured data using text-only model"""
+ try:
+ # Parse with text-only model with a timeout
+ chat_response = self.client.chat.parse(
+ model=TEXT_MODEL,
+ messages=[
+ {
+ "role": "user",
+ "content": f"This is a historical document's OCR in markdown:\n"
+ f"\n{ocr_markdown}\n.\n"
+ f"Convert this into a structured JSON response with the OCR contents. "
+ f"Extract topics, languages, and organize the content logically."
+ },
+ ],
+ response_format=StructuredOCRModel,
+ temperature=0
+ )
+
+ # Convert the response to a dictionary
+ result = json.loads(chat_response.choices[0].message.parsed.json())
+
+ # Ensure languages is a list of strings, not Language enum objects
+ if 'languages' in result:
+ result['languages'] = [str(lang) for lang in result.get('languages', [])]
+
+ except Exception as e:
+ # Create a basic result if parsing fails
+ print(f"Text model failed: {str(e)}. Creating basic result.")
+ result = {
+ "file_name": filename,
+ "topics": ["Document"],
+ "languages": ["English"],
+ "ocr_contents": {
+ "raw_text": ocr_markdown
+ }
+ }
+
+ return result
+
+# For testing directly
+if __name__ == "__main__":
+ import sys
+
+ if len(sys.argv) < 2:
+ print("Usage: python structured_ocr.py ")
+ sys.exit(1)
+
+ file_path = sys.argv[1]
+ processor = StructuredOCR()
+ result = processor.process_file(file_path)
+
+ print(json.dumps(result, indent=2))
\ No newline at end of file
diff --git a/config.py b/config.py
index a7a295d9a7d955b7232267a5b72b4bb6f0c8dfca..f999828a8664fad82e03b043f59bfff44e0b0b06 100644
--- a/config.py
+++ b/config.py
@@ -4,64 +4,14 @@ Configuration file for Mistral OCR processing.
Contains API key and other settings.
"""
import os
-import logging
-from dotenv import load_dotenv
-# Configure logging
-logger = logging.getLogger("config")
+# Your Mistral API key - get from Hugging Face secrets or environment variable
+# The priority order is: HF_SPACES environment var > regular environment var > empty string
+# Note: No default API key is provided for security reasons
+MISTRAL_API_KEY = os.environ.get("HF_MISTRAL_API_KEY", # First check HF-specific env var
+ os.environ.get("MISTRAL_API_KEY", "")) # Then check regular env var
-# Load environment variables from .env file if it exists
-load_dotenv()
-
-# Mistral API key handling - prioritizing Hugging Face environment
-# Priority order:
-# 1. HF_API_KEY environment variable (Hugging Face standard)
-# 2. HUGGING_FACE_API_KEY environment variable (alternative name)
-# 3. HF_MISTRAL_API_KEY environment variable (for Hugging Face deployment)
-# 4. MISTRAL_API_KEY environment variable (fallback)
-# 5. Empty string (will show warning in app)
-
-MISTRAL_API_KEY = os.environ.get("HF_API_KEY",
- os.environ.get("HUGGING_FACE_API_KEY",
- os.environ.get("HF_MISTRAL_API_KEY",
- os.environ.get("MISTRAL_API_KEY", "")))).strip()
-
-if not MISTRAL_API_KEY:
- logger.warning("No Mistral API key found in environment variables. API functionality will be limited.")
-
-# Check if we're in test mode (allows operation without valid API key)
-# Set to False to use actual API calls with Mistral API
-TEST_MODE = False
-
-# Model settings with fallbacks
-OCR_MODEL = os.environ.get("MISTRAL_OCR_MODEL", "mistral-ocr-latest")
-TEXT_MODEL = os.environ.get("MISTRAL_TEXT_MODEL", "mistral-small-latest") # Updated from ministral-8b-latest
-VISION_MODEL = os.environ.get("MISTRAL_VISION_MODEL", "mistral-small-latest") # faster model that supports vision
-
-# Image preprocessing settings optimized for historical documents
-# These can be customized from environment variables
-IMAGE_PREPROCESSING = {
- "enhance_contrast": float(os.environ.get("ENHANCE_CONTRAST", "3.5")), # Increased contrast for better text recognition
- "sharpen": os.environ.get("SHARPEN", "True").lower() in ("true", "1", "yes"),
- "denoise": os.environ.get("DENOISE", "True").lower() in ("true", "1", "yes"),
- "max_size_mb": float(os.environ.get("MAX_IMAGE_SIZE_MB", "200.0")), # Increased size limit for better quality
- "target_dpi": int(os.environ.get("TARGET_DPI", "300")), # Target DPI for scaling
- "compression_quality": int(os.environ.get("COMPRESSION_QUALITY", "100")), # Higher quality for better OCR results
- # # Enhanced settings for handwritten documents
- "handwritten": {
- "block_size": int(os.environ.get("HANDWRITTEN_BLOCK_SIZE", "21")), # Larger block size for adaptive thresholding
- "constant": int(os.environ.get("HANDWRITTEN_CONSTANT", "5")), # Lower constant for adaptive thresholding
- "use_dilation": os.environ.get("HANDWRITTEN_DILATION", "True").lower() in ("true", "1", "yes"), # Connect broken strokes
- "dilation_iterations": int(os.environ.get("HANDWRITTEN_DILATION_ITERATIONS", "2")), # More iterations for better stroke connection
- "dilation_kernel_size": int(os.environ.get("HANDWRITTEN_DILATION_KERNEL_SIZE", "3")) # Larger kernel for dilation
- }
-}
-
-# OCR settings optimized for single-page performance
-OCR_SETTINGS = {
- "timeout_ms": int(os.environ.get("OCR_TIMEOUT_MS", "45000")), # Shorter timeout for single pages (45 seconds)
- "max_retries": int(os.environ.get("OCR_MAX_RETRIES", "2")), # Fewer retries to avoid rate-limiting
- "retry_delay": int(os.environ.get("OCR_RETRY_DELAY", "1")), # Shorter initial retry delay for faster execution
- "include_image_base64": os.environ.get("INCLUDE_IMAGE_BASE64", "True").lower() in ("true", "1", "yes"),
- "thread_count": int(os.environ.get("OCR_THREAD_COUNT", "2")) # Lower thread count to prevent API rate limiting
-}
\ No newline at end of file
+# Model settings
+OCR_MODEL = "mistral-ocr-latest"
+TEXT_MODEL = "ministral-8b-latest"
+VISION_MODEL = "pixtral-12b-latest"
\ No newline at end of file
diff --git a/constants.py b/constants.py
deleted file mode 100644
index d21422aa5c91f41ac6711eee6f45e33cbac4f304..0000000000000000000000000000000000000000
--- a/constants.py
+++ /dev/null
@@ -1,193 +0,0 @@
-"""
-Constants for the Historical OCR application.
-
-This module contains all the constants used throughout the application,
-making it easier to maintain and update values in one place.
-"""
-
-# API limits
-MAX_FILE_SIZE_MB = 200
-MAX_PAGES = 20
-
-# Caching
-CACHE_TTL_SECONDS = 24 * 3600 # 24 hours
-MAX_CACHE_ENTRIES = 20
-
-# Image processing
-MAX_IMAGE_DIMENSION = 2500
-IMAGE_QUALITY = 100
-
-# Document types
-DOCUMENT_TYPES = [
- "Auto-detect (standard processing)",
- "Newspaper or Magazine",
- "Letter or Correspondence",
- "Book or Publication",
- "Form or Legal Document",
- "Recipe",
- "Handwritten Document",
- "Map or Illustration",
- "Table or Spreadsheet",
- "Other (specify in instructions)"
-]
-
-# Document layouts
-DOCUMENT_LAYOUTS = [
- "Standard layout",
- "Multiple columns",
- "Table/grid format",
- "Mixed layout with images"
-]
-
-# Preprocessing document types
-PREPROCESSING_DOC_TYPES = ["standard", "handwritten", "typed", "printed"]
-
-# Rotation options
-ROTATION_OPTIONS = [0, 90, 180, 270]
-
-# PDF settings
-DEFAULT_PDF_DPI = 100
-MIN_PDF_DPI = 72
-MAX_PDF_DPI = 300
-DEFAULT_MAX_PAGES = 3
-
-# Performance modes
-PERFORMANCE_MODES = ["Quality", "Speed"]
-
-# Custom prompt templates
-CUSTOM_PROMPT_TEMPLATES = {
- "Newspaper or Magazine": "This is a newspaper/magazine. Process columns from top to bottom, capture headlines, bylines, article text and captions.",
- "Letter or Correspondence": "This is a letter/correspondence. Capture letterhead, date, greeting, body, closing and signature. Note any handwritten annotations.",
- "Book or Publication": "This is a book/publication. Extract titles, headers, footnotes, page numbers and body text. Preserve paragraph structure and any special formatting.",
- "Form or Legal Document": "This is a form/legal document. Extract all field labels and values, preserving the structure. Pay special attention to signature lines, dates, and any official markings.",
- "Recipe": "This is a recipe. Extract title, ingredients list with measurements, and preparation instructions. Maintain the distinction between ingredients and preparation steps.",
- "Handwritten Document": "This is a handwritten document. Carefully transcribe all handwritten text, preserving line breaks. Note any unclear sections or annotations.",
- "Map or Illustration": "This is a map or illustration. Transcribe all labels, legends, captions, and annotations. Note any scale indicators or directional markings.",
- "Table or Spreadsheet": "This is a table/spreadsheet. Preserve row and column structure, maintaining alignment of data. Extract headers and all cell values.",
- "Other (specify in instructions)": "Please describe the document type and any special processing requirements here."
-}
-
-# Layout prompt additions
-LAYOUT_PROMPT_ADDITIONS = {
- "Multiple columns": "Document has multiple columns. Read each column from top to bottom, then move to the next column.",
- "Table/grid format": "Document contains table data. Preserve row and column structure during extraction.",
- "Mixed layout with images": "Document has mixed text layout with images. Extract text in proper reading order."
-}
-
-# Content themes for subject tag extraction
-CONTENT_THEMES = {
- # Historical Periods
- "Prehistoric": ["paleolithic", "neolithic", "stone age", "bronze age", "iron age", "prehistoric", "ancient", "archaeology", "artifact", "primitive"],
- "Ancient World": ["mesopotamia", "egypt", "greek", "roman", "persia", "babylonian", "assyrian", "pharaoh", "hieroglyphics", "cuneiform", "classical", "antiquity", "hellenistic", "republic", "empire"],
- "Medieval": ["middle ages", "medieval", "feudal", "crusades", "byzantine", "carolingian", "holy roman empire", "dark ages", "castle", "knights", "chivalry", "monastery", "plague", "viking", "norse"],
- "Renaissance": ["renaissance", "humanism", "reformation", "counter-reformation", "medici", "tudor", "elizabethan", "shakespeare", "machiavelli", "gutenberg", "printing press"],
- "Early Modern": ["early modern", "enlightenment", "age of reason", "scientific revolution", "colonial", "colonization", "imperialism", "revolution", "baroque", "bourbon", "habsburg", "stuart"],
- "18th Century": ["18th century", "1700s", "revolution", "american revolution", "french revolution", "enlightenment", "rococo", "neoclassical", "voltaire", "rousseau", "industrial"],
- "19th Century": ["19th century", "1800s", "victorian", "romantic", "napoleonic", "civil war", "industrial revolution", "manifest destiny", "colonial", "imperialism", "belle epoque", "fin de siecle"],
- "20th Century": ["20th century", "1900s", "world war", "great depression", "cold war", "interwar", "postwar", "modernism", "atomic", "post-colonial", "totalitarian", "fascism", "soviet", "civil rights"],
- "Contemporary": ["contemporary", "modern", "postmodern", "digital age", "globalization", "information age", "post-industrial", "post-colonial", "post-soviet", "post-war", "21st century"],
-
- # Geographic Contexts
- "European History": ["europe", "western europe", "eastern europe", "central europe", "mediterranean", "nordic", "iberian", "british", "habsburg", "bourbon", "prussia", "holy roman empire"],
- "Asian History": ["asia", "east asia", "south asia", "central asia", "southeast asia", "china", "japan", "india", "persia", "ottoman", "mongolian", "dynasty", "shogunate", "mughal", "silk road"],
- "African History": ["africa", "north africa", "west africa", "east africa", "sub-saharan", "sahel", "swahili", "maghreb", "nubian", "ethiopian", "zulu", "colonial africa", "apartheid"],
- "American History": ["america", "colonial america", "revolutionary", "antebellum", "civil war", "reconstruction", "frontier", "westward expansion", "manifest destiny", "native american", "indigenous"],
- "Latin American": ["latin america", "mesoamerica", "caribbean", "aztec", "mayan", "inca", "colonial", "viceroyalty", "independence", "revolution", "hispanic", "creole", "mestizo", "indigenous"],
- "Oceanic History": ["oceania", "pacific", "australian", "aboriginal", "indigenous", "polynesian", "melanesian", "micronesian", "maori", "maritime", "exploration", "settlement", "colonial"],
-
- # Historical Methodologies & Approaches
- "Archival Research": ["archive", "manuscript", "primary source", "provenance", "document", "preservation", "cataloging", "repository", "collection", "papers", "fonds", "records", "registry"],
- "Oral History": ["oral history", "testimony", "interview", "narrative", "memory", "ethnography", "storytelling", "tradition", "folklore", "witness", "account", "recording", "indigenous knowledge"],
- "Historical Archaeology": ["archaeology", "excavation", "artifact", "material culture", "stratigraphy", "conservation", "field work", "site", "ruins", "preservation", "heritage", "restoration"],
- "Digital History": ["digital", "database", "digitization", "computational", "network analysis", "gis", "mapping", "visualization", "data mining", "text analysis", "digital humanities", "encoding"],
- "Historiography": ["historiography", "revisionism", "interpretation", "narrative", "discourse", "bias", "perspective", "theory", "methodology", "framework", "historical thinking", "meta-history"],
-
- # Historical Document Types
- "Administrative Records": ["record", "registry", "account", "ledger", "census", "tax roll", "inventory", "charter", "deed", "grant", "patent", "minutes", "docket", "survey", "assessment", "register"],
- "Diplomatic Documents": ["treaty", "agreement", "proclamation", "declaration", "diplomatic", "embassy", "consul", "dispatch", "communique", "protocol", "convention", "alliance", "international"],
- "Personal Papers": ["diary", "journal", "memoir", "autobiography", "correspondence", "letter", "personal", "private", "papers", "notes", "scrapbook", "commonplace book", "sketchbook"],
- "Media History": ["newspaper", "gazette", "periodical", "pamphlet", "broadside", "print culture", "press", "editorial", "journalism", "reporter", "editor", "circulation", "readership", "subscriber"],
- "Visual Materials": ["photograph", "illustration", "print", "map", "atlas", "cartography", "engraving", "woodcut", "lithograph", "panorama", "portrait", "landscape", "sketch", "drawing", "plate"],
- "Legal Documents": ["legal", "law", "statute", "code", "constitution", "legislation", "decree", "ordinance", "bylaw", "regulation", "case", "trial", "testimony", "deposition", "verdict", "judgment"],
-
- # Historical Themes & Movements
- "Economic History": ["economic", "commerce", "trade", "market", "merchant", "finance", "banking", "currency", "coin", "inflation", "recession", "depression", "exchange", "capital", "labor", "guild"],
- "Social History": ["social", "society", "class", "status", "hierarchy", "everyday life", "community", "neighborhood", "urban", "rural", "poverty", "wealth", "leisure", "entertainment", "customs"],
- "Political History": ["political", "politics", "government", "state", "monarchy", "republic", "democracy", "aristocracy", "parliament", "congress", "election", "regime", "policy", "reform", "revolution"],
- "Intellectual History": ["intellectual", "idea", "philosophy", "theory", "concept", "movement", "thought", "discourse", "debate", "enlightenment", "rationalism", "empiricism", "ideology"],
- "Cultural History": ["cultural", "culture", "custom", "tradition", "ritual", "ceremony", "festival", "celebration", "holiday", "folklore", "music", "art", "literature", "fashion", "consumption"],
- "Religious History": ["religious", "religion", "church", "theology", "belief", "faith", "worship", "ritual", "sacred", "clergy", "monastery", "temple", "mosque", "synagogue", "pilgrimage", "sect"],
- "Military History": ["military", "war", "conflict", "battle", "campaign", "siege", "army", "navy", "soldier", "officer", "regiment", "battalion", "artillery", "cavalry", "infantry", "strategy", "tactics"],
- "Science History": ["scientific", "science", "experiment", "discovery", "theory", "hypothesis", "observation", "laboratory", "academy", "research", "natural philosophy", "medicine", "technology"],
- "Environmental History": ["environmental", "ecology", "climate", "weather", "landscape", "agriculture", "farming", "forestry", "conservation", "pollution", "resource", "sustainability", "natural"],
-
- # Specialized Historical Topics
- "Migration History": ["migration", "immigration", "emigration", "diaspora", "exile", "refugee", "settlement", "colonization", "population movement", "forced migration", "displacement", "resettlement"],
- "Maritime History": ["maritime", "naval", "shipping", "navigation", "sailor", "piracy", "privateering", "admiralty", "port", "harbor", "shipyard", "vessel", "sail", "trade route", "exploration"],
- "Gender History": ["gender", "women", "feminist", "sexuality", "masculinity", "femininity", "patriarchy", "suffrage", "domestic", "family", "marriage", "emancipation", "rights", "equality"],
- "Labor History": ["labor", "worker", "union", "strike", "apprentice", "guild", "factory", "workshop", "wage", "hours", "working conditions", "industrialization", "mechanization", "automation"],
- "Urban History": ["urban", "city", "town", "metropolitan", "municipal", "civic", "suburb", "neighborhood", "planning", "infrastructure", "utilities", "housing", "development", "gentrification"],
- "Rural History": ["rural", "countryside", "village", "agricultural", "farming", "peasant", "yeoman", "tenant", "sharecropper", "enclosure", "common land", "manor", "estate", "plantation"],
- "Colonial History": ["colonial", "colony", "settlement", "frontier", "borderland", "territory", "dominion", "province", "governance", "administration", "native", "indigenous", "contact zone"],
- "Indigenous History": ["indigenous", "native", "aboriginal", "first nations", "tribal", "reservation", "sovereignty", "land rights", "treaty rights", "cultural preservation", "oral tradition"],
-
- # General Historical Terms
- "Historical": ["history", "historical", "historiography", "heritage", "legacy", "tradition", "memory", "commemoration", "preservation", "conservation", "restoration", "interpretation", "significance"],
- "Chronology": ["chronology", "timeline", "periodization", "era", "epoch", "age", "century", "decade", "millennium", "year", "date", "dating", "chronological", "contemporary", "synchronic", "diachronic"],
- "Heritage": ["heritage", "preservation", "conservation", "landmark", "monument", "historic site", "museum", "archive", "collection", "artifact", "relic", "antiquity", "cultural heritage", "patrimony"]
-}
-
-# Period tags based on year ranges
-# These ranges are used to assign historical period tags to documents based on their year.
-PERIOD_TAGS = {
- (0, 499): "Ancient Era (to 500 CE)",
- (500, 999): "Early Medieval (500–1000)",
- (1000, 1299): "High Medieval (1000–1300)",
- (1300, 1499): "Late Medieval (1300–1500)",
- (1500, 1599): "Renaissance (1500–1600)",
- (1600, 1699): "Early Modern (1600–1700)",
- (1700, 1775): "Enlightenment (1700–1775)",
- (1776, 1799): "Age of Revolutions (1776–1800)",
- (1800, 1849): "Early 19th Century (1800–1850)",
- (1850, 1899): "Late 19th Century (1850–1900)",
- (1900, 1918): "Early 20th Century & WWI (1900–1918)",
- (1919, 1938): "Interwar Period (1919–1938)",
- (1939, 1945): "World War II (1939–1945)",
- (1946, 1968): "Postwar & Mid-20th Century (1946–1968)",
- (1969, 1989): "Late 20th Century (1969–1989)",
- (1990, 2000): "Turn of the 21st Century (1990–2000)",
- (2001, 2099): "Contemporary (21st Century)"
-}
-
-# Default fallback tags for documents when no specific tags are detected.
-DEFAULT_TAGS = [
- "Document",
- "Historical",
- "Text",
- "Primary Source",
- "Archival Material",
- "Record",
- "Manuscript",
- "Printed Material",
- "Correspondence",
- "Publication"
-]
-
-# Generic tags that can be used for broad categorization or as supplemental tags.
-GENERIC_TAGS = [
- "Archive",
- "Content",
- "Record",
- "Source",
- "Material",
- "Page",
- "Scan",
- "Image",
- "Transcription",
- "Uncategorized",
- "General",
- "Miscellaneous"
-]
-
-# UI constants
-PROGRESS_DELAY = 0.8 # Seconds to show completion message
diff --git a/error_handler.py b/error_handler.py
deleted file mode 100644
index 18750cdf24b9aa67e24b1e25b465bf5a11ff538e..0000000000000000000000000000000000000000
--- a/error_handler.py
+++ /dev/null
@@ -1,65 +0,0 @@
-import logging
-import streamlit as st
-import time
-from constants import MAX_FILE_SIZE_MB
-
-# Configure logging
-logger = logging.getLogger("error_handler")
-logger.setLevel(logging.INFO)
-
-def handle_ocr_error(exception, progress_reporter=None):
- """
- Handle OCR processing errors and provide user-friendly messages
-
- Args:
- exception: The exception that occurred
- progress_reporter: ProgressReporter instance for UI updates
-
- Returns:
- str: User-friendly error message
- """
- error_message = str(exception)
-
- # Complete progress reporting if provided
- if progress_reporter:
- progress_reporter.complete(success=False)
-
- # Check for specific error types and provide helpful user-facing messages
- if "rate limit" in error_message.lower() or "429" in error_message or "requests rate limit exceeded" in error_message.lower():
- friendly_message = "The AI service is currently experiencing high demand. Please try again in a few minutes."
- logger.error(f"Rate limit error: {error_message}")
- return friendly_message
- elif "quota" in error_message.lower() or "credit" in error_message.lower() or "subscription" in error_message.lower():
- friendly_message = "The API usage quota has been reached. Please check your API key and subscription limits."
- logger.error(f"API quota error: {error_message}")
- return friendly_message
- elif "timeout" in error_message.lower() or "timed out" in error_message.lower():
- friendly_message = "The request timed out. This may be due to a large document or high server load. Please try again or use a smaller document."
- logger.error(f"Timeout error: {error_message}")
- return friendly_message
- elif "file size" in error_message.lower() or "too large" in error_message.lower():
- friendly_message = f"The file is too large. Maximum file size is {MAX_FILE_SIZE_MB}MB."
- logger.error(f"File size error: {error_message}")
- return friendly_message
- else:
- # Generic error message for other errors
- logger.error(f"OCR processing error: {error_message}", exc_info=True)
- return f"An error occurred during processing: {error_message}"
-
-def check_file_size(file_bytes):
- """
- Check if file size is within limits
-
- Args:
- file_bytes: File content as bytes
-
- Returns:
- tuple: (is_valid, file_size_mb, error_message)
- """
- file_size_mb = len(file_bytes) / (1024 * 1024)
-
- if file_size_mb > MAX_FILE_SIZE_MB:
- error_message = f"File size {file_size_mb:.2f} MB exceeds limit of {MAX_FILE_SIZE_MB} MB"
- return False, file_size_mb, error_message
-
- return True, file_size_mb, None
diff --git a/image_segmentation.py b/image_segmentation.py
deleted file mode 100644
index b0137dcedb1947f229aa0cc238fb43cbdf309325..0000000000000000000000000000000000000000
--- a/image_segmentation.py
+++ /dev/null
@@ -1,253 +0,0 @@
-"""
-Image segmentation utility for OCR preprocessing.
-Separates text regions from image regions to improve OCR accuracy on mixed-content documents.
-Uses content-aware adaptive segmentation for improved results across document types.
-"""
-
-import cv2
-import numpy as np
-from PIL import Image
-import io
-import base64
-import logging
-from pathlib import Path
-from typing import Tuple, List, Dict, Union, Optional
-
-# Configure logging
-logging.basicConfig(level=logging.INFO,
- format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
-logger = logging.getLogger(__name__)
-
-def segment_image_for_ocr(image_path: Union[str, Path], vision_enabled: bool = True, preserve_content: bool = True) -> Dict[str, Union[Image.Image, str]]:
- """
- Prepare image for OCR processing using content-aware segmentation.
- Uses adaptive region detection based on text density analysis.
-
- Args:
- image_path: Path to the image file
- vision_enabled: Whether the vision model is enabled
- preserve_content: Whether to preserve original content without enhancement
-
- Returns:
- Dict containing segmentation results
- """
- # Convert to Path object if string
- image_file = Path(image_path) if isinstance(image_path, str) else image_path
-
- # Log start of processing
- logger.info(f"Preparing image for Mistral OCR: {image_file.name}")
-
- try:
- # Open original image with PIL
- with Image.open(image_file) as pil_img:
- # Check for low entropy images when vision is disabled
- if not vision_enabled:
- from utils.image_utils import calculate_image_entropy
- ent = calculate_image_entropy(pil_img)
- if ent < 3.5: # Likely line-art or blank page
- logger.info(f"Low entropy image detected ({ent:.2f}), classifying as illustration")
- return {
- 'text_regions': None,
- 'image_regions': pil_img,
- 'text_mask_base64': None,
- 'combined_result': None,
- 'text_regions_coordinates': []
- }
-
- # Convert to RGB if needed
- if pil_img.mode != 'RGB':
- pil_img = pil_img.convert('RGB')
-
- # Get image dimensions
- img_np = np.array(pil_img)
- img_width, img_height = pil_img.size
-
- # Analyze text density to determine if advanced segmentation is needed
- # This replaces document-specific logic with content-aware analysis
- from utils.image_utils import estimate_text_density
- text_density = estimate_text_density(img_np)
-
- # Use adaptive approach for documents with unusual text distribution
- if text_density['pattern'] == 'varied' or text_density['uppercase_sections'] > 0:
- logger.info(f"Using adaptive segmentation for document with varied text density pattern={text_density['pattern']}, uppercase_sections={text_density['uppercase_sections']}")
-
- # Detect content regions based on text density
- from utils.text_utils import detect_content_regions
- regions = detect_content_regions(img_np)
-
- # Create visualization with green borders around the text regions
- vis_img = img_np.copy()
-
- # Draw regions on visualization
- for x, y, w, h in regions:
- cv2.rectangle(vis_img, (x, y), (x+w, y+h), (0, 255, 0), 3)
-
- # Add text to indicate we're using adaptive processing
- font = cv2.FONT_HERSHEY_SIMPLEX
- cv2.putText(vis_img, "Adaptive region processing", (30, 60), font, 1, (0, 255, 0), 2)
-
- # Create visualization images
- text_regions_vis = Image.fromarray(vis_img)
- image_regions_vis = text_regions_vis.copy()
-
- # Create a mask highlighting the text regions
- text_mask = np.zeros((img_height, img_width), dtype=np.uint8)
- for x, y, w, h in regions:
- text_mask[y:y+h, x:x+w] = 255
-
- _, buffer = cv2.imencode('.png', text_mask)
- text_mask_base64 = base64.b64encode(buffer).decode('utf-8')
-
- # Extract region images
- region_images = []
- for i, (x, y, w, h) in enumerate(regions):
- region = img_np[y:y+h, x:x+w].copy()
- region_pil = Image.fromarray(region)
-
- region_info = {
- 'image': region,
- 'pil_image': region_pil,
- 'coordinates': (x, y, w, h),
- 'padded_coordinates': (x, y, w, h),
- 'order': i
- }
- region_images.append(region_info)
-
- # Return the adaptive segmentation results
- return {
- 'text_regions': text_regions_vis,
- 'image_regions': image_regions_vis,
- 'text_mask_base64': f"data:image/png;base64,{text_mask_base64}",
- 'combined_result': pil_img,
- 'text_regions_coordinates': regions,
- 'region_images': region_images,
- 'segmentation_type': 'adaptive'
- }
- else:
- # SIMPLIFIED APPROACH for most documents
- # Let Mistral OCR handle the entire document understanding process
- logger.info(f"Using standard approach for document with uniform text density")
-
- # For visualization, mark the entire image as a text region
- full_image_region = [(0, 0, img_width, img_height)]
-
- # Create visualization with a simple border
- vis_img = img_np.copy()
- cv2.rectangle(vis_img, (5, 5), (img_width-5, img_height-5), (0, 255, 0), 5)
-
- # Add text to indicate this is using Mistral's native processing
- font = cv2.FONT_HERSHEY_SIMPLEX
- cv2.putText(vis_img, "Processed by Mistral OCR", (30, 60), font, 1, (0, 255, 0), 2)
-
- # Create visualizations and masks
- text_regions_vis = Image.fromarray(vis_img)
- image_regions_vis = text_regions_vis.copy()
-
- # Create a mask of the entire image (just for visualization)
- text_mask = np.ones((img_height, img_width), dtype=np.uint8) * 255
- _, buffer = cv2.imencode('.png', text_mask)
- text_mask_base64 = base64.b64encode(buffer).decode('utf-8')
-
- # Return the original image as the combined result
- return {
- 'text_regions': text_regions_vis,
- 'image_regions': image_regions_vis,
- 'text_mask_base64': f"data:image/png;base64,{text_mask_base64}",
- 'combined_result': pil_img,
- 'text_regions_coordinates': full_image_region,
- 'region_images': [{
- 'image': img_np,
- 'pil_image': pil_img,
- 'coordinates': (0, 0, img_width, img_height),
- 'padded_coordinates': (0, 0, img_width, img_height),
- 'order': 0
- }],
- 'segmentation_type': 'simplified'
- }
-
- except Exception as e:
- logger.error(f"Error segmenting image {image_file.name}: {str(e)}")
- # Return None values if processing fails
- return {
- 'text_regions': None,
- 'image_regions': None,
- 'text_mask_base64': None,
- 'combined_result': None,
- 'text_regions_coordinates': []
- }
-
-def process_segmented_image(image_path: Union[str, Path], output_dir: Optional[Path] = None, preserve_content: bool = True) -> Dict:
- """
- Process an image using segmentation for improved OCR, saving visualization outputs.
-
- Args:
- image_path: Path to the image file
- output_dir: Optional directory to save visualization outputs
-
- Returns:
- Dictionary with processing results and paths to output files
- """
- # Convert to Path object if string
- image_file = Path(image_path) if isinstance(image_path, str) else image_path
-
- # Create output directory if not provided
- if output_dir is None:
- output_dir = Path("output") / "segmentation"
- output_dir.mkdir(parents=True, exist_ok=True)
-
- # Process the image with segmentation
- segmentation_results = segment_image_for_ocr(image_file)
-
- # Prepare results dictionary
- results = {
- 'original_image': str(image_file),
- 'output_files': {}
- }
-
- # Save visualization outputs if segmentation was successful
- if segmentation_results['text_regions'] is not None:
- # Save text regions visualization
- text_regions_path = output_dir / f"{image_file.stem}_text_regions.jpg"
- segmentation_results['text_regions'].save(text_regions_path)
- results['output_files']['text_regions'] = str(text_regions_path)
-
- # Save image regions visualization
- image_regions_path = output_dir / f"{image_file.stem}_image_regions.jpg"
- segmentation_results['image_regions'].save(image_regions_path)
- results['output_files']['image_regions'] = str(image_regions_path)
-
- # Save combined result
- combined_path = output_dir / f"{image_file.stem}_combined.jpg"
- segmentation_results['combined_result'].save(combined_path)
- results['output_files']['combined_result'] = str(combined_path)
-
- # Save text mask visualization
- text_mask_path = output_dir / f"{image_file.stem}_text_mask.png"
- # Save text mask from base64
- if segmentation_results['text_mask_base64']:
- base64_data = segmentation_results['text_mask_base64'].split(',')[1]
- with open(text_mask_path, 'wb') as f:
- f.write(base64.b64decode(base64_data))
- results['output_files']['text_mask'] = str(text_mask_path)
-
- # Add detected text regions count
- results['text_regions_count'] = len(segmentation_results['text_regions_coordinates'])
- results['text_regions_coordinates'] = segmentation_results['text_regions_coordinates']
-
- return results
-
-if __name__ == "__main__":
- # Simple test - process a sample image if run directly
- import sys
-
- if len(sys.argv) > 1:
- image_path = sys.argv[1]
- else:
- image_path = "input/handwritten-journal.jpg" # Example image path"
-
- logger.info(f"Testing image segmentation on {image_path}")
- results = process_segmented_image(image_path)
-
- # Print results summary
- logger.info(f"Segmentation complete. Found {results.get('text_regions_count', 0)} text regions.")
- logger.info(f"Output files saved to: {[path for path in results.get('output_files', {}).values()]}")
diff --git a/input/The Magician, or Bottle Cungerer.jpeg b/input/The Magician, or Bottle Cungerer.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..8e2045676510f0e481670d6763f3930ae6b203da
--- /dev/null
+++ b/input/The Magician, or Bottle Cungerer.jpeg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:3becaf6f5548a794436864885bb125f3fa09f1e6f7bdd76e8878f2d36ff26232
+size 2960280
diff --git a/input/americae-retectio.jpg b/input/americae-retectio.jpg
deleted file mode 100644
index e23be5e413f0b2c5a8421cb7b4e93a64819bd439..0000000000000000000000000000000000000000
--- a/input/americae-retectio.jpg
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:3ea42f6d3f7c0331a08321c26978c9011843965de99735a178de8167fdede544
-size 451559
diff --git a/input/baldwin-letter-1.jpg b/input/baldwin-letter-1.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..4e6b833fc8ab797f608dfe7c4e92642ca8b773d3
--- /dev/null
+++ b/input/baldwin-letter-1.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a30d7d9f224c777a1697507200a87e41be5fd590efbe8271fa41dbd8bd8a158d
+size 135244
diff --git a/input/baldwin-letter-2.jpg b/input/baldwin-letter-2.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..b19ec5985abd7d36aef556b7427d2524c54d5d13
--- /dev/null
+++ b/input/baldwin-letter-2.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:8b605a6eabd466da265e9e1aa3576160c4dbee06643ece5a18cdb1e45f3f683a
+size 114136
diff --git a/input/baldwin-letter.jpg b/input/baldwin-letter.jpg
deleted file mode 100644
index 3107ff733b3bd2d0bb820dda6dd17dc65b7766fe..0000000000000000000000000000000000000000
--- a/input/baldwin-letter.jpg
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:bf2feb9fdf509fdb58b5c8f3b9a4c2fbc6e21d3e55c19b8f3ade8e37cccfe030
-size 195092
diff --git a/input/flier.png b/input/flier.png
new file mode 100644
index 0000000000000000000000000000000000000000..a02e7743490614e3f9884a88aa9ad15214609a34
Binary files /dev/null and b/input/flier.png differ
diff --git a/input/gender.jpg b/input/gender.jpg
deleted file mode 100644
index 5c9db9cffd3f12d592fe733fee7e347616a814b4..0000000000000000000000000000000000000000
--- a/input/gender.jpg
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:af612b5c7ef59bc6c9bf7cbcedb88c69628ff8c46c66f1cb8df83729f5df59f8
-size 129906
diff --git a/input/handwritten-journal.jpg b/input/handwritten-journal.jpg
deleted file mode 100644
index eebd5f6fea26e404b20d427b2d1379d0515ec226..0000000000000000000000000000000000000000
--- a/input/handwritten-journal.jpg
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:279f7c915ae54aafb30e6d70e480eb74e73b6aa92de20f60cd13019e9debbb62
-size 1459485
diff --git a/input/harpers.pdf b/input/harpers.pdf
deleted file mode 100644
index 8a1176f2f7b8a2d74f042a5415d6946d8e4a821b..0000000000000000000000000000000000000000
--- a/input/harpers.pdf
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:3c9030714b07bb5f7c9adf8b175975baa9b4f40402da62d69cad9b0d4ba61b94
-size 14931299
diff --git a/input/letter-1.jpg b/input/letter-1.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..4e6b833fc8ab797f608dfe7c4e92642ca8b773d3
--- /dev/null
+++ b/input/letter-1.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a30d7d9f224c777a1697507200a87e41be5fd590efbe8271fa41dbd8bd8a158d
+size 135244
diff --git a/input/letter-2.jpg b/input/letter-2.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..b19ec5985abd7d36aef556b7427d2524c54d5d13
--- /dev/null
+++ b/input/letter-2.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:8b605a6eabd466da265e9e1aa3576160c4dbee06643ece5a18cdb1e45f3f683a
+size 114136
diff --git a/input/letter-3.jpg b/input/letter-3.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..fa0cc25dd4affd353900e34e9e986d6fa435ee8e
--- /dev/null
+++ b/input/letter-3.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:7fe2d81bb4e8bef7cdbf87c58a8cc180c49c313e5099de167ae37bbbfb895e88
+size 230837
diff --git a/input/magician-satire.jpg b/input/magician-satire.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..8e2045676510f0e481670d6763f3930ae6b203da
--- /dev/null
+++ b/input/magician-satire.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:3becaf6f5548a794436864885bb125f3fa09f1e6f7bdd76e8878f2d36ff26232
+size 2960280
diff --git a/input/menu.pdf b/input/menu.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..276af61cf2f4ac361eaa86b3c430c527b1e59230
--- /dev/null
+++ b/input/menu.pdf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:42d96008f374f5be8046b569c868e33f4e5a0e5e166c245d324b44140c7e6c2e
+size 2554815
diff --git a/input/milgram-flier.png b/input/milgram-flier.png
index aaa701cfd54519b72b05ee861537c7f537c266af..a02e7743490614e3f9884a88aa9ad15214609a34 100644
Binary files a/input/milgram-flier.png and b/input/milgram-flier.png differ
diff --git a/input/okeefe-menu.pdf b/input/okeefe-menu.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..276af61cf2f4ac361eaa86b3c430c527b1e59230
--- /dev/null
+++ b/input/okeefe-menu.pdf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:42d96008f374f5be8046b569c868e33f4e5a0e5e166c245d324b44140c7e6c2e
+size 2554815
diff --git a/input/okeefe-recipe.jpg b/input/okeefe-recipe.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..1701f0ba9840b4b978a4bae4d14c2780516c5f26
Binary files /dev/null and b/input/okeefe-recipe.jpg differ
diff --git a/input/photo-baldwin-letter.jpg b/input/photo-baldwin-letter.jpg
deleted file mode 100644
index 3b8b36c3230dc68f3a3fd6fe5bc5a629cd25f859..0000000000000000000000000000000000000000
--- a/input/photo-baldwin-letter.jpg
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:c34d82f1e463aa471c326488d02b6ec1e7083842bfd9456292c94b2d54c71d96
-size 245212
diff --git a/input/recipe.jpg b/input/recipe.jpg
index 880bf752d43db6863a80c7af9c235d49a5f6954c..1701f0ba9840b4b978a4bae4d14c2780516c5f26 100644
Binary files a/input/recipe.jpg and b/input/recipe.jpg differ
diff --git a/letterhead_handler.py b/letterhead_handler.py
deleted file mode 100644
index 19e0c78799f2a3a7d497d144d23ff86d407c3609..0000000000000000000000000000000000000000
--- a/letterhead_handler.py
+++ /dev/null
@@ -1,197 +0,0 @@
-"""
-Specialized handler for letterhead and marginalia documents.
-Enhances OCR quality by providing document-specific prompts for common layouts.
-"""
-
-import re
-import logging
-from pathlib import Path
-from typing import Union, Dict, Any, Optional, List
-
-# Configure logging
-logging.basicConfig(level=logging.INFO,
- format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
-logger = logging.getLogger(__name__)
-
-def is_likely_letterhead(image_path: Union[str, Path], features: Optional[Dict[str, Any]] = None) -> bool:
- """
- Detect if an image is likely a letterhead document with marginalia.
- Uses path/filename patterns and optional image features (if provided).
-
- Args:
- image_path: Path to the image file
- features: Optional dict of image features from preprocessing
-
- Returns:
- bool: True if likely a letterhead document
- """
- # Convert to string path for pattern matching
- path_str = str(image_path).lower()
-
- # Check for common letterhead filename patterns
- letterhead_patterns = [
- r'letter(head)?[^/]*\.jpg',
- r'hotel[^/]*\.jpg',
- r'baldwin.*\.jpg',
- r'business.*letter.*\.jpg',
- r'correspondence.*\.jpg'
- ]
-
- for pattern in letterhead_patterns:
- if re.search(pattern, path_str):
- logger.info(f"Detected likely letterhead document: {Path(image_path).name}")
- return True
-
- # If features are provided, use them for additional detection
- if features:
- # Check for ALL CAPS sections that might be marginalia
- if features.get('uppercase_sections', 0) > 1:
- logger.info(f"Detected likely letterhead document with marginalia by features: {Path(image_path).name}")
- return True
-
- return False
-
-def get_letterhead_prompt(image_path: Union[str, Path], features: Optional[Dict[str, Any]] = None) -> Optional[str]:
- """
- Generate a specialized prompt for letterhead documents to improve OCR quality.
-
- Args:
- image_path: Path to the image file
- features: Optional dict of image features from preprocessing
-
- Returns:
- str: Custom prompt for letterhead OCR or None if not applicable
- """
- if not is_likely_letterhead(image_path, features):
- return None
-
- # Path-specific customizations for known problematic documents
- path_str = str(image_path).lower()
-
- # Most specialized prompt for baldwin documents
- if "baldwin" in path_str:
- return """
- This image shows a hotel letterhead with a handwritten letter. Please extract the text with the following guidelines:
-
- 1. Identify and separate the letterhead elements:
- - Header: The hotel name, address, and contact information at the top
- - Marginalia: The amenities description in ALL CAPS along the margins
-
- 2. Extract the main handwritten letter content separately
-
- 3. Note any image captions separately
-
- 4. Format the output as follows:
- - HEADER: [header text]
- - MARGINS: [marginalia text]
- - LETTER: [handwritten letter text]
- - CAPTIONS: [any image captions]
-
- Be careful not to duplicate content between sections, especially with margin text.
- """
-
- # General letterhead prompt
- return """
- This appears to be a letterhead document. Please extract the text with the following guidelines:
-
- 1. Identify the header/letterhead section with company name, logo, address, etc.
- 2. Identify any margin text or notes that appear separate from the main content
- 3. Extract the main letter/document body separately
- 4. Format the output as follows:
- - LETTERHEAD: [letterhead text]
- - MARGIN_NOTES: [any text in margins]
- - BODY: [main document body]
-
- Be careful not to duplicate content between sections.
- """
-
-def clean_letterhead_ocr_output(text: str) -> str:
- """
- Clean OCR output from letterhead documents by handling section markers
- and reducing duplication.
-
- Args:
- text: OCR text from letterhead document
-
- Returns:
- str: Cleaned text with proper section formatting
- """
- if not text:
- return ""
-
- # Find any section markers added by the specialized prompt
- section_markers = [
- "HEADER:", "LETTERHEAD:", "MARGINS:", "MARGIN_NOTES:",
- "LETTER:", "BODY:", "CAPTIONS:"
- ]
-
- # Check if the text has any section markers
- has_sections = any(marker in text for marker in section_markers)
-
- if has_sections:
- # Split text into sections while preserving section headers
- sections = {}
- current_section = "UNKNOWN"
- current_text = []
-
- for line in text.split('\n'):
- # Check if this line is a section marker
- is_marker = False
- for marker in section_markers:
- if marker in line:
- # Save previous section
- if current_text:
- sections[current_section] = '\n'.join(current_text).strip()
- current_text = []
-
- # Start new section
- current_section = marker.replace(':', '')
- # Keep any text after the marker on this line
- remainder = line.split(marker, 1)[1].strip()
- if remainder:
- current_text.append(remainder)
- is_marker = True
- break
-
- # If not a marker, add to current section
- if not is_marker:
- current_text.append(line)
-
- # Save the last section
- if current_text:
- sections[current_section] = '\n'.join(current_text).strip()
-
- # Format with standard order and clear section headers
- formatted_sections = []
-
- # First add letterhead/header info
- if "LETTERHEAD" in sections:
- formatted_sections.append(f"--- LETTERHEAD ---\n{sections['LETTERHEAD']}")
- elif "HEADER" in sections:
- formatted_sections.append(f"--- LETTERHEAD ---\n{sections['HEADER']}")
-
- # Add margins/notes
- if "MARGIN_NOTES" in sections:
- formatted_sections.append(f"--- MARGIN NOTES ---\n{sections['MARGIN_NOTES']}")
- elif "MARGINS" in sections:
- formatted_sections.append(f"--- MARGIN NOTES ---\n{sections['MARGINS']}")
-
- # Add main content
- if "BODY" in sections:
- formatted_sections.append(f"--- DOCUMENT BODY ---\n{sections['BODY']}")
- elif "LETTER" in sections:
- formatted_sections.append(f"--- DOCUMENT BODY ---\n{sections['LETTER']}")
-
- # Add captions if present
- if "CAPTIONS" in sections:
- formatted_sections.append(f"--- IMAGE CAPTIONS ---\n{sections['CAPTIONS']}")
-
- # Add unknown sections
- if "UNKNOWN" in sections and sections["UNKNOWN"]:
- formatted_sections.append(f"--- ADDITIONAL CONTENT ---\n{sections['UNKNOWN']}")
-
- # Join everything with clear separation
- return "\n\n".join(formatted_sections)
- else:
- # If no section markers were found, return the original text
- return text
\ No newline at end of file
diff --git a/modules/content/__init__.py b/modules/content/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..9b5767eca0096d8d3be1514fcb79c97e0f4caf7b
--- /dev/null
+++ b/modules/content/__init__.py
@@ -0,0 +1,36 @@
+"""
+Module initialization file for the workshop modules.
+"""
+from . import module1, module2, module3, module4, module5, module6
+
+# Module registry for easy access by module number
+modules = {
+ 1: module1,
+ 2: module2,
+ 3: module3,
+ 4: module4,
+ 5: module5,
+ 6: module6
+}
+
+# Module names for navigation and display
+module_names = [
+ "Introduction",
+ "Text-Image Relations",
+ "OCR Technology",
+ "Methodological Approaches",
+ "Interactive OCR",
+ "Conclusion"
+]
+
+def get_module(module_number):
+ """Get a module by its number (1-6)"""
+ if module_number in modules:
+ return modules[module_number]
+ raise ValueError(f"Unknown module number: {module_number}")
+
+def get_module_name(module_number):
+ """Get a module name by its number (1-6)"""
+ if 1 <= module_number <= len(module_names):
+ return module_names[module_number - 1]
+ return f"Module {module_number}"
\ No newline at end of file
diff --git a/modules/content/module1.py b/modules/content/module1.py
new file mode 100644
index 0000000000000000000000000000000000000000..822f36dd501269596ad5f37b510027a362eb375a
--- /dev/null
+++ b/modules/content/module1.py
@@ -0,0 +1,85 @@
+import streamlit as st
+from layout import gray_container, blue_container, yellow_container, card_grid, key_concept
+
+def render():
+ """Module 1: Introduction and Problematization"""
+
+ st.title("Module 1: Introduction and Problematization")
+
+ # Workshop overview in gray container
+ overview_content = """
+
Workshop Overview
+
+ This interactive workshop explores the application of OCR technology to historical documents,
+ combining theoretical understanding with practical experiences. Designed for historians,
+ archivists, and digital humanities scholars, it offers both conceptual frameworks and hands-on skills.
+
+ """
+ gray_container(overview_content)
+
+ # For historians section with blue background
+ historians_content = """
+
For Historians:
+
+ How might OCR technology transform our access to and interpretation of historical
+ documents? What new research questions become possible when large archives
+ become machine-readable?
+
+ """
+ blue_container(historians_content)
+
+ # What is OCR section with yellow background
+ ocr_content = """
+
What is OCR?
+
+ Optical Character Recognition (OCR) technology enables computers to extract text from images and documents.
+ Modern OCR uses AI vision models to understand both the text and its visual context.
+
How might the capabilities of vision-language models change our approach to digitizing historical archives?
+ """
+ research_question(research_content)
+
+ # Display history if available
+ if 'processing_history' in st.session_state and st.session_state.processing_history:
+ with st.expander("Your OCR Processing History"):
+ st.markdown("You've already processed the following documents:")
+
+ for item in st.session_state.processing_history:
+ st.markdown(f"**{item['fileName']}**")
+ col1, col2 = st.columns(2)
+ with col1:
+ st.write(f"**Topics:** {', '.join(item['result'].get('topics', ['Unknown']))}")
+ with col2:
+ st.write(f"**Vision model used:** {'Yes' if item['useVision'] else 'No'}")
\ No newline at end of file
diff --git a/modules/content/module4.py b/modules/content/module4.py
new file mode 100644
index 0000000000000000000000000000000000000000..81dac95f5f7b62960b00dbe68f0ed1b7be357812
--- /dev/null
+++ b/modules/content/module4.py
@@ -0,0 +1,124 @@
+import streamlit as st
+from pathlib import Path
+from layout import gray_container, tool_container, key_concept, quote
+
+def render():
+ """Module 4: Methodological Approaches"""
+
+ st.title("Module 4: Methodological Approaches")
+
+ col1, col2 = st.columns([1, 1])
+
+ with col1:
+ hybrid_content = """
+
Hybrid Methodologies
+
+
1. Computational + Human Reading
+
+
OCR for initial processing and discovery
+
Human review for context and interpretation
+
Iterative refinement of computational outputs
+
+
+
2. Close + Distant Reading
+
+
Distant reading through large-scale OCR processing
+
Close reading of selected passages
+
Zooming between scales of analysis
+
+ """
+ gray_container(hybrid_content)
+
+ # Check if the diagram image is available and display it
+ input_dir = Path(__file__).parent.parent / "input"
+ diagram_path = input_dir / "diagram.jpg"
+
+ if diagram_path.exists():
+ try:
+ from PIL import Image
+ with Image.open(diagram_path) as img:
+ st.image(img, caption="Historical VLM architecture", use_column_width=True)
+ except Exception:
+ # If there's an error, just show a placeholder
+ st.image("https://placekitten.com/800/400", caption="Historical VLM architecture placeholder")
+ else:
+ # If the file doesn't exist, show a placeholder
+ st.image("https://placekitten.com/800/400", caption="Historical VLM architecture placeholder")
+
+ with col2:
+ mistral_content = """
+
Mistral-OCR-Latest: State-of-the-Art
+
+
The Mistral-OCR model represents a significant advancement:
+
+
Multimodal Understanding: Processes both visual and textual information
Historical Font Adaptation: Trained on diverse historical typography
+
+ """
+ gray_container(mistral_content)
+
+ # Check if the workflow image is available and display it
+ workflow_path = input_dir / "workflow.jpg"
+
+ if workflow_path.exists():
+ try:
+ from PIL import Image
+ with Image.open(workflow_path) as img:
+ st.image(img, caption="Mistral OCR workflow", use_column_width=True)
+ except Exception:
+ # If there's an error, just show a placeholder
+ st.image("https://placekitten.com/800/400", caption="Mistral OCR workflow placeholder")
+ else:
+ # If the file doesn't exist, show a placeholder
+ st.image("https://placekitten.com/800/400", caption="Mistral OCR workflow placeholder")
+
+ # Practical workflow section
+ workflow_content = """
+
Practical Workflow
+
+
A typical historical OCR workflow with Mistral-OCR includes:
+
+
Selection: Choosing appropriate documents
+
Preprocessing: Enhancing images before OCR
+
OCR Processing: Running documents through vision-enhanced OCR
+
Post-processing: Cleaning up outputs and structured extraction
+
Verification: Cross-checking results against originals
+
Integration: Incorporating OCR outputs into research materials
+ """
+ gray_container(limitations_content)
+
+ # Quote
+ quote_content = "The most powerful digital humanities work occurs at the intersection of computational methods and traditional humanistic inquiry."
+ quote(quote_content, "Dr. Sarah E. Bond, Digital Humanities Scholar")
\ No newline at end of file
diff --git a/modules/content/module5.py b/modules/content/module5.py
new file mode 100644
index 0000000000000000000000000000000000000000..6bc50f5e03de8e234a6c3369da6b68ce4a0faee0
--- /dev/null
+++ b/modules/content/module5.py
@@ -0,0 +1,547 @@
+import streamlit as st
+import io
+import tempfile
+from pathlib import Path
+from datetime import datetime
+from layout import tool_container, key_concept, research_question, upload_container
+import sys
+
+# Import the necessary modules for OCR processing
+sys.path.append(str(Path(__file__).parent.parent))
+try:
+ from process_file import process_file as process_file_util
+ process_file = process_file_util
+except ImportError:
+ # Fallback if process_file is not available
+ def process_file(uploaded_file, use_vision=True, custom_prompt=None):
+ """Fallback function for processing files"""
+ st.warning("Using mock processing function. Real OCR functionality is not available.")
+ return {
+ "file_name": uploaded_file.name,
+ "languages": ["English"],
+ "topics": ["History", "Document"],
+ "ocr_contents": {
+ "content": f"This is mock OCR content for {uploaded_file.name}. Vision model: {use_vision}"
+ }
+ }
+
+def render():
+ """Module 5: Interactive OCR Experiment"""
+
+ st.title("Module 5: Interactive OCR Experiment")
+
+ # Introduction to the interactive experiment
+ intro_content = """
+
Interactive OCR Experiment
+
+ This interactive experiment allows you to process historical documents with OCR and analyze the results.
+ Try different settings and compare the outcomes to understand the strengths and limitations of OCR technology.
+
+ """
+ st.markdown(intro_content, unsafe_allow_html=True)
+
+ # Create tabs for different activities
+ experiment_tab, compare_tab, analyze_tab = st.tabs(["Process Documents", "Compare Results", "Analysis Guide"])
+
+ # Try to import PDF tools if available
+ try:
+ from pdf2image import convert_from_bytes
+ pdf_support = True
+ except ImportError:
+ pdf_support = False
+ st.warning("PDF preview functionality is limited. The pdf2image module is required for PDF previews.")
+
+ with experiment_tab:
+ # Create a two-column layout
+ col1, col2 = st.columns([1, 1])
+
+ with col1:
+ # Tool container for document selection and options
+ st.subheader("Step 1: Select Document & Options")
+
+ # Processing options
+ use_vision = st.checkbox("Use Vision Model", value=True,
+ help="Use the vision model for improved analysis")
+
+ # Additional prompt
+ st.markdown("### Custom Research Prompt (Optional)")
+ st.markdown("""Provide additional instructions to guide the OCR analysis.
+ Focus on specific aspects of historical research you're interested in.""")
+ custom_prompt = st.text_area("Research Prompt",
+ placeholder="E.g., Focus on identifying dates and historical figures...",
+ help="Optional instructions to guide the analysis")
+
+ # Sample document selection
+ input_dir = Path(__file__).parent.parent / "input"
+
+ if input_dir.exists():
+ sample_files = list(input_dir.glob("*.jpg")) + list(input_dir.glob("*.png")) + list(input_dir.glob("*.pdf"))
+
+ if sample_files:
+ st.markdown("#### Sample Documents")
+ sample_options = ["Upload my own document"] + [f.name for f in sample_files]
+ sample_choice = st.selectbox("Choose a document:", sample_options)
+
+ if sample_choice != "Upload my own document":
+ # Process the selected sample file
+ selected_file = next((f for f in sample_files if f.name == sample_choice), None)
+
+ if selected_file:
+ # Store the selected sample file in session state
+ with open(selected_file, "rb") as f:
+ file_bytes = f.read()
+
+ st.session_state.sample_file = {
+ "name": selected_file.name,
+ "bytes": file_bytes
+ }
+
+ # Preview the selected sample
+ if selected_file.suffix.lower() == ".pdf" and pdf_support:
+ try:
+ with st.spinner("Generating PDF preview..."):
+ images = convert_from_bytes(file_bytes, first_page=1, last_page=1, dpi=150)
+ if images:
+ st.image(images[0], caption=f"Preview: {selected_file.name}", use_column_width=True)
+ except Exception:
+ st.info(f"PDF selected: {selected_file.name}")
+ else:
+ # For images display directly
+ try:
+ from PIL import Image
+ img = Image.open(io.BytesIO(file_bytes))
+ st.image(img, caption=f"Preview: {selected_file.name}", use_column_width=True)
+ except Exception:
+ st.info(f"Selected: {selected_file.name}")
+ else:
+ # Clear the sample file if "Upload my own" is selected
+ if 'sample_file' in st.session_state:
+ del st.session_state.sample_file
+
+ # Display file uploader
+ upload_html = """
+
Upload a document to get started
+
Supported formats: PDF, JPG, PNG
+ """
+
+ upload_container(upload_html)
+ uploaded_file = st.file_uploader("Choose a file", type=["pdf", "png", "jpg", "jpeg"], label_visibility="collapsed")
+
+ if uploaded_file is not None:
+ # Display preview of the uploaded file
+ file_ext = Path(uploaded_file.name).suffix.lower()
+
+ if file_ext == ".pdf" and pdf_support:
+ try:
+ # Convert first page of PDF to image for preview
+ pdf_bytes = uploaded_file.getvalue()
+ with st.spinner("Generating PDF preview..."):
+ images = convert_from_bytes(pdf_bytes, first_page=1, last_page=1, dpi=150)
+ if images:
+ st.image(images[0], caption=f"PDF Preview: {uploaded_file.name}", use_column_width=True)
+ else:
+ st.info(f"PDF uploaded: {uploaded_file.name}")
+ except Exception:
+ st.info(f"PDF uploaded: {uploaded_file.name}")
+ elif file_ext != ".pdf":
+ st.image(uploaded_file, use_column_width=True)
+ else:
+ st.info(f"PDF uploaded: {uploaded_file.name}")
+ else:
+ # No sample files, just show the uploader
+ upload_html = """
+
Upload a document to get started
+
Supported formats: PDF, JPG, PNG
+ """
+
+ upload_container(upload_html)
+ uploaded_file = st.file_uploader("Choose a file", type=["pdf", "png", "jpg", "jpeg"], label_visibility="collapsed")
+
+ if uploaded_file is not None:
+ # Display the file preview
+ file_ext = Path(uploaded_file.name).suffix.lower()
+ if file_ext == ".pdf" and pdf_support:
+ try:
+ pdf_bytes = uploaded_file.getvalue()
+ with st.spinner("Generating PDF preview..."):
+ images = convert_from_bytes(pdf_bytes, first_page=1, last_page=1, dpi=150)
+ if images:
+ st.image(images[0], caption=f"PDF Preview: {uploaded_file.name}", use_column_width=True)
+ except Exception:
+ st.info(f"PDF uploaded: {uploaded_file.name}")
+ elif file_ext != ".pdf":
+ st.image(uploaded_file, use_column_width=True)
+ else:
+ st.info(f"PDF uploaded: {uploaded_file.name}")
+ else:
+ # No input directory
+ upload_html = """
+
Upload a document to get started
+
Supported formats: PDF, JPG, PNG
+ """
+
+ upload_container(upload_html)
+ uploaded_file = st.file_uploader("Choose a file", type=["pdf", "png", "jpg", "jpeg"], label_visibility="collapsed")
+
+ # Process button
+ st.subheader("Step 2: Process the Document")
+
+ # Get the file to process (either uploaded or sample)
+ file_to_process = None
+ if 'sample_file' in st.session_state and sample_choice != "Upload my own document":
+ # Create a FileUploader-like object from the sample file
+ class SampleFileObject:
+ def __init__(self, name, data):
+ self.name = name
+ self._data = data
+
+ def getvalue(self):
+ return self._data
+
+ file_to_process = SampleFileObject(
+ st.session_state.sample_file["name"],
+ st.session_state.sample_file["bytes"]
+ )
+ elif 'uploaded_file' in locals() and uploaded_file is not None:
+ file_to_process = uploaded_file
+
+ # Process button
+ process_button = st.button(
+ "Process Document",
+ disabled=file_to_process is None,
+ use_container_width=True
+ )
+
+ if process_button and file_to_process is not None:
+ with st.spinner("Processing document..."):
+ try:
+ # Process the file
+ result = process_file(file_to_process, use_vision, custom_prompt=custom_prompt if custom_prompt else None)
+
+ if result:
+ st.success("Document processed successfully!")
+
+ # Store result in session state for display in the right column
+ st.session_state.current_result = result
+
+ # Add to processing history
+ history_item = {
+ "id": datetime.now().timestamp(),
+ "fileName": file_to_process.name,
+ "timestamp": datetime.now().isoformat(),
+ "result": result,
+ "useVision": use_vision
+ }
+
+ if 'processing_history' not in st.session_state:
+ st.session_state.processing_history = []
+
+ st.session_state.processing_history.append(history_item)
+
+ st.experimental_rerun()
+ else:
+ st.error("Failed to process document.")
+ except Exception as e:
+ st.error(f"Error processing document: {str(e)}")
+
+ # Experiment instructions
+ experiment_content = """
+
Experiment Instructions
+
+
Step 1: Select a document and choose your options
+
Step 2: Process the document with the selected options
+
Step 3: Analyze the results in the panel on the right
+
Step 4: Try again with different settings (e.g., toggle vision model)
+
Step 5: Compare results between different runs
+
+ """
+ key_concept(experiment_content)
+
+ with col2:
+ # Results display
+ st.subheader("Step 3: View Results")
+
+ if 'current_result' in st.session_state and st.session_state.current_result:
+ result = st.session_state.current_result
+
+ # Display results in a tool container
+ result_html = f"""
+
Vision model used: {'Yes' if latest['useVision'] else 'No'}
+ """
+ tool_container(latest_html)
+
+ # History in expander
+ with st.expander("View Complete Processing History"):
+ for i, item in enumerate(reversed(st.session_state.processing_history)):
+ st.markdown(f"""
+
You're comparing the same document processed with different models.
+ This is an excellent way to evaluate the impact of vision capabilities on OCR accuracy.
Process at least two documents to enable side-by-side comparison. Try processing
+ the same document with and without the vision model to see the differences in OCR quality.
+ """
+ research_question(need_more_content)
+
+ # Analysis guide tab
+ with analyze_tab:
+ st.subheader("Analysis Guide")
+
+ st.markdown("""
+ ### How to Analyze OCR Results
+
+ When analyzing OCR results from historical documents, consider these key factors:
+
+ 1. **Text Accuracy**
+ - Check for common OCR errors (e.g., mistaking "e" for "c", "l" for "1")
+ - Assess recognition of period-specific typography and writing styles
+ - Evaluate handling of degraded or damaged text areas
+
+ 2. **Structure Preservation**
+ - Does the OCR maintain paragraph and section breaks?
+ - Are columns and tabular data correctly preserved?
+ - How well are page transitions handled?
+
+ 3. **Special Elements**
+ - Recognition of footnotes, marginalia, and annotations
+ - Handling of illustrations, diagrams, and decorative elements
+ - Treatment of watermarks, signatures, and stamps
+
+ 4. **Metadata Extraction**
+ - Accuracy of detected languages, topics, and document type
+ - Identification of dates, names, and key entities
+ - Recognition of document purpose and context
+ """)
+
+ col1, col2 = st.columns(2)
+
+ with col1:
+ challenge_content = """
+
Common OCR Challenges
+
+
Typography Variations: Historical fonts that differ from modern text
+
Material Degradation: Fading, stains, tears affecting legibility
+
Handwritten Elements: Marginalia, signatures, and annotations
+
Complex Layouts: Multi-column formats and decorative elements
+
Language and Terminology: Archaic terms and multilingual content
Contextual Reading: Use context to interpret unclear passages
+
Error Patterns: Identify and correct systematic OCR errors
+
Hybrid Analysis: Combine OCR search with close reading
+
Comparative Processing: Try different settings on documents
+
Iterative Refinement: Use insights to improve future processing
+
+ """
+ gray_container(tips_content)
+
+ # Show example analysis if there's processing history
+ if 'processing_history' in st.session_state and st.session_state.processing_history:
+ with st.expander("Example Analysis from Your Documents"):
+ # Pick the latest document
+ latest = st.session_state.processing_history[-1]
+
+ st.markdown(f"""
+ #### Sample Analysis for: {latest['fileName']}
+
+ **Document Context:**
+ - Languages: {', '.join(latest['result'].get('languages', ['Unknown']))}
+ - Topics: {', '.join(latest['result'].get('topics', ['Unknown']))}
+ - Vision model used: {'Yes' if latest['useVision'] else 'No'}
+
+ **What to Look For:**
+ 1. Check how well the model identified key topics and languages
+ 2. Evaluate the completeness of extracted text
+ 3. Note any systematic errors in text recognition
+ 4. Assess how well document structure was preserved
+ """)
\ No newline at end of file
diff --git a/modules/content/module6.py b/modules/content/module6.py
new file mode 100644
index 0000000000000000000000000000000000000000..5efa8ee87b1f737172445d3f7caa20dd79c588b4
--- /dev/null
+++ b/modules/content/module6.py
@@ -0,0 +1,154 @@
+import streamlit as st
+from layout import gray_container, key_concept, quote, tool_container
+from datetime import datetime
+
+def render():
+ """Module 6: Conclusion and Future Directions"""
+
+ st.title("Module 6: Conclusion and Future Directions")
+
+ col1, col2 = st.columns([3, 2])
+
+ with col1:
+ summary_content = """
+
Workshop Summary
+
Throughout this workshop, we've explored:
+
+
Text-Image Interdependence: The complex relationship between textual and visual elements
+
OCR Technology: The evolution of OCR and its application to historical materials
+
Methodological Approaches: Hybrid strategies for working with historical texts
+
Practical Application: Hands-on experience with OCR processing tools
+ """
+ gray_container(research_content)
+
+ # Inspiring quote
+ quote_content = "The digital humanities are not about building, they're about sharing. The digital humanities are not about the digital at all. They're all about innovation and disruption. The digital humanities are really an insurgent humanities."
+ quote(quote_content, "Matthew Kirschenbaum, Professor of Digital Humanities")
+
+ # Additional resources
+ resources_content = """
+
This workshop was designed as an educational resource for historians, archivists, and digital humanities scholars.
+
It demonstrates the integration of modern AI vision-language models with historical research methodologies.
+
Special thanks to the digital humanities community for continued innovation in computational approaches to historical research.
+ """
+ st.markdown(acknowledgment_content, unsafe_allow_html=True)
+
+ # Restart the workshop button
+ if st.button("Start Workshop Again", use_container_width=True):
+ # Reset the session state to start the workshop again
+ if 'current_module' in st.session_state:
+ st.session_state.current_module = 1
+
+ # Do not reset the processing history
+
+ st.experimental_rerun()
\ No newline at end of file
diff --git a/modules/educational_module.py b/modules/educational_module.py
new file mode 100644
index 0000000000000000000000000000000000000000..6bc50f5e03de8e234a6c3369da6b68ce4a0faee0
--- /dev/null
+++ b/modules/educational_module.py
@@ -0,0 +1,547 @@
+import streamlit as st
+import io
+import tempfile
+from pathlib import Path
+from datetime import datetime
+from layout import tool_container, key_concept, research_question, upload_container
+import sys
+
+# Import the necessary modules for OCR processing
+sys.path.append(str(Path(__file__).parent.parent))
+try:
+ from process_file import process_file as process_file_util
+ process_file = process_file_util
+except ImportError:
+ # Fallback if process_file is not available
+ def process_file(uploaded_file, use_vision=True, custom_prompt=None):
+ """Fallback function for processing files"""
+ st.warning("Using mock processing function. Real OCR functionality is not available.")
+ return {
+ "file_name": uploaded_file.name,
+ "languages": ["English"],
+ "topics": ["History", "Document"],
+ "ocr_contents": {
+ "content": f"This is mock OCR content for {uploaded_file.name}. Vision model: {use_vision}"
+ }
+ }
+
+def render():
+ """Module 5: Interactive OCR Experiment"""
+
+ st.title("Module 5: Interactive OCR Experiment")
+
+ # Introduction to the interactive experiment
+ intro_content = """
+
Interactive OCR Experiment
+
+ This interactive experiment allows you to process historical documents with OCR and analyze the results.
+ Try different settings and compare the outcomes to understand the strengths and limitations of OCR technology.
+
+ """
+ st.markdown(intro_content, unsafe_allow_html=True)
+
+ # Create tabs for different activities
+ experiment_tab, compare_tab, analyze_tab = st.tabs(["Process Documents", "Compare Results", "Analysis Guide"])
+
+ # Try to import PDF tools if available
+ try:
+ from pdf2image import convert_from_bytes
+ pdf_support = True
+ except ImportError:
+ pdf_support = False
+ st.warning("PDF preview functionality is limited. The pdf2image module is required for PDF previews.")
+
+ with experiment_tab:
+ # Create a two-column layout
+ col1, col2 = st.columns([1, 1])
+
+ with col1:
+ # Tool container for document selection and options
+ st.subheader("Step 1: Select Document & Options")
+
+ # Processing options
+ use_vision = st.checkbox("Use Vision Model", value=True,
+ help="Use the vision model for improved analysis")
+
+ # Additional prompt
+ st.markdown("### Custom Research Prompt (Optional)")
+ st.markdown("""Provide additional instructions to guide the OCR analysis.
+ Focus on specific aspects of historical research you're interested in.""")
+ custom_prompt = st.text_area("Research Prompt",
+ placeholder="E.g., Focus on identifying dates and historical figures...",
+ help="Optional instructions to guide the analysis")
+
+ # Sample document selection
+ input_dir = Path(__file__).parent.parent / "input"
+
+ if input_dir.exists():
+ sample_files = list(input_dir.glob("*.jpg")) + list(input_dir.glob("*.png")) + list(input_dir.glob("*.pdf"))
+
+ if sample_files:
+ st.markdown("#### Sample Documents")
+ sample_options = ["Upload my own document"] + [f.name for f in sample_files]
+ sample_choice = st.selectbox("Choose a document:", sample_options)
+
+ if sample_choice != "Upload my own document":
+ # Process the selected sample file
+ selected_file = next((f for f in sample_files if f.name == sample_choice), None)
+
+ if selected_file:
+ # Store the selected sample file in session state
+ with open(selected_file, "rb") as f:
+ file_bytes = f.read()
+
+ st.session_state.sample_file = {
+ "name": selected_file.name,
+ "bytes": file_bytes
+ }
+
+ # Preview the selected sample
+ if selected_file.suffix.lower() == ".pdf" and pdf_support:
+ try:
+ with st.spinner("Generating PDF preview..."):
+ images = convert_from_bytes(file_bytes, first_page=1, last_page=1, dpi=150)
+ if images:
+ st.image(images[0], caption=f"Preview: {selected_file.name}", use_column_width=True)
+ except Exception:
+ st.info(f"PDF selected: {selected_file.name}")
+ else:
+ # For images display directly
+ try:
+ from PIL import Image
+ img = Image.open(io.BytesIO(file_bytes))
+ st.image(img, caption=f"Preview: {selected_file.name}", use_column_width=True)
+ except Exception:
+ st.info(f"Selected: {selected_file.name}")
+ else:
+ # Clear the sample file if "Upload my own" is selected
+ if 'sample_file' in st.session_state:
+ del st.session_state.sample_file
+
+ # Display file uploader
+ upload_html = """
+
Upload a document to get started
+
Supported formats: PDF, JPG, PNG
+ """
+
+ upload_container(upload_html)
+ uploaded_file = st.file_uploader("Choose a file", type=["pdf", "png", "jpg", "jpeg"], label_visibility="collapsed")
+
+ if uploaded_file is not None:
+ # Display preview of the uploaded file
+ file_ext = Path(uploaded_file.name).suffix.lower()
+
+ if file_ext == ".pdf" and pdf_support:
+ try:
+ # Convert first page of PDF to image for preview
+ pdf_bytes = uploaded_file.getvalue()
+ with st.spinner("Generating PDF preview..."):
+ images = convert_from_bytes(pdf_bytes, first_page=1, last_page=1, dpi=150)
+ if images:
+ st.image(images[0], caption=f"PDF Preview: {uploaded_file.name}", use_column_width=True)
+ else:
+ st.info(f"PDF uploaded: {uploaded_file.name}")
+ except Exception:
+ st.info(f"PDF uploaded: {uploaded_file.name}")
+ elif file_ext != ".pdf":
+ st.image(uploaded_file, use_column_width=True)
+ else:
+ st.info(f"PDF uploaded: {uploaded_file.name}")
+ else:
+ # No sample files, just show the uploader
+ upload_html = """
+
Upload a document to get started
+
Supported formats: PDF, JPG, PNG
+ """
+
+ upload_container(upload_html)
+ uploaded_file = st.file_uploader("Choose a file", type=["pdf", "png", "jpg", "jpeg"], label_visibility="collapsed")
+
+ if uploaded_file is not None:
+ # Display the file preview
+ file_ext = Path(uploaded_file.name).suffix.lower()
+ if file_ext == ".pdf" and pdf_support:
+ try:
+ pdf_bytes = uploaded_file.getvalue()
+ with st.spinner("Generating PDF preview..."):
+ images = convert_from_bytes(pdf_bytes, first_page=1, last_page=1, dpi=150)
+ if images:
+ st.image(images[0], caption=f"PDF Preview: {uploaded_file.name}", use_column_width=True)
+ except Exception:
+ st.info(f"PDF uploaded: {uploaded_file.name}")
+ elif file_ext != ".pdf":
+ st.image(uploaded_file, use_column_width=True)
+ else:
+ st.info(f"PDF uploaded: {uploaded_file.name}")
+ else:
+ # No input directory
+ upload_html = """
+
Upload a document to get started
+
Supported formats: PDF, JPG, PNG
+ """
+
+ upload_container(upload_html)
+ uploaded_file = st.file_uploader("Choose a file", type=["pdf", "png", "jpg", "jpeg"], label_visibility="collapsed")
+
+ # Process button
+ st.subheader("Step 2: Process the Document")
+
+ # Get the file to process (either uploaded or sample)
+ file_to_process = None
+ if 'sample_file' in st.session_state and sample_choice != "Upload my own document":
+ # Create a FileUploader-like object from the sample file
+ class SampleFileObject:
+ def __init__(self, name, data):
+ self.name = name
+ self._data = data
+
+ def getvalue(self):
+ return self._data
+
+ file_to_process = SampleFileObject(
+ st.session_state.sample_file["name"],
+ st.session_state.sample_file["bytes"]
+ )
+ elif 'uploaded_file' in locals() and uploaded_file is not None:
+ file_to_process = uploaded_file
+
+ # Process button
+ process_button = st.button(
+ "Process Document",
+ disabled=file_to_process is None,
+ use_container_width=True
+ )
+
+ if process_button and file_to_process is not None:
+ with st.spinner("Processing document..."):
+ try:
+ # Process the file
+ result = process_file(file_to_process, use_vision, custom_prompt=custom_prompt if custom_prompt else None)
+
+ if result:
+ st.success("Document processed successfully!")
+
+ # Store result in session state for display in the right column
+ st.session_state.current_result = result
+
+ # Add to processing history
+ history_item = {
+ "id": datetime.now().timestamp(),
+ "fileName": file_to_process.name,
+ "timestamp": datetime.now().isoformat(),
+ "result": result,
+ "useVision": use_vision
+ }
+
+ if 'processing_history' not in st.session_state:
+ st.session_state.processing_history = []
+
+ st.session_state.processing_history.append(history_item)
+
+ st.experimental_rerun()
+ else:
+ st.error("Failed to process document.")
+ except Exception as e:
+ st.error(f"Error processing document: {str(e)}")
+
+ # Experiment instructions
+ experiment_content = """
+
Experiment Instructions
+
+
Step 1: Select a document and choose your options
+
Step 2: Process the document with the selected options
+
Step 3: Analyze the results in the panel on the right
+
Step 4: Try again with different settings (e.g., toggle vision model)
+
Step 5: Compare results between different runs
+
+ """
+ key_concept(experiment_content)
+
+ with col2:
+ # Results display
+ st.subheader("Step 3: View Results")
+
+ if 'current_result' in st.session_state and st.session_state.current_result:
+ result = st.session_state.current_result
+
+ # Display results in a tool container
+ result_html = f"""
+
Vision model used: {'Yes' if latest['useVision'] else 'No'}
+ """
+ tool_container(latest_html)
+
+ # History in expander
+ with st.expander("View Complete Processing History"):
+ for i, item in enumerate(reversed(st.session_state.processing_history)):
+ st.markdown(f"""
+
You're comparing the same document processed with different models.
+ This is an excellent way to evaluate the impact of vision capabilities on OCR accuracy.
Process at least two documents to enable side-by-side comparison. Try processing
+ the same document with and without the vision model to see the differences in OCR quality.
+ """
+ research_question(need_more_content)
+
+ # Analysis guide tab
+ with analyze_tab:
+ st.subheader("Analysis Guide")
+
+ st.markdown("""
+ ### How to Analyze OCR Results
+
+ When analyzing OCR results from historical documents, consider these key factors:
+
+ 1. **Text Accuracy**
+ - Check for common OCR errors (e.g., mistaking "e" for "c", "l" for "1")
+ - Assess recognition of period-specific typography and writing styles
+ - Evaluate handling of degraded or damaged text areas
+
+ 2. **Structure Preservation**
+ - Does the OCR maintain paragraph and section breaks?
+ - Are columns and tabular data correctly preserved?
+ - How well are page transitions handled?
+
+ 3. **Special Elements**
+ - Recognition of footnotes, marginalia, and annotations
+ - Handling of illustrations, diagrams, and decorative elements
+ - Treatment of watermarks, signatures, and stamps
+
+ 4. **Metadata Extraction**
+ - Accuracy of detected languages, topics, and document type
+ - Identification of dates, names, and key entities
+ - Recognition of document purpose and context
+ """)
+
+ col1, col2 = st.columns(2)
+
+ with col1:
+ challenge_content = """
+
Common OCR Challenges
+
+
Typography Variations: Historical fonts that differ from modern text
+
Material Degradation: Fading, stains, tears affecting legibility
+
Handwritten Elements: Marginalia, signatures, and annotations
+
Complex Layouts: Multi-column formats and decorative elements
+
Language and Terminology: Archaic terms and multilingual content
Contextual Reading: Use context to interpret unclear passages
+
Error Patterns: Identify and correct systematic OCR errors
+
Hybrid Analysis: Combine OCR search with close reading
+
Comparative Processing: Try different settings on documents
+
Iterative Refinement: Use insights to improve future processing
+
+ """
+ gray_container(tips_content)
+
+ # Show example analysis if there's processing history
+ if 'processing_history' in st.session_state and st.session_state.processing_history:
+ with st.expander("Example Analysis from Your Documents"):
+ # Pick the latest document
+ latest = st.session_state.processing_history[-1]
+
+ st.markdown(f"""
+ #### Sample Analysis for: {latest['fileName']}
+
+ **Document Context:**
+ - Languages: {', '.join(latest['result'].get('languages', ['Unknown']))}
+ - Topics: {', '.join(latest['result'].get('topics', ['Unknown']))}
+ - Vision model used: {'Yes' if latest['useVision'] else 'No'}
+
+ **What to Look For:**
+ 1. Check how well the model identified key topics and languages
+ 2. Evaluate the completeness of extracted text
+ 3. Note any systematic errors in text recognition
+ 4. Assess how well document structure was preserved
+ """)
\ No newline at end of file
diff --git a/modules/modular_app.py b/modules/modular_app.py
new file mode 100644
index 0000000000000000000000000000000000000000..b18cbb21b315e8cb8e42af4cb433afeceed2ea97
--- /dev/null
+++ b/modules/modular_app.py
@@ -0,0 +1,276 @@
+import streamlit as st
+from pathlib import Path
+import sys
+from layout import page_wrapper
+from modules import get_module, get_module_name, module_names
+
+# Set page configuration with dark theme
+st.set_page_config(
+ page_title="Historical OCR Workshop",
+ page_icon="📜",
+ layout="wide",
+ initial_sidebar_state="collapsed"
+)
+
+# Initialize session state for workshop navigation
+if 'current_module' not in st.session_state:
+ st.session_state.current_module = 1
+
+if 'workshop_started' not in st.session_state:
+ st.session_state.workshop_started = False
+
+if 'processing_history' not in st.session_state:
+ st.session_state.processing_history = []
+
+def navigate_to_module(module_number):
+ """Navigate to a specific module"""
+ st.session_state.current_module = module_number
+ st.rerun()
+
+# Welcome screen if workshop hasn't been started
+if not st.session_state.workshop_started:
+ def welcome_screen():
+ """Renders the welcome/start screen"""
+ # Hero section with eye-catching design
+ st.markdown("""
+
+
Historical OCR Workshop
+
Unlock the potential of historical documents with modern OCR technology
+
+ This interactive workshop explores the application of OCR technology to historical documents,
+ combining theoretical understanding with practical experiences. Designed for historians,
+ archivists, and digital humanities scholars, it offers both conceptual frameworks and hands-on skills.
+
+ Optical Character Recognition (OCR) technology enables computers to extract text from images and documents.
+ Modern OCR uses AI vision models to understand both the text and its visual context, making it powerful for
+ historical research and digital humanities.
+
+ """, unsafe_allow_html=True)
+
+ with col2:
+ # Add an engaging research question
+ st.markdown("""
+
+
For Historians:
+ How might OCR technology transform our access to and interpretation of historical documents?
+ What new research questions become possible when large archives become machine-readable?
+
+ """, unsafe_allow_html=True)
+
+ # Display a sample historical document image
+ input_dir = Path(__file__).parent / "input"
+ sample_path = input_dir / "magellan-travels.jpg"
+ if sample_path.exists():
+ try:
+ from PIL import Image
+ with Image.open(sample_path) as img:
+ st.image(img, caption="Sample Historical Document", width=300)
+ except Exception:
+ pass
+
+ # What you'll learn section with visual learning outcomes
+ st.markdown('
What You\'ll Learn
', unsafe_allow_html=True)
+
+ # Create three columns for clean layout
+ col1, col2, col3 = st.columns(3)
+
+ with col1:
+ st.markdown("""
+
+
Conceptual Understanding
+
+ - Text-image relationships in historical documents
+ - Evolution of OCR technology
+ - AI vision models for document analysis
+ - Historical typography challenges
+
+ """, unsafe_allow_html=True)
+
+ with col2:
+ st.markdown("""
+
+
Methodological Approaches
+
+ - Critical frameworks for OCR in historical research
+ - Hybrid computational-traditional methods
+ - Error analysis and interpretation
+ - Contextual reading strategies
+
+ """, unsafe_allow_html=True)
+
+ with col3:
+ st.markdown("""
+
+
Practical Skills
+
+ - Processing historical documents with OCR
+ - Analyzing and structuring extracted information
+ - Integrating OCR into research workflows
+ - Building searchable archives
+
+ "The digital turn in historical research is not just about converting analog to digital;
+ it's about transforming how we access, analyze, and interpret the past."
+
+ — Dr. Jane Winters, Professor of Digital Humanities
+
", unsafe_allow_html=True)
+
+ for i, name in enumerate(module_names, 1):
+ btn_style = "primary" if i == current_module else "secondary"
+ if st.button(f"{i}: {name}", key=f"nav_module_{i}", type=btn_style, use_container_width=True):
+ st.session_state.current_module = i
+ st.rerun()
+
+ # About the workshop in a collapsible section
+ with st.expander("About the Workshop"):
+ st.markdown("""
+ This interactive workshop explores OCR technology for historical documents.
+
+ **How to use this workshop:**
+ 1. Navigate through modules sequentially
+ 2. Expand content sections to read more
+ 3. Try the interactive OCR experiment
+ 4. Reflect on research questions
+
+ For help or more information, use the reference materials in Module 6.
+ """)
+
+ # Processing history if available
+ if st.session_state.processing_history:
+ with st.expander("Your Activity"):
+ st.markdown(f"Documents processed: {len(st.session_state.processing_history)}", unsafe_allow_html=True)
+
+ # Show the most recent document processed
+ latest = st.session_state.processing_history[-1]
+ st.markdown(f"""
+
+ Latest document: {latest['fileName']}
+ Processed with {' vision model' if latest['useVision'] else ' basic OCR'}
+
+ """, unsafe_allow_html=True)
+
+ # Render the current module content using the page wrapper
+ page_wrapper(module.render, current_module)
+
+# At the bottom of the page, create the hidden navigation buttons for the fixed navigation bar
+if st.session_state.workshop_started:
+ # Previous navigation button (hidden, activated by the fixed nav)
+ if st.session_state.current_module > 1:
+ if st.button("←", key=f"nav_prev_{st.session_state.current_module-1}", label_visibility="collapsed"):
+ st.session_state.current_module -= 1
+ st.rerun()
+
+ # Next navigation button (hidden, activated by the fixed nav)
+ if st.session_state.current_module < 6:
+ if st.button("→", key=f"nav_next_{st.session_state.current_module+1}", label_visibility="collapsed"):
+ st.session_state.current_module += 1
+ st.rerun()
+
+ # Module navigation dots (hidden, activated by the fixed nav)
+ for i in range(1, 7):
+ if st.button(f"{i}", key=f"nav_dot_{i}", label_visibility="collapsed"):
+ st.session_state.current_module = i
+ st.rerun()
\ No newline at end of file
diff --git a/ocr_processing.py b/ocr_processing.py
deleted file mode 100644
index 50faef5d589e5d3400177fc41bc3295248effbad..0000000000000000000000000000000000000000
--- a/ocr_processing.py
+++ /dev/null
@@ -1,665 +0,0 @@
-# Standard library imports
-import os
-import hashlib
-import tempfile
-import logging
-import time
-from datetime import datetime
-from pathlib import Path
-
-# Configure logging
-logging.basicConfig(level=logging.INFO,
- format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
-logger = logging.getLogger(__name__)
-
-# Third-party imports
-import streamlit as st
-
-# Local application imports
-from structured_ocr import StructuredOCR
-# Import from updated utils directory
-from utils.image_utils import clean_ocr_result
-# Temporarily retain old utils imports until they are fully migrated
-from utils import generate_cache_key, timing, format_timestamp, create_descriptive_filename, extract_subject_tags
-import preprocessing
-from error_handler import handle_ocr_error, check_file_size
-from image_segmentation import segment_image_for_ocr, process_segmented_image
-
-@st.cache_data(ttl=24*3600, max_entries=20, show_spinner=False)
-def process_file_cached(file_path, file_type, use_vision, file_size_mb, cache_key, preprocessing_options_hash=None, custom_prompt=None):
- """
- Cached version of OCR processing to reuse results
-
- Args:
- file_path: Path to the file to process
- file_type: Type of file (pdf or image)
- use_vision: Whether to use vision model
- file_size_mb: File size in MB
- cache_key: Cache key for the file
- preprocessing_options_hash: Hash of preprocessing options
- custom_prompt: Custom prompt to use for OCR
-
- Returns:
- dict: OCR result
- """
- # Initialize OCR processor
- processor = StructuredOCR()
-
- # Process the file
- with timing(f"OCR processing of {file_type} file"):
- result = processor.process_file(
- file_path,
- file_type=file_type,
- use_vision=use_vision,
- file_size_mb=file_size_mb,
- custom_prompt=custom_prompt
- )
-
- return result
-
-def process_file(uploaded_file, use_vision=True, preprocessing_options=None, progress_reporter=None,
- pdf_dpi=150, max_pages=3, pdf_rotation=0, custom_prompt=None, perf_mode="Quality",
- use_segmentation=False):
- """
- Process the uploaded file and return the OCR results
-
- Args:
- uploaded_file: The uploaded file to process
- use_vision: Whether to use vision model
- preprocessing_options: Dictionary of preprocessing options
- progress_reporter: ProgressReporter instance for UI updates
- pdf_dpi: DPI for PDF conversion
- max_pages: Maximum number of pages to process
- pdf_rotation: PDF rotation value
- custom_prompt: Custom prompt for OCR
- perf_mode: Performance mode (Quality or Speed)
-
- Returns:
- dict: OCR result
- """
- if preprocessing_options is None:
- preprocessing_options = {}
-
- # Create a container for progress indicators if not provided
- if progress_reporter is None:
- from ui.ui_components import ProgressReporter
- progress_reporter = ProgressReporter(st.empty()).setup()
-
- # Initialize temporary file paths list
- temp_file_paths = []
-
- # Also track temporary files in session state for reliable cleanup
- if 'temp_file_paths' not in st.session_state:
- st.session_state.temp_file_paths = []
-
- try:
- # Check if file size exceeds maximum allowed size
- is_valid, file_size_mb, error_message = check_file_size(uploaded_file.getvalue())
- if not is_valid:
- progress_reporter.complete(success=False)
- st.error(error_message)
- return {
- "file_name": uploaded_file.name,
- "topics": ["Document"],
- "languages": ["English"],
- "error": error_message,
- "ocr_contents": {
- "error": error_message,
- "partial_text": "Document could not be processed due to size limitations."
- }
- }
-
- # Update progress
- progress_reporter.update(10, "Initializing OCR processor...")
-
- # Determine file type from extension
- file_ext = Path(uploaded_file.name).suffix.lower()
- file_type = "pdf" if file_ext == ".pdf" else "image"
- file_bytes = uploaded_file.getvalue()
-
- # For PDFs, we need to handle differently
- if file_type == "pdf":
- progress_reporter.update(20, "Preparing PDF document...")
-
- # Create a temporary file for processing
- temp_path = tempfile.NamedTemporaryFile(delete=False, suffix=file_ext).name
- with open(temp_path, 'wb') as f:
- f.write(file_bytes)
- temp_file_paths.append(temp_path)
-
- # Track temp files in session state for reliable cleanup
- if temp_path not in st.session_state.temp_file_paths:
- st.session_state.temp_file_paths.append(temp_path)
- logger.info(f"Added temp file to session state: {temp_path}")
-
- # Generate cache key
- cache_key = generate_cache_key(
- file_bytes,
- file_type,
- use_vision,
- preprocessing_options,
- pdf_rotation,
- custom_prompt
- )
-
- # Use the document type information from preprocessing options
- doc_type = preprocessing_options.get("document_type", "standard")
- modified_custom_prompt = custom_prompt
-
- # Enhance the prompt with document-type specific instructions
- # Check for letterhead/marginalia document types with specialized handling
- try:
- from utils.helpers.letterhead_handler import get_letterhead_prompt, is_likely_letterhead
- # Extract text density features if available
- features = None
- if 'text_density' in preprocessing_options:
- features = preprocessing_options['text_density']
-
- # Check if this looks like a letterhead document
- if is_likely_letterhead(temp_path, features):
- # Get specialized letterhead prompt
- letterhead_prompt = get_letterhead_prompt(temp_path, features)
- if letterhead_prompt:
- logger.info(f"Using specialized letterhead prompt for document")
- modified_custom_prompt = letterhead_prompt
- # Set document type for tracking
- preprocessing_options["document_type"] = "letterhead"
- doc_type = "letterhead"
- except ImportError:
- logger.debug("Letterhead handler not available")
-
- # Add document-type specific instructions based on preprocessing options
- if doc_type == "handwritten" and not modified_custom_prompt:
- modified_custom_prompt = "This is a handwritten document. Please carefully transcribe all handwritten text, preserving line breaks and original formatting."
- elif doc_type == "handwritten" and "handwritten" not in modified_custom_prompt.lower():
- modified_custom_prompt += " This is a handwritten document. Please carefully transcribe all handwritten text, preserving line breaks and original formatting."
- elif doc_type == "newspaper" and not modified_custom_prompt:
- modified_custom_prompt = "This is a newspaper or document with columns. Please extract all text content from each column, maintaining proper reading order."
- elif doc_type == "newspaper" and "column" not in modified_custom_prompt.lower() and "newspaper" not in modified_custom_prompt.lower():
- modified_custom_prompt += " This appears to be a newspaper or document with columns. Please extract all text content from each column."
- elif doc_type == "book" and not modified_custom_prompt:
- modified_custom_prompt = "This is a book page. Extract titles, headers, footnotes, and body text, preserving paragraph structure and formatting."
-
- # Update the cache key with the modified prompt
- if modified_custom_prompt != custom_prompt:
- cache_key = generate_cache_key(
- open(temp_path, 'rb').read(),
- file_type,
- use_vision,
- preprocessing_options,
- pdf_rotation,
- modified_custom_prompt
- )
-
- progress_reporter.update(30, "Processing PDF with enhanced OCR...")
-
- # Process with cached function if possible
- try:
- result = process_file_cached(temp_path, file_type, use_vision, file_size_mb, cache_key,
- str(preprocessing_options), modified_custom_prompt)
- progress_reporter.update(90, "Finalizing results...")
- except Exception as e:
- logger.warning(f"Cached processing failed: {str(e)}. Using direct processing.")
- progress_reporter.update(60, f"Processing error: {str(e)}. Using enhanced PDF processor...")
-
- # Import the enhanced PDF processor
- try:
- from utils.pdf_ocr import PDFOCR
-
- # Use our specialized PDF processor
- pdf_processor = PDFOCR()
-
- # Process with the enhanced PDF processor
- result = pdf_processor.process_pdf(
- pdf_path=temp_path,
- use_vision=use_vision,
- max_pages=max_pages,
- custom_prompt=modified_custom_prompt
- )
-
- logger.info("PDF successfully processed with enhanced PDF processor")
- progress_reporter.update(90, "Finalizing results...")
- except ImportError:
- logger.warning("Enhanced PDF processor not available. Falling back to standard processing.")
- progress_reporter.update(70, "Falling back to standard PDF processing...")
-
- # If enhanced processor is not available, fall back to direct StructuredOCR processing
- processor = StructuredOCR()
- result = processor.process_file(
- file_path=temp_path,
- file_type="pdf",
- use_vision=use_vision,
- custom_prompt=modified_custom_prompt,
- file_size_mb=file_size_mb,
- max_pages=max_pages
- )
- progress_reporter.update(90, "Finalizing results...")
- else:
- # For image files
- progress_reporter.update(20, "Preparing image for processing...")
-
- # Apply preprocessing if needed
- temp_path, preprocessing_applied = preprocessing.apply_preprocessing_to_file(
- file_bytes,
- file_ext,
- preprocessing_options,
- temp_file_paths
- )
-
- if preprocessing_applied:
- progress_reporter.update(30, "Applied image preprocessing...")
-
- # Apply image segmentation if requested
- # This is especially helpful for complex documents with mixed text and images
- if use_segmentation:
- progress_reporter.update(35, "Applying image segmentation to separate text and image regions...")
-
- try:
- # Perform image segmentation with content preservation if requested
- preserve_content = preprocessing_options.get("preserve_content", True)
- segmentation_results = segment_image_for_ocr(
- temp_path,
- vision_enabled=use_vision,
- preserve_content=preserve_content
- )
-
- if segmentation_results['combined_result'] is not None:
- # Save the segmented result to a new temporary file
- segmented_temp_path = tempfile.NamedTemporaryFile(delete=False, suffix='.jpg').name
- segmentation_results['combined_result'].save(segmented_temp_path)
- temp_file_paths.append(segmented_temp_path)
-
- # Check if we have individual region images to process separately
- if 'region_images' in segmentation_results and segmentation_results['region_images']:
- # Process each region separately for better results
- regions_count = len(segmentation_results['region_images'])
- logger.info(f"Processing {regions_count} text regions individually")
- progress_reporter.update(40, f"Processing {regions_count} text regions separately...")
-
- # Initialize StructuredOCR processor
- processor = StructuredOCR()
-
- # Store individual region results
- region_results = []
-
- # Process each region individually
- for idx, region_info in enumerate(segmentation_results['region_images']):
- # Save region image to temp file
- region_temp_path = tempfile.NamedTemporaryFile(delete=False, suffix='.jpg').name
- region_info['pil_image'].save(region_temp_path)
- temp_file_paths.append(region_temp_path)
-
- # Create region-specific prompt
- region_prompt = f"This is region {idx+1} of {regions_count} from a segmented document. Extract all visible text precisely, preserving line breaks and structure."
-
- # Process the region
- try:
- region_result = processor.process_file(
- file_path=region_temp_path,
- file_type="image",
- use_vision=use_vision,
- custom_prompt=region_prompt,
- file_size_mb=None
- )
-
- # Store result with region info
- if 'ocr_contents' in region_result and 'raw_text' in region_result['ocr_contents']:
- region_results.append({
- 'text': region_result['ocr_contents']['raw_text'],
- 'coordinates': region_info['coordinates'],
- 'order': region_info['order']
- })
- except Exception as region_err:
- logger.warning(f"Error processing region {idx+1}: {str(region_err)}")
-
- # Sort regions by their order for correct reading flow
- region_results.sort(key=lambda x: x['order'])
-
- # Import the text utilities for intelligent merging
- try:
- from utils.text_utils import merge_region_texts
- # Use intelligent merging to avoid duplication in overlapped regions
- combined_text = merge_region_texts(region_results)
- logger.info("Using intelligent text merging for overlapping regions")
- except ImportError:
- # Fallback to simple joining if import fails
- combined_text = "\n\n".join([r['text'] for r in region_results if r['text'].strip()])
- logger.warning("Using simple text joining (utils.text_utils not available)")
-
- # Store combined results for later use
- preprocessing_options['segmentation_data'] = {
- 'text_regions_coordinates': segmentation_results.get('text_regions_coordinates', []),
- 'regions_count': regions_count,
- 'segmentation_applied': True,
- 'combined_text': combined_text,
- 'region_results': region_results
- }
-
- logger.info(f"Successfully processed {len(region_results)} text regions")
-
- # Set up the temp path to use the segmented image
- temp_path = segmented_temp_path
-
- # IMPORTANT: We've already extracted text from individual regions,
- # emphasize their importance in our prompt
- if custom_prompt:
- # Add strong emphasis on using the already extracted text
- custom_prompt += f" IMPORTANT: The document has been segmented into {regions_count} text regions that have been processed individually. The text from these regions should be given HIGHEST PRIORITY and used as the primary source of text for the document. The combined image is provided only as supplementary context."
- else:
- # Create explicit prompt prioritizing region text
- custom_prompt = f"CRITICAL: This document has been preprocessed to highlight {regions_count} text regions that have been individually processed. The text from these regions is the PRIMARY source of content and should be prioritized over any text extracted from the combined image. Use the combined image only for context and layout understanding."
- else:
- # No individual regions found, use combined result
- temp_path = segmented_temp_path
-
- # Enhanced prompt based on segmentation results
- regions_count = len(segmentation_results.get('text_regions_coordinates', []))
- if custom_prompt:
- # Add segmentation info to existing prompt
- custom_prompt += f" The document has been segmented and contains approximately {regions_count} text regions that should be carefully extracted. Please focus on extracting all text from these regions."
- else:
- # Create new prompt focused on text extraction from segmented regions
- custom_prompt = f"This document has been preprocessed to highlight {regions_count} text regions. Please carefully extract all text from these highlighted regions, preserving the reading order and structure."
-
- # Store segmentation data in preprocessing options for later use
- preprocessing_options['segmentation_data'] = {
- 'text_regions_coordinates': segmentation_results.get('text_regions_coordinates', []),
- 'regions_count': regions_count,
- 'segmentation_applied': True
- }
-
- logger.info(f"Image segmentation applied. Found {len(segmentation_results.get('text_regions_coordinates', []))} text regions.")
- progress_reporter.update(40, f"Identified {len(segmentation_results.get('text_regions_coordinates', []))} text regions for extraction...")
- else:
- logger.warning("Image segmentation produced no result, using original image.")
- except Exception as seg_error:
- logger.warning(f"Image segmentation failed: {str(seg_error)}. Continuing with standard processing.")
-
- # Generate cache key
- cache_key = generate_cache_key(
- open(temp_path, 'rb').read(),
- file_type,
- use_vision,
- preprocessing_options,
- 0, # No rotation for images (handled in preprocessing)
- custom_prompt
- )
-
- # Process the file using cached function if possible
- progress_reporter.update(50, "Processing document with OCR...")
- try:
- # Use the document type from preprocessing options
- doc_type = preprocessing_options.get("document_type", "standard")
- modified_custom_prompt = custom_prompt
-
- # Check for letterhead/marginalia document types with specialized handling
- try:
- from utils.helpers.letterhead_handler import get_letterhead_prompt, is_likely_letterhead
- # Extract text density features if available
- features = None
- if 'text_density' in preprocessing_options:
- features = preprocessing_options['text_density']
-
- # Check if this looks like a letterhead document
- if is_likely_letterhead(temp_path, features):
- # Get specialized letterhead prompt
- letterhead_prompt = get_letterhead_prompt(temp_path, features)
- if letterhead_prompt:
- logger.info(f"Using specialized letterhead prompt for document")
- modified_custom_prompt = letterhead_prompt
- # Set document type for tracking
- preprocessing_options["document_type"] = "letterhead"
- doc_type = "letterhead"
- except ImportError:
- logger.debug("Letterhead handler not available")
-
- # Add document-type specific instructions based on preprocessing options
- if doc_type == "handwritten" and not modified_custom_prompt:
- modified_custom_prompt = "This is a handwritten document. Please carefully transcribe all handwritten text, preserving line breaks and original formatting."
- elif doc_type == "handwritten" and "handwritten" not in modified_custom_prompt.lower():
- modified_custom_prompt += " This is a handwritten document. Please carefully transcribe all handwritten text, preserving line breaks and original formatting."
- elif doc_type == "newspaper" and not modified_custom_prompt:
- modified_custom_prompt = "This is a newspaper or document with columns. Please extract all text content from each column, maintaining proper reading order."
- elif doc_type == "newspaper" and "column" not in modified_custom_prompt.lower() and "newspaper" not in modified_custom_prompt.lower():
- modified_custom_prompt += " This appears to be a newspaper or document with columns. Please extract all text content from each column."
- elif doc_type == "book" and not modified_custom_prompt:
- modified_custom_prompt = "This is a book page. Extract titles, headers, footnotes, and body text, preserving paragraph structure and formatting."
-
- # Update the cache key with the modified prompt
- if modified_custom_prompt != custom_prompt:
- cache_key = generate_cache_key(
- open(temp_path, 'rb').read(),
- file_type,
- use_vision,
- preprocessing_options,
- 0,
- modified_custom_prompt
- )
-
- result = process_file_cached(temp_path, file_type, use_vision, file_size_mb, cache_key, str(preprocessing_options), modified_custom_prompt)
- progress_reporter.update(80, "Analyzing document structure...")
- progress_reporter.update(90, "Finalizing results...")
- except Exception as e:
- logger.warning(f"Cached processing failed: {str(e)}. Retrying with direct processing.")
- progress_reporter.update(60, f"Processing error: {str(e)}. Retrying...")
-
- # If caching fails, process directly
- processor = StructuredOCR()
-
- # Apply performance mode settings
- if perf_mode == "Speed":
- # Use simpler processing for speed
- pass # Any speed optimizations would be handled by the StructuredOCR class
-
- # Use the document type from preprocessing options
- doc_type = preprocessing_options.get("document_type", "standard")
- modified_custom_prompt = custom_prompt
-
- # Check for letterhead/marginalia document types with specialized handling
- try:
- from utils.helpers.letterhead_handler import get_letterhead_prompt, is_likely_letterhead
- # Extract text density features if available
- features = None
- if 'text_density' in preprocessing_options:
- features = preprocessing_options['text_density']
-
- # Check if this looks like a letterhead document
- if is_likely_letterhead(temp_path, features):
- # Get specialized letterhead prompt
- letterhead_prompt = get_letterhead_prompt(temp_path, features)
- if letterhead_prompt:
- logger.info(f"Using specialized letterhead prompt for document")
- modified_custom_prompt = letterhead_prompt
- # Set document type for tracking
- preprocessing_options["document_type"] = "letterhead"
- doc_type = "letterhead"
- except ImportError:
- logger.debug("Letterhead handler not available")
-
- # Add document-type specific instructions based on preprocessing options
- if doc_type == "handwritten" and not modified_custom_prompt:
- modified_custom_prompt = "This is a handwritten document. Please carefully transcribe all handwritten text, preserving line breaks and original formatting."
- elif doc_type == "handwritten" and "handwritten" not in modified_custom_prompt.lower():
- modified_custom_prompt += " This is a handwritten document. Please carefully transcribe all handwritten text, preserving line breaks and original formatting."
- elif doc_type == "newspaper" and not modified_custom_prompt:
- modified_custom_prompt = "This is a newspaper or document with columns. Please extract all text content from each column, maintaining proper reading order."
- elif doc_type == "newspaper" and "column" not in modified_custom_prompt.lower() and "newspaper" not in modified_custom_prompt.lower():
- modified_custom_prompt += " This appears to be a newspaper or document with columns. Please extract all text content from each column."
- elif doc_type == "book" and not modified_custom_prompt:
- modified_custom_prompt = "This is a book page. Extract titles, headers, footnotes, and body text, preserving paragraph structure and formatting."
-
- result = processor.process_file(
- file_path=temp_path,
- file_type=file_type,
- use_vision=use_vision,
- custom_prompt=modified_custom_prompt,
- file_size_mb=file_size_mb
- )
-
- progress_reporter.update(90, "Finalizing results...")
-
- # Add additional metadata to result
- result = process_result(result, uploaded_file, preprocessing_options)
-
- # Make sure file_type is explicitly set for PDFs
- if file_type == "pdf":
- result['file_type'] = "pdf"
-
- # Check for duplicated text patterns that indicate handwritten text issues
- try:
- from utils.helpers.ocr_text_repair import detect_duplicate_text_issues, get_enhanced_preprocessing_options, get_handwritten_specific_prompt, clean_duplicated_text
-
- # Check OCR output for duplication issues
- if result and 'ocr_contents' in result and 'raw_text' in result['ocr_contents']:
- ocr_text = result['ocr_contents']['raw_text']
- has_duplication, duplication_details = detect_duplicate_text_issues(ocr_text)
-
- # If we detect significant duplication in the output
- if has_duplication and duplication_details.get('duplication_rate', 0) > 0.1:
- logger.info(f"Detected text duplication issues. Reprocessing as handwritten document with enhanced settings...")
- progress_reporter.update(75, "Detected duplication issues. Reprocessing with enhanced settings...")
-
- # Save original result before reprocessing
- original_result = result
-
- # Get enhanced preprocessing options for handwritten text
- enhanced_options = get_enhanced_preprocessing_options(preprocessing_options)
-
- # Reprocess with enhanced settings and specialized prompt
- handwritten_prompt = get_handwritten_specific_prompt(custom_prompt)
-
- # Process the image with the enhanced settings
- try:
- # Apply enhanced preprocessing to the original image
- enhanced_temp_path, _ = preprocessing.apply_preprocessing_to_file(
- open(temp_path, 'rb').read(),
- Path(temp_path).suffix.lower(),
- enhanced_options,
- temp_file_paths
- )
-
- # Process with enhanced settings
- processor = StructuredOCR()
- enhanced_result = processor.process_file(
- file_path=enhanced_temp_path,
- file_type="image",
- use_vision=use_vision,
- custom_prompt=handwritten_prompt,
- file_size_mb=file_size_mb
- )
-
- # Check if the enhanced result is better (less duplication)
- if 'ocr_contents' in enhanced_result and 'raw_text' in enhanced_result['ocr_contents']:
- enhanced_text = enhanced_result['ocr_contents']['raw_text']
- _, enhanced_issues = detect_duplicate_text_issues(enhanced_text)
-
- # Use the enhanced result if it's better
- if enhanced_issues.get('duplication_rate', 1.0) < duplication_details.get('duplication_rate', 1.0):
- logger.info("Enhanced processing improved OCR quality. Using enhanced result.")
- result = enhanced_result
- # Preserve document type and preprocessing info
- result['document_type'] = 'handwritten'
- result['preprocessing'] = enhanced_options
- else:
- # If enhancement didn't help, clean up the original result
- logger.info("Enhanced processing did not improve OCR quality. Cleaning original result.")
- result = original_result
- # Clean up duplication in the text
- if 'ocr_contents' in result and 'raw_text' in result['ocr_contents']:
- result['ocr_contents']['raw_text'] = clean_duplicated_text(result['ocr_contents']['raw_text'])
- else:
- # Fallback to original with cleaning
- logger.info("Enhanced processing failed. Cleaning original result.")
- result = original_result
- # Clean up duplication in the text
- if 'ocr_contents' in result and 'raw_text' in result['ocr_contents']:
- result['ocr_contents']['raw_text'] = clean_duplicated_text(result['ocr_contents']['raw_text'])
- except Exception as enh_error:
- logger.warning(f"Enhanced processing failed: {str(enh_error)}. Using cleaned original.")
- # Fallback to original with cleaning
- result = original_result
- # Clean up duplication in the text
- if 'ocr_contents' in result and 'raw_text' in result['ocr_contents']:
- result['ocr_contents']['raw_text'] = clean_duplicated_text(result['ocr_contents']['raw_text'])
- except ImportError:
- logger.debug("OCR text repair module not available")
-
- # 🔧 ALWAYS normalize result before returning
- result = clean_ocr_result(
- result,
- use_segmentation=use_segmentation,
- vision_enabled=use_vision,
- preprocessing_options=preprocessing_options
- )
-
- # Complete progress
- progress_reporter.complete()
-
- return result
- except Exception as e:
- # Handle errors
- error_message = handle_ocr_error(e, progress_reporter)
-
- # Return error result
- return {
- "file_name": uploaded_file.name,
- "topics": ["Document"],
- "languages": ["English"],
- "error": error_message,
- "ocr_contents": {
- "error": f"Failed to process file: {error_message}",
- "partial_text": "Document could not be processed due to an error."
- }
- }
- finally:
- # Clean up temporary files
- for temp_path in temp_file_paths:
- try:
- if os.path.exists(temp_path):
- os.unlink(temp_path)
- logger.info(f"Removed temporary file: {temp_path}")
- except Exception as e:
- logger.warning(f"Failed to remove temporary file {temp_path}: {str(e)}")
-
-def process_result(result, uploaded_file, preprocessing_options=None):
- """
- Process OCR result to add metadata, tags, etc.
-
- Args:
- result: OCR result dictionary
- uploaded_file: The uploaded file
- preprocessing_options: Dictionary of preprocessing options
-
- Returns:
- dict: Processed OCR result
- """
- # Add timestamp
- result['timestamp'] = format_timestamp()
-
- # Add processing time if not already present
- if 'processing_time' not in result:
- result['processing_time'] = 0.0
-
- # Generate descriptive filename
- file_ext = Path(uploaded_file.name).suffix.lower()
- result['descriptive_file_name'] = create_descriptive_filename(
- uploaded_file.name,
- result,
- file_ext,
- preprocessing_options
- )
-
- # Extract raw text from OCR contents for tag extraction without duplicating content
- raw_text = ""
- if 'ocr_contents' in result:
- # Try fields in order of preference
- for field in ["raw_text", "content", "text", "transcript", "main_text"]:
- if field in result['ocr_contents'] and result['ocr_contents'][field]:
- raw_text = result['ocr_contents'][field]
- break
-
- # Extract subject tags if not already present or enhance existing ones
- if 'topics' not in result or not result['topics']:
- result['topics'] = extract_subject_tags(result, raw_text, preprocessing_options)
-
- return result
diff --git a/ocr_utils.py b/ocr_utils.py
index 7306cbd8228ddfb5c1ab6dbc8d84df80d187f2ea..ec27512dc1bf4b9576315571bc2d21893eb3f84a 100644
--- a/ocr_utils.py
+++ b/ocr_utils.py
@@ -1,38 +1,74 @@
"""
-OCR utility functions for image processing and OCR operations.
-This module provides helper functions used across the Historical OCR application.
+Utility functions for OCR processing with Mistral AI.
+Contains helper functions for working with OCR responses and image handling.
"""
-import os
+import json
import base64
-import logging
+import io
from pathlib import Path
-from typing import Union, Optional
-
-# Configure logging
-logging.basicConfig(level=logging.INFO,
- format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
-logger = logging.getLogger(__name__)
-
-# Try to import optional dependencies
-try:
- import pytesseract
- TESSERACT_AVAILABLE = True
-except ImportError:
- logger.warning("pytesseract not available - local OCR fallback will not work")
- TESSERACT_AVAILABLE = False
+from typing import Dict, List, Optional, Union, Any
try:
from PIL import Image
PILLOW_AVAILABLE = True
except ImportError:
- logger.warning("PIL not available - image preprocessing will be limited")
PILLOW_AVAILABLE = False
+from mistralai import DocumentURLChunk, ImageURLChunk, TextChunk
+
+def replace_images_in_markdown(markdown_str: str, images_dict: dict) -> str:
+ """
+ Replace image placeholders in markdown with base64-encoded images.
+
+ Args:
+ markdown_str: Markdown text containing image placeholders
+ images_dict: Dictionary mapping image IDs to base64 strings
+
+ Returns:
+ Markdown text with images replaced by base64 data
+ """
+ for img_name, base64_str in images_dict.items():
+ markdown_str = markdown_str.replace(
+ f"", f""
+ )
+ return markdown_str
+
+def get_combined_markdown(ocr_response) -> str:
+ """
+ Combine OCR text and images into a single markdown document.
+ Ensures proper spacing between text and images.
+
+ Args:
+ ocr_response: Response from OCR processing containing text and images
+ See https://docs.mistral.ai/capabilities/document/ for API reference
+
+ Returns:
+ Combined markdown string with embedded images
+ """
+ markdowns: list[str] = []
+ # Extract images from page
+ for page in ocr_response.pages:
+ image_data = {}
+ for img in page.images:
+ image_data[img.id] = img.image_base64
+
+ # Replace image placeholders with actual images
+ page_markdown = replace_images_in_markdown(page.markdown, image_data)
+
+ # Ensure proper spacing between paragraphs and images
+ # Add extra newlines between paragraphs to improve rendering
+ page_markdown = page_markdown.replace("\n", "\n\n")
+
+ # Add page separator for multi-page documents
+ markdowns.append(page_markdown)
+
+ # Join pages with clear separators for multi-page documents
+ return "\n\n---\n\n".join(markdowns)
def encode_image_for_api(image_path: Union[str, Path]) -> str:
"""
- Encode an image as base64 data URL for API submission with proper MIME type.
+ Encode an image as base64 for API use.
Args:
image_path: Path to the image file
@@ -47,53 +83,130 @@ def encode_image_for_api(image_path: Union[str, Path]) -> str:
if not image_file.is_file():
raise FileNotFoundError(f"Image file not found: {image_file}")
- # Determine mime type based on file extension
- mime_type = 'image/jpeg' # Default mime type
- suffix = image_file.suffix.lower()
- if suffix == '.png':
- mime_type = 'image/png'
- elif suffix == '.gif':
- mime_type = 'image/gif'
- elif suffix in ['.jpg', '.jpeg']:
- mime_type = 'image/jpeg'
- elif suffix == '.pdf':
- mime_type = 'application/pdf'
-
# Encode image as base64
encoded = base64.b64encode(image_file.read_bytes()).decode()
- return f"data:{mime_type};base64,{encoded}"
+ return f"data:image/jpeg;base64,{encoded}"
+def process_image_with_ocr(client, image_path: Union[str, Path], model: str = "mistral-ocr-latest"):
+ """
+ Process an image with OCR and return the response.
+
+ Args:
+ client: Mistral AI client
+ image_path: Path to the image file
+ model: OCR model to use
+
+ Returns:
+ OCR response object
+ """
+ # Encode image as base64
+ base64_data_url = encode_image_for_api(image_path)
+
+ # Process image with OCR
+ image_response = client.ocr.process(
+ document=ImageURLChunk(image_url=base64_data_url),
+ model=model
+ )
+
+ return image_response
-def try_local_ocr_fallback(file_path: Union[str, Path], base64_data_url: Optional[str] = None) -> Optional[str]:
+def ocr_response_to_json(ocr_response, indent: int = 4) -> str:
"""
- Try to perform OCR using local Tesseract as a fallback when the API is unavailable.
+ Convert OCR response to a formatted JSON string.
Args:
- file_path: Path to the image file
- base64_data_url: Optional base64 data URL if already available
+ ocr_response: OCR response object
+ indent: Indentation level for JSON formatting
Returns:
- Extracted text or None if extraction failed
+ Formatted JSON string
"""
- if not TESSERACT_AVAILABLE or not PILLOW_AVAILABLE:
- logger.warning("Local OCR fallback is not available (missing dependencies)")
- return None
+ # Convert response to JSON
+ response_dict = json.loads(ocr_response.model_dump_json())
+ return json.dumps(response_dict, indent=indent)
+
+def get_combined_markdown_compressed(ocr_response, max_width: int = 800, quality: int = 85) -> str:
+ """
+ Combine OCR text and images into a single markdown document with compressed images.
+ Reduces image sizes to improve performance.
- try:
- logger.info("Using local Tesseract OCR as fallback")
+ Args:
+ ocr_response: Response from OCR processing containing text and images
+ max_width: Maximum width to resize images to (preserves aspect ratio)
+ quality: JPEG quality (0-100) for compression
+
+ Returns:
+ Combined markdown string with embedded compressed images
+ """
+ if not PILLOW_AVAILABLE:
+ # Fall back to regular method if PIL is not available
+ return get_combined_markdown(ocr_response)
+
+ markdowns: list[str] = []
+
+ # Process each page
+ for page in ocr_response.pages:
+ image_data = {}
+
+ # Process and compress each image
+ for img in page.images:
+ try:
+ # Decode base64 image
+ img_bytes = base64.b64decode(img.image_base64.split(',')[1] if ',' in img.image_base64 else img.image_base64)
+
+ # Open with PIL
+ pil_img = Image.open(io.BytesIO(img_bytes))
+
+ # Resize if needed (maintain aspect ratio)
+ original_width, original_height = pil_img.size
+ if original_width > max_width:
+ ratio = max_width / original_width
+ new_height = int(original_height * ratio)
+ pil_img = pil_img.resize((max_width, new_height), Image.LANCZOS)
+
+ # Convert to bytes with compression
+ buffer = io.BytesIO()
+ format = pil_img.format if pil_img.format else 'JPEG'
+ if format.upper() == 'JPEG' or format.upper() == 'JPG':
+ pil_img.save(buffer, format=format, quality=quality, optimize=True)
+ else:
+ # For non-JPEG formats (PNG, etc.)
+ pil_img.save(buffer, format=format, optimize=True)
+
+ # Convert back to base64
+ compressed_base64 = base64.b64encode(buffer.getvalue()).decode('utf-8')
+ mime_type = f"image/{format.lower()}" if format else "image/jpeg"
+ image_data[img.id] = f"data:{mime_type};base64,{compressed_base64}"
+
+ except Exception as e:
+ # If compression fails, use original image
+ image_data[img.id] = img.image_base64
+
+ # Replace image placeholders with compressed images
+ page_markdown = replace_images_in_markdown(page.markdown, image_data)
- # Use PIL to open the image
- img = Image.open(file_path)
+ # Ensure proper spacing between paragraphs and images
+ page_markdown = page_markdown.replace("\n", "\n\n")
- # Use Tesseract to extract text
- text = pytesseract.image_to_string(img)
+ # Add page to list
+ markdowns.append(page_markdown)
- if text:
- logger.info("Successfully extracted text using local Tesseract OCR")
- return text
- else:
- logger.warning("Tesseract extracted no text")
- return None
- except Exception as e:
- logger.error(f"Error using local OCR fallback: {str(e)}")
- return None
+ # Join pages with clear separators
+ return "\n\n---\n\n".join(markdowns)
+
+# For display in notebooks
+try:
+ from IPython.display import Markdown, display
+
+ def display_ocr_with_images(ocr_response):
+ """
+ Display OCR response with embedded images in IPython environments.
+
+ Args:
+ ocr_response: OCR response object
+ """
+ combined_markdown = get_combined_markdown(ocr_response)
+ display(Markdown(combined_markdown))
+except ImportError:
+ # IPython not available
+ pass
\ No newline at end of file
diff --git a/output/.gitkeep b/output/.gitkeep
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/output/example-1.html b/output/example-1.html
new file mode 100644
index 0000000000000000000000000000000000000000..8d05265eff794c3a1bebe6505cf942ae249cae0b
--- /dev/null
+++ b/output/example-1.html
@@ -0,0 +1,448 @@
+Source file changed.
+
+Rerun
+Always rerun
+Deploy
+
+
+ Options
+
+
+ Model Settings
+
+Use Vision Model
+
+
+ Image Preprocessing
+
+Preprocessing Options
+
+Convert to Grayscale
+
+Apply Thresholding
+
+Denoise Image
+
+Adjust Contrast
+
+0
+-5
+5
+
+
+ PDF Options
+
+PDF Settings
+
+PDF Resolution (DPI)
+
+150
+72
+300
+
+Maximum Pages to Process
+
+
+ Historical Document OCR
+
+ <#historical-document-ocr>
+
+
+ Powered by Mistral AI
+
+ <#powered-by-mistral-ai>
+
+Document Processing
+
+About
+
+Upload an image or PDF file to get started.
+
+Using the latest |mistral-ocr-latest| model for advanced document understanding.
+
+Choose a file
+
+Drag and drop file here
+Limit 500MB per file • PDF, PNG, JPG, JPEG
+
+Browse files
+
+ *
+
+ ymca-letter.png
+ 4.4MB
+
+
+ Document Preview
+
+ <#document-preview>
+
+0
+
+Image Preprocessing Preview
+
+*Original Image*
+
+0
+
+*Preprocessed Image*
+
+0
+
+Process Document
+
+Document Analysis
+
+Technical Details
+
+
+ Document Metadata
+
+ <#document-metadata>
+
+*Document processed successfully*
+
+*File Name:* img-0.jpeg
+
+*Languages:* Chinese
+
+*OCR Confidence:* 85.0%
+
+*Topics:* Young Men's Christian Association, Board of Managers,
+Philadelphia, Central Branch
+
+
+ Document Contents
+
+ <#document-contents>
+
+Structured View
+
+Raw JSON
+
+With Images
+
+
+ Header
+
+ <#header>
+
+THE YOUNG MEN'S CHRISTIAN ASSOCIATION
+
+
+ Image
+
+ <#image>
+
+img-0.jpeg
+
+
+ Central Branch
+
+ <#central-branch>
+
+*address:* CENTRAL BRANCH
+
+*location:* 1421 ARCH STREET PHILADELPHIA 2, PENNA. LOCUST 3-8100
+
+*title:* of Philadelphia and Vicinity
+
+
+ Board Chairman
+
+ <#board-chairman>
+
+THOMAS E. WALTON, Jr.
+
+
+ Executive Secretary
+
+ <#executive-secretary>
+
+NORMAN I PULLER
+
+
+ Board Of Managers
+
+ <#board-of-managers>
+
+ * JOSEPH DELACY
+
+ * WALTER R. DUNCAN
+
+ * RALPH B. EATON
+
+ * ALBERT H. FENSTERMACHER
+
+ * AUSTIN J. GAUGEL
+
+ * JOSEPH H. GEIS, JR.
+
+ * ARTHUR R. GREMEL
+
+ * ELIOT B. HANSCOM
+
+ * CHARLES E. HANTHORN
+
+ * GEORGE IRELAND
+
+ * GEORGE L. KLEWER
+
+ * ALEXANDER H. RALSTON
+
+ * ARTHUR SAXON, JR.
+
+ * HARRY W. SCHOB, SR.
+
+ * WALTER J. SCHOB
+
+ * HARVEY T. STEPHENS
+
+ * CHARLES E. SWANSON
+
+ * LAWRENCE W. THOMASON
+
+ * THOMAS E. WALTON, JR.
+
+ * D. ALEXANDER WIELAND
+
+ * THOMAS A. WOOD, JR.
+
+
+ Footer
+
+ <#footer>
+
+*president:* HARRY G. KUCI, President
+
+*general_secretary:* A PARTICIPANT IN THE UNITED FUND CHARLES U.
+SHELLENBERGES, General Secretary
+
+*image:* img-1.jpeg
+
+Export Content
+
+Download as HTML
+
+{
+"file_name":"img-0.jpeg"
+"topics":[
+0:
+"Young Men's Christian Association"
+1:
+"Board of Managers"
+2:
+"Philadelphia"
+3:
+"Central Branch"
+]
+"languages":[
+0:
+"Chinese"
+]
+"ocr_contents":{
+"header":"THE YOUNG MEN'S CHRISTIAN ASSOCIATION"
+"image":"img-0.jpeg"
+"central_branch":{
+"address":"CENTRAL BRANCH"
+"location":"1421 ARCH STREET PHILADELPHIA 2, PENNA. LOCUST 3-8100"
+"title":"of Philadelphia and Vicinity"
+}
+"board_chairman":"THOMAS E. WALTON, Jr."
+"executive_secretary":"NORMAN I PULLER"
+"board_of_managers":[
+0:
+"JOSEPH DELACY"
+1:
+"WALTER R. DUNCAN"
+2:
+"RALPH B. EATON"
+3:
+"ALBERT H. FENSTERMACHER"
+4:
+"AUSTIN J. GAUGEL"
+5:
+"JOSEPH H. GEIS, JR."
+6:
+"ARTHUR R. GREMEL"
+7:
+"ELIOT B. HANSCOM"
+8:
+"CHARLES E. HANTHORN"
+9:
+"GEORGE IRELAND"
+10:
+"GEORGE L. KLEWER"
+11:
+"ALEXANDER H. RALSTON"
+12:
+"ARTHUR SAXON, JR."
+13:
+"HARRY W. SCHOB, SR."
+14:
+"WALTER J. SCHOB"
+15:
+"HARVEY T. STEPHENS"
+16:
+"CHARLES E. SWANSON"
+17:
+"LAWRENCE W. THOMASON"
+18:
+"THOMAS E. WALTON, JR."
+19:
+"D. ALEXANDER WIELAND"
+20:
+"THOMAS A. WOOD, JR."
+]
+"footer":{
+"president":"HARRY G. KUCI, President"
+"general_secretary":"A PARTICIPANT IN THE UNITED FUND CHARLES U. SHELLENBERGES, General Secretary"
+"image":"img-1.jpeg"
+}
+}
+"confidence_score":0.85
+"raw_response":"OCRResponse(pages=[OCRPageObject(index=0, markdown="THE YOUNG MEN'S CHRISTIAN ASSOCIATION\n\n\nCENTRAL BRANCH\n1421 ARCH STREET PHILADELPHIA 2, PENNA. LOCUST 3-8100\nof Philadelphia and Vicinity\n\nTHOMAS E. WALTON, Jr. Board Chairman\n\nNORMAN I PULLER Executive Secretary\n\nCENTRAL BRANCH BOARD OF MANAGESS JOSEPH DELACY WALTER R. DUNCAN RALPH B. EATON ALBERT H. FENSTERMACHER AUSTIN J. GAUGEL JOSEPH H. GEIS, JR. ARTHUR R. GREMEL ELIOT B. HANSCOM CHARLES E. HANTHORN GEORGE IRELAND GEORGE L. KLEWER ALEXANDER H. RALSTON ARTHUR SAXON, JR. HARRY W. SCHOB, SR. WALTER J. SCHOB HARVEY T. STEPHENS CHARLES E. SWANSON LAWRENCE W. THOMASON THOMAS E. WALTON, JR. D. ALEXANDER WIELAND THOMAS A. WOOD, JR.\n", images=[OCRImageObject(id='img-0.jpeg', top_left_x=643, top_left_y=30, bottom_right_x=864, bottom_right_y=260, image_base64='data:image/jpeg;base64,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'), OCRImageObject(id='img-1.jpeg', top_left_x=315, top_left_y=352, bottom_right_x=1298, bottom_right_y=1594, image_base64='data:image/jpeg;base64,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gRp0jiYAfNncwXP8Qevf4IIbW3it7eKOGCJAkccahVRQMAADgADjFAGX4W8N2fhHw5aaHYSTyWtrv2POwLnc7OckADqx7VsUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFRmRhcJEIZCjIzGUFdqkEYU85yckjAI+U5I4zJQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQBXbyrzzIT56+TKm4jfFlhtcYYY3LyAcEqfmU5+YVYoooAKKKKACo5BMXhMUkaoHzKGQsWXaeFORtO7acnPAIxzkSUUAFFFFABRRRQAUUUUAFFFFABUcE8N1bxXFvLHNBKgeOSNgyupGQQRwQRzmpKy/D2mzaNo0GlO0bwWKLb2sik7nhVQELjGA4HBxwcbht3bVANSiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiisPxpPNa+BfENxbyyQzxaZcvHJGxVkYRMQQRyCDzmgC5da7o9jqMGnXmq2NvfT7fJtprhEkk3Hau1ScnJBAx1NaFfAk8811cS3FxLJNPK5eSSRizOxOSSTySTzmvtvwJ/yTzw1/wBgq1/9FLQB0FFFFABRRRQAUUUUAFRzwQ3VvLb3EUc0EqFJI5FDK6kYIIPBBHGKkooAKjmghuUCTxRyoHVwrqGAZWDKee4YAg9iAakooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKjjhWJ5nUyEyvvbdIzAHaF+UE4UYUcDAzk9SSZKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACq/263/tH7AJM3Qi85kVSdqZwCxHC5OcZxu2tjO1sWKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD//2Q==')], dimensions=OCRPageDimensions(dpi=200, height=1678, width=1300))], model='mistral-ocr-2503-completion', usage_info=OCRUsageInfo(pages_processed=1, doc_size_bytes=94073))"
+"processing_time":8.566082954406738
+}
+THE YOUNG MEN'S CHRISTIAN ASSOCIATION
+
+img-0.jpeg
+
+CENTRAL BRANCH
+
+1421 ARCH STREET PHILADELPHIA 2, PENNA. LOCUST 3-8100
+
+of Philadelphia and Vicinity
+
+THOMAS E. WALTON, Jr. Board Chairman
+
+NORMAN I PULLER Executive Secretary
+
+CENTRAL BRANCH BOARD OF MANAGESS JOSEPH DELACY WALTER R. DUNCAN RALPH B.
+EATON ALBERT H. FENSTERMACHER AUSTIN J. GAUGEL JOSEPH H. GEIS, JR.
+ARTHUR R. GREMEL ELIOT B. HANSCOM CHARLES E. HANTHORN GEORGE IRELAND
+GEORGE L. KLEWER ALEXANDER H. RALSTON ARTHUR SAXON, JR. HARRY W. SCHOB,
+SR. WALTER J. SCHOB HARVEY T. STEPHENS CHARLES E. SWANSON LAWRENCE W.
+THOMASON THOMAS E. WALTON, JR. D. ALEXANDER WIELAND THOMAS A. WOOD, JR.
+
+img-1.jpeg
+
+
+ Raw Processing Results
+
+ <#raw-processing-results>
+
+{
+"file_name":"img-0.jpeg"
+"topics":[
+0:
+"Young Men's Christian Association"
+1:
+"Board of Managers"
+2:
+"Philadelphia"
+3:
+"Central Branch"
+]
+"languages":[
+0:
+"Chinese"
+]
+"ocr_contents":{
+"header":"THE YOUNG MEN'S CHRISTIAN ASSOCIATION"
+"image":"img-0.jpeg"
+"central_branch":{
+"address":"CENTRAL BRANCH"
+"location":"1421 ARCH STREET PHILADELPHIA 2, PENNA. LOCUST 3-8100"
+"title":"of Philadelphia and Vicinity"
+}
+"board_chairman":"THOMAS E. WALTON, Jr."
+"executive_secretary":"NORMAN I PULLER"
+"board_of_managers":[
+0:
+"JOSEPH DELACY"
+1:
+"WALTER R. DUNCAN"
+2:
+"RALPH B. EATON"
+3:
+"ALBERT H. FENSTERMACHER"
+4:
+"AUSTIN J. GAUGEL"
+5:
+"JOSEPH H. GEIS, JR."
+6:
+"ARTHUR R. GREMEL"
+7:
+"ELIOT B. HANSCOM"
+8:
+"CHARLES E. HANTHORN"
+9:
+"GEORGE IRELAND"
+10:
+"GEORGE L. KLEWER"
+11:
+"ALEXANDER H. RALSTON"
+12:
+"ARTHUR SAXON, JR."
+13:
+"HARRY W. SCHOB, SR."
+14:
+"WALTER J. SCHOB"
+15:
+"HARVEY T. STEPHENS"
+16:
+"CHARLES E. SWANSON"
+17:
+"LAWRENCE W. THOMASON"
+18:
+"THOMAS E. WALTON, JR."
+19:
+"D. ALEXANDER WIELAND"
+20:
+"THOMAS A. WOOD, JR."
+]
+"footer":{
+"president":"HARRY G. KUCI, President"
+"general_secretary":"A PARTICIPANT IN THE UNITED FUND CHARLES U. SHELLENBERGES, General Secretary"
+"image":"img-1.jpeg"
+}
+}
+"confidence_score":0.85
+"raw_response":"OCRResponse(pages=[OCRPageObject(index=0, markdown="THE YOUNG MEN'S CHRISTIAN ASSOCIATION\n\n\nCENTRAL BRANCH\n1421 ARCH STREET PHILADELPHIA 2, PENNA. LOCUST 3-8100\nof Philadelphia and Vicinity\n\nTHOMAS E. WALTON, Jr. Board Chairman\n\nNORMAN I PULLER Executive Secretary\n\nCENTRAL BRANCH BOARD OF MANAGESS JOSEPH DELACY WALTER R. DUNCAN RALPH B. EATON ALBERT H. FENSTERMACHER AUSTIN J. GAUGEL JOSEPH H. GEIS, JR. ARTHUR R. GREMEL ELIOT B. HANSCOM CHARLES E. HANTHORN GEORGE IRELAND GEORGE L. KLEWER ALEXANDER H. RALSTON ARTHUR SAXON, JR. HARRY W. SCHOB, SR. WALTER J. SCHOB HARVEY T. STEPHENS CHARLES E. SWANSON LAWRENCE W. THOMASON THOMAS E. WALTON, JR. D. ALEXANDER WIELAND THOMAS A. WOOD, JR.\n", images=[OCRImageObject(id='img-0.jpeg', top_left_x=643, top_left_y=30, bottom_right_x=864, bottom_right_y=260, image_base64='data:image/jpeg;base64,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'), OCRImageObject(id='img-1.jpeg', top_left_x=315, top_left_y=352, bottom_right_x=1298, bottom_right_y=1594, image_base64='data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCATaA9cDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD3+iiigAooooAKKKKACo54VubeWBzIEkQoxjkZGAIxwykFT7ggjtUlU9Ts5r61SGC7ktXW4glMiZyVSVXZOCOGVSh9mPB6EAuUUUUAFFFFABRRRQAUUUUAFFFRmZVuEgIk3ujOCI2K4UgHLYwD8wwCcnnGcHABJRUc88Nrby3FxLHDBEheSSRgqooGSSTwABzmo7C+t9T062v7OTzLW6iSaF9pG5GAKnB5GQR1oAsUVXtrr7RtV7eeCbykleOVPubs/LuGULAqchWOOD0IJsUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFU9Jma50axncXYeS3jdheRqk4JUH94qgBX9QAADnFXKr2tr9k88C4nlWWVpVWZ9/l7uSqk87c5IBJxnAwoVQAWKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooqneyXyXWnLaQxyQPcFbxmPMcXlSEMvI58wRjvwx47gAuUUUUAFFFFABRRRQAUUUUAFFFFABRRRQBHPPDa28txcSxwwRIXkkkYKqKBkkk8AAc5qSo54Ibq3lt7iKOaCVCkkcihldSMEEHggjjFcvr+leK/7Et7PwtqOm6fPDZeSD5ASISh4dpRCr7UEazqFycb164yADrKjjhWJ5nUyEyvvbdIzAHaF+UE4UYUcDAzk9SSY7D7Z/Z1t/aPkfbvKT7R9nz5fmYG7ZnnbnOM84qxQAUUUUAFFU7xWmuLW2awjuLdn82WWVl2xFCGQhTks+/aRwANpO4EKGj0rTf7O+2/u7FPtN3Jcf6Ja+RndjmT5m3yccvxnjgYoA0KKKKACiiigAooooAKKKKACiiigAooooAKKKKACo5hMyAQSRo+9SS6Fht3DcMAjkrkA9iQcHGDJRQAUUUUAFFFFABRRXzJ+0Fqtj/wsGwbTbm7TWNOt0S4dfkWE58yLY3B3/OSSOOVxyDQB9F67qf9ieHtT1byfO+w2ktz5W7bv2IW25wcZxjODWX4G8Z2Pjrw1HrFjHJCQ5huIH5MMoAJXOMMMMCCOoIyAcgWLlrHxt4Kuk0y/jez1aylhiukXcFDqUzt4OQScqcEEEHBrx/9maeZrfxLbtLIYEe2dIyx2qzCUMQOgJCqCe+0elAHvlFFFABRXP8AhTxpoXjWzuLnRLvzlt5TFKjqUdeTtYqedrAZB/DgggdBQAUUUUAFFFFABRRVe+dIrOSaW8+xww4lknJUBUUhm3FgQFIBBPYE4IOCACxRRRQAUUUUAFFFFABRRRQAUUUUARySMjwqsMkgd9rMpXEY2k7myQcZAHGTlhxjJEcNr5V5c3JuJ5Gm2gI7/JEqjgKo4GSWJJyxzgnCqFsUUAFFFFABRRRQAUUUUAFV7e3dJZJppfMmf5fkLKgQMxQBCxAYBsFhjdjsAALFFABUc88Nrby3FxLHDBEheSSRgqooGSSTwABzmpKjJm+0IqxxmAoxdy5DBsjaAuMEEbsnIxgcHPABh2vg7S7TxG+ujz5bsyzzL57+YI3mSFHKFgWX5YFAweAzAfLgDYsYbi3s44rq6+1TJkGYxhC4ydpYDjdjGSAATkgKDtFO6u2m1ldFe1kW3ubKWVrpbpY2yGRNiKrCTOHyXGAvy4OW40IIIbW3it7eKOGCJAkccahVRQMAADgADjFAElFFRyQrK8LsZAYn3rtkZQTtK/MAcMMMeDkZweoBABJRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRUZgha4S4aKMzojIkhUblViCwB6gEqpI77R6UASUUUUAFFFFABUckbO8LLNJGEfcyqFxINpG1sgnGSDxg5Uc4yDJRQAUVHAZmt4muI445ygMiRuXVWxyAxAJGe+Bn0FSUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQBGJ4WuHt1ljM6IrvGGG5VYkKSOoBKsAe+0+lSUVHPMttbyzuJCkaF2EcbOxAGeFUEsfYAk9qAJKKjgmW5t4p0EgSRA6iSNkYAjPKsAVPsQCO9SUAFFFFABRRRQAUUUUAFFFFABRRRQBHPCtzbywOZAkiFGMcjIwBGOGUgqfcEEdqkoooAKjkmWJ4UYSEyvsXbGzAHaW+YgYUYU8nAzgdSAZKr2F19u062vPs89v58SS+TcJskj3AHa69mGcEdjQAWdjb6fA0NrH5cbSyTEbicvI7SOefVmY+2eOKsUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFRyGYPCIo42QviUs5Uqu08qMHcd20YOOCTnjBACAzNbxNcRxxzlAZEjcuqtjkBiASM98DPoKkoooAKKKKACiiigCOeeG1t5bi4ljhgiQvJJIwVUUDJJJ4AA5zXkfxG1H4Z6/4L169GoaHJqrRMIriDyjdvNGdqAHBcqWRVyOChyDt5qx4/wDgp/wnHih9a/4SWe13xJH5Etv56x7Rj5DvXap67efmLHPOBc034DeA7G3aK4sbvUHLlhLdXbqwGB8o8vYMcZ6Z5PPTABwHw3+Nmg+E/BFnoWqadqTT2jyBZLVUdXVnL5O5lIOWIxz0BzzgSfs9XiJ4y8S2WmD/AIlEsQljN06i5wkhEWVB5+V23EAgHbyMjNPwLpY+HPhPxfq/jbwzG6b4LO2tr2OPN1IGYsibs7k3eWxYAghNw3bONf4A6DfXWva140ntI7Gzu0eC2hhh2ROXkDv5YzkIhQKOCOSM5UigD3yvN/jd4nTw/wDDq7tY5/LvtV/0SFRtJKH/AFpIP8OzKkgHBdemcj0ivn/xBb3HxE/aJg0cywXGkaFskkQESR7E2vKGQtgs0jCJsDgAZB2mgDvPgv4Uh8NfD6zuCI2vNVRb2eRcH5WGY0zgHAUg4OcMz4ODXolFY/iHxVoXhSzF1rmpwWUbfcDkl5MEA7UGWbG4ZwDjOTxQBsUVh+GvGGgeMLeefQdSjvEgcJKAjIyEjIyrAHB5wcYOD6GtygAooooAKKKKACiiigAoqMTwtcPbrLGZ0RXeMMNyqxIUkdQCVYA99p9KKAJKKrzfbG+0JB5EX7oeRM+XxId2d0Y2/KPlPDZOSPlwCZJoIblAk8UcqB1cK6hgGVgynnuGAIPYgGgAmmWBA7iQguqfJGznLMFHCgnGTyegGScAE0QmZkJnjjR97ABHLDbuO05IHJXBI7EkZOMmSigAooooAKKKKACiiigAooooAKjnhW5t5YHMgSRCjGORkYAjHDKQVPuCCO1SVHPG01vLEk0kDuhVZYwpZCR94bgRkdeQR6g0ASUVGZlW4SAiTe6M4IjYrhSActjAPzDAJyecZwcV70TTPHZrbSNb3CSLcXCXBiMS7cDaVO/eSRjGMAMdwIUMAXKKKKAI5IIZnheWKN3hffEzKCUbaVyvodrMMjsSO9EAmW3iW4kjknCASPGhRWbHJCkkgZ7ZOPU1JVe8e4iiEtunm+Xud4VUF5gFOEQllVWLbeWOOCOM7gAWKz9b1X+xNHn1H7BfX/k7f9GsIfNmfLBflXIzjOT7A1oVn6nruj6J5X9rarY2HnZ8v7XcJFvxjONxGcZHT1FAGhRVPSZL6bRrGXVIY4NQe3ja6ijOVSUqN6jk8BsjqfqauUAFFFFABRRRQAUUUUAFFFFAFe+vrfTrOS6upPLhTAJCliSSAqqoyWYkgBQCSSAASakMyrcJARJvdGcERsVwpAOWxgH5hgE5POM4OK+p2TX9qkKSxxlbiCbc8KygiOVXIw3AJC4DdVJDDkCjUYJrlLeKKK0kT7RG8puVLBFRt+VXu+5VwSRtJ3c7drAFyiq9ha/YdOtrP7RPceREkXnXD75JNoA3O3djjJPc1YoAKKKKACiiigAqOaRokDJDJMS6rtQqCAWALfMQMAHJ74BwCcAyUUAFFFFABRRWffX17BLJDaaRPdN5QeOUyxpCXLBdjEtvGAdxIQ/KDjc2FIBoVHOJmt5Vt5I45yhEbyIXVWxwSoIJGe2Rn1FR3NvLPu8u9nt8xPGPKCHDNjD/ADKfmXBx/D8xyDxiSGNokKvNJMS7NucKCAWJC/KAMAHA74AySckgElFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFAEc0KzoEcyAB1f5JGQ5Vgw5Ug4yOR0IyDkEipKjnhW5t5YHMgSRCjGORkYAjHDKQVPuCCO1SUAFFFFABRRRQAUUUUAFFFFABUc88Nrby3FxLHDBEheSSRgqooGSSTwABzmpKKACiiigAooooAKjMyrcJARJvdGcERsVwpAOWxgH5hgE5POM4OJKKACo4DM1vE1xHHHOUBkSNy6q2OQGIBIz3wM+gqSigCOCeG6t4ri3ljmglQPHJGwZXUjIII4II5zUlFFABRRRQAUUUUAFFFFABRRRQAVT1Kya9t18mWOG7hcS207wrL5TgEZwexUspwVO1mAZSci5RQBHHMsrzIokBifY26NlBO0N8pIwwww5GRnI6ggSVHPPDa28txcSxwwRIXkkkYKqKBkkk8AAc5qSgAooooAKKK4f/hZVn/wtf/hA/wCz5/O8rP2veNvmeX5u3b/d2fxZzu424+agDuKp6rqtjoel3Gp6ncx21nbpvllfoo/mSTgADkkgDJNXK5P4h+B4fH/hpdJlvpLJ47hLiKZYw4DAMuGXIyNrN0I5we2CAeAT+JLH4g+IJfE/jvVI7TQbFylpo1tLvnlONwjRRyA2BvlbaCcKCvGzqNN+IXxI8dBrTwNodpo+mWqHypEiDKgjjH7jzHHl5JK4UKCMrkhQxro/BvwB0LRfKvPEMv8Aa98uG8nBW2jb5Tjb1kwQwy2FYHlK6T4q61Y+E/hbqMKwRolzbnTLS3jGxQZEKgDAIUKgZscD5cZGRQBT+E/xI/4TDwldXGszQQ3+lYF7MfkRo9pKzNkBVztfIBwNhPAIA4/9nqxuNT1HxL4u1CPzLq6lEK3W4Dc7EyTDYOBkmI9PYdxXUfAjwxceH/AP2q9ggjuNVlF2jJguYCi+WGI/4EwGTjf2JIHCW3hf4qeDBJ4J8PRxjT9QvZJU1mFMfIY1Vi7ZPkgKAegbcp2M3cA7/wCJ3xcsfBCSaZp5juvEBRXWJk3xQgsv+twykEqWIAyehOARnhPCfgnxB8XdRt/F3jLUd2hPLI8FjHKw3AEKURRxFGSuCc7zs9SHrh/Fvw+s9A8Q+HvCdlqX2rxDebEv3Zh9mhklcLGq4G8YBJO4ZIKsAM4H1npWlWOh6Xb6ZpltHbWdumyKJOij+ZJOSSeSSSck0Ac/oHw18JeFtUXUtF0yS0uwhTet5OwZT1DKzkMOhwQeQD1ArrKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAjnghureW3uIo5oJUKSRyKGV1IwQQeCCOMUQQQ2tvFb28UcMESBI441CqigYAAHAAHGKkooAKKKKACiiq9rdfa/PIt54lilaJWmTZ5m3gsoPO3OQCQM4yMqVYgFiiq9jdfbbOO4NvPbs2Q0M6bXRgSCD2OCDyCVPUEggmxQAUUUUAFFFcvrXi/8AsnxRb6J9lgTzdPnvvtl9dfZoD5Y/1attbcw+8/TYnzfN0oA6iisfw9rkuu2ZuH02e1jHCTNIjw3HJBeFgdzR8ZVmVNysrAYPGxQBHPPDa28txcSxwwRIXkkkYKqKBkkk8AAc5ohnhuULwSxyoHZCyMGAZWKsOO4YEEdiCKy/+EfiHi2PX0uJ/MFpLbPC8ruh3tEQyqW2x48rkKo3bsnkc7FABRXN+KNG1rVr/RX0fVZNLS2uGe7uI5SWaIoQYxCVMbkkj5n+5jIByRXSUAFFFFABRRRQBXcW8F4svkf6Rc4hMqQliQodlDMBwoy+NxAy2By2DYqOSCGZ4Xlijd4X3xMyglG2lcr6HazDI7EjvUlABVeZLxry2aGeBLVd32iN4SzycfLtYMAmD1yrZ6cdasVHOJmt5Vt5I45yhEbyIXVWxwSoIJGe2Rn1FAElFFFABRRRQAUUUUAFeT+M/ij4g8CeNJItU8Oef4Wfylt72FWVyWALfOSULArLiMhScA5A5PrFV76ws9Ts5LO/tILu1kxvhnjEiNggjKng4IB/CgCnoHiPSPFOlrqWi30d3aFym9QVKsOoZWAKnocEDgg9CK1K8P1n4Jap4e1F9c+HOtz2V0uW+xTS43AEvsV+jLkIAkgIOMs1dR8PPih/wkuo3fhzxBBBp3iazlkjeCNv3c2wkNsOT8y4OVycgbgSMhQD0iiiigAooooAKKKKACiiigAooooAKKKKAMuCzu7fxLeXb3d3cWl3bxrHC2wRWrRls7cEEmTzM9D/AKs5bGxRqUUUAFFFFABRRXhdzf8AxU+GWqXWoamZPFfh93lmlZG5jzk7vuloQoQEgAxKGIBycgA90orm/BnjnRfHWlvfaPLIDE+ya3nAWWE843AEjBAyCCQeR1BA6SgAooooAKKKKACiiigAooooAKjmjaVAqTSQkOrbkCkkBgSvzAjBAwe+CcEHBElFAEc5mW3la3jjknCExpI5RWbHALAEgZ74OPQ0QGZreJriOOOcoDIkbl1VscgMQCRnvgZ9BUlFABUcMKwIUQyEF2f55Gc5Zix5Yk4yeB0AwBgACpKKACiiigAooooAKKKKACiisPVfGXhnQ3uItT1/Tbae3TfLA9yvmqNu7/V53EkYIAGTkYzmgDcorzdPjn4Gl1y102K/neO4wPtzQGOCJjnAcvhh0HO3aNwyQM49IoAKjhEyoRPJG772IKIVG3cdowSeQuAT3IJwM4ElFABRRRQAV86anqUOlftZJcTrIyPcQW4CAE7pbVYlPJHG5wT7Z69K+i6+bPjbeQ+HvjPoWsxWkbvBb2t5Ki4QztHM/wB5sHnairkg4AHpQB7n4j8aaF4TvNLttZu/szalK0ULsp2LtAyzt0VclRk/3gegYjoK+OPG83iDxjFcfEHUbX7LplzdpY2cbSMwACuQI89VGw7m4BdjgfeC/S/gfW9Oj0nRPCslxs1200S2mubJkYPCojRSH4wrZI+UndyDjFAHSatqUOjaNfapcLI0FlbyXEixgFiqKWIGSBnA9RXz5eW2u/tA65eXNqv9l6FpUUiae88QYPM235XYHO5gATt3BAFGCTlvo+igDzf4d/ES41e8l8KeK4fsHiyxykkbgKLsAZ3rjjdjkgcEfMvy5C+iTzw2tvLcXEscMESF5JJGCqigZJJPAAHOa5Pxv8O9L8a/ZrqSafT9Xs+bTUbQ7ZIyMlQf7yhsNjIIOcFcnPkHjTx74x8IeF7rwF4jhgutSuLQLHrENxv8y2c7SGVkyzYEiFjtPfk/MwBJ8MoB8Q/jTrPi67ikms7NzPB5qxjYxOy3V155WNSQR0aMHOev0XXl/wACNBs9H8A/aYb2xvbq/lE9w9qQxhyi7YXYc7lByQcbS5GO59QoAKKKKACiiigAooooAr2d9b6hA01rJ5kayyQk7SMPG7RuOfRlYe+OOKsVHHGyPMzTSSB33KrBcRjaBtXABxkE85OWPOMASUAFFFFABRWfff2iup6W9n89q0rx3qHbhYzGzK4zzuDqijBxiRsg8FdCgAooooAKKKKACiiigAooooAKKKKAK9xfW9tLHFLJ++l5SJFLuw3KpYKMnaC6ZbGFzkkDmi4vre1ljjuJPK8zhXdSEyWVApf7oYs6gKTls8A4OC3huI57t57rzo5ZQ8CeWF8lNigpkfe+YM2T/fx0AqxQAUUUUAFFFFABRRRQAUUUUAFFFU9VOpLpdwdHjtH1DZiAXbssW71YqCcDrgdcYyM5ABHbzS6n9jvra58qx+d9ihH+0qeI2DgsPLI+cY+Y5TJXDK2hUcEjTW8UrwyQO6BmikKlkJH3TtJGR04JHoTUdrcS3Hn+bZT2vlytGvnFD5qjo67Gb5T23YbjkCgAt7G3tJ7uaCPZJdyiac7id7hFjB56fKijj09c1YrL0/TZo9UutWu2jF3d28EDQwklIlj3tgMQC53yyfNhcjaNoIJOpQAUUUUAFV7z7HHELy98hI7TdOJpsAQ4Uhn3H7vylgT6E9qsVh+JPDQ8SJZI+ralYJaXCXKrZNGBJIjBkL70bcFYZC9M8kHAwAXBrVk8sKRNPOs0STRzQW0ksLo7BVIkVSh5IPXhfmOFBNaFFRmNjcJKJpAioymIBdrEkYY8ZyMEDBA+Y5B4wARzQ3El5bSR3XlW8e4yxCMEykjCgseijJJAGSQvIAIaxUc0KzoEcyAB1f5JGQ5Vgw5Ug4yOR0IyDkEiieCG6t5be4ijmglQpJHIoZXUjBBB4II4xQBj6zJ4mGs6RFokOmnT2dm1KW8LblQMmFi2n75UyEZBHy8kd9yiigAooooAKKKKAI5o2lQKk0kJDq25ApJAYEr8wIwQMHvgnBBwRJRRQAUUUUAFFFFABRRRQAUUUUAFFFFAHhc3ij4y+L9UC6DoEnhyzjdVY3cKg4bA3M0y5cAqx/dpkA4IJxXOfALVri++J2r3N1bfaru/tJZp7sYUxkyKzEjcF2sxGcKSDtxhd1fS9cXqHj7wB4QuJrebVtNtZ57iR7iOzj8xvOBAcyiJSQ+cAlsE49jQB2lFeJ3v7QkOoPHZeEvDGpajqEySBEnUAqwXIISMuZB1JGV4HXnIj+3fG/Wv9Plk0rwvYJaecz3CxLGAOSXD+ZIjYPO7aAF5weoB7hRXyZf6f40+JnjHUdHsNcu/EFnaXC77uVlgtFx+7EoRWKAfeI25ZlywGSRX0f4D8N3PhPwdYaRe38l7dxJ+9laR3UHsibidqKoCgAAfLnAJNAHSUUUUAFFFFABRRRQAUUUUAFFRziZreVbeSOOcoRG8iF1VscEqCCRntkZ9RRBI01vFK8MkDugZopCpZCR907SRkdOCR6E0ASUUUUAFFFcv4n+IfhbwjFN/amrQC6i4NlCwknLFdyjYOVyMYLYXkZIzQB534T8JXw+Pmsa1p+k3eh+H7NGhVPsv2eK6OwR7UUEBkLK0oIBHyqSAWFewalq2m6NbrcapqFpYwM4RZLqZYlLYJwCxAzgE49jXjf8Awsb4i+O7ryPBHh3+ztMm/wBXql7HnCrLtL72/d9MAoA7DDYzjjU0T4HwT3H2/wAda1d+Jbs26RJHLNKqwHOWAffvcAk4+6OSSuTwAaGt/HfwRo9x5ENzd6m4d0kNhCGVCpx95yoYHnBUsDj6Z9A0rVbHXNLt9T0y5jubO4TfFKnRh/MEHIIPIIIOCK5ef4T+CJvD8ujp4ftIInQqtxGgNwhJzuErZfIPqSMcEEcVueFvDdn4R8OWmh2Ek8lra79jzsC53OznJAA6se1AGxRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAVn6nruj6J5X9rarY2HnZ8v7XcJFvxjONxGcZHT1FSarpdprWl3Gm36SPaXKbJUSV4yy9xuQg4PQjPIyDwTXnemfAHwNYeb9pt77Ut+Nv2u6K+XjOceUE65756DGOcgFfWf2g/B2n7005b7VJPKLo0MPlRl+cIxkww6DJCnAPc8Vj3HjX4xeJftkegeDf7IhXZse7j2zJ64acqj5Kt0Q4BHfBr1zRPDei+HLfyNG0u0sUKIjmGIK0gUYXe3VyMnliTyfWtCSeGF4UlljR5n2RKzAF22lsL6narHA7AntQB4/N8GvE2uIB4j+I2pXMFw6yXtlEjeUx3BmVMvtAB+6dmBgHaMYrc0b4GeBtI2NLYT6lMkolWS+nLYxjClU2oy5HRlOckHI4r0iigDh0+E3hODxpa+J7Wx+y3FtgraQqi2xcAgP5e3hhkEbSOVBxnJPcUUUAFFFFABRRRQAV80ftAWNxqfxT0aws4/MurrT4IYU3AbnaeUKMngZJHWvpeqd5pOm6hcWtxe6faXM9o++2kmhV2hbIOUJGVOVByPQelAHh/x2g03wz8OfDPhSxikVEuN8LbVAKxRlXL4x87NKGJA5O4nHfqPghoFzF4al8Ua0sk+tau+5by5Z3nNsAqopL8gEqWGOCpQ5OAB6Br/hzSPFOltputWMd3aFw+xiVKsOhVlIKnqMgjgkdCa0IIIbW3it7eKOGCJAkccahVRQMAADgADjFAElFFed678XtI8K+LNV0TX7K7tEtbdJ7O5RTIL3KglVGBtO7KgklSVbJXGKAOw8R6/Y+FvD95rWpNILS1QM/lruZiSFVQPUsQOcDnkgc18+eCdEvPjJ8Rb/xH4jt55dCh3qI3c7Bn/V26spUjaG3kqO3zDL5Mccfjf496pMzTR6f4dtLjcqsD5UJOBtXABmlC5POAMnlA4B+g/DHhjS/COhw6RpEHlW8fLM3Lyuerue7HA/IAAAAAA831L4MX+j6jNqXw98ST6EzeU39nvJIYZHQ9WfJJXBJ2sr8kjo2BXs/i54l8IxG1+I3hS+i8vbGuo2UalJXKghTz5ZYgOxKP2xtGDj2io54Ibq3lt7iKOaCVCkkcihldSMEEHggjjFAHP6B4/wDCnih1i0fXLSednKLAxMUrkLuO2NwGIxzkDHB9DXSVwev/AAc8EeIEbdo8enz7AizadiAqA2fuAbCTyCSpOD7DHLn4d/EPwjcJdeEfGUmo2kCMqaZqjtt8pSCkSgkoSQu3cPLx2IBOAD2SivG4/i14v8NvMvjnwLdw28L5mv8ATkYxRqyjYOSyMSxAJ8wdcYyMHsPC/wAVfCHixEW01SO1u3dUFnfFYZSzMQoUE4cnHRC3UZwTigDtKKKKACiiigAooooArpZoLxrqQ+bNyImdFzCjBNyKQAdpKBjknJ74AAsUUUAFFFFABRRRQAUUUUAFFFFABRRRQBnw2t4niG9vHuN1jLaQRRQ7ydkiPMXbb0GQ8YyOTt56CtCiigArn9d8U2fh6eb7VL9o2Wj3RtLZA08UcaSu8r5cfuzsVAcAByASd3y9BRQBHBI01vFK8MkDugZopCpZCR907SRkdOCR6E1HdX9nY+R9su4Lfz5Vgh86QJ5kjfdRc9WODgDk1YrPvbDTm1Gy1a6tPNu7PdDbSiNpGi84qrEAZxnCgtjhd2SF3UAaFRwiZUInkjd97EFEKjbuO0YJPIXAJ7kE4GcCSigCOeZba3lncSFI0LsI42diAM8KoJY+wBJ7UQTw3VvFcW8sc0EqB45I2DK6kZBBHBBHOaJp4bZA88scSF1QM7BQWZgqjnuWIAHckCiATLbxLcSRyThAJHjQorNjkhSSQM9snHqaAJKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigCMzKtwkBEm90ZwRGxXCkA5bGAfmGATk84zg4koooAKKKKACiiigAorm/HOqeI9H8NSXfhfR49U1BXGYnbhIwCzNtBBc8bQqnJLA84wfJ4fCvxc+ITk+I9Zk0DSZUY+QhCFo5VJ2eVGQWAwqkSsCAx6ncKAPRPE/xb8HeFZZra61P7VfQ8NaWS+a4O7aVJ4RWBByrMCMdORnz+5+MfjXxVui8DeD5/s7yvbC+liafDHGxsgCOJgCGIYsoyM8DntPCnwX8IeGrcG4sY9YvGTbJPfxrIvRc7Yz8qjK5HVhkjcRXolAHg7fCn4k+LvMPi3xp9ntbqJHktYXeVQ424RoV2RDGMkqT8wzznNdho3wM8DaRsaWwn1KZJRKsl9OWxjGFKptRlyOjKc5IORxXpFFAFexsLPTLOOzsLSC0tY87IYIxGi5JJwo4GSSfxryfXNG8a/FDXL3SL9P7B8H2OoNE+Ayz36p0ZcjDLkAg4CjeD+8KcewUUAZ+jaHpfh7TksNIsILK1XB2QpjcQANzHqzYAyxyTjk1oUUUAFFFFABRRRQAUUUUAFFFFABRRRQBy/je+8Y2OnWz+DdIsdRumlxOt3Lt2Jg4IBZQee+7jjg5JXz+G3+PGtXlzO97pWgxjbsgcQuh4wdpCyt2ydx/i444HtFFAHjc3wa8Ta4gHiP4jalcwXDrJe2USN5THcGZUy+0AH7p2YGAdoxiuw0T4T+CNDt/Kh8P2l07IiyS36C4Zyo+98+QpOSTtCg+nAx2lFABRUYhVbh5wZN7oqEGRiuFJIwucA/MckDJ4znAxxfgP4paF49lntbJZ7a/i8yQ2syEkwqwCybh8vO5crnIORyBuIB3FFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABXm/jvwP4s17xlpGt+HPE39lx20QgmRnf5B5gdmVBlZN2FyjYB8tQSR09IooAKKKKACiiigAooooAKKKjBm+0OrRxiAIpRw5LFsncCuMAAbcHJzk8DHIBJRRRQAVT1LSdN1m3W31TT7S+gVw6x3UKyqGwRkBgRnBIz7mrlFAFexsLPTLOOzsLSC0tY87IYIxGi5JJwo4GSSfxqxRRQAUVX+32f9o/2d9rg+3eV5/wBm8weZ5edu/b1254z0zVigAooooAK4/wARfC7wd4n8yS+0WCK6fzCbq0Hkyb36uSuA7Z5+cNznjk57CigDwvVtI8V/Bq40+58L3mpa54bmuFhm0u4QSeUSQFVWXlS7u+CiqN20MGJAPulFFABUc0ywIHcSEF1T5I2c5Zgo4UE4yeT0AyTgAmpKKACiiigAooooAKKKKACiiigAooooAKKKKACiio4YVgQohkILs/zyM5yzFjyxJxk8DoBgDAAFAElFRzTLAgdxIQXVPkjZzlmCjhQTjJ5PQDJOACakoAKKKKACiiigCOSRkeFVhkkDvtZlK4jG0nc2SDjIA4ycsOMZIkoqvZ2NvYRGK1j8qH5QsSsdkYVQoVF6IoCj5VAHU4ySSASCeFrh7dZYzOiK7xhhuVWJCkjqASrAHvtPpUlV7Oxt7GIx28e3dtLuzFnkIUIGdzlnbaqjcxJOBzVigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiio4IVtreKBDIUjQIpkkZ2IAxyzElj7kknvQBJRRRQAUUUUAFFFFABRRRQBXv7630zTrm/vJPLtbWJ5pn2k7UUEscDk4APSs/w94q0LxXZm60PU4L2NfvhCQ8eSQNyHDLnacZAzjI4rYrj/AA98MPCvhXXDq+i2U9rcGLygou5WQDndkFjuzlchsgbFIAIyQDsKjM8K3CW7Sxid0Z0jLDcyqQGIHUgFlBPbcPWpKjkghmeF5Yo3eF98TMoJRtpXK+h2swyOxI70ASUUUUAFFFFABRRRQAUVhnxj4cXxBcaDJrFpDqkDxI1tM/lszSAFAm7AckEcLnGRnrW5QAUUUUAFFFFABRRRQAUVHCZmQmeONH3sAEcsNu47TkgclcEjsSRk4yZKACuf0rwT4d0TxDe6/p2n+Tqd95n2ifzpG373Dt8pYqMsAeAK3IZ4blC8EscqB2QsjBgGVirDjuGBBHYgipKACo5JlieFGEhMr7F2xswB2lvmIGFGFPJwM4HUgGn/AGV/xUP9r/b77/j0+y/YvO/0b7+7zNmP9Z23Z6cVoUAFFFFABRRRQAUVh+LdDvvEXh+XTtO1270W4d1YXdqMsADkqeQcH/ZZT05IyD5Xpfizxr8LLy003x+n9o6BPK0cesxu07xswDcsfmZR83ysob720sFC0Ae4UVHBPDdW8VxbyxzQSoHjkjYMrqRkEEcEEc5qSgAoqnquq2Oh6XcanqdzHbWdum+WV+ij+ZJOAAOSSAMk14X4s+KGu/EHUV8MfDaC+H3pJ7xGEMkioeNrEjy4+h3MVZiwXA5DAHtln4k0XUNZutHs9UtLjULVN89vFKGaMbipzjuGGCOq5GcbhnUr5s+Cvhww/GPWhdWMYOjpcIrWxkMEE3mCPCsxyQV80KHySAT1GR9J0AFFFFABRRWfret6d4c0efVtWuPs9jBt8yXYz7dzBRwoJPJA4FAGhXP+NvE9v4R8Jahq808EU0cTC1WbJEs5U+WmByckc47AnIAJFzw5r9j4p8P2etaa0htLpCyeYu1lIJVlI9QwI4yOOCRzXiHx81i01TxVpHhmfVY7GztEW4u5Qzy4aR1TBiQffSPdINxGVcgYJAYA7/4U+OPEfjbS57zWtDjtbfefs17b/LFLjAZdjMXyDn5hlThhwV59AMbG4SUTSBFRlMQC7WJIwx4zkYIGCB8xyDxjweb4m+I9eceGPhToUgsbFFt47913lY1UbD+8+SMEI4HmElhj7rcV1nww8f6jqN5eeE/GUnkeKrOVtqSRLGZ48BuNvysw5PygAptYbvmNAHqFFFRyCYvCYpI1QPmUMhYsu08Kcjad205OeARjnIAJKKKKACiiigAqv9ht/wC0ft4jxdGLyWdWI3JnIDAcNg5xnO3c2MbmzYrLutRubfxLptgLaRrO6t7hnnWB2EcqGMorOPlQFWl+9jJUAHPBANSiivO/i/8AECbwJ4ahGn+X/a2oO0VsXUkRKo+eTGMErlQAe7A4IBBAOD1e6+PGqaxq1hbW/wBnhjxE32NIYoSrLgNDLL85yBnIbcpPO04AyG8XfE74SXlmniST+0rG9/eLFeXInLECMyBZMl0ZQQvOUyWIDda9v8Lahqlh8OrTVPGFz/p0Vo93eymHYY0+Z8MiqMMqYBAXqD16nwS+8XaV468a3fivxHeyR+HdDeJrPRWaN57kFgNqxsQrAld8nJIXC5IwQAfTdhdfbtOtrz7PPb+fEkvk3CbJI9wB2uvZhnBHY0X99b6Zp1zf3knl2trE80z7SdqKCWOBycAHpXJ/DTxrfeOvD82qXmjSaeguHSCTdujnTJxtJ5JX7rHGCRx3VeP/AGitfbT/AAdY6LE0ivqlwWkwqlWiiwxUk8g72iIx/dPPYgHOfCrT7z4h/FDVfHuoieC3tpd0AjkKZkI2pHuVQHVIxhhkE5TcCGIP0PXB+C9V0Dwxbr4ENzHDqGhaYl1fsdwgUsA8riR8cbnDHOABIMdGC07H4y6FqfxFj8K2FvPdwyZjTUYMyI0wySAoGTGAD+8zjjONvz0AekUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUVHJMsTwowkJlfYu2NmAO0t8xAwowp5OBnA6kAgElFFYfiXxhoHg+3gn17Uo7NJ3KRAozs5AycKoJwOMnGBkeooA2BIxuHiMMgRUVhKSu1iScqOc5GATkAfMME84krL0TxJoviO38/RtUtL5AiO4hlDNGGGV3r1QnB4YA8H0rUoAKKKKACiiigAooooAr/YLP+0f7R+yQfbvK8j7T5Y8zy87tm7rtzzjpmo7w3cVxazW8ck8e/wAqaFXRcKxH7zkZJUjpuX5Wc4Zgqm5VNbBZXsrm+Ec95aodsiKyoHZQGdULEA4yASSQGYA4ZsgFyiiigAooooAKKjmnhtkDzyxxIXVAzsFBZmCqOe5YgAdyQKIJ4bq3iuLeWOaCVA8ckbBldSMggjggjnNAElFFFABRRVezmuJ4jJcWv2bdtKRtIGcAqCQ+PlDBiwwrMOAc84ABYoqOaeG2QPPLHEhdUDOwUFmYKo57liAB3JAqSgAooooAKKKKACiq819bwXltaSSf6Rc7jFGFLEhRlmOOijIBY4GWUZywBsUAeZ/F/wCJjeBdLhsdMEb61fIxiZirC2QceYV6kk5CgjBIYnO3acv4S/EPxTr3iG98L+K7LZfWlobnzpIDBN99flkjwByJFwQF4XvnNcR4ib/hOv2lbXTllgurO0u4rfy548J5cA8yeMjb83zCYc5Bz124rbjnmh/a5mSKWREmTZKqsQHX7EGw3qNyqcHuAe1AHvlFFFABRRRQAUUUUAFFFFABWXr/AIj0jwtpbalrV9HaWgcJvYFizHoFVQSx6nAB4BPQGtSvL/j9/Z3/AAq+f7b/AMfH2uH7D97/AF2Tnpx/q/N+9x+OKAPRNK1Wx1zS7fU9MuY7mzuE3xSp0YfzBByCDyCCDgirlef/AAS/5JDoX/bx/wClElFx8XvD9v8AEWPweUnaZpfszXabWiE527IxtJJ5LKSQNrDBGMlQD0CiiigAooooAjmmWBA7iQguqfJGznLMFHCgnGTyegGScAE1JVPVtSh0bRr7VLhZGgsreS4kWMAsVRSxAyQM4HqKx/BnjnRfHWlvfaPLIDE+ya3nAWWE843AEjBAyCCQeR1BAAPN/i/8UfEfgjxrptlpDWhtPsX2iWGeHcJmZnTDHIIC7ARtI5JzkcV7ZXzh8ent7j4p+HrLVryeHSBaQmcoSfKRp3EjquD821R0BztHBwK6/WP2g9CivEsvDmlX2uXTypHHtBhSXcP4MguW3ELtKDPOD0yAewUV4v8AC34y6x4z8UNouraTB+8iaSOewjcLDtGT5oZm+U8AMMYYgYO7I9ooAKjhnhuULwSxyoHZCyMGAZWKsOO4YEEdiCKkooAKKKKACq9/fW+madc395J5draxPNM+0naigljgcnAB6UX919h065vPs89x5ETy+Tbpvkk2gnai92OMAdzXzp4k/aFm1zw1qWkweGo7V723e3Mz3plCK42sdoRcnaTjng4PPQgFj4I6Cvi/xrrfjXWLSNzDcGaFPJbyvtMjFyyEnGUHQHJG9TkEA17XdeOPDVj4og8NXOrwR6vNtCW5DdWGVUtjarHspIJyuB8wz84eFtS+JN94LtPDPhTRJ7WxmleUanZwPA85BYtuuCQg5AXIIPyKuex9P+G/wOt/Cuoxazr9zBqGpRcwQxKTDA4Jw+WwXbG0jIG054JAYAHsFFFFABRRVe+ieezkhjXd5uI3xO0JCEgMVdRuDBSSMY5A5XqACSGeG5QvBLHKgdkLIwYBlYqw47hgQR2IIqSiigAorP1nXNL8Pac9/q9/BZWq5G+Z8biATtUdWbAOFGSccCvJ7745XmtatJo/gHw1Pq10cBLmcEIP3gUuYxyIyCvzMyY3cgY5AOc1+zm+BPxBsNT0e7kbw7q7nz7A5dljQrvXkgMVD5RiQeSDkZLd/wCKPjn4Q8PO9vaTSaxdhGIWxKtEG2gqGlJxg5xlN2MHIyMHyT4u6N4xeXw3deJdQ+26nqfnLDpdpD8lmdyERJtJ3sd6gnBPygbnwDXt/g74T+FvBN59usIJ7m/G4Jd3kgd41YAEKAAo6HnG75mGcHFAHkHhiw+Ivir4gXXiTQLSfwvY6lKk803l7bcxsp2vsfAuGI3HIBBd93ybsj6XoooAKKKKACiiigDD1fxj4c0DVLTTdX1i0sru6RniSd9o2juzdEBwQCxGSCBkio/GPg7S/HGh/wBk6t56wrKs0ckD7XjcZGRkEHgsOQevrgjxSys1+Iv7SWpDUreObT9KeUPa3MrOpSDEShRjGDKQ5Q8ctnPf3PxV/bv/AAi+o/8ACNeR/bPlH7L5+Nu7POM8bsZ27vl3YzxmgCxoej2/h/Q7LSLR55LeziWKNp5TI5A9Sf5DAHQAAADzfxP8fvC2iyzW2lxT6zdR8BoSI4CwbDDzDycAEgqrKcjB5yD4WeKbj4m+Ada0nxBeebfDzLW4khiEb+RMhCtwNm7PmAYH8IyO54T4paDoPhlNI8AeF9BjbVtReKV76cI8su5jGqCR+VLOmTtKKMdMM2ADT0/4YeMfiRrA1v4gX09jYPukhsY5MSRhlXaEQ7liXpnPzkphhk7q9o8MeGNL8I6HDpGkQeVbx8szcvK56u57scD8gAAAAI/CPhtfCfhq00db+7vjAgDT3MjMScAYUEkIgAACDgAdzkncoA+fPg3CulfGzxfo9kZIdPhS6RLcSMVxHcqqZyeSqkgE5PJ9TX0HXz/8LP8Ak4Xxr/2/f+laV9AUAFFFFABUc8EN1by29xFHNBKhSSORQyupGCCDwQRxipKKAPE9Q8MeL/hK+r6t4He0vfDciG4m02+dibXapZnX5l3AKuMhtxBAKsVDVwnw38Ny/FzxzqWreK5J7qGCKOS4lhZIfMkyqxowUD5SiODtAPy9QTz7X8Y9fXw/8MtVbdH598n2GFXVmDGQEOOOhEfmEE8ZA69Dj/B5NN8IfCawvdWvY9OGpXElwzX8qwrub5UC7scNHGrDrnJI4oA9Ah0pdD8NHTPDltaWxt7dksopd3lK+Dt34+Ygtyx6nJPJNeb/AA0+Gmr2niCbxr41nkm8RSu/lQ+aG8nIKFmKnaSVyFUfKq++AkniH48eHYLMW/hYT63q9z+7toUtpFQSEgLu3BWbOeFUEnGMrkGuX/sH4u/E39/q17/wjmkyfu2tcvBujb5JP3Qy78KTtlIB3fKcE4APQPGXxh8LeEPNtvtP9pammV+x2jBtjDcMSP8AdTDLgjlhkHaa4P4Y618S/GnjWPxBdXkkGgqjCVZYStrJGWb93CnG5wwI8zJKhfmJ+63ceEfgx4U8KvaXjQSajqlu4lW7uWOFfaBlYwdoAOWGQxBP3jgEeiUAFFFFABRRRQAUUUUAFfP7W/8Awn37TUmL3fY6BskXA2MPIK5QZX5v9Ic5z1Xdg/dr2zxLqU2jeFdX1S3WNp7KymuI1kBKlkQsAcEHGR6ivJ/2dYbGx8C61rExjgc3rJcXEkm1RFFErDOTgAeY5z789BgA7D4ueFda8YeCjpmhzRrOtws8sTymMToqt+79CS2wgNgZAJIxXnHgv9nib7Qt34wuYxAER0srOUli2QWWV8YAA+U7Cc5yGGOfY9B8ceGvEunXl/pOrwTWtlzcu4aLyRjO5g4BC4B+bpweeDjH8KfFjwt4x1y40jTZ547qPJh+0xiMXSjOTHzk4AzhgGwc44bAB2kEENrbxW9vFHDBEgSOONQqooGAABwABxivA/jdrFxpHxT8H3t4k7aRYeVdoEiHzOJ8yhWOMttSLgnjI6Z5+gK5vx7YaRfeCtUfWtMj1K0s7eS8+zs5jJaNSw2uvKE4IyOxI5BIIB8waTpuvfFX4g6g9o13bwapcMbycl5o7aEkyKjsAAQBGAgOASijjHH034F8C6X4D0MWFgPNuJMNdXbrh7hx3PooycL2z3JJPD/s4/8AJPNQ/wCwrJ/6Kir2CgAooooAKKKKACiiigCMzwrcJbtLGJ3RnSMsNzKpAYgdSAWUE9tw9akoooAKKKKACiiigAooooAK+fJoV+Kf7QN7pepGS40HRkmQ2c0jR4CARvs2HqZiGySCVAz0C19B18qW2qR+DX1HWNds5L7XJNdvLSe50/WJ7J7jYqmVSI4wvlb3V8gqxJUY2gigDoPEUNj4K/aE0C28JmPT0u3t47+C2kypM05DoyEkKChQhcAD5SAODX0XXzR8JL63k+KH9reNpL6PX9RiWbSp7tTHHMZAykjpnK/LHj5MZA52CvpegAooooAKKKKACiiigAooooAKKKKACio4IIbW3it7eKOGCJAkccahVRQMAADgADjFSUARwwQ2yFIIo4kLs5VFCgszFmPHcsSSe5JNRy2aPK00R8i4fyw88aLvdEYsEJIOV5ce29sEE5qxRQAUUUUAFFFFABRRRQAUUUUAFFFFABVe/vrfTNOub+8k8u1tYnmmfaTtRQSxwOTgA9KsV5v8c9TvNN+F96tnDO32uWO2mmhYr5EZOWZsD7rbRGQcA+Zj2IBwHwKilbVvEfxA13UYLe1fdbS3E7JGkk0siSOxOQEwdnGMHzOOmK5vxH48sdI+P154t01Y9VtIHCII5diyn7MIWw+DwGzyAQcccHNbngr4FXGv6Houo6zrM8OmXMUl01jCBvjL7RGyNll+dArMSARtVcHqt/4aRW//AA0F4jg05Z7OzsLSW1SAzmfckLxQhS0gLbflDAAjbhVztGCAe0eFtYvNe8OWmp3+kz6TdTb99lPnfFh2UZyqnkAHoOtak0ywIHcSEF1T5I2c5Zgo4UE4yeT0AyTgAmsPxxda7Y+C9UufDVv5+rxxZt0CBz1G4qp+8wXcQOckAYPQ4/wt1LxjqnhdpvGVn5F0JWEEkkflTSpnkyRBQEweAeMjsOGYA7io4ZlnQugkADsnzxshyrFTwwBxkcHoRgjIINSUUAFFRzxtNbyxJNJA7oVWWMKWQkfeG4EZHXkEeoNSUAFfP/7ONzql9qPiSa41WeW1HlyS28vz+bPIWPnbichsIQf724Z+6K9Q+J3i7/hC/A17qUTbb6X/AEay4z++cHDfdI+UBnwRg7cd64/9nvwxcaP4SvdXvIJ4JtVlQxLJgB4EX5HA6jJd+vUBSBg5IB7BXj/7R3/JPNP/AOwrH/6Klr2CuX+IHhTS/GHhK5sNWufscMObmO7LYFu6qf3jZIBUAtkHsTyDggA8/wBd+K2j+BvAOnaBpmoQaprselR28c+nSJLBBIqBN7OeDggsFwSdo3BQwNSfCP4VzaVcQeMvEc12+uXCPIltPkNAzlgzyHcS7spzhsbdzZBbkcZ8Fvhfp3iSd/EOrTwXun2cqxx2iq22SbYjnzNwGVXeBtGQzA8lR8/0vQAUUUUAFFFZ+rRXFzFBZwrOIbiXy7meCcRPDEFZiQcZ+Yqsfy4YeZuBBXIANCvnDxtba78F/Fuoav4XXbpGuRMil4h5NtMWLbVVSF3JglNy42sww2GNfQ9nZRWMRjiedlO3mad5TwoUcuSeijPqck5JJOH4/wBRsdJ8B6zf6jbWlzBDbllgu4POiklyBErJ3Bk2fTrkYzQB88fDTwafHnxBmm8RXcmsWENu809z50kn2jBMUas+Q8ZOC6h8MVj+7g5rr/FfiKx8N3B+Hnwq0uNNYu38i7urTmRDliUEhOS65bLscRjIBBB2VPhZZ2Gg/CrWtS1PxJ/wjtxrvmJa3XnR7/KgBG+JPvM295FIHzfd27SQ1GmeP/hj8M4JR4RsL7WtSliAa9mzHvG8kozuAUwMH5I8HCZJIyAD0/4ZfDu3+H2hyRGb7Rqd5te9mUnYSudqID/Cu5uSMnJJxwo6zUtW03RrdbjVNQtLGBnCLJdTLEpbBOAWIGcAnHsa+cB8WfH/AMQ/EFv4f8PvaaSb15Y0WEYYRkEkvK2SCigncgU9cDOAOng/Z4m1G/i1DxP4uu76eRAboRxEyM2zAAmkZiQCAMlOQOg7AHeeFPix4W8Y65caRps88d1Hkw/aYxGLpRnJj5ycAZwwDYOccNjuK5PQPhp4P8L6oup6PoscF4qFFlaaSUoD1272IBxxkc4JHQmusoAKKKKACvC/2hPFs2l3Hh3S9PmjS8t7gaqWMZLRMhKwsM/KQT5mQQfujoOvulfOGv3z+M/2mtLtbOSDy9Lu4oUcqy5FuTNKDnqwYSqMAA4XtzQB19t8QvFOg/GFfDfi5LFNP1XZ9hFqS62+7Kx7W2hn3OCjbwPm+YbVGDh/tMzzLb+GrdZZBA73LvGGO1mURBSR0JAZgD23H1qPxV/xU37UOiab/wAe39meR+8+/wCb5atddOMZ3bOpxjPPSmftFXVtea9oOmSvJELK3kurmQbCfKlkSMeWrOpkcGNjtGOOc4DEAHvdhDcW+nW0N5dfa7qOJEmuPLEfmuAAz7RwuTk4HTNSQxtEhV5pJiXZtzhQQCxIX5QBgA4HfAGSTkn5o8Q/GHxX4+v5/DPhjT44bTUXa3hiWIPcTRMhVldmJRQQSxIA2gfewCT7f8OfCE3gfwdb6PcahJeTh2lkJYmONmxlIweQgPPuSzYGcAA6yq81t515bSusDxwbnUPFudZCNoZWz8vymRTxk7+oGQbFFABRRRQB4va/A281rxDPq3jrxLPq+2VhDFCSvmR79yhif9Wpy4MaABc/K1esaNoel+HtOSw0iwgsrVcHZCmNxAA3MerNgDLHJOOTVi/vrfTNOub+8k8u1tYnmmfaTtRQSxwOTgA9K+ZPEHxGvvH9hr9/dazJoGn2NksVppNpd/vLyWVwpDj5fOTaH3dNgKkK3zbgDY+KqaR4g+PWi6be3tpDZ21vBHqLXcpgjVQzzMhfjlo2ABB6sBkHOPf9N1bTdZt2uNL1C0voFco0lrMsqhsA4JUkZwQce4rwT4W/BnS9f05vEPiG0vo7OeVjYafJLt3wEfK8jqFZs54wEzs3YKsBR8T/AATb/C2Kz8V+DNRvtLmlu1tntklLpgqXGCeSuYslX3Bs9gMEA+h6Kx9A1O81fwlo2qmGA3V7aW9xKgYoi71Vn28MeAWIB64AJGcjYoAKKKKACiiigD5//Zu/0zUfFWo3X7++b7Pm5l+aQ7zKz/MefmKqT6kDPSvoCvn/APZl/wCZp/7dP/a1fQFAHlep/C+bSPHknjbw74kj0VHd59RjuozJFtIZpCSWXKMwUlSRtyWDDaoHL/DKCbx98YNZ+ILxSQWFq5S1G0rvYx+UgP3gSIhlgG4ZlI4OKk+N3xTt47O78HaJL5txJ+71C6jcgRAHmFSDyxxhuwBK8knbY+EnjnVL6XT9C0PwZPF4WtIjDJcm48145S25pGlYIjZLgmNRuG4leAFoA9wooooA8D8Dxr4f/aX8T2F9NH598lw1uEDMGMjpcBc44IjznPGQQCeM++V8/wD/ADd5/n/nwr6AoAKKp6teTado19e29pJeT29vJLHbR53TMqkhBgE5JGOh69DXh/8Awlnxg8fJc2mi6JHolmbhoXuihieJCzoVLyHJKkHcY1DArwAeCAe2anruj6J5X9rarY2HnZ8v7XcJFvxjONxGcZHT1FcPqfxw8HWOoxWFi99rN1JKYQmmW/mfOCAACxUPuJ42bs49xnl9M/Z8/tHzb7xp4kvr7U5sbmtJc4xkDMkqsz/KE7LjBHPBr0zQPAHhTwu6y6PodpBOrl1nYGWVCV2nbI5LAY4wDjk+poA+fPiB4w8WfEDXNK8Lahov9gQ3F3Eba1u43Vy7/u1d3ZQSoJfG1R1OQxANd/o/7P8Aby/ZJvFviG+1WS3ihjjt4mKRxIuSYdzZYx84G3ZgZ6E8YmiGbxn+07fXU8cbW+jPMBDcOZAqwjyVKAjAPmsJMcYJJySOfoOgDD0Dwd4c8LIq6Lo9paOEKecqbpWUtuIaRsuwzjgk9B6CtyiigAooooAKKKKACiiigAooooA4P4zzzW3wk154JZInKRIWRipKtMisOOxUkEdwSK8M8LXOu/EXQdE+G2lN9gsbLzrjULkygrLGZdwYpgE7S+AoJDMVJxjI9L/aK1yGy8HWOjNaxyz6jcF0kkjDeSsWCxU5yrkuq5wflLjjNdR8IfC9j4c+H2mTW8cbXepW8d5dXATDSFxuVTyeFVto7dTgFjQB4J4h8O6jpHxc1bwh4Wup1bU/Ls3Yoqlo5VjlkDCJAEjB5O1QAikYxkHc+DfhqGy+N2pWUtxJK+hpdCKRQEErK4gyw5wCrscA9cc+uh8B4JvEfxG8Q+K7uK03qjuyhTmOa4kLZjznA2rIuc5w2OcmvP4/FWp3ni/xJH4chjz4quJbVFnij83ZNNkIGPCEhtp5xznqqsAD6r1nUdUvvC76j4JuNKv7rl4fObzYbgKSGRXRwA2RwSSMjBxnI8Y8WfFm/wBR+HGq+H/EelX2heJbiJTGBBJDHPEZlBADHcuU8wEHKkK3PO2vZ/A/hv8A4RHwXpehmTzJLWL964bIMjEu+04Hy7mbGRnGM815f8Yry48Y+NNB+G2niD97Kl3c3AcO8Jw4IK5AG2Pc5BOWyuMfxAHYfBbRP7E+F+l77fybi+3Xsvz7t+8/I3UgZjEfAx7jOa9Arw9/h941+GV5dap4D1L+0tIjiMj6ReszvKxADYRQFZhtDBlKscbcHo3ceC/ir4a8a/ZbW2uvs2ryxGR9PmDBlK/eCvgK/qMHO3kgYIAB3FFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFRkzfaEVY4zAUYu5chg2RtAXGCCN2TkYwODniO+t5buzkghvZ7KRsYngCF0wQeA6svPTkHr680AWK+bE8L6LN+0/d6TqEclzaS3D3ghlQOskrQ+eVfkYQMzEcNnaFIIJNfSdfP/jzwP4sufjpBqfhiSeCa7ijuE1FoXWC1dIyhR5AGByIuhHPmBSMHkAP2iP8AkYfCf9l/8hv97s+y/wDHz9+Pycbfm+/v2++7HOa+gK8n8EfCW8svFFz4r8bX8Gr620vmQeWS0cbYGJDuVfmHRVACoACOcbfWKACiq8P2wXlyJvINqdpt2TIccYZWB4OCMhgRndjaNu5rFABRRRQAUVn6xrml+H7NLvV7+Cyt3lSFZJn2guxwB/Mn0AJOACRT1/xdovhvw02v397GbAoGhaFg5uCwyqx84YsORzjGSSACQAblFfOH/C1/iV/yO39mf8Uj9r8v7L5SeXt+7jzMeZ148z7u/jH8Fe/6HrNn4h0Oy1ewffa3cSypkglc9VbBIDA5BGeCCKANCiio4Z4blC8EscqB2QsjBgGVirDjuGBBHYgigCSiiigAooooAKKK8X+M/jvUYtRsPBHhe4ni1m8liMs1tcLGy7jhId2cqzHaxOVwu3qGOAD2iisvw5HrUPh+zi8RTWk+rIhW4ltARG5BOGGQOSuCeAM5wAMVqUAFFFFABRRVPSdSh1nRrHVLdZFgvbeO4jWQAMFdQwBwSM4PqaALlFFRzyNDbyypDJO6IWWKMqGcgfdG4gZPTkgepFAElU9V0qx1zS7jTNTto7mzuE2SxP0YfzBBwQRyCARgirlFAEcEENrbxW9vFHDBEgSOONQqooGAABwABxivL/hl8LNS8GeKtZ1vV9Ujv57lDFBMjsWkVn3u8oYZDkqh4Y9WyTwa9UooAKKKjknhheFJZY0eZ9kSswBdtpbC+p2qxwOwJ7UASUUUUAFFFFAHzR8U9TvPib8ULPwdosMB/s+WW2jmdiu6QgGZmyOFTyyMAEnaSN24AdX8PvFGteCPFkPw18XyRzAoo02+DkIAVysYZwpdCQUU4yGGwZGNvqkPhjS4PFtz4njg26ncWi2kjjgFA2ckDqxwgJOeEUDHOfF/2iP+JR4h8J6/Y/utTTzds/3seS8bx/KcrwzsenOec8UAfQFFFFAHi/7N19byeDdWsFkzdQ6h50ibT8qPGgU56cmN/wAvcVofEn4xP4O1hNN0nS/t0lrLF/aUsysscYdSyxKw/wCWjKCwJyAB0Y5C838I5F8M/GPxd4VWGOztJnka2imLCQiKQ+UqbjlgYpGbuSADnGc+kfEh9N0L4feK9UNlGs97ZG3nmhiUSSs48mMueCwUuOpOBnHpQBueGPE+l+LtDh1fSJ/Nt5OGVuHicdUcdmGR+YIJBBOxXk/7PNjcWnw1kmnj2R3eoSzQHcDvQKkZPHT5kYc+npivWKACo554bW3luLiWOGCJC8kkjBVRQMkkngADnNV9Uv202wa6Sxu75w6ItvaKrSMWcLxuIAA3ZJJAABJ6UQPDrOjRPcWUiwXtuDJaXkQDBXXlJEORnBwRz3FAHmfif4/eFtFlmttLin1m6j4DQkRwFg2GHmHk4AJBVWU5GDzkcB8Q/HnxC1jwvaas+l/2D4avJYzbNHIrTSOCJEYscOMNEXVlVOMctwT7X4Y+G3hTwjcPdaVpUYuy7MtxMTLJGCW+VC2doCsV4wSANxY815f8bzDr3xL8GeFriOSOB3QyTxuNxW4mWMgAggECLOefvdOOQDmPHngE+FPBXh+GbSbvU/EmqusU168kjm1ZVQJaxojFGOPkXO7IRtoxjZ2fhz4A6ddRWt54lt/sckcUANhY3TOJGVQZGmdgfmdiRtj2qoHDEnNXPiB8U/Edn43/AOEK8H6XHNqRQRvNKm9vMdA6mMZCgKpyWfI65AC5PWfDu+8dyWctl440jyriPLw6gksBEoJ+4yRtwwzwQMEDnBGWAPLPiFpOm6N8ffBlvpen2ljAz2LtHawrEpb7U4yQoAzgAZ9hX0XXhfxhs4dP+LngLXri7jigkuIopPMwqxLDOrlyxOMYl9sbevPHulAEck8MLwpLLGjzPsiVmALttLYX1O1WOB2BPapKjmnhtkDzyxxIXVAzsFBZmCqOe5YgAdyQKkoAKKKKAMvxLqU2jeFdX1S3WNp7KymuI1kBKlkQsAcEHGR6ivCP2e9AvtR8S6l4vvljuIESS3W4nbfK1y5RmYZyc7SQWOCfMwM5bH0XVexsLPTLOOzsLSC0tY87IYIxGi5JJwo4GSSfxoA+dPEev2Phb9p281rUmkFpaoGfy13MxNiFVQPUsQOcDnkgc1z97qU3xx+KmnW8qx6Ok1ubeIqDOUWNJJctyu4ltw4xgEdcZPufib4O+FvFfig6/qJvhcP5fnwwzhY59gAG7ILDKgKdpXgcYOTXlGg6ppdv+0JrWsate2Oj2Oi/aUijVNiOkKfZkjRc/e2fNhQc7SAvPAB7n4M8DaL4F0t7HR4pCZX3zXE5DSzHnG4gAYAOAAAByepJPSV4f4u/aJ06z3W3hSz/ALQm4/0y7VkhH3TwnDtwWHOzBAPzCq/wa8bePvGPiif+0dQgvNGtYv8AS/NhjjZWYHy9mxQSxKnr8u0N320Ae8UUUUAFFFFABXD2/wAIfA1trkmrLoMDyPyLeQl4FY7txER+XndjGNo2rtCnr3FFAHD+KviVZ+GfGWieF/7Pnub7U5YB5m8JHFHJI0e7PJLAr93ABB+8K8w+NH2zxx8UNF8F6R5D3FtEcl8p5ckg3vuY8FRGiN8oJ5I5PAv+INbd/wBpyxZLT/kFWi2gZmYo7yxt5ZcqjGNTJcxx5wcZB74rzi81c+Etf1PU9P8AFk97qepxX0dxcWEMKBi1wyKd4Ztm7Z53yqCAUCkb96gH1vYWNvpmnW1hZx+Xa2sSQwpuJ2ooAUZPJwAOtWK8z+CU/i+68HNceJ5ZJraVw+nSXLM1w8ZyWLk8lCcFCecZ/h2V6ZQAUUUUAFFFFAHz/wDsy/8AM0/9un/tavoCvn/9mX/maf8At0/9rV7R4n8T6X4R0ObV9Xn8q3j4VV5eVz0RB3Y4P5EkgAkAHgnxv8G+D/BmjaVFomkSWuoXlw7eaJ5JF8pF+ZTvc4JZ0IwOx5HfT8TfFW48O+HtB8GeDbqC/wBagtLa2nv7MC4jDqiqI4QQRIzEdcHAOMbidvCfFTx1qXjq40+8k0iSx0WN5xpkskTBp1JRXJbO1iCg4X7ucEnrX0H4D+F+heB7OF0ggvdXXdv1J4SHOS2NoLN5fyttO0jdjmgDpPDlrq9n4fs4Ne1CPUNUVCbm5jiEasxJOAAAMAELnAzjOBnFalFcH8RviTD8OktpLizkvnvUItYI8RgMjDzC8hJwCrrtAQ8qcnkYAOH8Qx2Og/tSaDfyzSKNRt1Zyw3ASvHJboqgDIBKp1zyScgdPdK+f/iSUv8A40+ANbsp4LrTLqW2hguoJlkSR47o7wCpPTev547HH0BQAUUUUAFV7++t9M065v7yTy7W1ieaZ9pO1FBLHA5OAD0qxXP+O/8AknniX/sFXX/opqAPHP2adKVrjX9YktpN6JFawXB3BcMWaRB2J+WInuOOmeff554bW3luLiWOGCJC8kkjBVRQMkkngADnNeF/BfxPpfhH4Qatq+rz+Vbx6rIFVeXlcwxYRB3Y4P5EkgAkch4m8T6p8SvDmo+I9bn+waJo93BHa6XB8ovHkc7lErZBmSPnO1sA52qCcgHo/iT9obw1pnlpodrPrUjYLN81tGo5yMuu4sMDjbjB65GKw5vGXxc8TaNe+INOtLTw9otlbzXaySwgtcRBQ6geYGLnb0dVVDzz0A9I8EfDvw54XsrK9ttAjtNWa3jMzzy/aJYpNmGAfoD8zAlAob0xgDH+POpQ2PwrvLeVZC9/cQ28RUDAYOJctz02xsOM8kfUAB8FfGev+MvDV5PrsccptbjyY75dqmckbmVkUAAqCvIABDDjIJPplcX8J9Eh0P4ZaHDF5bPc263ksixhC7Sjf83qVUqmT1Cjp0HaUAFFFFABRRRQAUUVz/h7UbPxLo9/PbahfXdpNd3ECyviLaqsUIhkiC7o+Mq4JbnltwIABn/EL4e6d8QNHS2uX+z30Gfsl5hn8jcyF/kDKG3BMc9OorsKKKAKdnpOm6fcXVxZafaW092++5khhVGmbJOXIGWOWJyfU+tc3oPwx8LeG/FF54g06x8u7uP9WhIMdrkfN5S4+Xd364GQu1SRXYUUAeR+I/jHN4I8Wa5o+v6ZJcJGkcukvaQGITKyknzGdzwGwm5QeVc47Vz/AMBvDN9qes6j8QNXMcj3LzRweZBhpJXYNJMpwAo+8ny9cuOMYPuGpaTpus262+qafaX0CuHWO6hWVQ2CMgMCM4JGfc1YgghtbeK3t4o4YIkCRxxqFVFAwAAOAAOMUASVxfjb4YeHPG1vO91aR22qOhEeowriRWwoBcDAkACgYboMgFc5rtKKAPC7XxX8Q/hUi2vjDTZNe0NXz/akErSvGGZM5kbkgFiAsgUknAbaBXqnhjxt4f8AF1nDNpGpQSzSRea1o0iieIA4O+PORgkDPTkYJBBO5PBDdW8tvcRRzQSoUkjkUMrqRggg8EEcYryvxJ8BtC1HUY9S8PX0/h6+WUS5t1LxhgS25F3KUbJXG1goC8L3oA9Yorx/Q/hx8RNH8aWV5L4+nvdGglV5BPcTO8yY+ZDCxZOTlc7jj7w5AFewUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFY/ir+3f8AhF9R/wCEa8j+2fKP2Xz8bd2ecZ43Yzt3fLuxnjNaFjf2ep2cd5YXcF3ayZ2TQSCRGwSDhhwcEEfhVigDl/h/deKb7wlbXPi63gg1KTBVI0KP5e0YMq9FkJ3EgYAyBgHIHSRxsjzM00kgd9yqwXEY2gbVwAcZBPOTljzjAElFABRRRQBz/jTwnZ+NfC91ot4/lebh4ZwgdoZFOVYA/iDjBKlhkZzXypcz6uyaX8O/EcsmlxafqYUPKwVLZZW+dpF4EgG7er7hgM/JDDb9l1yfjP4c+HPHSI2r20i3cSbIry3fZKi7gcZwQw68MDjc2ME5oA4P49Xll4f+Gul+G7MeUs0scUEO+TiCFeeQcNg+UMPnOc4yAw6T4Ya74bufB1r4b8MavaS6hp9kDKDFMVErZLyBZNjOnmEkgEY3AfLkVz/xZ+GniPx3410V7KeNNHW38qaR5ci1bcWZ/LyNxZSgG3qUAYqADXonhPwXoXgrTms9FtPK83aZ5nYvJMyjALMfxOBhQScAZNAHB/A3x3r/AIzt9bi166juns3haKUQrG2HD5U7QAQNgI4zyeTxjm/Cd1F8LPjbqfhQ2+3SNaliFtM6ODHkMYQv3t67naIk9xkkYIqT4VTzL8ffHFussggd7x3jDHazLdKFJHQkBmAPbcfWtD4t6bDF8XPh1qitJ59xexW7qSNoWOeNlI4znMrZ57Dp3AOg+L/xMbwLpcNjpgjfWr5GMTMVYWyDjzCvUknIUEYJDE527Tc+Emj+KdN8L/afFGrX11cX22ZLO+y0lpywwXLFjuXYdpxtORjJNeeaNCvjz9pfU7i/MiwaG7vDA0jOCbd1iXacjaPMPm45Gcg5yTX0HQAUUUUAYerXmm+CfB19e29paW1np9vJLHbR7YI2bkhBgYBZjjpyW6EmvJ/grod3fXt58SPFF1HLcX7+RZT3EiZLM/ls2MfISwWJACOCy4wVz2nxi8M674r8DDTtAHmXAu45ZYPOEfnxgMNuSQpwxVsMQPlz1AFeKat8MNf074eav4h8YS3cd5pqW1tptu14swWLzArA43AIA/yhWGCGyMYyAfVdV7+1+3adc2f2ie38+J4vOt32SR7gRuRuzDOQexri/gxPNc/CTQXnlklcJKgZ2LEKszqo57BQAB2AArvKAPA7TSvEfwJ1SwaG5k8QeG9VuFt57aGLZKtw2dpjj3HLlV4wcNgq2CEavdPNe707zbNvKkli3QtcQMNhIypeMlW44ypKntwasUUARwCZbeJbiSOScIBI8aFFZsckKSSBntk49TVfSdNh0bRrHS7dpGgsreO3jaQgsVRQoJwAM4HoKuUUAFFFFABRRXi/xw8YeIPB+ueFr3SLnyIR57tH5rFLgjywUljwAVAIwc5+ZsbSASAe0Vx+tfETS9B8faT4UvYZ1m1KIOlyBlFd3KRoQOfmKsCe3y9iSvWTzw2tvLcXEscMESF5JJGCqigZJJPAAHOa+cPjB4j0i717wh468N30d86O6AOCEDW8iSKGQhXUkynIOMjaRjOSAfSdZ+p67o+ieV/a2q2Nh52fL+13CRb8YzjcRnGR09RWhXB+PvhTovj+4jvru4u7TUIbdoIpoWBUjJK70I5CsSeCpOSM9MAHeVHPPDa28txcSxwwRIXkkkYKqKBkkk8AAc5ryf4Ba1rV54a1HR9YguwNIuFt4ZbgEFRg5g5AOYyvQkkB1GAAK9coAKKp6jfrpyW80pjWB7iOB3ZmypkbYm0BTkmRkXnAAYnPGDcoAK8n+OPgPWfGenaPNocH2m6spZEa33omUkCkvudgODGBjnO72r1iigAooooA+f8Axp/xTn7TXh7UbP55tS+zecs3Kr5ha2bbjGPkAIzn5vbiug/aJvtLj8DWlheSTi/muxNZJEvysUGHLk/whZD053FewNU/jJDY2PxL+HmsTGOBzeqlxcSSbVEUU0TDOTgAeY5z789BjA0Twr4m+JnxZ/4SHxToV3p2k2zpI1veRsq7F/1cKCRfnBYZfAAOXPylgCAeh/BbwpqnhnwWj6vc3wuLzEi6fcN8lmgLEBVydrNuLN06gEAqc+kUUUAFFFFABXz/AOKv+Km/ah0TTf8Aj2/szyP3n3/N8tWuunGM7tnU4xnnpX0BXg/i/UNC+H3x5bxRrRnvftmniWC3tIz5ltJtWEMdzKrKyJJ3JBJyowGIBqfEm8+IP/CwbSDwVokkb/YvLbUxYwt5wJLlDO+QiDaMKSp3E8HKmu88Aa14g1/wul94l0b+yr8yugh2sm9AeH2MSydxgnnbu6MK830b9pHS7vUUh1fQp9PtXwPtENx9o2EkDLLtU7QMkkZPHAOa9Y1u81STwvPe+FRY3t+0Sy2YnfMMwJB4ZSM5XO05AyRk4oA8X/aa/wCZW/7e/wD2jX0BXyx8XfFusa/p2mWHiTwbPo+p2crhLxpX8ubAAlEaldpUsEOQzYAGCc5P1HBMtzbxToJAkiB1EkbIwBGeVYAqfYgEd6AJKKKKACiiigAooooAK878V/CPwbrusnxDqgkskhTzLpIHjgglAZnd5Ttzk5O5twOB1GM16JXk/j/wT8QPHWovYf2zpWn+GWlT/RonkaRlU/ff92Nzc52bgoIXuNxAOAlj/wCFl643hH4f6bBo/hW38v7dfRwbDcKmdjzHhn77EY7mOWbHOz3/AMMeGNL8I6HDpGkQeVbx8szcvK56u57scD8gAAAADwx4Y0vwjocOkaRB5VvHyzNy8rnq7nuxwPyAAAAA2KACiiigAooooAK8v8dfGXTvDt4dF0C3/trXzKbcwR7tkMmMKCQP3jbiBsXnhgSpGD6hXN6L4A8KeHtUm1PStDtLe8lcv5oBYxk7s+XuJEYIYjCYGOOgFAHzJoHhjWPit8StUg1O8/s++bzbq8aaJ2aHawTy1Rjn5SyqFLDao9sH3/wb8HvC3hDyrn7N/aWpphvtl2obYw2nMafdTDLkHlhkjca8/wDhZ/ycL41/7fv/AErSvoCgAooqOQTF4TFJGqB8yhkLFl2nhTkbTu2nJzwCMc5ABJUchmDwiKONkL4lLOVKrtPKjB3HdtGDjgk54wQzKtwkBEm90ZwRGxXCkA5bGAfmGATk84zg4koAKKKKAPmz4La0vgrw/wCPtU1KCRH05LYPbyBkYy5mVYzwSpLkLkjjPPSvf7ZIfEfhq1bWNJjCXlvFLPp93GJBGxAbYysOSreoHI6CvnC5+FGvWfxSj0RNGu5fC9zqcdwfJdzb/ZQ7EB5MjDrGXXk7sk4zuBP1HQB8+ftJwQ2tv4St7eKOGCJLpI441CqigQgAAcAAcYrq/ij8MNL1CLV/G1rfX1hrdnafaleGT5GeFchsfeVtqBQVYYwDgnOc/wCP3hXXfEv/AAjX9i6ZPe+XLNDJ5IB2NJ5e0t/dX5Gyx+UY5IyK6j4t2vi7VfC/9i+FdM+0/b9yXk4ukiaKMFTtAZlzv5B6jaGBHzZABH8HfGur+N/Ctxd6xBH59rcC3FzEgVZsIpJI3E78nJwFX5hjuB3Gq6VY65pdxpmp20dzZ3CbJYn6MP5gg4II5BAIwRXP/DnwYvgXwdb6Q0kct2ztPdyx7trytjOMnoFCr0GducAk11lAHg/jD4BPHqMOqeBrv7FIssW20llYeSQcGVJSSwx8rYOTwxBztWveKKKACiiigArn/Hf/ACTzxL/2Crr/ANFNXQVn67pn9t+HtT0nzvJ+3Wktt5u3ds3oV3YyM4znGRQB8ifDPwDN4/8AEps2lkt9PtkEt5cIhJC5wEU4wHbnGewY4O3B9T+KNjYa9458IfDnRo/s9rFK017a2LRxRxI5DEhegkVFlflekgxndiuw+DXgvUfBXh7U7PV7SCK+l1BiJomV/OhVFCHcOdud5AbBGTwM1wnwsnh8YfF7xF44vpY1t7dwlqbhhE6NM3lQDC/KT5amPBJyWXGSc0AfQdeP/FXwZ4x8feKNK0m2igtfDUH7x7z7Ru+cj5meLIJZQMKAD985YbiF9gooAr2Fjb6Zp1tYWcfl2trEkMKbidqKAFGTycADrViiigArk18X3f8Awmd1pM+m2lrpMFwloNUuNRRDNcPEkiwpCVyznzBxnGATnPy10FtqK3tvbXFvbXZincqTLA0DRABvmdJNrgZXA4J+YHGMkXKAMu21y21a3kl0Ka01NAkgE8N0jQLKoUiN3UsVJ3A5CtgAk9gdSiigCOEzMhM8caPvYAI5Ybdx2nJA5K4JHYkjJxk09F0az0DTBp1gnl2qyyypGAAE8yRpCqgAAKC5AHYAVoUUAFFFFABXkemfE/Xrn41v4Knj0aXTxcToJ7UOzhViaRQWL43jAVht4IYV6Rr/AIj0jwtpbalrV9HaWgcJvYFizHoFVQSx6nAB4BPQGvmjSfHutN8Y9d1bw3aR6zqGrPJZ2AkQqvleYhRio2nAiiA5K4+8x4OQD6roqnpMl9No1jLqkMcGoPbxtdRRnKpKVG9RyeA2R1P1NXKACiiqepatpujW63GqahaWMDOEWS6mWJS2CcAsQM4BOPY0ASWd9b6hA01rJ5kayyQk7SMPG7RuOfRlYe+OOKsVXsfsbWccth5BtZszo8GNkm8ly4I4O4sWz3znvVigAooooAKKKKACiiigAooooAKKKKACiiuD+KNr40OjW2peDNQkiudPd5ZrOOJXa5UrjgEEMVBY7CPmzkfMqggHeVx/xU1D+zPhf4huM432ht/9Xv8A9aRH03L/AH+ueOuGxtOX8K/ifD4+sJbe7jjg1q1TfcRxgLG6l2AMYLsxAAQMT0LD1FYfx78YaXaeC7nw3HcwT6neyxpJbpLl7dFKy72ABxnCAA4zvyM4NAB8M77S/h18EoNc1mSe3W8lkumikX55HY7Y1iXjO5I1YZ9S2QvI6TwF8VtF8fXt1YWdvd2t5AjTeVOoIeIPtDBlJGcFMg4wWwCwBNeaeBfhbeCIeKviS0403SrQPbWN25lIhRd43ryVjUZxFjJOQQAMNb+ANlDqfirxV4pisZLGB38m1gjA8hFkcyNGDtAJQLEOMYDdORgA98oorm9f8f8AhTwu7RaxrlpBOrhGgUmWVCV3DdGgLAY5yRjkeooA6SivH7z9ozwnBeCK3sNVuYVlZHmWJFBQA4dAWyckLwwXgk9RtPrkEy3NvFOgkCSIHUSRsjAEZ5VgCp9iAR3oAkorzPxt8UtS0HxBP4c0HwlqWq6oluZt/lt5YUhdsiqgLSIGJVvu8rjPpT8I/ErxTL8QF8H+NNBgsL65iMlq9oDtGFZzkl2DKQpG5TwykEHnaAesVx/xA8NXGtadBqVj4hn0W/0ffdWsxlC2wfAy0wI5UKGXPYO2QwJU9hXjfxZvZvGHizRfhlpd9Hbvdv8AadQmySEVVLqhCtydqs+xgMnyjkdaAOI+B1/far8Xr3UIjGTc2Us2oNcNl3LMhdk2KqgmYqcEYCkjJIBO/wDtJzzWtx4SuLeWSGeJ7p45I2KsjAwkEEcgg85qP4Z6TpfhP4/a7oVlcz/Z4tPaG3+2/JLK+YXYAFV3dHIwOVGRkc0ftNf8yt/29/8AtGgCwNYuPg78SvFN9rOm31zoGuSi4hvLa3BzKzM6pkuFGN8wIJ3HarAAGuw8MfG7wn4o1yHSIVvrK4n4ha9jREkfsgKu3zHtnGemckA+gX1/Z6ZZyXl/dwWlrHjfNPII0XJAGWPAySB+NeVzeBNf8Q/HUeItetY00PTEVtPZJlYSFMGNeCrg72aQ7gRlSuWXBoA9cooooAK8/wDjb/ySHXf+3f8A9KI69Arzv44zww/CTV0lljR5ngSJWYAu3nI2F9TtVjgdgT2oAk+CX/JIdC/7eP8A0okrn/Gni/4nTeKLrw/4S8Lz28C4SPUZrcNvIG4usjHyVUjgBsn6Mdq9BoWoW/gj4G6Zqlrp09zHa6VFdG2gJLO8ih3Yk5wu52ZjztXJAwMVueMPEU3g/wAB3+tziO4u7S3UDZEQjzMQinaWyE3sCRuJAzyTQB4pq/xq1mx0fVtC1WGAeKtLuxDZanaQI8e9G8uVysn3WK+YNyryJCNq459X+GvhrxZ4b06+i8VeIf7WkmlVoF815vKAGCfMcBjnj5cYG3I5Y188LdeEZfhRqbahqf23xjeXa3Uay2r+ZC3mBW/f7fn3JuYhnK5IwoYEn3P4Q/EHRfEPhXTNFE8drq1hbx2htJZBumCJgPH03AqpJA5XBzxgkA9MooooAKKKjjhWJ5nUyEyvvbdIzAHaF+UE4UYUcDAzk9SSQDwD4sS33w8+LOmeNdNuJCmpIBcW4kx5oi2LJGflwEZPLwTuIbJ4wtP/AGmv+ZW/7e//AGjXQftHf8k80/8A7Csf/oqWuQufDfiX4jz+BNFm0Kex0Cw0q3dtQZ1/eRMkYkYMNyhjsARCN3O5gATtAOj8d+FfiH8QPGtzorTR6V4Xt0RlkWVjFOhYHcwGDJKCpOw4C7RyNwd+U+OfhjS/COh+ENI0iDyreP7WWZuXlc+Tl3PdjgfkAAAAB3ev/FW+vPiRonhjwQdN1RHcPdymbMco2sWjEgBChUG8su45wMHDK3MfHUTeIPiX4T8LNJHBBKkYScIWZWnm8tiRkAgCNSBx359AD6DoorH8VXNxaeF9Rms9VsdKuhERDe3+PJicnCliTgZJABOcEjhvukA2KK8T+DHxZm154fCuvvJLqYQ/Y7wgsbhVUkrIf74UE7j94Dn5uW9soAjmjaVAqTSQkOrbkCkkBgSvzAjBAwe+CcEHBElFRwmZkJnjjR97ABHLDbuO05IHJXBI7EkZOMkAkooooAKKKjgMzW8TXEccc5QGRI3LqrY5AYgEjPfAz6CgDwP9pr/mVv8At7/9o19AV86fHCwvte+K/hzR5Rd2thdJDaQTyLuiLvLiR0AbBIDIGB2n5R22k/RdABRRRQAUUUUAFfLHxuh/sr4mat9ttoLmDVbSG4gKHbLCyp5SsGxx8yNleVZT2YKyfU9eZ/FPwFoGu3Gm+JtZu47K30x1Goyyu22W0BZvLCrzvLkKCpB+c/eIUUAcJ8ZfE6ah8LPBtleT+dq9/Fb6lMV2jA8ghmZRjbuaQ4wMHa3TFdXH8X/CHgfQdG0Ga8k1e7srKG3nfSVWWJWSNBkOxUMDzjbnoc4PFcZocMvxr+MMmvT223w9pXl5hnKFvLG4xxlcHdvcMzA5AUsu77ufa5/Bfgm1t5bi48NeH4YIkLySSWEKqigZJJK4AA5zQBX8KeOfDPxBsA1hLG8qP5j2F2FE8ZjdSrlMngNsIYZAOOQRgdZXypr+tw+JviDYP8LPDkljeae5Ed1YRCP7QuVAdowoWNAWYEv1VwHwPlH1XQAUUUUAFFFFAEc8K3NvLA5kCSIUYxyMjAEY4ZSCp9wQR2qSiigCvY3X22zjuDbz27NkNDOm10YEgg9jgg8glT1BIIJsUUUAFRwzLOhdBIAHZPnjZDlWKnhgDjI4PQjBGQQakooAKKKKAI4ZGlQs8MkJDsu1ypJAYgN8pIwQMjvgjIByBJRRQAVXvr+z0yzkvL+7gtLWPG+aeQRouSAMseBkkD8asVz/AI4XQm8F6o3iWLztIji824iEhjMm0hlVSGX5iwUAZGSQO9AHk/wXht9Y+KfjjxLZ3W+1MsiQjyyPMSedpFfnBGBEOCP4u2OfeK8D+AmlNqXirxJ4yhto7HT5nktbe0j2lVLuspQYxgIoQfdAO7jGCK98oAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiio5I2d4WWaSMI+5lULiQbSNrZBOMkHjByo5xkEAkooooA8/+M/iT/hHPhrf7Y982pf8S+PK5VfMVtxPIx8gfB5+bbxjNY/w/wDhrpd58GbbSNZT7QurY1JmRuYHdR5bRnaNrBAuc55LDlTitz4mfDOH4i2+nK2qSafPYvIUcQiVWVwu4Fcqc5RcHPrwc8dpYWNvpmnW1hZx+Xa2sSQwpuJ2ooAUZPJwAOtAHjdlq2r/AAd8Y2Xh7WtQk1HwlqjhNOu7mYeZZbdq4YsQAigoGHC4wy4O5D7RPCtzbywOZAkiFGMcjIwBGOGUgqfcEEdq8X/aT1KGLwro2lssnn3F6bhGAG0LGhVgec5zKuOOx6d/WPDUN9beFdIg1QyHUI7KFLoySb2MoQB8tk7juzzk5oAuQ2UUH2fY858iIwpvnd8qdvLZJ3t8o+Zst15+Y5sUUUAFFFFABRRRQAUUUUAFFFFAGfrOh6X4h057DV7CC9tWydkyZ2kgjcp6q2CcMMEZ4NeEeG7G3+Hf7SEmjWcf2bStQiMMLXbH7kkYkURscZ/eoIxnOcEctzX0PXj/AMWNC0638b+FvGV7rljp8enyxfaIrqRt8scUyuPJjRGZ2+dt3YZXoMmgD1i/vrfTNOub+8k8u1tYnmmfaTtRQSxwOTgA9K+bND0mH4q+NdZ8YeKNTktvC1jcBVa8cQhoyx8qAPnYgAK7sHOXHd9w0Pif8UG8apH4K8OaddqL64gXzrkLEboM2Y1VGGQjExOHJU46gA86eifALWJ9Og0/xR4tnOmQSs6abYSO8YyDhlMmFRtzNn5Dxnn5uADQ8Q/HqKe8GleBdIn1q/k+5K8L7DgBjtiGJH43g52425+YVn2/wy8Y/ErUbPWviBqf2SwMTvDp9t8k0AY5VNpUqnYkks+FVW5+77B4e8K6F4Usza6HpkFlG33ygJeTBJG5zlmxuOMk4zgcVsUAU9K0qx0PS7fTNMto7azt02RRJ0UfzJJySTySSTkmrlFFABRRRQAUUUUAFFFFABRRRQAUVHJIyPCqwySB32sylcRjaTubJBxkAcZOWHGMkRxX1vLKsQk2zN5hWKRSjsI2CuwVsEqCV+YDB3KQcMCQCxRRRQB5H4++D1zq3iCPxJ4N1CPRtYd2NyfNeFXJBBkVkBKuckMAMNnPBzu8c1/4d3lp8StL8Ly6z/aep6l5Ul9PDGZWt5JGJk3Ddubav7ws20lTkgda+o9f8Y+HPCyM2taxaWjhA/ks+6VlLbQVjXLsM55APQ+hr5wufE+pa58d7nxJ4G06TU5w4FuksDFWXyVgLuAQVTJyCxXGRuxyKAPb/ih4a8R+MNLsNB0e4tLfTLu4A1SaUZdI1w6FR3G5eQMEnZyF3Gq58R+APhDoyaCt9HFJCjO1tEPOuJpNoOZNowHYFcF9oxjGFHHHz+CfGnicy6n8TfFMejeH2Q+fYW14sSx/vP3atwYsbiCGYuxwoPPKyfDDTfhLq+rXml6NpM+oX1pExNxrEIlFzF5gBdVPyDBKAZRGww4+9QBGnxe8aeObi7svAHheMCBH8y5upFZlVjiNhuKoj9TtJfOD1CnNfTfgLq/iO/bW/HPiCT7ZdOZLiC1AaTlBgGQ/KpU8bVVlAUBTjGPfKKAOP8I/DHwt4L2y6bY+bfDP+nXZEk38XQ4AThivyhcjGc12FFFABXhfirw54r0H45ReNtM8Pya1ZzukcUdvMFIdrcw4c4JQAgtuI24IywJ490qOeNpreWJJpIHdCqyxhSyEj7w3AjI68gj1BoA+eNM+M/ivSLXW77xHPpsk8iMun6W6hZYpxLtZWVBujRAWJExDNtUKfvGuv+Cvgy+skvPG2uyRyaprqedHt6rFI3mMzbTty52tgD5Qo5GSoz/CvwCW08Sy6x4r1SPWAlw8kcGxiLg5BWSYsckk5JTkE4yzDIPql14q0Kx8QwaBeanBb6nPEs0MExKeYrPsUKx+UsWBAUHcfSgDH1X4a+H9Y8c2Xi26ScX9p5ZEcTKkcjoSUd8LuZh8v8XRFBBAweb+Nnw/1fxtpemXGi+XNd6c8g+yswQyrJsyVZiACuwHBxkE85AB9UooA+eNYPxV+LUSaNLof9g6ZHsa4Nyklukr7c5YtlnXepIVVO3cu7OA1e96VBfWul28Gp3sd9eRptluUg8kSn+9syQDjGcHGc4AHAuUUARzzw2tvLcXEscMESF5JJGCqigZJJPAAHOa4/W/ix4I0O382bxBaXTsjtHFYOLhnKj7vyZCk5AG4qD68HEfjf4VeH/Huo21/qct9BdQReSHtJVXemSQCGVhwS3TH3jnPGK+ifBbwNonkP8A2R9vuId376/kMu/OfvR8RnAOB8vYHrzQBx7/ABd8XeN7y6sfhz4b3RwxEy3d6U3puACkAsI0YHdgEvuxnGARXIeOvG3jHTtH1Lwn4i1Lw5r8c20TzW0n7yB9zYTCGP5kaHccocEqGOGC16X8V/Gdj8PPBy6HoMdpaaheo0cFvbfuvssTbt0yhANp3Z28j5iTztIrxzX/AAHrMfwv0vxdrGqWMKxxRW9lp4jSNmtmJZCGTAaQl2cggnaWYtkFaAPo/wCHeqaXqngHRjpF79rt7a0itWdk2OrxoqlXTJ2twDjJ6ggkEE3NA1a+1S/1tLhNN+yWd6ba1ks7vzmYKilhKMfI4Y8jqM4xgB38f+H0dt4A+O+r+C7Ka7l0+8t0WPzQjMZVhWYM7ADACtKBgfxDIPUe+UAeN/FPwB4U8PfDXxHqelaHaW95K8D+aAWMZM6Z8vcSIwQxGEwMcdAKw/g+l/47+JmreP8AVrOCPyIlijMIkRBOUWP5Mkg4jVtwJOPMU45GND9onXtds9HtNHt7LZol9g3F7gN5kituWH/YxtD56t0HCtnM0fxL8Q7TwdpvhHwr4B1LSbuFAv2+6RipIy8jDzkCIXbJ+ZiBkqBnGAD6DorzP4QeEPF/g7S5rPXtQtH090WS2skZpHt5G5cb+Ao9VG4EkkEc7vTKACiiigDzf462Nvd/CjUpp498lpLBNAdxGxzIsZPHX5XYc+vrivILX4leNfFHhfR/BfhfT54Lu1ijhludPdvMkjQqiHP/ACxUfLubdgnuqkg/T7/Y9QiurOTyLmMZguYWw4G5QSjr7qwOD1DDsay/DXg/QPB9vPBoOmx2aTuHlIdnZyBgZZiTgc4GcDJ9TQBz/wAM/hnY+ANLLuY7nWrhALq7A4A6+XHnkID36sRk9AF4P4uw/wBk/F/wj4n1O2nGhQ/Z45LqM8JIkzvzgMeAQ23GWAYKcgke4Jdb9Rms/s86+VFHL5zJiN95cbVbuw2ZI7Bl9aL6ws9Ts5LO/tILu1kxvhnjEiNggjKng4IB/CgDyfxJ+0N4a0zy00O1n1qRsFm+a2jUc5GXXcWGBxtxg9cjFc5Z/Dvxv8Vr2DWvHN/JplhG7rDZeSUlRN4JVIyAEBGQHfc3yLkMMGvY9H8DeFtAvHvNL0GxtrppXmEyxAujMMMEY5KLjI2rhRk4HNdBQBj+GPDGl+EdDh0jSIPKt4+WZuXlc9Xc92OB+QAAAAGxVea8SP7RHEPtF1DEJTaxOvmMDu28MQBuKsASQMg88GixmuLizjlurX7LM+SYTIHKDJ2hiON2MZAJAOQCwG4gFiq6W23UZrrbB+8ijjysWJDtLn5nz8y/PwuBgljk7uLFFABVeG/s7i8ubOG7gkurXb9ohSQF4twyu5RyuRyM9asV86fBjR5vFnxN1jx1LBJbWkNxPLEgYkGact8m7bhgqO2cYOShxg0AfRdFeF/Fn40Q2lu+g+Eb6Oa5lT/SdSt5AywqR92JhwXI6sPu9vm5TsPglot9o3wysRfzyObx2vIYmORBE4G1V5IwQN/bBkORnNAEnj34peFvBs4trxf7Q1m3/ew2cKAtEzIwVmc8R5BIOMttfO0g1ydp8c9Vi8a2Gl694Qu9Js7tFQRNHI90HZiqyKpVS6EjbtC5zkgkjaew8K/CvSPC/jHVfESTSXk92+62+1ZeS13ZMmJCxLlifvEbgMgk5YnzTwA9x8T/AI23vi64TOmaX88EUyhggIZYE2ljtbhpCVyA6npuBoA+h6Kyx4k0VvED6CNUtP7WRFc2ZlAkwwLDA7napYgcgYJwCCdSgAooooAKp6rpVjrml3GmanbR3NncJslifow/mCDggjkEAjBFXKKAMvQPDmkeFtLXTdFsY7S0Dl9iksWY9SzMSWPQZJPAA6AVqVwfxM+Jlj4A0sIgjudauEJtbQngDp5kmOQgPbqxGB0JXU+H+oeJdS8JW03ivTvsWpjCnJUGdNoKyMg/1bHJBU4wVPAzgAGxpmhaPonm/wBk6VY2HnY8z7JbpFvxnGdoGcZPX1NaFFFABRRRQAUUUUAFc345v/E2meGpL7wpY2l9fwOHkt7hWYvFg7tgUgl84OM8gEDJwD0lV7++t9M065v7yTy7W1ieaZ9pO1FBLHA5OAD0oA5f4d/ECz+IOhy30Nv9jureUxXFoZhIU7qwIwSpHQkDlWHOM1ueI7XV7zw/eQaDqEen6oyA21zJEJFVgQcEEEYIBXODjOcHGK8X/ZmgmW38S3DRSCB3tkSQqdrMolLAHoSAykjtuHrXvlAHg958T/iL8PJRb+M/D0GpWo3JHfwnyhMxY7T5igp91WwmxWxgnHOfWPCfjTQvGunNeaLd+b5W0TwupSSFmGQGU/iMjKkg4Jwa3J4Ibq3lt7iKOaCVCkkcihldSMEEHggjjFfOnxE0GL4N+KNJ8SeD737H9q3RnTZS8isqhd/JzujOVyrNuDEFT/cAPo+io4JlubeKdBIEkQOokjZGAIzyrAFT7EAjvUlABRRRQAV4P8evEtxqeo6X4B0dvMurqWN7lEkA3OxxDETuwMk7iGH/ADzYGvbNW1KHRtGvtUuFkaCyt5LiRYwCxVFLEDJAzgeorwv4JaVeeLvGmsfELWDvkSV47fDEgTOPm25YkKkbBArAjDjBytAHtHhXw9b+FPC+naHatvjtIghfBHmOTl3wScbmLHGeM4HFbFY//CWeG/7R/s7/AISDSvt3m+R9m+2x+Z5mduzbnO7PGOua2KACiiigAooooAKKKKAOf8aeLLPwV4XutavE83ysJDAHCNNIxwqgn8ScZIUMcHGK5/4T6x4x8QaHdav4qSCO3vJRLpyrF5biI5zx/wA8/u7CcseSSQVJ4f463GqeI/FvhzwHpsX/AB87bokjcGdmeNWbCllVFWRiRnhiSPlr3igAoorzfxD8ZdC0+8GmeHrefxNq7/cttNy6HADH94Ac/KWPyBsbSDtoA9Ioryf4OfETxB49vNc/taGxW3tvKeMwBkMZcEbApzuU7GbJbIJxyCNvrFABRRRQBX8m4Oo+cbrFqIti26xjlycl2Y8nAAAAxjLZ3ZXbYrPt7jUf7Yu7a5sv9E4ktbuIrs27VBRwW3eZv3nhdu3bzuyK0KACiiigAoqnquq2Oh6XcanqdzHbWdum+WV+ij+ZJOAAOSSAMk1434DPiz4meNIfGmrTz2HhyxlZrGxjmdUkcBkG3BG7G5t0h+9ymNuVUA9wooooAKKKKACiiigAooooAKKp6tHfTaNfRaXNHBqD28i2ssgyqSlTsY8HgNg9D9DXh8/gf42a7cSz3viyOyeFzAgjvngWVAciQLAmMHJ5YBuOQMCgD2y+13R9Miklv9VsbSOOUQO89wkYWQqHCEk8MVIbHXBz0ri9f+NvgjQXaJdQk1OdXCtHpyCUAFc7t5IQjoDhicnGODjm9M/Z20sajLe+IdfvtXkeUSkKnk+Yckv5jEuzbvUFT15yePQNG+HPg7QNh07w7YpIkomSaaPzpEcYwVeTcy4wCMHg89aAPK5vGnxX8fuLLw94fk0LT7pFdb10ZSIWUKT57gAj5twMa78Abc4OZIvht4d8AeF77xR8QJINa1nzTNGklxJ5cs2SyRjIzIznli6kYzkYVifdJJ4YXhSWWNHmfZErMAXbaWwvqdqscDsCe1fOHjHVb74y/E238K6LcyRaLZu4aX/WREoTvuTs4IIwqZODkcrvNAGn8D/Dd5r3ii98fara+TCu6PT0VSse4gofKG7iONB5aqQVwcA5SvoCqelaVY6HpdvpmmW0dtZ26bIok6KP5kk5JJ5JJJyTVygAooooAKKKKAIzPCtwlu0sYndGdIyw3MqkBiB1IBZQT23D1qSs99Mxri6rbzeXI8Qt7pCu4TRrvZMc/Kys7EEcEMwIJ2lLhnhW4S3aWMTujOkZYbmVSAxA6kAsoJ7bh60ASUUUUAV7i+t7Se0hnk2SXcphgG0ne4RpCOOnyox59PXFWKKKACiiigArP1nXNL8Pac9/q9/BZWq5G+Z8biATtUdWbAOFGSccCtCsfxJ4W0bxdp0dhrln9rtY5RMqea8eHAIByhB6MfzoA871H47eFNNS30zwvp13rE+yOK0t7WAwRZ3bREMjcCB0CoRyB64y3tfi78RpVuHuP+EP0gSgpCHeKcrucEnH7xmA6hjGrfKQB1HrmieG9F8OW/kaNpdpYoURHMMQVpAowu9urkZPLEnk+talAHm/hj4P+DvBcUOqXS/a76x/0htQvZNqRFV5YJkIqggsC2SvXdwCPKPh5460bw1L4o8b62YJdb1KV1tLC2Zw7FmEkgI5VIyzx4ZiT+7fGSCD7/47/wCSeeJf+wVdf+imrwTwj4KGpfCu01nw34dtNY8QvqYS6F/NG6LDG4kwqGQBQdkakMN5Dv8AwsDQBoReEviH8ZXg1XX72PR9DkRJLeEBthG1gHjg3ZJJ/idgSHypK4FHwW02HRvjZ4n0u3aRoLK3ureNpCCxVLmNQTgAZwPQV6v4H8WeI9fuLyy8Q+EbvRp7JFVrlmzBPJkhtmQDjIyNpcY6t93d5n8LP+ThfGv/AG/f+laUAfQFFFFABRRUccbI8zNNJIHfcqsFxGNoG1cAHGQTzk5Y84wAASVHIZg8IijjZC+JSzlSq7Tyowdx3bRg44JOeMGSigAr58+J8EMP7RPhB4oo0eZ7B5WVQC7faWXLep2qoyewA7V9B18uadea18Q9S8R+PnuLQXfhpIruytLqIlUiWV5wgeMpkqsbLllO7cfunDAA+n554bW3luLiWOGCJC8kkjBVRQMkkngADnNeT+GvjNN4n+JE+g6Xosl9o7uPIvIgY5Io1XDyyKxwULYx9wgEcMxC1zfiQeMfFnhCPXvGGuaVp/g9MSXdpoj+dJMVkKqFI3qzM+1RmQop2sRlTWxqnijQvg/p3hE+HdKguNE1eJpLu58si7uI1CMkm44y371m2sMfwjYOgB7RRRRQAVl+I9fsfC3h+81rUmkFpaoGfy13MxJCqoHqWIHOBzyQOa1Kr31hZ6nZyWd/aQXdrJjfDPGJEbBBGVPBwQD+FAHzh8PfCeo/FjxpdeNvETwfYIrtTLEiKRcSIFKwhDnEYXYCWySOOSSw7f8AaO/5J5p//YVj/wDRUtesWNhZ6ZZx2dhaQWlrHnZDBGI0XJJOFHAyST+NeT/tHf8AJPNP/wCwrH/6KloAr/F7wxql/q3h7x/4Vg/tWaw8omGH96JEWTzInRU5dSWbdg9CpAwCR7JBI01vFK8MkDugZopCpZCR907SRkdOCR6E1n+GtSm1nwrpGqXCxrPe2UNxIsYIUM6BiBkk4yfU1Y1XVbHQ9LuNT1O5jtrO3TfLK/RR/MknAAHJJAGSaAPK/wBo7/knmn/9hWP/ANFS16B4E/5J54a/7BVr/wCilry/4keKfDXxL+GeqjQ7ye4vtHlS9FsImVyiuYmkKkfNHsdnJH3cru29K7z4S6lNqvwr8P3E6xq6W5twEBA2xO0SnknnagJ989OlAHaUUUUAFRzwrc28sDmQJIhRjHIyMARjhlIKn3BBHapKKAI4YIbZCkEUcSF2cqihQWZizHjuWJJPckmpKKKACuX8Y+P9C8C/2d/bUk6/b5THH5MRfaoxudv9ldy5xljngHnHUV86eJxD44/aZsNJmktJLOxeOH5UEqyLEhnkjcE4JLb4z6dwSDkA+h4JlubeKdBIEkQOokjZGAIzyrAFT7EAjvUlFcf4u+J3hbwXui1K+82+GP8AQbQCSb+HqMgJwwb5iuRnGaAOworxfwx+0Ppesa5DYavpP9kW83yrdtd+aiP2D/Iu1Tz83OOM4GSPaKACiiigCOeCG6t5be4ijmglQpJHIoZXUjBBB4II4xXg4+DnjfSbjUPD2g+JY4fCepvunaRj5ioCoKlQv3ypI+QqHCYYqCFr1T4heLP+EK8F32sxpBJdJtjtoZn2iSRiAPdsDLFRyQp5HUZdn8UdNi8BweKPENpJovno729lLKry3SgjBhHBcHcvJC4zk/LhiAcHdfA7w34O8jxLqeuTz6ZpUS3F5aTQRn7VInO1SxChXbaojIYnO3dzkemeB/iHovj+3vJdJS7iezdVmiuowrAMDtYbSQQdrDrn5TkDjPhGh6PqXxx+JFzq+rQXdpooRgzwsxWFUUBIY3ZWXfl1YjAzl2AGcV6H+zj/AMk81D/sKyf+ioqAOo+LetxaL8OtQR7iCCTUcafG86OyDzflcnYCRiPzGzg/d6E4B8o+GvxM8D+A7NdLig1Wb7ZKZLzU5rWKMqcqqDYjsxjVd5PzEg5wDvwvqfxK+G0PxCTSC95JbvYXGWAxiSF2TzQDg4fauVPIyMEc5HmEVpbfET4paPoXh/To4/B3hZ9rYRJYHAfc7E5+YSsiqMsxIBfHLCgD6HjghheZ4oo0eZ98rKoBdtoXLep2qoyewA7VJRRQAUUUUAFZfiPX7Hwt4fvNa1JpBaWqBn8tdzMSQqqB6liBzgc8kDmtSvnz4qa23j34h2fgG01K0s9LsnM15dzBSiSpG7yMWB6JHuGPl+bcGPAIAD4c+F774peJbnxz41jkns43C2VuyYgmIJ+VQTnykPbozE5JIcH6DrD8IpoNt4atLDw5e2l3p9kgtxJbSpICwAJLFON5zuPTJbPetygAooqOSeGF4UlljR5n2RKzAF22lsL6narHA7AntQBJRRRQAUUUUAFeL/Hn4gW+m6HL4U066zqd5tF4IycwQHkqWBGGfgbecoWyBuXPtFed6B8FfCHh3xKuuWsV3NLG5e3t7mVXigYnIKjbklegLFsdeoBABqfDHwj/AMIX4GstNlXbfS/6Te85/fOBlfvEfKAqZBwdue9dhRXi/jv4529r5+k+C4/7Tv8AypS98ilo7Ypksyrj95hVdt33AMHLDIoA9orh/iJ8NbP4h/2T9q1Cez+wSsT5SBvMjfbvXn7rfIuG5A5yp7WPhr43/wCE98JLqslr9muopTbXKKcoZFVWLJznaQwODyORzjJ7CgAooooAKKK5/wAWeNNC8Fact5rV35Xm7hBCil5JmUZIVR+AycKCRkjIoA8z/aC8TzLYWHgzTkklvNTdJZ440LM0YfEaAbTktIM/Kcjy8Yw1U9B+AuqXmh6daeJ/FV9HYpmV9HtjuSBzuxtcsUDAtkkIerAE53VH8F9C1LxR4x1L4ja7FJl3cWbNu2vI+VYpls7EXMYByPmwDlK7Dx78ZLHwL4ltdHl0i7uyyLLcyg+UI0Y8GPcMSnAboQMjG7O7aAR2/wAAfA0NnJBJb308jxeWs8l0Q6NljvAUBd3zAcgr8i8Z3Fq9t8EE0HbL4S8Y65pF0ZUkkZ2WWOULkgNGoQNyf4iRjIwc12Hg74geH/HFn5uk3W24XcZLKcqs8YBA3FATleV+YEjnGc5A6igDxe48d/EX4eyxr400SDWNIXiTVdOGDyy/M2AFGN+0KyR7iBhuCT3HhH4neFvGm2LTb7yr45/0G7Ajm/i6DJD8KW+UtgYziuwrz/xj8JNC8Ty/2jYf8STXVlWZNRs0IO4MWLMgKhmJJO/hshecDBAPQKK8j+HOv+P7PxZc+DfFOnSX6WiBzqpbAih2kId2398HZQATh8792dpC+sCeFrh7dZYzOiK7xhhuVWJCkjqASrAHvtPpQBJXP+J/G3h3wd9l/t/UPsf2rf5P7mSTdtxu+4pxjcvX1roKy9a8N6L4iSFNY0u0vhA4eLz4gxQ7lbg9QCVXI6MBg5HFAHzhceLb3Uf2g31fwNNHePfvFbw+dHKsUimFEcyKMPsUqWPHGwN2r2f4jfFCx+HiW0U2nXd7eXSF4UUeXEQGAO6UgjOCThQx6ZwGBPnf7O8Nnd+IfFmp2lt9khHlJBbZEnlRyPIwXzGG84CKOoB6kEgY+gKAPC/sXxP+K73MeoSyeEfD4domtzC6SyqVfgg4aQYZVbJRD1Ckgiuo1vw54c+Fnwy1290Wxkt7sWTQC+Q7roySBY1bzCQVG/YxC4AIJAzXpleN/HgTatceDfCqyRwwatqeHnKFmjYFI1IGQCMTMSO+ByKANT4A6Z9g+F8Fz53mf2hdzXO3bjy8ERbc55/1Wc8fex2yfTI54ZnmSKWN3hfZKqsCUbaGw3odrKcHsQe9eL/BDxbrk+s6r4N8QTSPPpVukdtEI4gtusLeU6Fk5Y5aMA/NnaefXP8AgDDc6n4q8VeJEN3b6fO+xbaSR5Vkd3L8ysfneNRjJBJ83JxnkA98ooooAKKKKACiiuT+I3jNfAvg641dY45btnWC0ik3bXlbOM4HQKGbqM7cZBIoA8z+JV9cfE74gWPw70WTy7Wxlaa/u9okjVwvJ+XJGwMyckZd9pxgGvbNK0qx0PS7fTNMto7azt02RRJ0UfzJJySTySSTkmvG/hp4T8U6F4LuPFFha2N74n12WKRJNRnLCO1chjIxA3Fm3F2XdyAp5ZdhsTeEfjdPeW10/jXShJb7tgRiiHcMHcggCv7bgcdRg0Ae0UV4/wDZ/jnof+k/bdD8Q7v3f2TCpszzvzti6Yx94/e6dwf8L3/sr/ka/BWuaN5v/Ht8m/zcfe/1gj6ZXpnr27gHsFFU9K1Wx1zS7fU9MuY7mzuE3xSp0YfzBByCDyCCDgirlABRRRQAUUUUAFFFFABRRRQB4P8AtI6xpZ07SdEKb9XWX7Wr+V/q4CHQjef7zAcD/nnk4+XNz4X6p4c+Hnwat/EmpPJG+p3Eu9ki3vLIhkVIlwOm2NiNxwCzcjNd54x+HmheKLW+ujpNidbe0mit7uRSuJHi2K0mPvbflwSGK4yuDXzR4F0rXfiBqOm+Dvt850S0la9mhMwVYIyVEjLkHLc4UYIDOTgBmNAHoFr8a/iPrfn3Wh+D4LqxErIphsbi42dwrOjAFgCM8DrnAzXr/gXx1pfjzQxf2B8q4jwt1aO2Xt3PY+qnBw3fHYggakMGkeEfDRSCKOx0nTbdnKopIjjUFmPGSx6knkk5PJNeP/s06bNFo2v6ozR+RcXEVuigncGjVmYnjGMSrjnsencA90ooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAOD+M881t8JNeeCWSJykSFkYqSrTIrDjsVJBHcEivP/D0nxBHwY8IxeA4Yy7Pdtdykw7lAnfYo804wcsTgE/KOR39Q+J2mw6r8MvEVvO0iolk9wChAO6Ieao5B43IAfbPTrXiHww0r4ka14OceFPEGm6bpkF6VaOSMJI0q7JNxZYmLA5UctyBtI28UAe3/D/WfE2taNeS+K9Kj03UIL17dYo4mRWRVX5huZtwLFhuBwccV5RokbeH/wBqu+sLGaTyL55muA4Viwkg+0Fc44AkxjHOAASec9X8L/ix/b/hLV77xRPBbSaN5ZuLsR7EeNlwpIBOZCyPkKADlQoycVxmv+IdIb9obwtr3hq5tLxNRS2W4kVy43SloDuGco4iK/KcYIGR1yAfRdFFFABRRRQAUUUUAFfLHwzv7PRPBvxJ07VruCwvptPMEdtdyCKR5BHOpQK2CWBIGBzkivX/AB7F8TtV1gaP4VWx0/SJYvn1QzgSAlWDKeCydsFFJB2neMkL5p4N8I6Rpfx81Dw74lmj1yUW7PDLdwFhcXDokpLKSwJ2NLyxIyM9cUAZ+j+IvFGrfA+88O6N4Yjl03TkkN/qk0qMoj3PMwRHAG8AjkFivBABKkcH4S0e48XeLdE0F3nlhklERCygGKAMZJNm7gYBkbHqTwSefUPhrpuu2vhf4m+FrOH+0b6LbYpbJcBI/McywySKXwBwA3OCwQDjjB8PrWz+Dmo6hqvjm4+w6ncWhistOhcTySx5DMxCZVcsqqhZlBIfPTNAH0XBBDa28VvbxRwwRIEjjjUKqKBgAAcAAcYrm/E/xD8LeEYpv7U1aAXUXBsoWEk5YruUbByuRjBbC8jJGa5PUrT4keLLe7k1PUrTwJo8KMzCGYT3BChG3PKrKFQYc5VlOMhgRzWppvgLw/8ADXQ7vVNC8Pz6vq1tE0kbOyvcyn5uFJACcMQdigkDo5wCAc34q+NOr6f4ai1vR/CF3Dp9y6RQX2rkRhpGBfiFTudCgyHDAZ+mD2nwx17XfEngay1HxBZfZrt+EkwF+1R4G2bb/Du546HG4YVgK5/T/AeqeMPEI8S/EKCAxxbksNAD+ZHaAOpV2dW2ux2ncCCGBGcABF9QoAK8L/aW1KaLRtA0tVj8i4uJbh2IO4NGqqoHOMYlbPHYdO/ulc3458GWPjrw1Jo99JJCQ4mt505MMoBAbGcMMMQQeoJwQcEAEngT/knnhr/sFWv/AKKWpPFfhLSPGejHS9ZhkkgD+ZG0chRo5NrKHGOCQGPBBHqDWhpOmw6No1jpdu0jQWVvHbxtIQWKooUE4AGcD0FXKAPlj4m/C6XwTpOn6rdanBqNr/x4BLezSydW8t2jY7Qwk5Uli3zHAGecr7f8K7X+wfhR4eivriBPMiEqvvwv7+QvGuTj5j5irj+8cDPFbHjHwdpfjjQ/7J1bz1hWVZo5IH2vG4yMjIIPBYcg9fXBHzRp/gzWPFPxKHgmXXZ9T0/RpWjknad1WC3RlR/LV87W4VNoBG4DkqN1AH1vRUcEK21vFAhkKRoEUySM7EAY5ZiSx9yST3qSgAooooAx/FX9u/8ACL6j/wAI15H9s+Ufsvn427s84zxuxnbu+XdjPGa4fwL448Yz6xpvh7xl4Znt7q8tGuYNQiTClFVcmVBkI2evIwzouxcg16hWH4o8XaL4O0t7/WL2OEBGaKAMDLORgbY0zljll9hnJIHNAG5Xz58E5G1z4ueLfEdpDIunzJO26QqGQzTh41IBPO1H6ZAx15GfQ/AXjfWPEHhfU/FfiG1sdO0Rd8tn5Bd5BDGX8xpOTnG3AwASVY7cFc/OHhrVvEsvhLXfDvh+2gW3aJ9Q1O5j2pO1tGoDIXLDMYznaBk7mHRiCAez+N/jBcX+o23hj4cN9v1e4l2Ndxxh0QqTlU3ja3QkucoF5BOcroeDvgdpekXn9r+Jrn+3tWl3PIs674A7gbiQ2TI2d/zN1znaCAaj/Z4Wxb4fXE1vYRwXYvXhurgNua4ICspPoAsm0L04J6sa9Q1WC+utLuINMvY7G8kTbFcvB5wiP97ZkAnGcZOM4yCOCAeCfFe8T4j/ABQ0TwTpI/eWMskU92HVlBcI0mBkZ8tYzkEglgy4BAz9D15/8Pvhbb+CNR1DVrnVZ9W1e9yr3cqlMISGYY3NuZmGSxJ6DGOc+gUAFFFFAHh/7Smp+V4e0PSfJz9pu3ufN3fd8pNu3GOc+dnOeNvfPHnmqWVt/wAJLqcvxVl1LTLm3t4V0/S9KhTBiJIVYW5iWKMAjGeSTzuDA+j/ABkgmuviX8PLe8itJtLlvVQRspZnYzRCUOD8pQr5YA/3s9qk/aR+x/8ACG6Tv8j7d/aH7rdjzPL8t9+3vtz5eccZ257UAemeCzC3gXw81vHJHAdMtjGkjh2VfKXALAAE474GfQV5H+zRfXEmneIrBpM2sMsE0abR8ruHDHPXkRp+XuazPg58L9aun0rxRqOo3enWFtcfarK0QkPOGUBn64RHCoDwS6g9BtJ0/wBnS6+3aj4yvPs8Fv58tvL5NumyOPcZjtReyjOAOwoA6T4r+JFvAvgmy1S0sHvkZNQ1GeVkish5bSRRSOMBTNsK8knZu+VtwrjNA+Inhb4ZaHNpPh3Rp9b1KDY+q6hFIFglbhWZZdpJjV2CLlAp3ZBJbLdHc/s/2Oo+NbrXdT1uS4s7q9lu5bFLbyywdi2zzN5OMkAkAEjOME5HAeL9KsfEHxUg8A+HdH0a2tbd1tIru0j/AHqjZG8ryNvAleMJIMNzy45YggA+k9D1mz8Q6HZavYPvtbuJZUyQSueqtgkBgcgjPBBFaFU9K0qx0PS7fTNMto7azt02RRJ0UfzJJySTySSTkmrlABRRRQBw/wAU/HSeBfCUlxEc6neboLJQy5Vyp/ekNnKpwTwckqDjdkeOfDz4K33jDS7XVvEd9d2WlhNtnbpzLJEdzblLZEaFm3D5TuyxwMhjsfE21vvHfxw0bwdcJJa6fAgMbt8vmoy+ZNIjbDztTYB8w3R9snHv88jQ28sqQyTuiFlijKhnIH3RuIGT05IHqRQB5HrPwF06Kd9S8G6vfaFqaZMAEzNGvyFSoYfvF3Hq25sAt8p4ANB+Jmu+F9ci8M/Eq08iSSV0g10KEgm+6VzhQu35uXGNuVDKDuatj4d/E9/Et5LoHiSx/sjxNDlvsrxtEJkxuBVX+YMFIJUk5HzDIyF7TX/DmkeKdLbTdasY7u0Lh9jEqVYdCrKQVPUZBHBI6E0AaEE8N1bxXFvLHNBKgeOSNgyupGQQRwQRzmq+p6bDqtqlvO0iolxBcAoQDuilWVRyDxuQA+2enWvFzH4r+B9wkomk1rwEbhlMQAM9orkYY8Dad2QMHYxzkIzjHrHh7X9F8b+H4NT09o7q0d1YxyqC0MqEMFdedrqwUj8CCRg0AblFFFABRRRQAUUUUAeX/HzWbzSPhq0Vm/l/2hdpZzOCQwjKu7AEEddgU5yCrMMc1j+GPAul+GfgfrGrxDz9T1Xw/NPNcuuCiPAXES+ijIz/AHiMnooWn+0tqU0WjaBparH5FxcS3DsQdwaNVVQOcYxK2eOw6d9zxD8JLjxn4N8H2F5qf9lXWj6esMyeQJ8uY4gwyHA4MZ6ZzmgDL/Zsv1k8K6zpwMe+C9E5wzbsSIFGRtxj92cYYnrkDALe2V8//sy/8zT/ANun/tavoCgAooooAK87+IHwj03x9rNnqlxqV3aTwIkEixqrK8KszEDPKuS/3skDH3TXolFAFPStKsdD0u30zTLaO2s7dNkUSdFH8ySckk8kkk5JqvrulaLqdkj69bWk9nZObr/S8GJCEZS7BvlICs33uB16gESa5rFv4f0O91e7SeS3s4mlkWCIyOQPQD+ZwB1JABI8At7fxT8bLyTU9fvf7F8G2Uv2gIwKII8MGMblQsjLsIZ2OE3EgYO2gD3PRPDfhnT7j+2NC0vTbd7q3RBcWUSqskWdw27eMHIOR97C5ztGNyvB/wBnd0fUfFken3l9/ZEUsRtbW4KnhzJh3AHEm1FB2kA9wcLj3igAooooAKKKKACiiigD5/8A2Zf+Zp/7dP8A2tX0BXgf7OsLaVrPi/R70xw6hC8KPbmRS2Y2lV8YPIViASMjkeor3ygArwfU5rfxd+1Dptutr9otdFiCSSQyF1DxK8odiv3dsrqhBP3lweuK9s1a8m07Rr69t7SS8nt7eSWO2jzumZVJCDAJySMdD16GvJ/gd4W1KC413xZ4itbuDWr64aLbdWzQNtJEkjhTgEOxH8PHlnB5IoAwL7w9r/gDxH8RPFt5BJHp95ZXaWV7aTruEtxPH5WBuDqVLcnHG04zxnu/gd4euNA+Gts9037zUpTfhMD5EdVCcgnOVRW7Y3YIyKr/ABQ8OeIvF/iHw1pMGk/afDMF3Hc6lL9pjTf8+0rgkONse85U8+Z6qK9MgghtbeK3t4o4YIkCRxxqFVFAwAAOAAOMUASUUVHPMttbyzuJCkaF2EcbOxAGeFUEsfYAk9qAJKKKKACvnzxXMvxS+Oun+GAJH0XR3dLjbGxBK/NNuICsgZlWHOcAgEfewfoOqcek6bDqk2qRafaJqEybJbtYVErrxwz4yR8q8E9h6UAXKK8/+JXwtt/iJ9hlOqz2F1ablVtpmjZG5I8ssAGyB8wIyBg5wu2PwZ4G8X+G9be41Hx7d6rp5fBtbiJpDMuw4O53YxEOxOFzkKuTzgAHolZfiPQLHxT4fvNF1JZDaXSBX8ttrKQQysD6hgDzkccgjitSigDl/BfgDQvAdnPBo8c5kuNv2ieeUu8u0sVyBhRjeR8oHvk811FFFABRRRQAUUUUAFFRmZVuEgIk3ujOCI2K4UgHLYwD8wwCcnnGcHBHGyPMzTSSB33KrBcRjaBtXABxkE85OWPOMAAElRxyM7zK0MkYR9qsxXEg2g7lwScZJHODlTxjBIIIVuHuFijE7oqPIFG5lUkqCepALMQO24+tSUAFfLCeBdLvfjb4h8H6iPs/2zz202SyX5LV2AuIyV+UbRHlSuO+Bjhh9T1nzaHpdxrltrcthA+p20TQw3RT50RuoB/PHpuYDG5sgHk/xEP/AAr74FxeF5tZnvL+4xZ284Plu8fmb2BUuSIxH+7OCR8yjABrrPg5oC+H/hlpS7Y/Pvk+3TMjMwYyAFDz0Ij8sEDjIPXqfMPFV+vxb+NOlaDp5jvdB0x/3siMxjkQENO+9VyA2FjBzgkKQRuzX0XQAUUUUAFFFFABRRRQAVHHCsTzOpkJlfe26RmAO0L8oJwowo4GBnJ6kkyUUAFFFFABRRRQAUUUUAFFRzQrOgRzIAHV/kkZDlWDDlSDjI5HQjIOQSKkoAp6tpsOs6NfaXcNIsF7byW8jRkBgrqVJGQRnB9DXgmnaFrvws+FXjhPEJg8u/22VjbrdgiRnDRvKg91YNjAYiI5CgA19D14H4ojvvi18Xn8HyTXel6LoqNLcRSDDzFWCmVFxjLCRQhYkBSWH3ipANT4Pp4Y8MfDpV13V9DiuNc33EsN1cQgtAcxqjZY71+Vzg9C7qQCDXYWHwt8A/2jba/p2jQed5qXtvPb3Unl7sh1ZFD7NucEADbjtis+x+BXgG0s44JtLnvZFzmee7kDvkk8hGVeOnAHT15rh/Cj3Hgv9oK48FaLeTxeHriUyvZyESDJtTIMEjIwSBkHJCqGLYoA+gKKKKACiiqcEMFzcRaqhuw8luEVJJJUUITu5hYgK/uVDDofSgC5RVd7XdeLcpcTxsMB0D5SRQHwCpyBy+SVwx2qCSBisPxX488OeDLcvrOoxxzlN0dpH888nDYwg5AJUjccLngkUAdJXzR8SvEWl6f8QLHx74L1ixuryKVra8i3bsyou3dtZstG8Z2bkG0bMhtzZr1Ox1rxh4nuJb66gk8G+GbZHlM9yIzezhS4ywkBSFAAGbcpPAwxDEjgP2ZoIWuPEtw0UZnRLZEkKjcqsZSwB6gEqpI77R6UAHhvwf4r8V+Jb3xhoKSeDLDWrd/OllmF1LKJSWZ4V2qyBmVGySpG7chIwBf+K/gfwX4f8CqjX0djqklw10l3cxtc3eozLE25GfO4BmKlm+4GIOBur2y2vEu9sluPMtXiSWK6R1aOUNn7pBycAA5xghhgnnHzJ4N8G2ni3wB4u8a+Ibu71LULa3uIrfz5nJWSOBXEjNnLkZUAHgAHIORgA6OHTPFPx3/svUNSm/srwzb7RNCilfOmG4SPDktv6ABmwE3lQHKsW9s8OaBY+FvD9noumrILS1QqnmNuZiSWZifUsSeMDngAcV5f8O/HltoPwPk1nVFtIvsdxcxwxRyov2mVmaVUCIP3RLORt28Ku7AWu88AeMf+E68LprX9mT6fuleLy5W3K20/eRsDcvOM4GGDDtkgHUUUUUAFFFY994s8N6ZeSWd/4g0q0uo8b4Z72ON1yARlScjIIP40AbFFV96ahp3mWd5iO4izDdW5V8Bh8roSCp6gjIIPoRVfRE1GLR4ItWn+0X0e6OSfyVi87axAfYrMBuADcHv0X7oACx0z7DqeqXSTbo9QlS4aMrykgjWM4bP3SsceBjIIY5IIC09D8HeHPDVxc3GjaPaWc9w7NJJGnzfMQSoJ5VMqDsGFGOAK3KKACiiigAooooA4v4j/ABDsfAGjJLMkkuoXiSrYxLHuUuq/efkYQMyA4Ofm4B5x534F8C6p8RNcHj3x6PNt5MNYae64R0HKkqekIzkL/Hnc2Qfn9s1LSdN1m3W31TT7S+gVw6x3UKyqGwRkBgRnBIz7mrlABXH+KNC0fRPh54r/ALJ0qxsPO0q58z7JbpFvxE+M7QM4yevqa7CvM/jzqUNj8K7y3lWQvf3ENvEVAwGDiXLc9NsbDjPJH1ABl/s4/wDJPNQ/7Csn/oqKvYK8f8Ja0nwy+Beiare6NPL9olElwLRVJ2TSErM7A4/1ewDPfYh29vSPDHifS/F2hw6vpE/m28nDK3DxOOqOOzDI/MEEggkA2KKKKACio4Z4blC8EscqB2QsjBgGVirDjuGBBHYgipKAPC/2hL9tK1nwRqKGQPaXE86+Wyhsq0DcFlYA8d1I9QelXP2iPsd58PNI1GDyJ92oR+RcphsxvFIx2sP4W2qeODgegqx+0J4YuNY8JWWr2cE882lSuZVjwQkDr87kdTgonToCxIwMjn9C8DXniv8AZvs7PTpfPvnu5b6CK6kO1GSR0McRBATIBPzbl3O2cZDKAeweBP8Aknnhr/sFWv8A6KWvI/2ZoJlt/Etw0Uggd7ZEkKnazKJSwB6EgMpI7bh61YuF+JHwz+H2mXK3cmpRJZS29zY+UJm05yHaOZZAhykahQwclRjAJBUpofs3ib/hBdTZpIzAdTYIgQhg3lR7iWzggjbgYGMHk54AO0+J3i7/AIQvwNe6lE22+l/0ay4z++cHDfdI+UBnwRg7cd68M8MaLbfCLxZo2teNJ5Ibx7e7lisLMpK8JChEMmCRiQNKFxwCq5bG7b1fxU8AePvFvjlzYxwXWiSRRw27SyxhLRSVMhw3zK29NxZAWKkLkj5RgaZ4Aew+NGgaX4yv/wC27+9iN3Mr7pY3CpOFDSOQzY8mLgrggkHhfmANfwLo/iD4mfEUfEXUXn0vTLaUfY0ilbMgTgRIT/yz67zgBizgAZbb9AVHBBDa28VvbxRwwRIEjjjUKqKBgAAcAAcYqSgAooooAr/YLP8AtH+0fskH27yvI+0+WPM8vO7Zu67c846ZrP8AEninRvCOnR3+uXn2S1klEKv5TyZcgkDCAnop/KtisfxPomha9oc1r4jt4JtNj/fyGZzGItvO/eCCmBnJBHBIPBNAFizsNHkvDrllaWL3V3Eub+GNC80ZA2/vByy4C45xwK0K8X/Z5v7yPwDqsuoXcCaRaXbeS8shBhwgeXcT8qx8qwx3Lk165purabrNu1xpeoWl9ArlGktZllUNgHBKkjOCDj3FAFieCG6t5be4ijmglQpJHIoZXUjBBB4II4xXifibwL4g+G2onxH8MxO1nL5cd9pAVrjIBGCFOWdTyDg713MQdpO33CigDH8K69/wk/hfTta+xT2X2yISeROPmXnHB7qcZVuMqQcDOK1I54ZnmSKWN3hfZKqsCUbaGw3odrKcHsQe9SUUAFFFFABRVeJLwSqZZ4Hj/eblSEqTlgY8HccYXIPHzHkbR8psUAeD/Gi9+3fFPwPof2CC48qWObZcPiOfzp1Ty3G04X91ycNw3Tjm5qsPxl8WeJbjTAY/D2irceRJc2ki4CA7xIrkiVyVKjK7QTwQnz4xPiZewTftD+G980FvHp/2Mzyz3MSIqrK0rEkt8vyno2GPYEFSfoegDwP4DiHw/wCOvGXhZpJJ54nwk4QKrLBK8bEjJIJMikDnvz6++V8+fDCeGH9onxekssaPM9+kSswBdvtKthfU7VY4HYE9q+g6ACiiigAooqvfW73VnJbxy+V5mFdgWB2EjcFKsrKxXIDA/KSDzjBALFeT/HnxO+k+F7PQoJ/ssmtymKW6bdsigQr5mSnzc7lBAVsrvGM4r1isPxdpWgat4au08TW0c+l26G6l3bsxhAWLqU+YEDP3eSCRzkigDxvw38QNG8IeHpPDPw70fVfE2pLm4muvs7iOR2QBpdgy4UNsXZheB97PzNkaH8X/AIi6r8RbLTjFAWlu1t5dJNr5aJj5X3NtaVNuGYkk7cEkEDbWvqnjpNXvLvwr8HvD0EVxdxK0+p2cC2pKKDuwCq7cbgokcjBYhRkq1d58L/htN4KS/wBR1i8jv9e1FyZ7hcuEXcTgOwDsWJ3MTjJC8fLkgHolFRyGYPCIo42QviUs5Uqu08qMHcd20YOOCTnjBkoAKKKKACio55Ght5ZUhkndELLFGVDOQPujcQMnpyQPUiiCRpreKV4ZIHdAzRSFSyEj7p2kjI6cEj0JoA8H8ImHw9+074g0+WOTfqSTiLa4kAaQJc5Y4XA2q3ABwSBlsbj75Xz/AOKv+KZ/ah0TUv8Aj5/tPyP3f3PK8xWtevOcbd/QZzjjrX0BQBHPPDa28txcSxwwRIXkkkYKqKBkkk8AAc5rzfxR8c/CHh53t7SaTWLsIxC2JVog20FQ0pOMHOMpuxg5GRg6njf4W6P4+1G2vNW1DVYvs0XlRw20yLGMkkttZG+Y8AkdQq+lbHhvwP4a8I+YdD0iC0kkyGmy0khBxld7ktt+UHbnGRnGaAPHPEPxN+KF94am8RaboUehaDG8bC5dVeUqwUD/AFv30ZnUhlj9Bng57/4Qap401rw1NqXi143iuXV9PdoljlaPHJKqANh4KnGT8x6Fa9EooAKKKKAI5IIZnheWKN3hffEzKCUbaVyvodrMMjsSO9SUUUAFFFFAHkfi74033hXxZd6EfBd3OYUMscrXWwzRBSzSqqow2AKxzngKc7SCB2ngXx1pfjzQxf2B8q4jwt1aO2Xt3PY+qnBw3fHYggdRXz/YeGfi34K1zxM2h2FjqjavL5j6m7RIWb52EixmRQrZkOVKlcjAyOoB9AUV80eAb3x7qnxmfSrrxPfS/wBn3c0mogTl4CkbFXCxsQu1mIUYX5dwYAbePpegAooooAKKKKACiiigCukO3UZp/s0C74o089T+8fBc7WGPuruyOTy7cDqbFRyQrK8LsZAYn3rtkZQTtK/MAcMMMeDkZweoBAZlW4SAiTe6M4IjYrhSActjAPzDAJyecZwcAElFFFABRRRQBx/g74a+H/A2o6je6Qk5kvcDE7K/kICTsjbbuCnIzknO1cnIrsK8De++Nvi7xBqdnawyeHrAu0RE0aRxwLhgNk2wvITt+/HnkgjaCMeweEdL1fR/DVpZ65rEmq6giDzbhlAA4A2qcAsBj7zZZjknGcAA3KKKKACiiigAooooAKKKjgjaG3iieaSd0QK0sgUM5A+8doAyevAA9AKAJKKKKACiiigAooooAKKKKACs/XNZs/D2h3ur377LW0iaV8EAtjoq5IBYnAAzySBWhXh/7QGs3l5LoXgnS333WpSiWaAEoXywSFSxIUqX3kg9CinjuAekeAPGP/CdeF01r+zJ9P3SvF5crblbafvI2BuXnGcDDBh2ycfwx8LLPw/4+1jxbNffbbq+lmkt4jbhRbeY5ZsEkktg7Qw28FuDu47DQ9Gs/D2h2WkWCbLW0iWJMgAtjqzYABYnJJxySTWhQAVTGmwpqgv4mkico4ljjIVJmbyxvcY+Z1WJVUnoCR9LlFABXH+Lvid4W8F7otSvvNvhj/QbQCSb+HqMgJwwb5iuRnGa4uPwl8ZdXeax1jxtaWVgX2tcWUaiVgFDBo9iIwG75TllPDcEY3dp4R+GPhbwXtl02x82+Gf9OuyJJv4uhwAnDFflC5GM5oA5e3uvij49ikVbeDwXpEvAlkRpL4rtZWABxj5hnOI2AKlScc7nhT4TeHPDVwNRuEk1jWmfzZNQvz5jeblWLKp4U7l3BuXGT8xrvKKAPL/jt4nt9F+H8+l+fPHf6t+6txDkZRWQy7m7KVO0jvvxgjOPKCfEWlfBe9i0mznsdBu5WvLi6W7juHlSR44o4i6hAq4DK4Xe+5MOsavg/TepaTpus262+qafaX0CuHWO6hWVQ2CMgMCM4JGfc186fFDQLj/hOYfBXgy/vpI76Izy6El2FtIHy0uFUuFXO0ybCBjIKkhgFAOv+GFnqnhX4B61qmfs91NFd6lZkp8yAQgIxVhjkx7h1BUqe9ch4B+I1n8O/hLc48i61m+1CWSztBKG2rsjXzJQpyigqcKcM3GODuHqfgTxZDrz33gu+8I3ekvpNuLea1nYXNusIVURGkIG4spOAQQyqSGNYfiPwR4f+GHw/wDGWraRbTzSahafZdk8inyElby9sbbd20eYCQSd2xcnPNAHCfB34V6R4wFxrmqzSXOl27i3W05iaSby1ZyxVshFL4XBy2MnbjafpeCCG1t4re3ijhgiQJHHGoVUUDAAA4AA4xXB/BL/AJJDoX/bx/6USV6BQAUUUUAFcP4x+E/hbxtefbr+Ce2vztD3dnIEeRVBADAgqeo5xu+VRnAxXSa/4j0jwtpbalrV9HaWgcJvYFizHoFVQSx6nAB4BPQGvF7/AON/i+/sNR17w54ZtE8O6e6xTXN/udizPhfuuoyQyZVd23PJwRQBqfDbRvFPh74i6ppGnJqqeCbTdEP7ZBBZhnDW+AAcybjlRtKHLfNtr2iuT+HPiu+8aeDrfWr/AE2OxeV2RPLl3rMFwrSAdUBcONpyRt6nOa6ygAoqOeNpreWJJpIHdCqyxhSyEj7w3AjI68gj1BqSgAoqOeeG1t5bi4ljhgiQvJJIwVUUDJJJ4AA5zUlABRRRQAVHHIzvMrQyRhH2qzFcSDaDuXBJxkkc4OVPGMEyUUAFfPHxo1Z/HHjnRfA+gTfaJIJSs5jkZoxOxwQ6qD/qlViWGdodxwQa+h6w9J8H6Boes6hrGnabHDqGouz3NwXZ2csxZsbidoLHJC4BwPQYANSwsbfTNOtrCzj8u1tYkhhTcTtRQAoyeTgAda8E1ITfDT4/6fZ+HpI7fTNfe2NxYhCIlWSQxkAZ6hgzqRjbu2gbcg+9399b6Zp1zf3knl2trE80z7SdqKCWOBycAHpXzx4DtP8AhaHxt1LxdNBs02wlS5RCNpLKNlurAPkNhN5IyuUI6MKAPo+iiigCvb2NvaT3c0EeyS7lE053E73CLGDz0+VFHHp65qxRRQAVHBBDa28VvbxRwwRIEjjjUKqKBgAAcAAcYqSigAqvY2FnplnHZ2FpBaWsedkMEYjRckk4UcDJJP41JHPDM8yRSxu8L7JVVgSjbQ2G9DtZTg9iD3qSgAr5/wDin/ycL4K/7cf/AErevVNZ+Iei6D410zwrfpdreaiiNDMsYaIF2ZFVjncCWXH3SORkgZI8g1fV18aftL6RBaWcajSrj7I32p2AkNu8kjuNnIIO7aMkEqu7glQAfRdFFFABRRRQAVT1bTYdZ0a+0u4aRYL23kt5GjIDBXUqSMgjOD6GrlFAHyp4p+F58B6Ncz+IfElpLAHlGk2EUckjXMjLtLldyCIgCJiQXHAB3YCt6H+z34RvNI0O91++WeFtT2LbQuSoaFeRIV3YO4twSuQFyDh+eo1D4S6Prfjk+Jtbv77U9u0xWNyUMKYLfJgKMxjK4X1DFi+416BQAUUUUAFFFFABRRRQAUUUUAfOHh6X/hNP2l7++XTt1jF9oS5huFx+5SA22XRwD8xK5QjI3YPQmuj1j4Na14cuJtU+G/iC7sXLiV9NlnKrIVLMqhujgZChZARydzc1c+GPww8QeD/H2s6zq19BeW80UsEdwZGM1wWeOTzWBzjOGBy2dwPUYY+wUAfOnhWx8X3v7RVvq3iPQpLS7CF7o28TNBEptXjjO8FhhtmM7j8wYdQQPouiigAooooAKjgkaa3ileGSB3QM0UhUshI+6dpIyOnBI9CakooAKKKKAKem6TpujW7W+l6faWMDOXaO1hWJS2AMkKAM4AGfYVcoooAKKKKAK99YWep2clnf2kF3ayY3wzxiRGwQRlTwcEA/hUk8EN1by29xFHNBKhSSORQyupGCCDwQRxipKKACiiigDwv9o3TZoLfw94ks2jgntbhrczxkrPuIEkRDAZwpjkPXgtx1NeweG9bh8R+GtN1mDywl5bpKUSQSCNiPmTcOpVsqeByDwKp+NPCdn418L3Wi3j+V5uHhnCB2hkU5VgD+IOMEqWGRnNV/AHg7/hBfC6aL/ac+obZXl8yVdqruP3UXJ2rxnGTlix74AB1FFFFABRRRQAUUUUAFFFFABRRRQAV5/wDGHxl/wiHga4+zTeXqeoZtbTa2HTI+eQYYMNq9GGcMyZ616BWXq/hvRfED2j6xpdpfG0dnh+0RBwpZdp4PBBHY8ZCnqoIAOD+BfhH/AIRzwMmpXC4vtZ23L8/dhAPlLwxB4JfOAf3mD92vUKKKACiiigAooooAKKKKACiiigAooqvZpcRRGK4fzfL2okzMC8wCjLuAqqrFt3CjHAPGdoALFFFFAHzprfiXxH8avFX/AAhumW8miaXbu8l8lwf3pVHxulXg5BKgRDPznknAK+96Ho9v4f0Oy0i0eeS3s4lijaeUyOQPUn+QwB0AAAAuRwQwvM8UUaPM++VlUAu20LlvU7VUZPYAdqkoAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigCOeeG1t5bi4ljhgiQvJJIwVUUDJJJ4AA5zXgfgSSzuPFF78WfGWpWOnQ3nnnS7eaceYVQeWxULjfsQeWBtLMTnAO0tofF7x1ca3eJ8PfB5nutTuZfKvWtmGMYO6DP6ucgKFIJOXA1PCPwO8KaGlouv+XqutOgnMckpESlGG7ZGMF0BdA2/IPHC7sUAbn/AAu34ef9DD/5JXH/AMbroLDx14T1P7MLPxJpUkl1sEMP2tBIxbG1dhO4McgbSM54xmj/AIQTwf8A9Cpof/guh/8Aia5/W/gt4G1vz3/sj7BcTbf31hIYtmMfdj5jGQMH5e5PXmgD0CivA9T8L+N/hAkms+HNfj1Lw7ao4aw1GYqkKMzH7hYKTnbhkKszNgLgkH0T4cfEqz+IlneNDp89ldWWz7RG7h0+cvt2sME8JzlRjOOetAHcUUUUAFFFFAFe/vrfTNOub+8k8u1tYnmmfaTtRQSxwOTgA9K8P+DGm/8ACX+NPEHxC1KGfzBdsljvuN4jZw29T0J2RtGgyAuG4GQNvrni3wrY+M/D8ujajNdxW8jq5a1l2NlTkZ6hh7MCOh6gEaGlaVY6HpdvpmmW0dtZ26bIok6KP5kk5JJ5JJJyTQBcrzP486lDY/Cu8t5VkL39xDbxFQMBg4ly3PTbGw4zyR9R6ZXifx+nm1a48K+D7OW0E+o3u8iRjujbIiiJxkhCZJOcHOzjoRQB3nwr0z+yPhf4etvO83faC53bduPOJl24yem/Ge+M8dK7Co4IIbW3it7eKOGCJAkccahVRQMAADgADjFSUAFFFFAEc8EN1by29xFHNBKhSSORQyupGCCDwQRxivnzxRq1v441HTfhb4BtvL0K1lU3d3bZMexT8zfeAeNSdxZj+8fbg5wW9n8caDeeJ/BeqaNYXv2K6u4tiTEkDgglWxztYAqevDHg9D4Y3wb0zwTZWur+NPGUmnwSu9rKmm28jFi6ONiy4JwVB3ZjwRle+aAPR7T4l/DfwVZWHhy01qOSC0RYPMtoTKijYW8xnjXaxY9SmTufkD5iPSIJ4bq3iuLeWOaCVA8ckbBldSMggjggjnNfOHgz4bWPxE8SvrkelSaT4Kt38uztif3t2FJyC/3iC24sxJIz5asduV2/jrquseFfGXhjxDpuqTp+6eMWgd1jPlyI7btrDcr7kDLxkRjk8YAPQPi3FrrfDrULjw/qM9ldWmLmUwMEeSFOXUPkFMD5sjk7NvO6pPhTr+p+Jvh5p+qaw0kl5I8qtOyxqJgJGAZQnAAHy8gHKnjGCeg8R6BY+KfD95oupLIbS6QK/lttZSCGVgfUMAecjjkEcVY0rSrHQ9Lt9M0y2jtrO3TZFEnRR/MknJJPJJJOSaALlFFFABUYghW4e4WKMTuio8gUbmVSSoJ6kAsxA7bj61JRQAUVn65qFxpWh3t/aadPqVxbxNJHaQEB5SOwz/TJ9AxwD5H4e/aKsdV1mCwv/Dl3apcOsUclrN9qbezAAFAikjk/d3HgAKc0Ae2UV5n8TvAviPXdU0vxL4V1eS31bSkYRW0ku1G6nKcYDtwrBvlYYBIC8+gaTHfQ6NYxapNHPqCW8a3UsYwryhRvYcDgtk9B9BQBJf2NvqenXNheR+Za3UTwzJuI3IwIYZHIyCelU9Eh0XS7f+wdGNpEmmoiPZwyAtAGG5d4zkFuWy3LcnnrWpXzx8ITceLfjN4h8Vxz3wsY/NcF5gCRK2IYpFycqEUkAcAxrzwMgH0PRVPVdVsdD0u41PU7mO2s7dN8sr9FH8yScAAckkAZJr501/4l+MfihrkOjeCLa+sIYt8oW2ufLmlAyA8kgKhFwQNucbj1Y7cAH0vRWfocOqW+h2UWt3UF1qaRKLmaCPYjv3IH9cDPXC52jQoAKKKKAPE/Engfxp4K8S6l4u8B30l6moXD3F/pjxqSRnzCME/vBu3gbcSAMAuck16J4F8daX480MX9gfKuI8LdWjtl7dz2PqpwcN3x2IIHUVj3Wh+H7fTtYM9hY2lrfxSHUplRYPNQht7SOuD0ZjuJ4yTmgDwzRJJviz8ev7ajMg0XRXSSGRQR+7ibMQyU6vJl9rYO0uAflFe5/wDCK6F/wlH/AAkv9mQf2z5Xk/a8HdtxjOOm7Hy7sbtvy5xxXmekeMPhX8KtLu9O0fUpNQud6vM9unny3JPT96AsRChugYAYb+InOWnxD+J3xAltU8H+H/7GsXwzX8yiVCAxRj5siBCoJGVVS/yHGeRQB7ZqWrabo1utxqmoWljAzhFkupliUtgnALEDOATj2NV9A8R6R4p0tdS0W+ju7QuU3qCpVh1DKwBU9DggcEHoRXjafs/6xrUtreeK/Gk93dLhJkVXnIjDE7UlkbIyCTymASeD39Y8J+C9C8Fac1notp5Xm7TPM7F5JmUYBZj+JwMKCTgDJoA6CiiigAooooAKKKKACiiigAooooAKKKKAI55Ght5ZUhkndELLFGVDOQPujcQMnpyQPUiqeiyXEumCS6sJ7CZ5ZWNvPcCd1BkYqSwZhyMHaCQudo4FSatHfTaNfRaXNHBqD28i2ssgyqSlTsY8HgNg9D9DUlnY29jEY7ePbu2l3ZizyEKEDO5yzttVRuYknA5oAsUUUUAeP+IfiJ4l8PfHKw8PzwwPoV/9nhghwoZhKwUzbhkhg+4bTgFV6AkNXsFfP/7SP+h6j4V1G1/cXy/aMXMXyyDYYmT5hz8pZiPQk4619AUAFFYeqeMvDOi3DW2pa/ptrcI6I8ElyokUuQFymcgfMDkjAHJwOa2IJ4bq3iuLeWOaCVA8ckbBldSMggjggjnNAElFFFABXz/rPxJ8e+NfFD+EfC2kT6FI2RK1yhW5jiIP7yQkfuV2uh+UFgcbWJIB94v7630zTrm/vJPLtbWJ5pn2k7UUEscDk4APSvnDwlrD6Rp3iH4ueIU8zUr2V7TR43iZ0adgSSN3IjULsBD8Ksi9cAgAdT8ceAfH2jaIPGH9s6ze7Ybm0vJpZ7OEyvtjBJbfuxtc4VSARjcGOfY/if42h8E+Drq6S4jTVLhDFYRkjc0hwC4BBBCA7jkYOACRuFeUeHpNN+GaT+NPGpkm8aamk1xa6XhQ+2Rl+dwE/cuW35ORhSwClgVGn4F8C6p8RNcHj3x6PNt5MNYae64R0HKkqekIzkL/AB53NkH5wD0T4V6p4j1rwHZ6l4meN7u5d3hcReW7Q5+UuuAMnkgqMFdh6k12lFFABRRRQAUUUUAFFFFABRRXmfjf4nX3hX4jaD4btdJjvYL5I2mxJiVzJI0aLHkhQQVz8xwc4yv3qAPTKKKKACiiigAooooAKKKKACiiigCOSZYnhRhITK+xdsbMAdpb5iBhRhTycDOB1IBkoooAKKKKACiiigAooooAKKKKACiiigAooqOORneZWhkjCPtVmK4kG0HcuCTjJI5wcqeMYJAAwq1wk5Mm9EZABIwXDEE5XOCflGCRkc4xk5koooAKKKKACiiigAooooAKKKKACo5oVnQI5kADq/ySMhyrBhypBxkcjoRkHIJFSUUAFFZ9xpzx/bLjSngtb+62F5ZomljYrxuaMOuW2/LuBBwEySFAooA0KKKKACvN/i98SP8AhBtDS202aA67ecQo/wAxgj5zMVxg4IwA2ATz8wVhXpFcf4g+Gvh/xR4tsfEWrpPcTWcSxLasy+RIFZmG9duW5cnGcHABBGQQDl/gX4FTw74XTX7oZ1LV4lkAZVPkwZJQKwyfnBVzyP4QQCvPWfEHwYvjPw1NaQSR22qRo32K8O4GMsNroSpzsdcow5GDnBwBXWVzev8Aj/wp4XdotY1y0gnVwjQKTLKhK7hujQFgMc5IxyPUUAeZ+DvDXxa8EeIbGyeaDWNCl8mK4LXhkjtYg+D5ayFGVlTPCgqQRwSBj3CqelarY65pdvqemXMdzZ3Cb4pU6MP5gg5BB5BBBwRVygDH8SeFtG8XadHYa5Z/a7WOUTKnmvHhwCAcoQejH86k0Dw5pHhbS103RbGO0tA5fYpLFmPUszElj0GSTwAOgFalFABRRRQAUUUUAFFFY/inxJZ+EfDl3rl/HPJa2uzekCgudzqgwCQOrDvQBsVy934Is774i2XjKa6nF1Y2gtreBAAnPmhmYkEnIl4Axjb3zgbGh6zZ+IdDstXsH32t3EsqZIJXPVWwSAwOQRnggitCgAooooAKKKr399b6Zp1zf3knl2trE80z7SdqKCWOBycAHpQBYrx/9ox7c+AbKGS8giuP7QSWKByd8wVHVtoAPTeCScAeuSAcPxD8UPFfj2/n0L4baddi3jdkl1FAA0iFDj5mAEAOHIJIY4XG05WsTx58KU8JfDqbWdW1G+1TW3u1dpreFfKR5du8zOwMjrlWAYsPmdflBZsgGpovx9h0zRtB0HS/DF3qM8Flb2jbpxGzzKoTaiqrlgSBg8E56etPxTL458S/CjW9R8bad9kt7W7hvdPJUQSKzSeSYjFgt5YV2ILYbO3lweN/wD8TvC3gv4Q6JFqV95t8PP8A9BtAJJv+PhuoyAnDBvmK5GcZrkPG3x0/4THwhfaB/wAI59j+1eX+/wDt3mbdsiv93yxnO3HXvQB3dx8Sm8H+CvhtFYw2l3BqNvBDczNKuI0iWJJFUllUPliMswClCCO49U1nXNL8Pac9/q9/BZWq5G+Z8biATtUdWbAOFGSccCvnT4k6fDp3wy+HdlrcElhqCJIGSzgASOEhDIXRipM/MZIyAW8zJGQa2/DXwq134hXlh4v8e6x58FxFFLHaR43yw4JVSVwsSn5WwmSdzZ2tk0AdJpXx3sdc8eW/h7TNCu7mzuLjyIr5JPnYY/1nlbchAckktkKCxAI2165WXpvhrQdGuGuNL0TTbGdkKNJa2qRMVyDglQDjIBx7CtSgAooooAK8r0H4uTeJfizJ4Z0vTY59FjSVGvUYs25M/vcrlfKJAUeu5Tu521qfGHxl/wAIh4GuPs03l6nqGbW02th0yPnkGGDDavRhnDMmetcP4F0q8+HXwqHiax8Oz6v4l1nHkJBAZTDCw3R79hJ8vC7zjBJZVOMAgA94orwue9+P81/LcpplpBE6FVtY2tDGhKbdwLOXyD83LEZ6gjijUrL4/wBjcLFb6naaghQMZbVbRVByflPmIhzxnpjkc9cAHcfGPX18P/DLVW3R+ffJ9hhV1ZgxkBDjjoRH5hBPGQOvQ+d+FfE+j/B74X6dc3Nn9q8Q65i9+xrKgdoScRszgHbHsGQDk7nYAD5tvKfEHxf44i0uDwv440+0F2E+1wThYmdt3mR7nC7oyNrSKAoVgQjZyPm7v4TfCbwtPpkXiW6l/ttZZbiO2WeALAY1kaNXMTZJYhC3zHA39MqGoAxNM8I+L/jXqkeu+LJpNJ0eFEFtFHAyiRW2s3kqx4DLyZTuySoAYDC+96ZoWj6J5v8AZOlWNh52PM+yW6Rb8ZxnaBnGT19TWhRQAVXv7630zTrm/vJPLtbWJ5pn2k7UUEscDk4APSrFRzwQ3VvLb3EUc0EqFJI5FDK6kYIIPBBHGKAOD/4Xb8PP+hh/8krj/wCN1zZ+P0Op3CW/hbwfrOsThGeaMgIyKCACBGJCRk4JOMcdc8dppnwr8DaR5v2bwzYyebjd9rU3OMZxjzS23r2xnjPQV1kEENrbxW9vFHDBEgSOONQqooGAABwABxigDxuG6+Ofib7O0dvpXhuHyjKJHRf3uduFZW811YDPG1ccg84Fecaf4YvPH3xQHhmXxdfa/pNhukk1JpS2IwF3+WHdur7U3DcDw2CBXt/xh8Zf8Ih4GuPs03l6nqGbW02th0yPnkGGDDavRhnDMmetZ/wL8I/8I54GTUrhcX2s7bl+fuwgHyl4Yg8EvnAP7zB+7QB0mifDLwX4euPtGneH7RZ96Osk26do2U5UoZCxQgnOVx0HoK6yiigAooooAKKKKACiiigAoqOeeG1t5bi4ljhgiQvJJIwVUUDJJJ4AA5zXjfxI+Olnovm6T4VeC/vni+bUEkDw27HGNuARI2MnrtBxnd8ygA9oorh/hP4r1Txj4Gh1LV7by7pJWg84LtW6CgfvQMYGSSpxxuVsY+6O4oAKKKKACiiigArH1DxVoWla5YaLfanBBqWoZ+zQOTl+wyei5PAzjcRgZPFbFeL/ALRPhhL7wvaeI4IM3WnyiKeRdo/cOcDdnlsPtAAPG9jjkkAHrGuaxb+H9DvdXu0nkt7OJpZFgiMjkD0A/mcAdSQASI/Dmv2Pinw/Z61prSG0ukLJ5i7WUglWUj1DAjjI44JHNeL6h8ZW13wLD4etPD+s6jr2q6Y1vI5gVFlJidJJowm4uA6vwFA4bkYxXEfCjwXqXj5NS0tvEF3YaDbPFLeWkLsRcMzcYTOzOIz8xBwQvB7AHQftC+JNF1y48PwaTqlpfvbJcPMbWUSKgcxhcsuRn5G4zkcZ6jOveap8YPiHqk9rpen3fhXS9iKwuMwFCMtu80oJWJK4/djABUEcknnPGPgzRvC3xf8ACnh/S4p47G8itYrvNw++cSzPFLuYEY3pkELgcnAGa+n6APnzQP2bJi6yeI9cjVA5Bg05SxZdvB8xwNp3dRsPA688e76VpVjoel2+maZbR21nbpsiiToo/mSTkknkkknJNXKKACiiigDn/Hf/ACTzxL/2Crr/ANFNXgHhb4d+KfF/w/0S60PxPY+TbXc0y28+Uk0+YNwI5UDONwAcr8oyVbBJzX0nq2mw6zo19pdw0iwXtvJbyNGQGCupUkZBGcH0NeF+DdS1T4H3l3onjCznfRL2WOW31Kyj8yCOVhhskqGOVXkfeHlZCkNmgDoPB/wSez1y38SeL9bn1bWYZUmRUlZkDpkKWkb55MAIR93G3B3CvYKy9E8SaL4jt/P0bVLS+QIjuIZQzRhhld69UJweGAPB9K8n+LPxZmtLh/CPhF5JtXlfyLm5twWaFiceVFjkyk8Ej7vQfN9wA9Y03xHpGr6pqWm6ffR3F3pjql4iAkRM2cDdjBPysCATggg4Nalcf8N/Atv4D8LxWOIJNSl/eX11EpHmvk4GTztUHaOnc4BY12FABRRRQAUUUUAFRzwrc28sDmQJIhRjHIyMARjhlIKn3BBHapKjknhheFJZY0eZ9kSswBdtpbC+p2qxwOwJ7UASVwY8CX138YH8aalqMbWlnbrBplrEPmAMZV/MO0cbnkIwSTuHIC4PeUUAFFZdr4j0i88QX2gwX0bapYoj3FsQVZVYAgjIwwwy5K5xkZxkVqUAFFFFABRRRQAVn65o9v4g0O90i7eeO3vImikaCUxuAfQj+RyD0IIJB0KKAPD7H4V/EnwlLHD4U8cwNYrEVEd4HVEJYswWIrKg5wdwweW/G4dV+OtrcJEPD+jXiW6NCZRJGFuTkYlP71SD8pIACj5zlemPZKKAPA4I/wBoSa4iieaOBHcK0sgsSqAn7x2gnA68An0BrU+EfxQ8ReJfENx4a12CC9mt4nkN/bNGNu18HftOx1JZVDRj04YEsMT4mfEy+8Y6oPA3gYSXMdw5guLi3PN0e6IegiAzufoQDyEBLep/DfwLb+A/C8VjiCTUpf3l9dRKR5r5OBk87VB2jp3OAWNAHYUUUUAFFFFABRRUc08NsgeeWOJC6oGdgoLMwVRz3LEADuSBQATzw2tvLcXEscMESF5JJGCqigZJJPAAHOakoooAKKKKACiiigAooooAKKKKAI4IIbW3it7eKOGCJAkccahVRQMAADgADjFSUUUAFFFFABRRRQAUUUUAFFV757yOzkawggnuhjZHPMYkbkZywViOM/wn0460UAWKKKKACiiigArxPxb8JPBfhyw8SeKtVm1K8ga3d44JbltyXDucESAFiSzIoLhgOS2/PHtlU9V0qx1zS7jTNTto7mzuE2SxP0YfzBBwQRyCARgigDw/4SePdF8GfC2V9f1ePf8AbZWtLKOYTT7NgIURgkxgusn3toy2SRuBPcfDb4s2PxCuLuxOnyafqFunnCEyeaskWQCwfaMEMwBBHcEE848c+JWjeD/h9Z3vhfRknv8AV737PLPc3whlNnGpdtsbKFaORvkyCCChHqK9r+FXgZvBvhoPfxRprV8kZvfKK7FCArGgCgLkKfmbksxYlm4NAHeUUUUAFRzzw2tvLcXEscMESF5JJGCqigZJJPAAHOakooAKKKKACvM/jzqUNj8K7y3lWQvf3ENvEVAwGDiXLc9NsbDjPJH1HpleX/EvwZ4g8aeLfDFqkUEvha3l82/U3DRnIb5twB5yg2oVGQWfJUHNAHceE7G40zwbodheR+XdWun28MybgdrrGoYZHBwQelbFFFABRRRQAUUVXv7630zTrm/vJPLtbWJ5pn2k7UUEscDk4APSgCva2Gj+HNOn+x2ljpdim6ebyY0gjXA+Z2xgDgDJPYe1eJ/ET4hzfEDw/reg+DtKk1DS7W3S71DVJCY1SND5hCq205yg68na+FON1VLi+8Y/HfUby00qT+yfB8MqK5mX75Bz8xHMkmDv8sEIMJk5wzerv8P7Oy+HV14Q8PXH9lx3ERie7aETO+7AkZwcbmZcrnjGRjAUAAHm/wAEvh/4O1nwvF4gu7X+0tSWWSGeG7G6GBwTgBCAGyjofm3YOMYINWPj7o/h/TrO18To89n4maVI7V7SVUMpUg+Y4+8dijAdeQWQHjGO4+F/gB/h/wCHprK4v/tl1dSieUpuEcZ2KNqqTg4IPz4UsMZHyjFzxp4M0X4i+H2s7iSPzYncWt7Dh2t5VJVh15G5drJn+HsQCADwzxtpmon4EeGdX16b7RqUuoF7dyqqY7eaNnwxU4kZzGshdvny5zznPv8A4E/5J54a/wCwVa/+ilryv49x2Oh/DTw34chmkZ4biNbdZBlnihhZCxIAGfnT0zngcHHqngT/AJJ54a/7BVr/AOiloA6CiiigAoorm/HniuHwZ4Ov9ZcxmdE2Wsb4/eTNwgxkEjPzEA52qxHSgDxjxxJN8VvjTZ+FLQyf2XpTtFPIgI2YINw+dmVOVEYzldyqc/NX0PBBDa28VvbxRwwRIEjjjUKqKBgAAcAAcYrwD4Z+Dr61+DWv+ItJuZIdc1O3lFnLbR+ZKkUROY04DK8jK4ypP/LMj5lrm1+IPjbxx4S0rwVpq+dfXPnQXFybyPz7xEXftbO3Yuw4LMT5m0jJ+cEA+m9N1bTdZt2uNL1C0voFco0lrMsqhsA4JUkZwQce4rz/AMSfEnUvB/xNstH1qztB4b1FEFtfJuR4mJCsZHY7CFbJYDGFZWzng+aeANPTwb+0Mnh7StR+12oie1uZsKfNIt/MdeM7cSp0ByNu0k859/8AE/hjS/F2hzaRq8Hm28nKsvDxOOjoezDJ/MgggkEA4P8AaCvIbb4ZGGW0jne6vYoopGxmBgGfevB52oycY4c89j1nw4sbfT/hr4chtY/LjbT4ZiNxOXkUSOefVmY+2eOK+dPiTa32l+ILTwKfEepato+lp5whFtuktEILkEZHmlIcMGJAAJA2DIH034e8VaF4rszdaHqcF7Gv3whIePJIG5DhlztOMgZxkcUAbFFFFABRRRQAUUV4f8YfjD/ZX2jwz4Zuf+JhzHe30bf8e3rGh/56erfw9B833ADkPF3iLRvH3xaZtY1iC18K6LhcszuLlFdQ4jWNjuaRjjcuP3ahj92u7uvjHrXiXVG034ceGpNTMT4lvbxSsWPn7blCBgoKs7KTyNuaw/hv8Bre406LVvGcU4kl+aLTA5j2oQeZSPmDHIIUEbcDdkkqOw+J+h+KdNs7PX/A+pX1tJpsS2x0e0jLwyRklVZIQCpZd3IKkbQCMFOQDDjj+PWs6pMzTaboUBTcqsIHiBGBtXAlfJ5PPHXkcCtCHSvjhp7m7fxB4f1MRIzfYnjCiY7ThcrEhBz0+ZRnGTjNegeDtcm8S+DtK1m4tZLae7t1eSN4ynzdCVBJOwkblOeVKnvW5QB5XJ8Q/Hnh7S4bvxR8PJDAj/6Xd6depII05Yv5Q3kBVHJZgMjquRVzRvjn4G1fYst/PpszyiJY76ArnOMMWTcirk9WYYwScDmvSK4v4jfD6x8deH7iFYLSLWFRfsl9JH8yFSSELDnYcsMcgbt2CQKAOwgnhureK4t5Y5oJUDxyRsGV1IyCCOCCOc14P4u8GeN/A/jW78W+CZLu+s724N3d2keXO9mOUeIEGVCZG2lRlQT0Khj6h8OfCE3gfwdb6PcahJeTh2lkJYmONmxlIweQgPPuSzYGcDrKAK9hNcXGnW015a/ZLqSJHmt/MEnlOQCybhw2DkZHXFZ/iy+uNM8G65f2cnl3Vrp9xNC+0Ha6xsVODwcEDrWxXF/Fq8hsfhX4gmntI7pGtxEI3xgM7qivyDyrMHHuo5HUAHgnhnUviT8S9OHhSx1ad7ODzJLq8uJnXKOCBHNKMs6n5gE5zuOcqo2+vn4b6F4D+GPiT7DD52pSaJcx3N/JnfL+7cnAyQi5/hH91cliM1H8ANEh074bpqK+W0+qXEkrsIwrKqMY1Qt1YAozDpjeeO59UoA8j/Z1nmm+HN0kssjpDqcqRKzEhF8uNsL6DczHA7knvXrlfP8A+zL/AMzT/wBun/tavoCgAooooA5u68baZYaprVjfW+pW40i3jup7g2UjxPE/8aFAxIBBByB91yMhGIseI9W1Kz8HXmq6DpkmoX624ltrSRGjZs46oQGyASdnDHG3gmtCO1u49UmuDqEklpKmPsskSYiYYwUZQDg/MWDbskjBUAg2JJ4YXhSWWNHmfZErMAXbaWwvqdqscDsCe1AHh8HiP44eKbeJbLQrTRE+0BHuZLcQsoxzlJ2YlPmByqE/LgZ5FRz/AAI1TUdDvr/xL4jvtY8QpaOLKKO43IHXeUQyS5LKSV/uYy3J617h9vs/7R/s77XB9u8rz/s3mDzPLzt37eu3PGemasUAfP8A8ELrXdZ8fXlzqlv5cei6JHo+woIzBtddiMp+bcfLlJJ6HI4GBR+zL/zNP/bp/wC1q98jghheZ4oo0eZ98rKoBdtoXLep2qoyewA7VXs9J03T7i6uLLT7S2nu333MkMKo0zZJy5AyxyxOT6n1oA8Luv8ATv2toILz/SIYNvkxzfOse2z8xdoPTDksMdG5619AV4H8KZ5tV+PHjLVElkvLQpcot2GMibTcJ5QD8jGxPlGei8cCvfKACiiigAooooAKr31hZ6nZyWd/aQXdrJjfDPGJEbBBGVPBwQD+FWKKAPK9b+AHgvUbfbp0d3pM6o4V4Z2lVmI+UushYkAjopXOTz0xseA/hRoPgR2uoGkv9QdEBubqNCYmCsGMWFygbccjceMDJxz3lFABRRRQAUUUUAFFFRyTLE8KMJCZX2LtjZgDtLfMQMKMKeTgZwOpAIBJXz/+0iXs9R8K39tPPDdL9oKOkzDYUMRVlGcK2T94YJwMngY+gK8f+Ofh6916fwoNIaCfV0u5ktrCURnzxsEjPiQ7Sq+UAQQQd4B9CAewVy/jrx1pfgPQzf35824kytraI2HuHHYeijIy3bPckA+b6z8V/iJd6c8OkfDfVdPunyPtE1tNcbAQRlV8tRuBwQTkccg5qv4L+DFnZ6xa6j4+1GC51m8lNxb6Y1yH89gu+Tzd3MrAtlguV+XksGxQBH8K9H1rxz4+l+JurLHZIj7IY4oCI7o+S0LbCzkqFwuTyCcgYwce+Vy+vfELw14Z8Q2eiaxf/ZLq6i81HeNvLUF9i7nxhcndyeAEJJHGeooAKKKKACiiigAooooAK8D+M/xbsZ9Lm8LeHLqO6NygF5fW8uUVDg+WjKcMWHDHkAErySdvAfFTRtesvGMWk6h4ok8S3kj7oYE3mSFpNp2+VysZY9EQngKcAFRXr/gP4IaBp3h+wn8T6VHd65v8+YPcM0cZzlY9qkIwAAyCGBJbkrigDkPB3jr4YfDV7iLTI9Z1a8lRFm1QWyKJBtBKxq7KyJuycEZyOSwCmuv0r9ofwhevbxX9tqWnu6ZlkeJZIom25IyhLsM8A7O4yBzjY1//AIVh4X1mw0zWvD2jWb3yFoZ5NJTyMhlXaZNmAfmz6KBliuVzueJdC8FQaHf3niDStKjsF82e4mkt1BDSYDuGA3eY2FGV+YkLjnFAHSQTw3VvFcW8sc0EqB45I2DK6kZBBHBBHOakrw/9m6+1S40PWrW5knfTLaWIWgdfkR23mRVb/vglc8bs4G459Y046wuvastzZwJpjSq1rcfa3aR/3UQI8ogqq7g/IZeRnZyWIBsUUUUAFV72xt9QgWG6j8yNZY5gNxGHjdZEPHoyqffHPFWKKACio4BMtvEtxJHJOEAkeNCis2OSFJJAz2ycepqSgAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAI4YIbZCkEUcSF2cqihQWZizHjuWJJPckmipKKACozMq3CQESb3RnBEbFcKQDlsYB+YYBOTzjODiSigAooqvvuIIMyJ9pkMuAIFCYRnwCQzfwqQWOedpIGSFoAsUUUUAcXo/wo8F6DrMOqado0cc8SEKsrtMobcrK48wsVdSnBUj7x68Y7SiigAooooAKKKKACiiigAooqOeeG1t5bi4ljhgiQvJJIwVUUDJJJ4AA5zQBJRVexv7PU7OO8sLuC7tZM7JoJBIjYJBww4OCCPwri/iZ8TLHwBpYRBHc61cITa2hPAHTzJMchAe3ViMDoSoB3lFfP9740+Mmh7fFetaTBDoEflyT2OIY12NtQDkmZWJYHnOGPIwCte6aTqUOs6NY6pbrIsF7bx3EayABgrqGAOCRnB9TQBcooooAr2thZ2Pn/Y7SC38+Vp5vJjCeZI33nbHVjgZJ5NWKKKACo4YIbZCkEUcSF2cqihQWZizHjuWJJPckmpK8z+FPxJ1Lxro2sX+vWdpYwae6n7ZFujgZSpLAlyQCgAJO7o44HUgHN+IfCvin4mfFUWuuaZ9g8L6JL8hcHF1GxBO1xgs0gUZwQIxwfmHze4V5foPxu0LVvGV54euofsuLv7LYXcUpuI7xvM2L91fl3fKQeVwTlhgZ9QoAKKKKAI4ZlnQugkADsnzxshyrFTwwBxkcHoRgjIINeV/F/wAAeJfG+o6JHp1/nSFlWO6tflHkEk5uMEjzMKcbc5GPl+82PWKKAK9hY2+madbWFnH5draxJDCm4naigBRk8nAA61534H+FK+CviDrOtWdxGukz2/kWdruZ5EDFHbcxAwFZML94kHk5HPplFAHzB8IrDWNe+Nt1rl1afZ5LSW5utQQxugiklDp5YBzhtznCsc4RupFe5+JfiN4c8JXtzZ6xcyQ3ENkt6kYTJuFZ2QJHzy+5ehxwc5wGK9BZ6Tpun3F1cWWn2ltPdvvuZIYVRpmyTlyBljlicn1PrWf4l8H6B4wt4INe02O8SBy8RLsjISMHDKQcHjIzg4HoKAPH/gXBN4q8Y+I/G+rxSSXm8JBIykxI0mS4QtkgooRQA3CPg8EV6J4l+Fuha5eNqtg0+h66NzJqWmOYn3MGyXAwGyXJY8Mem4Cus0rSrHQ9Lt9M0y2jtrO3TZFEnRR/MknJJPJJJOSauUAeTjT/AIv+FbyFrXUbHxbpkeyEW84S3nKAAs5Y4w3BXcXfO7JU9sd/2iP7PvLqz1vwbfafdRREpC1xlzIQCqurIpRSDndyRx8pzXuFRzwQ3VvLb3EUc0EqFJI5FDK6kYIIPBBHGKAPN/AXxisfHXiW60lLGPTwiM9qZ7rMtyAeyBNoIX5iN5I7bgCR6ZWHZ+DfDOnapBqdhoGm2l5AjpHLb2yxlQ2N33QBnAxnqAWA4Y53KAPJ/jX8SP8AhFNHOg6ZNPFrd/EHWaL5fs8JYgtuI+821lGORy2QQueU+Bnwwm+0Q+L9dtIxAE3abbzKSxbIInx0AAztyDnO4YwpPq/iL4c+HPFPiXTtd1a2knuLFNgiL/uplBLKJFI5CsScDGc4bcOK6ygArj/h34/t/iDoct/FYT2U0EpimifLoD1G2TADcEZHBHcYKk9Rf/bP7Ouf7O8j7d5T/Z/tGfL8zB278c7c4zjnFfJln8MfG76DrC6nHd6ZoulpcXksdw52STxRn7kecMWGB5g+XGcE4wQD67or588H/Ey+8G/BFL3UBJeXj3slno8VwcAxKi/N2ZokbcOM4O1MqMY9n8H6/N4p8J2Gtz6dJp73iM4tnYsVXcQpyVXIZQGBx0YdetAG5RRUc88Nrby3FxLHDBEheSSRgqooGSSTwABzmgCSivE9V+Kfijxre3Gl/C7S5JUtk3XGo3CIpHz4XYJDsAYA/fBYgn5V2k1XTx948+HOs6ZF8QntL/T9WRSssRRWsyGXzM+WnzFVcEqAQeNrcHIB7pXzp8bPHXiqG41Xwne6RaW+i3LoLa68qUtMqGOTKyEhSc43AKducZONx9b+J3i7/hC/A17qUTbb6X/RrLjP75wcN90j5QGfBGDtx3rzf4yf27/wpTw//wAJL5H9s/2hF9q8jG3d5U2M443Yxu2/LuzjjFAHWfBrxdouo+A9G0pb3TbfVIEe3awiYRuxQk7ghOWLLh2YZBJb0IHpleF2XwI8OeJfBWi6lZ3V3pmoXOmW0jsp82JpGVWd2RucnJGAygccdclz8E9e0rw1dWsHxLu4NPit5SbaVXhtdpBLB8SkKhydx2ngk4NAB+zfpd3Z2/ie4mSPyGuIbZZI5UkVpIw5cAqSCAJEO4cHPBNe6V4v+zpo6WPh7Xr1n3XUmofZJAkqyR4iQEFWXIOTK3IJBAGPf2igAoqvf31vpmnXN/eSeXa2sTzTPtJ2ooJY4HJwAelV9E1vTvEejwatpNx9osZ93ly7GTdtYqeGAI5BHIoA0KKKKACo442R5maaSQO+5VYLiMbQNq4AOMgnnJyx5xgCSigArL8Sa3D4c8NalrM/llLO3eUI8gjEjAfKm49CzYUcHkjg1qVj+KfDdn4u8OXeh38k8drdbN7wMA42urjBII6qO1AHlf7N+iTWnhrV9Zl8xUv7hIokaMqCsQPzq38QLSMvA4KHn09srP0PRrPw9odlpFgmy1tIliTIALY6s2AAWJyScckk1oUAFFFFAFe+v7PTLOS8v7uC0tY8b5p5BGi5IAyx4GSQPxqxXi/7SN9bx+DdJsGkxdTah50abT8yJG4Y56cGRPz9jXpnguCa18C+Hre4ikhni0y2SSORSrIwiUEEHkEHjFAG5Xj/AMWdd13w74+8F3Fj4i/s+wupWhlhlUeQmHQPJL8w3rtkHBI27CQQTkewV8qfG7x9pvjLWbKy0uKQwaS88bXLOpWdmZRlNpIKYjBDZ53dBjkA+q6K+f4fit8WJ9DudaTwhYjTbeJZ3nezmQGNuQ6gyguuOSVyAOTgc16Z8MPHE3j7wq+qXFjHaTwXBtZFjkLK7KiMXGRlQS/3cnGOpoA7SivJ/EPxx0vw/wDEUeH5bbzNMg/dXt9G28xynB+VVzlU5DD72c4HyYf1igAooooAKr2crzQM0jbmEsig+Q0XAdgBtY5OAAN3RvvDAIqxRQAV4H8VZ5m+Pvge3aWQwI9m6RljtVmumDEDoCQqgnvtHpXvE88Nrby3FxLHDBEheSSRgqooGSSTwABzmvnT4g+N9Ci+OWh65aSWOr2mn2kcUh8wmFZC0pDb1R87PMR8qGIK4HzDgA7fxp4/13w58ZPD2gWckD6ZqUVtHNBNEDtaS4ZGdWGGDbQAMkr7Vl/GzVbHTvHXw9nurmNEsr03VwB8zRxebCd5UZOPkfHHO046VwnjeLxj4l+Ifhi/1/Tv+Eck1OWK1014X3SQp5oIZgH3CRfOXOdnI6A5xJ8Svh5Y+CdZ8MSq+peILvU7h2voppMNeOrRblTaN6ly7Dqx5HJPUAseLvGWkeKfjd4f1bRbTUtXtNPSAeXZQkSzNG7ykxqwyQuRkEDO1hkDDV9LwSNNbxSvDJA7oGaKQqWQkfdO0kZHTgkehNeB/wDN3n+f+fCvRNf+Mfgjw+jbtYj1CfYHWHTsTlgWx98HYCOSQWBwPcZAO8orxuT4teL/ABI8K+BvAt3NbzPmG/1FGEUiqp3jgqikMCAfMPTGMnA5zxfffFax0Fp9d8SwabcalKI7HRtORTdySeauI0aNdwUKc7g7E/Kp5egD6HorzP4TeGfG+ipfXfjHWLuc3CIsNlPem5MRDNlmJyAcbcbGwQTuGQMemUAFc/448Sf8Ij4L1TXBH5klrF+6QrkGRiETcMj5dzLnBzjOOa6Co54Ibq3lt7iKOaCVCkkcihldSMEEHggjjFAHh/wD8IQzpP46v9QjvtQuHliRCwkeBi3zvIx5Erf+gvkk7+Ok8dfGzw/4ZszDo9xBrGpyxFofs0iyQRHOAZHU/U7V5OOduQax9W/Zw8P3Ussml6tfWG/cVjkVZ0jJZSAPuttC7xgsScqc8ENqWXw++HnwoSTxDqdxJIA8aQzaltmMT7sjykRAd+QDkAkBSRgbqAOT8CfD3WfiHqMHjbx7dzzRnymsrZ0QfaEUggsm3asJ5+UAb9xbgHLM+Iev618VPEq+EPBTSXOiwOi3t1GpEDS5Y7nkGR5Shcr/AHmUkB8JU/it/iH8W7g6Np/h+78P6DE+6VtT3Q+ecsUZ8rkj5R8iBgGIJJ+Uj1TwL4F0vwHoYsLAebcSYa6u3XD3DjufRRk4XtnuSSQDQ8K+Hrfwp4X07Q7Vt8dpEEL4I8xycu+CTjcxY4zxnA4rYoooAz7rVkt9RgsIbae7upNrSJBt/cRE482QswAXIOAMs21tqttbFiZLxry2aGeBLVd32iN4SzycfLtYMAmD1yrZ6cdaJobhry2liuvLhTcJoTGGEoI4IPBVgQMHJGCwIJKstigCvY3X22zjuDbz27NkNDOm10YEgg9jgg8glT1BIIJsVXt12z3Z8qdN0oO6WTcr/Ioyg3HavGMYX5gxxzuNigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKjnnhtbeW4uJY4YIkLySSMFVFAySSeAAOc1JQAUUVGJ4WuHt1ljM6IrvGGG5VYkKSOoBKsAe+0+lAElFFFABRRRQAUUUUAV7Oxt9PgaG1j8uNpZJiNxOXkdpHPPqzMfbPHFWKKjEEK3D3CxRid0VHkCjcyqSVBPUgFmIHbcfWgCSiio5oIblAk8UcqB1cK6hgGVgynnuGAIPYgGgCSiio4ZlnQugkADsnzxshyrFTwwBxkcHoRgjIINAElFFFABXh/7Qep/2j/YHguxh8/U7y7S5Vd23Gd0Ua5I2/MzNzu42c9Qa9ov7630zTrm/vJPLtbWJ5pn2k7UUEscDk4APSvkxta8V3Ot3vxds4I1jg1MQfOBIEDIVCMFA+QRlIy52kl1wdxJAB9V6Fa3lj4e0yz1G4+0X0FpFFcTby/mSKgDNuPJyQTk8mjXdM/tvw9qek+d5P260ltvN27tm9Cu7GRnGc4yKk0nUodZ0ax1S3WRYL23juI1kADBXUMAcEjOD6mrlAHzR8NfiRp3gf4S62Fmgl1sagXtbKXcPM3oihuByo2OTyPugZBZc9n8M/hnfTaofHPjkyXOuXDie3t7gcwHs7joHAxtTogA43ABOA8L+FIfFPx41HS5xH/ZOj3t1MLQ42LDHcNthRCCoQu4yuAMF+5rv4PiL438JeKrDS/Hmj2jWOpXEsNtc6apkkZt42lUViWQb1UKVDkEH5mBBANj486lDY/Cu8t5VkL39xDbxFQMBg4ly3PTbGw4zyR9R3mhaZ/Ynh7TNJ87zvsNpFbebt279iBd2MnGcZxk15f4+/4rD4w+FfCCfvbHT86lqCj99GcchJY+i8IFBbtcDjnDewUAFFFFABRRRQBw/jH4mad4YvP7IsLSfW/ETbSmlWasX2kFiWYK23CgnGC3KnGDuHiHwn+HP/Cf6TdJf+Ip7fSLS7DPpltJl2kMZAlKnKp2Abaxba44xmve59C0DwRb+I/FunaRH/aD2895cuZWLTFQ0jKGbdsDN12jHTg4FeV/s76NFqHh7xYt6nm2N/5VnJHh13KEk3jcAByJB91tw6kDKkgHqHhHRPh/b7W8KW+hzzWWf9ItHjuJot+7rLln5G4cnpkdBXYV80fFrw/oXwz1zQtR8H3E+mav80nkRymUKo4EhLsWG7JXaQVYBhxghvU9c+NHhDw9b2wuL6PULyRFaSDSJFuVjyDn94dqkAjHZuQdoBoA9EqvfX9nplnJeX93BaWseN808gjRckAZY8DJIH414f8A8LL+J3jeD/ijfCf2C1ki3LeS4kyVfDFJZdsR/u7drHhj9MCz+FHxF8fai1x4u1Oezt4pZMPey+cwYlg3kxKdqruRe6qVKldwxQB9JwTw3VvFcW8sc0EqB45I2DK6kZBBHBBHOakrD8JeFbHwZ4fi0bTpruW3jdnDXUu9sscnHQKPZQB1PUkncoAKKKKACiiigDzfSPiv/bfxQufB1noE5htpZopr8zfc8oEMxQKcKXAUEt/EvQnFHxE+MOl+A7yLTorX+1NTOGmt0m8sQIRkFm2t8x4wuOnJxld3nmgyX3hb9oHxfZ28Md1q1/b3rafGp/dtK4F1GrklcDau089T1xzXX/DD4MWPhq3tdY8QwR3WvBxNHGW3R2ZAOAADh3GcljkAgbem4gHaeANa8Qa/4XS+8S6N/ZV+ZXQQ7WTegPD7GJZO4wTzt3dGFdRRRQAUUUUAFRyTLE8KMJCZX2LtjZgDtLfMQMKMKeTgZwOpAMlFABXL/EXR9U1/4f6xpeivsv7iICMeb5e8BlLJu/2lDLzwd2CQM11FFAHyh4M8EeNde8TeHNO1fRr6PRdJlEpj1K1aKBIfN8yRcMuGZySMck8Z+VePquCCG1t4re3ijhgiQJHHGoVUUDAAA4AA4xUlFABXzpquv+Jvjf4luPDOhtHaeF7e43TXSK2HiBwjy5wWJKlljwvJ5zs3L9F1Ts9J03T7i6uLLT7S2nu333MkMKo0zZJy5AyxyxOT6n1oAp+GPDGl+EdDh0jSIPKt4+WZuXlc9Xc92OB+QAAAAHkf7Q1raXl/4UgvdQj0+Bkvj9pkieRVYJEVBCAtgsAuQDjOcHGK90r54/aXvreTUfDtgsmbqGKeaRNp+VHKBTnpyY3/AC9xQBPpcuo/HD4i2mq3OneT4P0SVjGkqrmVuG2NkEOzFULL91U4zkgtc/aW1KaLRtA0tVj8i4uJbh2IO4NGqqoHOMYlbPHYdO/onwu8O/8ACMfDrSLGSLy7qSL7TchofKfzJPmKuOu5QQmTz8g6dBy/xo+Hes+OP7Fm0ebzJLeUwvbylEjiR+Wm3fe42qCo3ZGMAEHcAeieGtNm0bwrpGl3DRtPZWUNvI0ZJUsiBSRkA4yPQVT8d/8AJPPEv/YKuv8A0U1dBWfrumf234e1PSfO8n7daS23m7d2zehXdjIzjOcZFAHl/wCzj/yTzUP+wrJ/6Kir2Cvnj4NfEHw74I8PanofiW5n02+TUGlKS20jdUVCuFBIZTGcggdR15x1/iH9oHwjplmG0fz9Zum6RpG8CLgj7zOuRkE42q3TBxnNAGh8bPFn/CM+AZ7aJIJLrVt9iqSPgrGyN5kgXq2BgegLqTnoT4HeHrjQPhrbPdN+81KU34TA+RHVQnIJzlUVu2N2CMivPNE8LeP/AImeNdP1nxlayWuk2Fx5nkXtt5abQwcwpA2CwbIUuwOQOWYqFP0XQAUVHPBDdW8tvcRRzQSoUkjkUMrqRggg8EEcYqSgAoorxfxbpXxd8UeLb7SrC/g0fSLbE1tdW8zwJMpZggMigyGTBIZeFG0HHKlgDvPFHxJ8KeEHeDVdVj+2KjN9jgBllyFDBSF4QkMMbyoOeuMmvM774+6xrV5JZ+CPCU95ImJN88bzOY8ANmKL7vzEDdvI9snjpPC/wF8KaGiS6qsmtXiur758xxKVYkYiU4IIwCHLA47AkV6ZY2FnplnHZ2FpBaWsedkMEYjRckk4UcDJJP40AfOHiyb4z2Phe41LxFq8+n2ltLHgWzxpJIzEr962XhRnneyqSVxlsCuv+CmjeNbnHibxD4g1VtPmiK2tjdXDS/aFOP3jB87V4+UjDN1yF+/7RRQAUUUUAfP/AO0j/pmo+FdOtf39832jFtF80h3mJU+Uc/MVYD1IOOlfQFeB/FWZm+PvgeAiPYj2bgiNQ2WumBy2MkfKMAnA5xjJz75QAV82fFDw3osPxs8K6Pb6XaWun3iWiT29rEIVcPcurZ2Y5K8Z68Cvou+v7PTLOS8v7uC0tY8b5p5BGi5IAyx4GSQPxr5k8dfEO5u/jLHf+H0tNWgsXht7KDy3liuZVDhWKAje6yTSbGX2IznJAPW/jj4huNA+Gtylqv7zUpRYF8j5EdWL8EHOVRl7Y3ZByK8Y0Px/qk3guy8AeDdEntdTuZVL30F3+/mfO9mG1U29ANzMdsa4JONw2Lb4cfEf4geI9NuvGxvotMk/eSTPLEDAhQHCwhh5bNtVT8nBOWBwa+h9G0PS/D2nJYaRYQWVquDshTG4gAbmPVmwBljknHJoA8/+HPwZ0vwd/p+qmDVdXOxkdov3dqRg/uwerBh/rDg4AwF5z6hRRQAUUUUAFFFFAHn/AI6+GH/Ce65aT3/iG+t9IhiCvpsA4eQb8SAklQ3zgZ2E4GM88cOvhbRvCP7SHhew0Oz+yWsmnyzMnmvJlzHcAnLknoo/Kuo8XfHTwt4c3W+mv/bd8MfJaSAQr908y4IPBP3Q3KkHFeMfE298a6pq2n+MNa0KfQ4z/o9g6bkePy5HddxJ3LJySCQm7blRgUAd38V7+0tfjn4Sk1fU5LPS7G3ivGYo8qoyyyNwi85cxopIHoTnFan/AAuHxT4qn8nwF4KnuYfN2C9vwfLOE3MrbSqI2SMZkORjjLDGp4D+EWgQaNYax4isJNU167T7XcvqQY7HkXLI0THBK5OS4J3ZPHAHqlAHhen/AAN1PxTfza98QdbkOoXSKXhsFjVlIRAu59uzIAKlVUjgHca9M0b4c+DtA2HTvDtikiSiZJpo/OkRxjBV5NzLjAIweDz1rqKjEbC4eUzSFGRVERC7VIJyw4zk5AOSR8owBzkAkrLk8OaRN4lh8RS2Mb6tDb/ZorliSUjyThRnAPzN8wGcEjODitSigAooooAKjnEzW8q28kcc5QiN5ELqrY4JUEEjPbIz6ipKKACvnT9oTX11jxBpPhTTWu57u0ctcW8asVeWUJ5SgfxuFJ6DjzMA5JA+i6+ePhDNo+o/EPxl45vLmC0sYJWeE3wRPK+0ysVYuThGAUpx18wjPqAcv8Rbj4naRZ6fL4s16eP+04pofsVtcBAEQgMJEiAjO4SDu2QcHGMV9N+HNNvtI8P2en6lqsmq3cCFHvZI9jSjJ25GTyFwMkknGTya8T/aa/5lb/t7/wDaNfQFABRRRQAVXurxLPyDKMRyyrEZC6qqFuFzuIzltqADJLOvGMkWKKACo4DM1vE1xHHHOUBkSNy6q2OQGIBIz3wM+goEjG4eIwyBFRWEpK7WJJyo5zkYBOQB8wwTzgkhWV4XYyAxPvXbIygnaV+YA4YYY8HIzg9QCACSiiigCOeNpreWJJpIHdCqyxhSyEj7w3AjI68gj1BqSiigAooqOAzNbxNcRxxzlAZEjcuqtjkBiASM98DPoKAJKKKKACiiigAooooAKKKKAI55Ght5ZUhkndELLFGVDOQPujcQMnpyQPUiiCeG6t4ri3ljmglQPHJGwZXUjIII4II5zUlRySMjwqsMkgd9rMpXEY2k7myQcZAHGTlhxjJABJUYhVbh5wZN7oqEGRiuFJIwucA/MckDJ4znAxJRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAVXhsooLy5ukecyXG3eHnd0G0YG1CSqe+0DPU5NWKy/EcetTeH7yLw7NaQas6BbeW7BMaEkZY4B5C5I4IzjIIzQB5P8VNfvvG+sxfDfwi0d1K77tWl25jhCMpCmTnaFYZbAznaoJOVrrPFfg7S9L+CWreHbTz0sbLT5JoyXy7PGTNliR/E65OAOpAxxix8O/h3b+CrOW7u5vt/iG+y99fuSxYk7iik87c8knljyewXuKAPO/ghqq6n8K9MT7TJPPZPLazb9xKEOWRMnqBG0eMcAYHbA9Erxv9nuRrHRvEXhy6hki1DTdTLXCkqVBZQm0EE5IaF89umCe3slAHzR8FlvNK+Ner6fqNzBPfeVdW1xNLcHdLIsqlmTIzIxKE4ODt3N2wfQ5/2gvBEN/LbIdSniRCy3UdsBG5CbtoDMHyT8vKgZ6kDmuk1P4Z+GtW8XxeJ7m3nF+MidVnby7oGMRhZFPVQoxtGAwJDBgSKP+FV+Bv7H/sv/AIRmx+z/AN/afO+9u/12fM6/7XTjpxQBy/wg8N6w2o63438T2s9trOqytHHDOro0UOQThWYkKSFVVYZCxDBw1eqQzw3KF4JY5UDshZGDAMrFWHHcMCCOxBFZ+peJdB0a4W31TW9NsZ2QOsd1dJExXJGQGIOMgjPsaNF1bQdSSZNC1DTbpI3LyrYzI4VnZmJbYeCzbjk9Tk+tAGpRRXJ+ONS8aafb2a+DdCtNTnkdjcPdTKqxKAMAKXQksSeQeNp45GADpL6yi1CzktZnnSN8ZME7wuMEHh0IYdOx56dKz5tR0LwpZ21rf6xBZRtu8k6lqBLyYOW+eVizY3DucZA6Yrzf/hD/AIteJedc8aQaLayfv1h0xD5kLnpESmwlQGI/1jcqPvfeFzRPgB4L06326jHd6tOyIGeadolVgPmKLGVIBJ6MWxgc9cgFPxX8W9G8RaHq3h/wpp+q6/fXmnyRg2do+yMPmNi2Rv8AlDA8KQcqMjPHMfDjQ/i1pnhe40rR7Cx0W1upWulvdTQiZXyiFQh3EZCfxR9MkHla6/wP48uNP8Uaj4H8SaXoeiSaZFLcJLYSCG2KAK+FQ+qs8hORgA5UEE1Y8M/GvS/FPxAHhqx02f7LL5gtb8vjzSiliTGQCqkK2Dkn7uQMnABnwfAxNa1h9c8b69PqupT+S8yWkS28ZKqAyEgZZcBRlQhwCep49A0TwN4W8OeQ2k6DY280G7y7jyg8y7s5/etlzwSOT046V5v8C4ryx8Q+PdJutRnvfseoIpllY/vJN8yvJgk4ZtiknJJwMk4r2igCu99bx6jDYNJi6mikmjTafmRCgY56cGRPz9jVio5JGR4VWGSQO+1mUriMbSdzZIOMgDjJyw4xkgmjaVAqTSQkOrbkCkkBgSvzAjBAwe+CcEHBABXjmbUEmEYu7QRXGwSNGqmUIwLbQwJ2EhkyQCRkrwVc2I4VieZ1MhMr723SMwB2hflBOFGFHAwM5PUkmSigDh/iV4a8WeJNOsYvCviH+yZIZWadfNeHzQRgHzEBYY5+XGDuyeVFc34S+I2v6X4xi8DePbWManI7C31RGVI7gHmP5cKCGwyhlwc7VK7txr1yvB/HmqeCtL+KsHiXXfEE+rT2HlpbaPptupNq0ZJBkl3gHbIHYoSGyy5yowQD3iivkjxJ8YfGvi/UY7bTrmfTYXlC29npjMsjsSQoLj53YhgMDCkgHaDX0n4cu9T0fwRZ3XjfUbSLUEQteXEjxxxoWc7VJACAgMq8cEjgnqQDyeSCaH9rmF5YpESZN8TMpAdfsRXK+o3KwyO4I7V75XzJ4l+IHhez+OGl+MtE8zULNbcC9MavGzSFZIiQJAORGUOBgHHUEk1634R+MXhbxlrC6TZC+tb6TPkxXcAHm4VmbBQsBgKT8xHUYzQB2mrWc2o6NfWVvdyWc9xbyRR3Med0LMpAcYIOQTnqOnUVcoooAKKKKACiiigAooooA8j8dx/GNPFly3hKaOTRXRGgVRa5jO0BlbzQGzuBPcYYc9h6xAJlt4luJI5JwgEjxoUVmxyQpJIGe2Tj1Nef+KvibN4U+IOlaFeaFdnS75NovY1LtJKxAHlouSwUnDL987gQOBv9EoAKKKKACsfXvCuheJ/sf9taZBe/Y5fOg80H5W7jjqpwMqcqcDIOBWxRQAUUUUAFFef+CfixpfjDXL/RJbOfSdTt5XWG1uzh5kXrxgbZBg7o+cdQWw2PQKAOX8Q/Drwj4qvBeaxokE90OsyM8TvwB8zIQWwFAG7OO2Kr6N8LPBOgail/p2gQJdJgo80kk2wgghlEjMFYEDDDketdhRQBTkvWkSFtPijvA9x5MjrMoSIKxEhY8nKlWXaATuwDtGWW5RRQAUUVnpeW8/iGayAnF1aWkcrHeRGUmdwPlBwWBgPJHAPB+ZqANCiiigAorH8T+J9L8I6HNq+rz+Vbx8Kq8vK56Ig7scH8iSQASPF/+GgNUtfEP9o3nh6ceFb393Zbl2SEI+HlVz8sjcnKA4BCjcMEsAfQFFRwTw3VvFcW8sc0EqB45I2DK6kZBBHBBHOakoAKKKKAPn/4p/8AJwvgr/tx/wDSt6+gK+f/AIp/8nC+Cv8Atx/9K3r6AoA4v4h/DyH4g2+mW9xqt3ZQWdx5skcIDLMpGDweA4HCtzt3Nwc15Z8FNG/sj4yeKrO1SeSx0+K5sxM4zjFwgQMwAG4hGPbO04HFe/399b6Zp1zf3knl2trE80z7SdqKCWOBycAHpXy58KfCHjrUbKfXvB+vWmmolwbeVJ5XAkZUByybGRgBJxu6HOMdaAPquivE7b4oeK/Aes22jfEnTo5LedyItXtQPmQMwLlVGHAynygIyrglSSAfZLG/s9Ts47ywu4Lu1kzsmgkEiNgkHDDg4II/CgCxRRRQAVT1LVtN0a3W41TULSxgZwiyXUyxKWwTgFiBnAJx7Go9ch1S40O9i0S6gtdTeJhbTTx70R+xI/rg464bG0+R6b8DtS1zWW1f4h+IZNTnDnbb2sjFWXcG2l2AKpkuNiKuMghh0oAuaz8etOlnfTfBukX2u6m+RARCyxt8hYsFH7xtp6rtXIDfMOCacPw78f8Ajm9M/j7X5LHSZkbfpWnz4Iw5KKVAMeAcNuJdsBQeeV9Y0Tw3ovhy38jRtLtLFCiI5hiCtIFGF3t1cjJ5Yk8n1rUoA5/Q/BHhvw9FZfYNGsUurSJYkvTaxidsLtLM4UEsRnJ75NdBRXk/j/xj4pvvFD/D/wAI6ZPb6hPEjyarKxRY4WHzumBwoyF8zOQ25VG7aaAOs8Z/Ebw54FRF1e5ka7lTfFZ26b5XXcBnGQFHXliM7WxkjFefyXnxX+JFwYLW0k8GaKHCySS7kuDgoTgkByepG0IpGVLGus8DfCfS/B+oy6zc3k+r67LuL31yMbSxJZkXJIZgcFizE84IDEH0CgD54+Eeh3i/FzWZdM8Tz6ppFh897eIxVL+Z1YKGUud2GaVg/wAwOzP8QNfQ9YfhrwfoHg+3ng0HTY7NJ3DykOzs5AwMsxJwOcDOBk+prcoAKKz7ua9j1jTkt7Wea1l81Ll1kjWOEbcq7A/OzbgFAXjDsW6LWhQAUUUUAFFFFAHN/EDVV0X4fa/ftcyWrpZSJFNHuDLK42R4K8g72Xnt1461454J8LW+ofs567Nq9n5kay3mp2B80jDxweWr/KezK4w3XHTGK7v486lDY/Cu8t5VkL39xDbxFQMBg4ly3PTbGw4zyR9RX8D+IdN+H3wU0O48RXMdq7W8txDbh1aW4V5WdRGufmJWRD/s7vmxzgA8U1iHVNS+H/gCLUrqe7juLu8hslgj86aOANBGIwvG5gyvtXPQqMgcD6/r5s8SfEnTfiD8S/Aa6XZ3cEFjqcRZ7rarOzzRcBVJAACDnPOTwMc/SdABRVexS4js447p/MmTKGQsGMgBIV2wqgMwwSAoAJIGQM1YoAKKKKACiiqb6eFt7S3sp5LCC2dNsdrHGFaNRjyiGUgJjA+XBGBgigCSzdJIGMd59rUSyAyZU7SHYFPlAHyEFPX5eSTk1YqOGCG2QpBFHEhdnKooUFmYsx47liST3JJqSgAooooAKjnjaa3liSaSB3QqssYUshI+8NwIyOvII9QakqnealDZ3FrbFZJbm5fEcMQBbaCN7nJACKCCSfUAZZlUgFiGZZ0LoJAA7J88bIcqxU8MAcZHB6EYIyCDUlFFABRRRQAUUUUAFFFFABRRRQAUVHHMsrzIokBifY26NlBO0N8pIwwww5GRnI6ggSUAFFV7Cxt9M062sLOPy7W1iSGFNxO1FACjJ5OAB1ooAsUUUUAFFFRwzw3KF4JY5UDshZGDAMrFWHHcMCCOxBFAElFFFABRXN614/8ACnh7VIdM1XXLS3vJXCeUSWMZO3HmbQRGCGBy+BjnoDXQQTw3VvFcW8sc0EqB45I2DK6kZBBHBBHOaAJKKKKACiiigAooooA8H+EGsPJ8ZPHVlEmLW9lnuyZYmSRSlwQo2tgrxM2QRnIHTBr3iqcek6bDqk2qRafaJqEybJbtYVErrxwz4yR8q8E9h6VcoA5vxX488OeDLcvrOoxxzlN0dpH888nDYwg5AJUjccLngkV5/HN43+LbzIok8N+CpX2NujK3d9AyhvlJBGCAORhcSEfvMEV2kPwz8NJ40ufFc9vPd6nLKs0Zup2kSB1GMop/AjdnbgbduBXYUAed6V8EPAemJb79Kkvp4H3+dd3DsXO7I3IpCEdsbcEDnPOeg8L+BNA8GXF7LoNrJapeJEssRmaRcoXww3EkE7yDzjgcDnPSVHOJmt5Vt5I45yhEbyIXVWxwSoIJGe2Rn1FAEe77ZBmGWeDbLgt5e1jsfDDDr91tpGQOVOVPIarFFFABRUcwmZAIJI0fepJdCw27huGARyVyAexIODjBkoA8L+LXw5u/EXxN8O3kFtGNP1J4rK7kgdElDKXd3O4cnyVODz/q8Y+6DX0iRdW/aUsrTR4Y/wCy/DNk1iu0sDHFHE0ZDbzliJZdmR1GDzy1eofEjwvfeMfBF5ounXcdvcTPGw804jcK4Yq52sQOMjbg5A5xkHm/ht8Pbf4WaHqmsa3dwS3zRNJczwoWS3gjyxCHbvOQNzcc4UY+XJAMP4SalDF8XPiLpbLJ59xey3CMANoWOeRWB5znMq447Hp39sr5/wD2a9E/5Dmvy2/9yyt59/8AwOVduf8ArickfQ9a+gKACiiub8c3/ibTPDUl94UsbS+v4HDyW9wrMXiwd2wKQS+cHGeQCBk4BAOgnnhtbeW4uJY4YIkLySSMFVFAySSeAAOc15f4o+PXhTQ0eLSmk1q8V2TZBmOJSrAHMrDBBGSCgYHHYEGuTs/hZ8QvHV4ZvH+vT2tiJVc2izLIWKgDKRofKjypYbuSDnKnOT6h4R+GPhbwXtl02x82+Gf9OuyJJv4uhwAnDFflC5GM5oA8os4fi18WYj9quv7D8PXO0sVjMCPGVAIRf9ZKrK5bDNsbkZGBjbsvD/w3+DRsm8Q3Ueoa9K4kjmkty7Rp5igOsQLCMLjO4/Mdr7Sfuj2ieeG1t5bi4ljhgiQvJJIwVUUDJJJ4AA5zXi+h6b8L/iZ8QbnVrVtSuNSt3aaWzuyxgulQgCYhgx2ZdAE3LwoBQDIIB6hP4lhm8HS+ItBt5NbjNuZraG0IDT4/hGeQQc5GCwwRtLfKfDNB8CeKfjJLF4p8T639n0iWVzbwREsQoZVZYkJ2xKdpXcctlASrZyfo+vM/G3xk0jw9cT6LoscmseIt5gjtoYy0cc2VAVyOWOW+6mTlSp2nmgDzj4jeEvDnwu8VeCNU02G7ECXAku1MnmNL5DxMXGcDewY5AIXgYC819F/YLP8AtH+0fskH27yvI+0+WPM8vO7Zu67c846Zr5c+Jml+MJ9LGveP9Qjtbw3Bt9M0mHy3Gw/NIw2vhUUbV3fOxO0NgYNe9/Cw6o/wz0SXWZ5576aJpmlnm813R3ZoyWyc/IV6njpxjFAHYUVTuryaG/sbWG0kmFw7+dIMhYI1QncTjBJbYoXIJ3EjIRsXKACiiigAooqvdXEtv5HlWU915kqxt5JQeUp6u29l+Ud9uW54BoAsUVXe4lSK6cWU7tDny0Upm4+UN8mWwMklfmK8g9sEm+4uNO3xJ9kupIsqtwok8pyOA4RsNg9Qrc44PegAurCzvvI+2WkFx5Eqzw+dGH8uRfuuuejDJwRyKsVTksp31SG7XU7uOBE2tZKsXlSHn5mJQvnkdGA+Ucdc3KACio5IIZnheWKN3hffEzKCUbaVyvodrMMjsSO9SUAFFU57W7e/t54dQkigRwZrYxIyyKEkGAcblJZ0YnJ/1QAAyxNygArH0/xVoWq65f6LY6nBPqWn4+0wITlOxwejYPBxnaTg4PFHivWf+Ee8Jatq4eBJLS0kli884RpAp2KeRnLbRgHJzgc14/8As8+Hre+i1Xxhft9r1NrtreKWYFnjO0NI+4nln8wAnGRg8/MRQB6B4o+FuheKPFGm+IJWns761lV53tHMbXKqPkBYYKsCF+cfNtBH90r3FU9S1bTdGt1uNU1C0sYGcIsl1MsSlsE4BYgZwCcexrw/xX8VfEfjW4OjfDSw1JoFfyrrUI7f5m3llXDciFCMtvYq3+5tOQD3yivD/hfret+HPiZq/gfxXrE+pX0sUb28z3cs6K6p5hRd44yjkknH+rxzkV7hQAUUUUAFFRzmZbeVreOOScITGkjlFZscAsASBnvg49DUlABXg/jbxJ4g+HnxmsL2fXb6fw9qex5orpGaCGIttkRFXgsgCsCoDfMN27JLe8V5H+0PpTXvw+t7+K2jkewvUeSY7d0UThkOCecFzFkD0B7ZABwkNrqPx9+Itzcm4ns/DWnbQEd1Lwxt0CqOPMkKMSTkLjBLbVB1PjJrmgWWjR/DbRNCkll09FuQ0JZVsiq7ycYJkJiaQsScDduJJzjs/gx/Z2g/Be31aX9xC32m9vZfmb7jspbHPRI1GAO3TJrzj4eWv2nwD8R/G2r3EBur20ubNZ3fYTI6F5AV4X53eEDHORgAZ5ALnww8NeJvGGjPq2l+PtZ0i0sb021rZSO1yqIioyA/OiMAGC42YOOmDivouvI/2dYJofhzdPLFIiTanK8TMpAdfLjXK+o3KwyO4I7V65QAUUUUAfPH7S9jbx6j4dv1jxdTRTwyPuPzIhQqMdODI/5+wr3+G/s7j7P5F3BL9piM8GyQN5sY25dcfeX515HHzD1FeV/tD6U178Pre/ito5HsL1HkmO3dFE4ZDgnnBcxZA9Ae2Rxnw31/xB4++KXh2/mgnksdA08wTStKzgEwMhldmPMkjntyQB12FqAPY/idqUOlfDLxFcTrIyPZPbgIATulHlKeSONzgn2z16VzfwB0z7B8L4LnzvM/tC7mudu3Hl4Ii25zz/qs54+9jtk9B8SfB15458Lpo9nqf2DN3FLMxUsskYJDKQCM4yHAPBZFHH3hseFfD1v4U8L6dodq2+O0iCF8EeY5OXfBJxuYscZ4zgcUAXNV0qx1zS7jTNTto7mzuE2SxP0YfzBBwQRyCARgivC/BdzefCX4qz+Cr9t+i6zKr2UrSlyu4ssTYUY3MQI3+UcgHO1Rn6ArwP8AaKhbVdZ8IaPZGObUJnmRLcSKGzI0SpnJ4DMCATgcH0NAHvlFFFABRRRQAUUUUAFFFc34l8feF/CFxBb67q0drPOheOMRvI20HGSEUkDOQCcZwcdDQB0lFU52bUtGlbS7+OJ7m3Jtb2NVmVSy/JIB0cDIOOhrw/xHZ/Fz4f8Ahi81E+NLS70uxcBWkQS3Dq8gVSfMiY5y44LnA4BOBQB7Ja+KtCvvEM+gWepwXGpwRNNNBCS/lqr7GDMPlDBiAVJ3D0rk/hT8Q9S+INvrFxe6VHZwWtwq20kRYq6sCdhJ4LqACSMZ3j5V75/wQ8I2On/Ddby4spDca4jNeR3S5WSIM6xqEIxsKHd0O7eeSMAeiaNoel+HtOSw0iwgsrVcHZCmNxAA3MerNgDLHJOOTQBoUUUUAFFFFABRRRQB4/8AtHf8k80//sKx/wDoqWuk0nwfoHjD4aeE4Ne02O8SDTLZ4iXZGQmFQcMpBweMjODgegrj/wBpO/WPwro2nEx7570zjLNuxGhU4G3GP3gzlgemAckrueMbfxT4Q+GOgXmiXv8ApnhyKD7bbxgvDcxrGI3yNu5lHXqoC7m+8qkAHH/EkJYfGnwBollBBa6Zay200FrBCsaRvJdHeQFA67F/LPc5+gK+dPGOq2Pib42fD7VbO5jS0u7exnjaTnB+0yHyzs3YfcNmOgbqQASPoeeCG6t5be4ijmglQpJHIoZXUjBBB4II4xQBXsNSh1F7wQLJstbg25kIGyRlVSxQg8hWJQ+jI47VcoooAKKKKACiiigAooooAKKKKACq9yLeDdfywb5LeJwHSEySBDgsqhQWOdi/KOpUcE4qxRQAUUVXv7630zTrm/vJPLtbWJ5pn2k7UUEscDk4APSgCxRUcMjSoWeGSEh2Xa5UkgMQG+UkYIGR3wRkA5AkoAKKKKACiiigAooooAKKKKAK8KXCXlz5j77d9rxEsMocYZAoUYXgMCWYkuw4AFFF9by3dnJBDez2UjYxPAELpgg8B1ZeenIPX15ooAsUUUUAeV/HjxbN4d8FJp1nNGl3q7vbsGjLE2+0+aVPQH5kXnnDnHIyNj4VfD+HwJ4aAl8w6tfpHLfFmGEYA4jUAkYXcwyM7iSc4wB538bv+J98U/B/he6+Sxk8rMkXEn7+fy35ORwI1xx1JzmvfJ54bW3luLiWOGCJC8kkjBVRQMkkngADnNAEleN+Ibz4l+N/FU+g6NaXfhnQ7W4aC41Jsq0yhziVGIViCEGFj/vfM21gR6pomt6d4j0eDVtJuPtFjPu8uXYybtrFTwwBHII5FWL+b7Pp1zP9pgtfLid/PuBmOLAJ3OMr8o6nkcDqOtAHh/wh+G/gbxH4LfUrmGfVLqf/AEe7W5zELaRSHKx7TkZBT5txJH93cy17hYWNvpmnW1hZx+Xa2sSQwpuJ2ooAUZPJwAOtfMHw4+Kv/CF+DbjQ9O0efUdbvNQZ7dP+Wfzxoi8DLO29R8gAyD94GuztvCHxU+IVvJN4n8RSeH9PmSTy7KFMMySBTseNCuU2kjEjFhggjkmgD3SivD774CeB9B0OTUdb1/VYobaINc3AkiRM8AlV8tjyei5Y8gcmrH7Ot/rt94e1X+0Lue40yCWKCy86QP5bKh3oufmChTFgfdHbvQB7RRRRQAUUUUAcP8RPibpfw+s4hLH9t1OfBhsUk2EpnBdmwdq8EDg5PAHDEbHg7xjpfjjQ/wC1tJ89YVlaGSOdNrxuMHBwSDwVPBPX1yB44kEPxI/aSu472KO50vRUdGtrpQMrD8mAF+8PPcthjypIP92voOgAqOSFZXhdjIDE+9dsjKCdpX5gDhhhjwcjOD1AIkooAKKKKACiiigAooooAK8T8W+Evip451mXQtRvdNsvDaOrG4tRsjuFDZDGPc0hfDZ2MQmUHOQGPtlFAHP+C/Cdn4K8L2ui2b+b5WXmnKBGmkY5ZiB+AGckKFGTjNdBRRQAUUUUAFFFFAEc8EN1by29xFHNBKhSSORQyupGCCDwQRxiuPu7TwR8LNLv9fXTrTThI7FmhQGWV3wfKjycgEqCEBCjBOAASODsvipqnhH4oa14f8d6tBPpq8wXFtZ4WAkCRBhfm2lGwQd5DBecZY4Fho2qfHzxRquq32oT2Ogafvh09fJ+6WB2ADO0sMK0nzZOVUYBUqAdP8JvE3in4kf8JGviUwXHh6aI25jWExbWfOY42UDKhCd2WLDMZzySes8G/DPwz8PLe5vwY57hHkmOo3oUNbxANgBuiAITuYY3ck4GFXpPC3huz8I+HLTQ7CSeS1td+x52Bc7nZzkgAdWPauf+Ldj4g1L4dahaeHI/NuJMC4jRmErwDl1jx1Y4AKn7ylgASQKAPJLKG++O/wATZL6UyReF9LeMm3nk5WIn7gCkHfKUYlgflHG47VB+j4IIbW3it7eKOGCJAkccahVRQMAADgADjFfOnhL40aX4T8F2OmaL4PnlmtojJqbifYm/Kp5xcKxO4lc7gu3KqMjFe3+C/FKeM/C9rrkVjPZxz5ASZlbJU4YqVPKhgwBIUnbnABFAHQUVn6KbL+zBHp8881vDLLDvnmklfekjI4LyEscOrDknpxxitCgAooooAKr2c1xPAz3Vr9mkEsiBPMD5RXYI+R/eUK2O27B5FWKr2E1xcadbTXlr9kupIkea38wSeU5ALJuHDYORkdcUAWKKKKACiiigAooooAp6bYLYW7AiNriZzNcyorDzZSBub5mYgcAAFjtUKoOFFXKp2dzM9xdWtxHIJIX3LJ5JSOSNiSm07mBIA2tyDlc7VVlzcoA8/wDjb/ySHXf+3f8A9KI6r/BWH7P8HNNlsraA3Uv2iQhj5YlkErqu9gCeiqu7BIAHBxirHxt/5JDrv/bv/wClEdYfgPX18Lfs4Qa0zRh7W3umi8xWZWlM8ixqQvOC5UduvUdaAOU0H4aeIPinrF54l8eXN9pqiXyUsvszRSbAuQIxIMJGCwxw24784PzH3fRtD0vw9pyWGkWEFlarg7IUxuIAG5j1ZsAZY5Jxya8j+G3xnsZNLhtfGXiKOTVLq4cpIbby0gT5FRJGVFUEku27kAfeYcCvbKAPC/ibps2mfHPwPrdq0duNQuILd2hJSR2SUK5fAGQY5UTqcgEHgDPuleJ/HWO+m8VfD+LS5o4NQe9lW1lkGVSUvBsY8HgNg9D9DXtlABRRRQBGDN9odWjjEARSjhyWLZO4FcYAA24OTnJ4GOZKKKACqeq6VY65pdxpmp20dzZ3CbJYn6MP5gg4II5BAIwRVyigDL8OaBY+FvD9noumrILS1QqnmNuZiSWZifUsSeMDngAcV5H4m+As8uomLwjq/wDZeiX3ljULGaaVlGwjDKOfM7sFcjDdGwfl9wrhx8W/Bz+LYfDsWp+fcTbFjuYF82B5XYBYg65y3IOcbR0LAgigDqND0e38P6HZaRaPPJb2cSxRtPKZHIHqT/IYA6AAAAaFFFABRRRQBHPBDdW8tvcRRzQSoUkjkUMrqRggg8EEcYrL0HwroXhj7Z/YumQWX2yXzp/KB+Zuw56KMnCjCjJwBk1sUUAFFV7+G4uNOuYbO6+yXUkTpDceWJPKcghX2nhsHBweuK8X+G3jLxTo3xAf4deLZvtsyeb5N0WMsm7aJVzIW5jKBiMgsCyg4AwAD2i6tftXkEXE8DQyrKrQvjdjgqwOQykEggjvkYYKwjvNJ03ULi1uL3T7S5ntH320k0Ku0LZByhIypyoOR6D0rL8WeNNC8Fact5rV35Xm7hBCil5JmUZIVR+AycKCRkjIrk/Bnxm03xr4sfRLHRdSjRk3w3LBWAUKS5lAP7sbsKCC2SwzjOKAPTKKKKACiiigAooooA+WPh94v+J1/o+oaF4Ui+3SCU3ct7cEPJAXYEgPK3l/MQxwQSSznrkiPWfAM3hT4oeDrPVpZNTfU7i2lvridC8U8zXBEqAsPnAUpndkndkgbgBY+FfxTh8E+GrzR10u71PULq9ElnawIFDswRCDJkkk7RhRGeR1O75ez+KOs2d/8TvhxYxvsv7XUIpbq0Ygvb+ZJAVVypK7vlPAJ7HoQSAe2QQQ2tvFb28UcMESBI441CqigYAAHAAHGK8L/aJ8Sa7YwWmgRxwJompRCR5toMkkkb5ZOvCjMTdASe+MiveK+cPj94l8P61qPhqKx1GDU47XzpLpbG4U4RzGABIAyhjsbsccEjBGQDb0j4T+L/E3hrS4vFHjG7tNPW3gjGkW8TKBbqFZUcHaBKOmWRyCo5OMDpNE8e3mv/FCDQfC4+2+FtNtGi1C9cGTMmDsYSsxL8qqg8lsyN8wAYcnMfGnxtvQYI5NK8BNcKCHdY3uEjcbjkBiznJIH+rBQDJZcn2Twx4Y0vwjocOkaRB5VvHyzNy8rnq7nuxwPyAAAAAANiiiigAooooAKKKKAPn/APaa/wCZW/7e/wD2jXvk8EN1by29xFHNBKhSSORQyupGCCDwQRxivJ/ih8Jta8eeJbC+tdejjsEQRyW90Di2GRuaIKMMWHJDEHKgbsYC+uUAeT6d8BtC0jxzY6/ZX04sbWX7QNPmUviQElNsgYEKp2EAhidvJOa9YoooAKKKKACiiigAooooAKKKKACiiigAooooAKjnghureW3uIo5oJUKSRyKGV1IwQQeCCOMVJRQBHCZmQmeONH3sAEcsNu47TkgclcEjsSRk4yZKjmkaJAyQyTEuq7UKggFgC3zEDABye+AcAnAMlABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFAHifjTTYdV/aX8H287SKiWSXAKEA7onnlUcg8bkAPtnp1r1DxpBNdeBfENvbxSTTy6ZcpHHGpZnYxMAABySTxivL/GmpQ6V+0v4PuJ1kZHsktwEAJ3SvPEp5I43OCfbPXpXtlAHif7Ouq2MPgXWoJrmOJ7O9a6uDJ8qxxNEoDljxj92/fjbzjiuYvdf8V/HTxBe6HozSaf4biQyFXUbQVDGIzsOSXcD5QSBjIVihY3Pid8J9Y0bUb3XPA8E6afeReTeWFhI/mfOTvVY1HMJwmUBOCTwFHHX/APXtHv/AAW2j6bZT291p21715AhE0kpf5gy4J4THzDIAVctjNAFP9nKCFvAt5cNFGZ01OZEkKjcqtFAWAPUAlVJHfaPSvVNc1i38P6He6vdpPJb2cTSyLBEZHIHoB/M4A6kgAkfKnw48dal4G0vUxpekSXuoavcW8FkXiZoi0e7euFILv8AvYwFH98E9g3o+j/FvxX4SuNNsfiTo0kdvfIJItRWMJIqEnJdF4JXKAqArKOSrEjIBQ1LT/F/xy8QKrwXegeErdBNavdQN+93A7JAuQJXYeh2opODk/P7f4c0Cx8LeH7PRdNWQWlqhVPMbczEkszE+pYk8YHPAA4rUooAKKKKACiiigD5o8KeIE8H6t8R3ubv7Z4pmuzY6dLDaqZ7i5aSYFkj5wpcIzDpwo5O0Ht/gD4t1TxFoeq2WrahPezWEqGOScbnCSbjgyFiX5VuCOPUggL3HiH4deEfFV4LzWNEgnuh1mRnid+APmZCC2AoA3Zx2xXlf7NkizXHi2VIY4Ed7VlijLFUBM3yjcScDpySfUmgD3yiiigDH17xVoXhj7H/AG1qcFl9sl8mDzSfmbueOijIyxwoyMkZFakE8N1bxXFvLHNBKgeOSNgyupGQQRwQRzmvL/j/AKJDqPw3fUW8tZ9LuI5UYxhmZXYRsgbqoJdWPXOwcdx0Hwl1KbVfhX4fuJ1jV0tzbgICBtidolPJPO1AT756dKAO0ooooAK83+KfgzWNYs5PEHh3XdVstXsLRkjtrWd1SaMEu6qE+bzGwvqG2ICOhG5rnxG8OeGvFVt4f1m5ks57i3WeO4kT9x8zlApYHKnKk5ICgDkirHj/AFq+8PeA9Z1XTIJJry3tyYtgyYySF8zGCCEBLkEYwpzgc0AZ/wALfGbeOPBUGoTxyLeW7/ZLtm24klVVJdcADDBgcYGCSOQATc8deOtL8B6Gb+/Pm3EmVtbRGw9w47D0UZGW7Z7kgHl/glo154U+Gs8uuJ9g867mvGS5BiaCMKqEyBwNv+rLem0g5rj/AApbXnxn+Itx4k1lfM8MaRKUs7GeIhHznYuAdpYYV5Mk5+VcFSMAHMfCbWdY8Q/HSLV5nneS7+0S3vlFyixmNtqtknEYbywoJwMIOuK9r+IHxV0XwJb+UTHqGrFwo0+KYKyDAJaQ4OwbSCMjLZGBjJHgGj+PdS0Hxf4pvdP0GRPEmtXEkNsHZmeyaSYs8flbf3j7toGQMFBwQSp9X+GHwgXTri18W+J55L7WJ0F0lvOjf6NKxLF5C3LyjI6gbW3dSAwALnw7+LN/r3iOXwv4r0r+y9bOXt0SCSMMAm8oyOSyttBYE8EehA3esV88eErn/hL/ANpzUNZsmgFrZec5ZZd4ljSMWyshAwdxZW9ME8nHP0PQAUUUUAeV+Lfghpvi/wAby+ILvWLuCC4RRcWsUa7iypsBSQ8KMKmQVboeRkY9I0rSrHQ9Lt9M0y2jtrO3TZFEnRR/MknJJPJJJOSauUUAFFFFAHhfx+12+vb3RfAmlpIZ9QdJ5VDbBMWcpFHncBjcGJDDGQhzwa9c8K+Hrfwp4X07Q7Vt8dpEEL4I8xycu+CTjcxY4zxnA4rxf4g6HrPhP4r3HxIl0SDVNCt/JlINwibX8tYVyGBIYPtYEKf4TkHONz4afFnxN478YzWT6DaLo6I7yTwlg1qDkx73Y4cnG3ACk8sMBSKAPXPtWNR+xm3nGYvNWbZmNsHBXcOjDKnBxkN8udrbbFFZ+hf2j/wj2mf2v/yE/skX2z7v+u2Df935fvZ6celAGhRRRQBHIJi8JikjVA+ZQyFiy7TwpyNp3bTk54BGOciSiigAooooAKKKKACiiigCOSCGZ4Xlijd4X3xMyglG2lcr6HazDI7EjvUlFFAHlf7QV+1n8MjApkxe3sUDbWUDADSfNlSSMxjoVOcc4yp5uzsbjUP2SWhtY/MkWKSYjcBhI7xpHPPoqsffHHNdR8etM/tD4dRzyTeVa2OoQXFyyrucRndEdi5AZgZQcEqDg8iq/hDxdoXgrQ/APhFmne61e0juC7gkQ+fudckLzulbYB2HLEY+YA8stdM8KWv7O99q4jjl168vUtDNLEWaKVZA4jjO35AYQWJB53EEnhR3bfFC80zwz4I8N+CoLHWddudPgSeEsZBBtiUbW2kANkMTlhsCEsMEEemar4C8M6t4auNAfSLS2sJn83baQrCY5cYEq7RgOBxnHI4OQSK5v4d/B7S/Ad5LqMt1/ampnKw3Dw+WIEIwQq7m+Y85bPTgYy24A5/4uX1vJ8U/hvYLJm6h1BJpE2n5UeeIKc9OTG/5e4r2ivA/Fwh8S/tO+H9OikkgfTUgMrsgIZog91heehUquT0OeDjn3ygAooooAKK8z1r4k6l4X+KkOh6/Z2lp4ZvUAs9Sfcp3bFyWbJXAfKkELtDKxOOT6ZQAUUV438TPGk3iXVB8NPCTxzanfOYb6684okCr8zxhh1O1Tv64GVwzEhQCPxP4n1T4pa5N4K8FT+Vo8fGr6yvKFDwUQjqpwRwf3mCAQgZjxGlnw14T+Pep3unzwWeiaDFO/kyTMHldYPLeKLzDl5DIzYBPODg4xXt/h3w9pfws8A3UcTT3ENnFLfXkoHzzuqZcqpOF4QALnsMknJPzx4E8LW/jHUdX8YeMb77PoVlKbi/nZSn2qVyWKKVGBkkZC/N8yhRlgQAdpB+0VqV14xigt9Bjm0WW4EUcEaM166n5RjDbS5PO3H+zn+KvoOvnj4f6S/xL+KE/i+WH7PoGjSolha+WyKAgPkRoAdq7MLIwBxuI+XD8fQ9ABRRRQAVX+32f9o/2d9rg+3eV5/2bzB5nl5279vXbnjPTNWK8v8f/AAmfX9YfxP4a1WfSvEp2Ay+eyRuAuwncoLI2zAyOCFxj5i1AHpk88Nrby3FxLHDBEheSSRgqooGSSTwABzmvD/Evxa03W/EEFn4C0CPVfEsqG3tNWltVVocjOY943EANJndtVcEncuayLzwF8XvH9xa2Hi27jtdPgfzfMleDaCSASEg++4UnG7A6jcM8+x+Dvh/4f8D2flaTa7rhtwkvZwrTyAkHaXAGF4X5QAOM4zkkA8UvPDy/DnQZ/FHj2C08Q+KNQdI9PtLydpxERGQzTbmxIFBUEAMAVTDDdlev+AXgZdH8PnxReRSLqGpIUgDFhstsgj5SByzLuzyCuwjGTnU0/wAB6p4g+J1/4r8YQQfZ7CUQ6JbRvghI5N8cxKN9TtbOS7ZCgKD6hQAUUUUAFFFFABRRUc88Nrby3FxLHDBEheSSRgqooGSSTwABzmgD5U8B+M7zwf4B1JdC0K+m13VdQS0i1AQF4F+T5EXqGmBZyExzvBOQNpseP/ht/wAIr8NdL1zVpJ38TXeoEX7tceap8xXfBOOWGwZOTlmflhtx1fwQ8W6R4X8ClNZmktYL3WJo47toyYEkEUGEdxwhIYkE4GEYkjFbn7R3/JPNP/7Csf8A6KloA9U1XS7TWtLuNNv0ke0uU2Sokrxll7jchBwehGeRkHgmvnTwx4Ns9A/aSh0OyhnvbDTv35e5USFM229XYhQBiR1wcDB296+l68L+CEEOueOvGfjCKK7jguLh0tTIoCsssrSsD1G9QsXAPG7vkGgD3SivN/Gnw78Sa9rF1qmh+PNV0rzIhtsBLIIfMVcDBRxsU4GflY5LHnOK4TXPHfxU+Hus22lardaNr13qKK1tHHDvZTuK7QkYjfLHHVSDjCnIYUAfQdFcH8M/HWpeL7fUbPXdIk0zWtMeMXMRiaNSsgZkIVzuU4U5Bz2IPOB3lABRRRQAUUUUAFU9S1bTdGt1uNU1C0sYGcIsl1MsSlsE4BYgZwCcexqPW/7Y/sef+wPsP9p/L5P2/f5P3hu3bPm+7uxjvivJ4fg1rPi/7PqfxC8U31xdNET9htQirbOdowrcp91RuCoMtzk4yQD2Cxv7PU7OO8sLuC7tZM7JoJBIjYJBww4OCCPwqxXN+EPBGkeB7e+t9Ha7EF3cGcxzTl1j4wFQdAAOMnLHjJOBjpKACiiigAooooAKKKKACiiigAooooAKKKKAKclzc/2pDbQW8bwBN9zLI7oUBzs2fIVkJKncNylRg87hmxOJmt5Vt5I45yhEbyIXVWxwSoIJGe2Rn1FV9OkWdLi4WG7hL3EislyWzlG8vcqkkKjBAwxgENuxljVygCMyMLhIhDIUZGYygrtUgjCnnOTkkYBHynJHGSCCG1t4re3ijhgiQJHHGoVUUDAAA4AA4xUlFABRRRQAUUUUAFFFFABRRRQAUVHGJg8xlkjZC+YgqFSq7Rwxydx3bjkY4IGOMkoAkooooA8H/aAR9F8Q+EvFdnZ77q1lIeZwxjJjdJIkbBwMkynjBIzzxx7pBPDdW8VxbyxzQSoHjkjYMrqRkEEcEEc5rk/id4R/4TTwNe6bEu6+i/0my5x++QHC/eA+YFkyTgbs9q8z+BHxE1LUb238H6hJafZLWykNpI7N58jK4ITJbBCoWwAOFQdgaAPcNW1KHRtGvtUuFkaCyt5LiRYwCxVFLEDJAzgeoryO9+LH/Ceado3h7whPPYa9rEvlXhMeWsIQCZWVyVDNgEqV52g/cbbXsF/Y2+p6dc2F5H5lrdRPDMm4jcjAhhkcjIJ6V86eDfCmr/DXxL4u1rUbCP7PoumXAsL+9jCxTTMV8kp83JdQVIU5G/aSCcEAufs4olr4h8SWstn5l0kUYF5EFkjjCuwZPNUkfOSpGCQwjJz8oqv8ZdT/AOE9+IukeDdEh826sZXt3nDbkMkmwtwoJCxhPmPUYbgbcnf/AGf9AuE8A67qNtPPZX2pSmC3uHiDogjQhJVUj58PI+ecHZjjBrqPhh8MH8Fy3msaxffb/EN9uWadJGZFQsGIBbBdmIDFmHsO5YA9IooooAKKKKACisPxR4u0Xwdpb3+sXscICM0UAYGWcjA2xpnLHLL7DOSQOa8zt/Ffj34pyyQ+Frb/AIRvw8eG1W5UtNMhZkPlnGN2OcL91k/1gyMgHqGs+K/D/h7eNX1qxspFiM3kzTqJGQZ5VM7m6EDAOSMDmvI/2abOFNG1+9W7jaea4iie2GN0aorFXPOcMZGA4H3Dye3aeG/hb4W8IeZrOot/aWppm5uNV1Nw2xhhmkAPyphlLbjlhk/Ma8I+E3jRPCUuqw2ek/b/ABDqnkWul7goQOWIKu5IIUlkOB12jJXggA+t68r+JHxJ1f4f+NdEMtnHP4burdxMEx5ryBhuKnPBRShAOA29hnOCvPx/F/x9oN7NL4v8DyR6XA/lzz2trLGIzvC7hIxZHHUAZAYkYb1ufGrwxqnj7Q/DOr+GIP7Qt13EKnyuyT+XscBsYXjnONuckYDEAHUfG3/kkOu/9u//AKUR0fBL/kkOhf8Abx/6USVX+NTpZ/BzUre4vPMmf7PEjzFVedxKhPAABYhWYhQOh4AFdJ8PoIbb4c+G0gijiQ6ZbuVRQoLNGGY8dyxJJ7kk0AdJRRRQByfxB8DWPjrw1NYzxRi/iRnsLgnaYZccZIBOwkAMMHI56gEeCeFfib4s1Dwvp3w+0WPbqU8otYNT8x2kigJ5GACV2jPzj7qLwoIDV9T18+fCNb4fHjxW+p2Emn3ktvczS2rtuMRe4ifG7owwwww4IwRwaAOX1zxN43gsL/4U6yI9S1Ce9hhiuZZy8hDOrqockbgzGMgvyoJBHTZ9D+G9OsfAfgPTbC+ubSzgsbdFuZ3n2xCVjl23vjhpGOM46gADpXjHwugm8T/HrXfEF1FqSCze5mQXCkNEzN5SQy5ztIjZwFzx5fHCkV1f7Rl5bxeAbK1kED3E+oIYldyHUKj7nUAjOMhTkEDf0yQQAemWWlaK+qSeI7G2tHvL63jRr6HDGaIcrhhwQRjkdQFzkKuMP4neLv8AhC/A17qUTbb6X/RrLjP75wcN90j5QGfBGDtx3rh7Xxj/AMKl+D3hA/2ZPq326Iy71byY4vMzNtZsN837zAGPmCMeMYrE0jwN43+J/iWy1rx/FJb6LA7FbKQm3fYSx2JGBuUblUMz4YrjDHAIAOj+APg3+xfC8niG8h232q/6neuGjtwflxlQRvOW4JDKIzXsFRwQQ2tvFb28UcMESBI441CqigYAAHAAHGKkoAjjjZHmZppJA77lVguIxtA2rgA4yCecnLHnGACCCG1t4re3ijhgiQJHHGoVUUDAAA4AA4xUlFAEcEK21vFAhkKRoEUySM7EAY5ZiSx9yST3qSo4YVgQohkILs/zyM5yzFjyxJxk8DoBgDAAFSUAFZ+t63p3hzR59W1a4+z2MG3zJdjPt3MFHCgk8kDgVoVHPBDdW8tvcRRzQSoUkjkUMrqRggg8EEcYoA8Hj1LxH8eNUmsIVk0jwRa3GZ5FGJbjGCqMckM/8WB8qbgW3EJn2jQPDmkeFtLXTdFsY7S0Dl9iksWY9SzMSWPQZJPAA6AVqUUARiNhcPKZpCjIqiIhdqkE5YcZycgHJI+UYA5zJRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQBx/xT0a81/4Z63p1gnmXTRLKkYBJfy3WQqoAJLEIQB3JFeIfs7aJ9u8c3erSW++HTbQ7Jd+PLmkO1eM85QSjoQPrivp+svSJdIZ7tNLt44Hd1ubpUtjCfNlXdmTKj96V2llb5wCpYDIyAalc34r8Wr4RfT7q/spDosrul7qKlmFkdv7vciqWYO3y54AOM9RW5svP7R3+fB9h8rHk+SfM8zP3t+7G3HG3bnPO7tUeq6VY65pdxpmp20dzZ3CbJYn6MP5gg4II5BAIwRQB4n4P+x+L/wBpDXfENn58ljp8R8qePBjeQRrb8sMgqw81lwQSFB7EV7oJGNw8RhkCKisJSV2sSTlRznIwCcgD5hgnnGH4U8F6F4Ks7i20S08lbiUyyu7F3bk7VLHnaoOAPx5JJPQUAFFFFAHB/FnwM3jjwc8FnFG2rWb+fZliq7j0aPcRwGXtkDcqZIArxTSvi94luPA2neC9JTGtSyrYQXibY2WAhUjRTnAkJJXecbVAPLEsPqevnj4Laa8Hxa8RRppN9Y6bBFJLDa3kLK1sWceRvDZxJ5TyAEnJVnwSCcgGPqdr4s+Ad5pMtnrUF9b6jFI0tk6v5AlAQPlMjOMpiQFWOMEAcH0/4M+CP7C0M+Jb+6+26vrsSXLzMNxjjf5wu8jcWbcGc5wSB127j4p8aPFc3iX4g3luDItnpTtZQRtkfMpxI+MkZLAjIxlVTIyK+o/CdjcaZ4N0OwvI/LurXT7eGZNwO11jUMMjg4IPSgDk7b4haL4k8eap8P8AWNDkQo5SFb2ISJdlBvOUIwowu9CchgAflOAdDUvB/gPQPBy2+o6baW2g6bcC/Kyu5XzRkBn5JkJ3bQrbs8Lg8CvIPjFer4R+N2k6/YxSCcW9veXCpMyGcq7oVzztDRoEOBjGcg5Oeb+INx4u8ceOdPsbyynXUbiIG10dSh+yK5JCnDHDFAru0gRh3VVVaAO//wCFka74k1H/AIRf4U6FBY6dDL5f9o/ZgI4lJ3b9m3ZEpKyfeDMwPADcUfBnxB4p/wCFh654V1zWp9QhsIpywmkMv75JY4yVdxv24zgZA5zjNdprt5Y/CL4QpaW9xIs8FubSzlSLcZLt1Zt+1iQBu3OQSQACBngHD+Angm48P6Hfavq2mz2ep3kvlRrcxhXWBcdARuXc+7IPXYhx0JAPYKKKKACiiigDxPxX8TfGmqeMb3wb4J0KSC/tXYSXNwqmQqu071D4REPIBbduDrjaSBXEXsfj7wH8S9A09vFMl9rGoJAG8+4llt2DTSRpHJu5dBknOAVLtt5G4/S+q6rY6Hpdxqep3MdtZ26b5ZX6KP5kk4AA5JIAyTXzBofjLSH8Waz8SPEdpJf3YuAlhpscJYQylSYWeXCxgKsexeGckF9uVyQD6rorL8Oa/Y+KfD9nrWmtIbS6QsnmLtZSCVZSPUMCOMjjgkc1qUAFFFFABRRRQAVz/jv/AJJ54l/7BV1/6Kaugrm/iDPDbfDnxI88scSHTLhAzsFBZoyqjnuWIAHckCgDzP4L+GLfxH8GdW0jV4J1sdQ1CQqy5QsAsWHQ98Oh9RlSCDyK5j4u/wDCa6P4SsPD3iXyNTsI7tZLPWodweTYsqhJwf8AloVZT/wE8udxHq/wS/5JDoX/AG8f+lElegUAcH8SfEsNj8IdS1GW3khfUbIQRWt0RDKGnXbtK8/OqszFR/cP1Gf8H4LTwz8GrbUryKSzR0n1C8d1ckqC2JNvJx5SIRtHIwRnPPN/H2S81PUfDHh4R30OmT3atcXaWRmj8xzsjC7fmaRV80+WMEhh17euW3h7TYfDVr4fnto73T7a3itxHdosgdYwApYEYJ+UHp1oA5uL4veBrjTr68ttegl+xxGVoWBhklwCQsay7d7HbjA7kZxkVx/wv8H3HijWJviV4vtt2pXcok0+3aIJGiBVCTBc5OAAE3dl3/MSrDlPiB4I8Ljx5oXgrwjo0a6hO/m3zi+fKx4J2bnZwh2B3OUJxsIBzg9X8G/E+qW/ibWfh7fz/brfRvOFreP8rhIpRHsI5yvII5+XGMkY2gHtFFFFABRUck8MLwpLLGjzPsiVmALttLYX1O1WOB2BPapKACiq6X1u941p5m24XJEbqVLgBCzLn76jzEBZcgE4JzxVigCnquq2Oh6XcanqdzHbWdum+WV+ij+ZJOAAOSSAMk14ReTeN/jjez2+niTRfBm9GV7iMgTqrkbsgZlfIY7AdilFBOQGPv8APBDdW8tvcRRzQSoUkjkUMrqRggg8EEcYogghtbeK3t4o4YIkCRxxqFVFAwAAOAAOMUAcP8L/AAVrXgbS7/TNT1mO/szcFrGJFIEKc5bnkFiQSgyFIJBJY13lRzzw2tvLcXEscMESF5JJGCqigZJJPAAHOa838T/HPwh4euEtrWaTWJ96iT7AVaONcqSfMJ2sdrEgLnlcEr1oA9MorxP/AIWR8SPGSXM/gfwjHFpZRkivL0gOxLOokQu6oSMcqA4UjknIFcQdU+Nut+IEslfWftmn3DI3lRJBAsgAfEjKFicEBSA5IIYYyG5APqOiqekx30OjWMWqTRz6glvGt1LGMK8oUb2HA4LZPQfQVcoAKKKKACiiigAooooAKKKKAI44VieZ1MhMr723SMwB2hflBOFGFHAwM5PUkmSio554bW3luLiWOGCJC8kkjBVRQMkkngADnNAElFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFRzwrc28sDmQJIhRjHIyMARjhlIKn3BBHasux8PRWl5Hezalqt7eLnMk964R8gjmFCsPA44QdM/e5q4NLtF1l9WVJBePbrbOwlfa0asWUFM7SQWbDYyNxGcGgC5WXH4c0iHxLN4iisY01aa3+zS3KkgvHkHDDOCflX5iM4AGcDFalFABXkfx0u9av9L0zwloem3dzJq1womkSE7CF+ZI95XaCSpcncCoiJPynNeuUUAef3HgPXdM8JaHpHhDxP8A2TJpMUhbdbCRLyZl6vuJ2qWZzjDAbwQMotSfDP4hzeObfUbe/wBKk07VNLeOK7jJO0swYHAPzKdyOCpzjjk846Dxh4lh8H+E7/Xp7eS4S0RSIUIBdmYIoyeg3MMnnAzweled/AHw9qljoeq+IdWafztblSSMTjLyIu4+cWJyd5kbqBnbu5DCgD2CiiigArL8Rya1D4fvJfDsNpPqyIGt4rskRuQRlTgjkrkDkDOMkDNalFAHifhL4Q6vrOsxeJ/iReyX14rsV02VhKo+bK72BK7MliIlG3kZONy17RBBDa28VvbxRwwRIEjjjUKqKBgAAcAAcYqSuP8AiR46t/AfheW+zBJqUv7uxtZWI818jJwOdqg7j07DILCgDg/jf4zvpXi8CeHI7ufUrtPMvBZ/O5i2sfJ2qC2SBubp8uOoY4p/s5+GNLl0m98Tywebqcd29pC78iFBGhJUdmO8gn0GBjLZ1Pg34Bu4biTx94hljm1TVUae3jCIdiyncZSQMB2B4C4wrEH7xCyfs4/8k81D/sKyf+ioqAOs+KPiPSNC8C6rb6lfRwT6jZXFtaRkFmlkMTAAAAnGSAWPAyMkZFU/gl/ySHQv+3j/ANKJK7DWdD0vxDpz2Gr2EF7atk7JkztJBG5T1VsE4YYIzwak0rSrHQ9Lt9M0y2jtrO3TZFEnRR/MknJJPJJJOSaAPK/2jv8Aknmn/wDYVj/9FS165BBDa28VvbxRwwRIEjjjUKqKBgAAcAAcYr50+Kvj+38VadpXhfV7CfQL+HVc6ms2ZvsaKNquu0DzVZZSwxj7nAIZWP0fQAUUUUAFV/sFn/aP9o/ZIPt3leR9p8seZ5ed2zd1255x0zViigCOOCGF5niijR5n3ysqgF22hct6naqjJ7ADtWH4n8E+HfGP2X+39P8Atn2Xf5P76SPbuxu+4wznavX0roKKACiiigAooooAKKKKACis/W9b07w5o8+ratcfZ7GDb5kuxn27mCjhQSeSBwKk0rVbHXNLt9T0y5jubO4TfFKnRh/MEHIIPIIIOCKAPO4fiJqi/Hu58FywwSaY8SpCQNrxOIPOLk87s5KkcdFIxht2P4k/aE0vSvFEen6ZYf2jpsMoW7vUl++MEMIR0bB2kMThsEDgh65D4931xovxKtbjSpPsN1Lp8Mz3NsojmLhpo8+YPmGUO04PIC5ztGPR/h34a0v4f/CqW68SLBB9tiNzqhuo8gIwwsLKVDHCkDYQfndwM5oA7zw5r9j4p8P2etaa0htLpCyeYu1lIJVlI9QwI4yOOCRzWpXg/wCzR9s/s7xFv8/7D5sHlbs+X5mH37e27Hl5xzjbntXvFABRXnfxA+LWm+A9U0+ya2j1F5nYXiQXSiW0UbCCUwcllckAlc4685HYeHNfsfFPh+z1rTWkNpdIWTzF2spBKspHqGBHGRxwSOaANSiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAjJm+0IqxxmAoxdy5DBsjaAuMEEbsnIxgcHPBBPDdW8VxbyxzQSoHjkjYMrqRkEEcEEc5qSigAooooAKjEEK3D3CxRid0VHkCjcyqSVBPUgFmIHbcfWiYzKgMEcbvvUEO5Ubdw3HIB5C5IHcgDIzkSUAfCljpC6pqWjadYXkcl3qLpC6yIyLbyvKyKpPO4bdjZA/ixjIr7rrzvS/hBoui/EhfFumzyWsSI7Jp0aARrK6lWYHsm1j8gHB6ED5a9EoA5fX/AABoXiPxDpev3kc6anpssUkM8MpG5Y3LqjKcqV3Ek4Ab3ryfT9U0uz/ac1y+8TGDS2iiZLN57jCBxHGiOXGFG+He2G6bsfeAr6Arn/Engfw14u8s65pEF3JHgLNlo5ABnC70Ibb8xO3OMnOM0Aamm6tpus27XGl6haX0CuUaS1mWVQ2AcEqSM4IOPcVcr5k8FWMNx+0DNF4FmksdFtHYyskglWS3QKsg+ZzvSSTGDkkb1YAbePou7u9Rh1jTra20v7RYz+b9rvPtCp9l2rlPkPL7jxx06mgDQooqvf31vpmnXN/eSeXa2sTzTPtJ2ooJY4HJwAelAFiisvw5r9j4p8P2etaa0htLpCyeYu1lIJVlI9QwI4yOOCRzWpQBT1XSrHXNLuNM1O2jubO4TZLE/Rh/MEHBBHIIBGCK+dPij4G8P+FbPQPCnhux+063ql2Ha5nuVM5AJSNCOFVWaUjI2g+VzkjI+l68j8Z/DTxff/ENPGPhnxFaQ3apsijvVbFqojCYT5XDBsyEjauC3cnNAHWaRJoXwz8DaTpmr6lY2H2a0JcNOf30gG6YxhvmfLsTgDPzAADgVX8KfFjwt4x1y40jTZ547qPJh+0xiMXSjOTHzk4AzhgGwc44bHjnxW+H+i+D9G+36p4h1nVvEmoOq2zzEFXCLGJGkJycDnHzZG5BghWavS/gp4C/4RTwuNS1Gy8nW9Qy0nmpiSCHI2x9TjON5GAcsAwygoA9QooooAKKKKACvP8A42/8kh13/t3/APSiOvQK8/8Ajb/ySHXf+3f/ANKI6ANT4Y6bDpXwy8O28DSMj2SXBLkE7pR5rDgDjc5A9sdetdZXP+BP+SeeGv8AsFWv/opa6CgArm/HniuHwZ4Ov9ZcxmdE2Wsb4/eTNwgxkEjPzEA52qxHSukr581eRvjR8XrTTrWGSTwvoTstxOCrJIN2XYOpBxKUVFwxOBvA+8AAdP8ABXwZfWSXnjbXZI5NU11POj29VikbzGZtp25c7WwB8oUcjJUZfwIt/wC2/EPi/wAaS2Pk/brto7dvN3bN7mWWPHGcZh+YqPbuK9A+J3i7/hC/A17qUTbb6X/RrLjP75wcN90j5QGfBGDtx3rm/gL4XXQ/AY1WWORbzWH8596MhWJSVjGCcEEbnDADIkHUAGgD1SiiigCOATLbxLcSRyThAJHjQorNjkhSSQM9snHqakoooAKK8Xv/AI9PqGo3On+C/Ct9rUkcTlZ8NwQSofykUsY+UPJUndjCms//AIRr4tfEeff4g1L/AIRrSJYsG2gJXejpgqYlbc3QbllYY3nA6rQB6B4n+Lfg7wrLNbXWp/ar6HhrSyXzXB3bSpPCKwIOVZgRjpyM8ePGfxW8a2cM3hjwtBothcbFF7eSK7rkhvMXft3RlSORG38WCT06zwp8IPCHhK4F3b2cl9eK+6O5v2WVouVI2gAKCCuQ2NwyeccV3lAHk8PwW/tv7PeeOfE+q61fJKZjCk2y2TdtLxqpBIUkEZXZxjAXFdxongbwt4c8htJ0Gxt5oN3l3HlB5l3Zz+9bLngkcnpx0roKjnnhtbeW4uJY4YIkLySSMFVFAySSeAAOc0AV49Shm1SawhWSR4EzPIoGyJjgqjHP3yp3YGcDBbG5N1yiigAooooAKKKKACiiigAooooAKKKKACiio5zMtvK1vHHJOEJjSRyis2OAWAJAz3wcehoAI42R5maaSQO+5VYLiMbQNq4AOMgnnJyx5xgCSiigAooooAKKKKACis/UdM+332k3PneX/Z921zt258zMMsW3OeP9bnPP3cd8jQoAKKKKACiiigAooooAKKKKACvnTxvrtyn7RAuf7X03R/7Ft41jubuJ3V4vL8yRSq53OVlkAGVzgAEMQT6P8SrD4kX7xR+DL60t7Brd0nUMEuGcqx4ZhhRhVVSpBDPk4A3L454P8FS+M/H2r3njvV4EbT99zqMT3aebLsdkZSVOI41MZDHjClNuAysAD3/QfG3hHx19s07StQg1DbF/pFtLC67o2+U/LIo3Lzg4yBkZ6jPUVh6J4O8OeHLj7Ro+j2lnObdLYyRJhjGpyAT1JJ5LHlsDJOBjcoAr3VhZ33kfbLSC48iVZ4fOjD+XIv3XXPRhk4I5FWKKKACo4J4bq3iuLeWOaCVA8ckbBldSMggjggjnNSUUAFeX+IvhL/wk3xVtfEt/f+bpEUUTPZynzC0iHiNVK7VhPDMMkkl+BuyPUKKAOL+JPi/UvA2jWmt2mnx39mtx5V5AVZWCsp2uJBkIAwAOVOdwGQevN/s8/Y/+FayfZvP87+0JftXm42+ZtTGzH8OzZ153bu2K9M1bTYdZ0a+0u4aRYL23kt5GjIDBXUqSMgjOD6GvP/CGkL8Gfh9rUuvXkdxaQ3r3KSWqMWdGEcaAqejswAxkgZHzYyaAPTKKw/Cni3SPGejDVNGmkkgD+XIskZRo5NqsUOeCQGHIJHoTVjWvEmi+HUhfWNUtLETuEi8+UKXO5V4HUgFlyeig5OBzQBJqehaPrflf2tpVjf8Ak58v7XbpLszjONwOM4HT0FaFFFABRRRQBh+MPEsPg/wnf69PbyXCWiKRChALszBFGT0G5hk84GeD0rH+GfjW+8deGjqd9o0mnlHEayhsxXJA+Zo8/MAGyMHIHQMxDYp/G3/kkOu/9u//AKUR0fBL/kkOhf8Abx/6USUAdxf31vpmnXN/eSeXa2sTzTPtJ2ooJY4HJwAelY9r448NXvhefxLb6vA2kQbhLcEMuwg42lSNwY5GFxk7lwDkZueJdNm1nwrq+l27RrPe2U1vG0hIUM6FQTgE4yfQ18qeErjWPF2h2Pw40iy8iCfUDe6heQl2LR/KN0ilgu1MA44DFYwAG5YA7y/8ceP/AIq3v2LwPY3ek6OrmOW88zYWw6nc82PkIUoTHGS2C33x0t6H4l8WfCvxbZaB451H+1NM1ba0d+1w8gtnLbCfNlC/KPlLrn5QwYdSG9c8H6douk+E7Cw8PXMdzpcKMsM6TiYSHcS7bxwSW3ZxwDkADGK8U+Ja/wDCZ/H7Q/DIinuLW08iK5tnk2IVJ86Zl+bjMRXJGGOzAzgUAfQ9FFFABRRRQBT1XSrHXNLuNM1O2jubO4TZLE/Rh/MEHBBHIIBGCK8fvfg/4j8J397q/wAOfEclqXcuumTfdKhG+XcxKyEEkLvUY3ZLZGT7JfX9nplnJeX93BaWseN808gjRckAZY8DJIH415nr/wAd/Dmn3rabodrd6/qBcRxLajbFJIX27A/JY9wVVg2Rg88AHkmt2fiqT4yeG7Lx6YL26a7tYlOyIxzWxuCB8qAAqTv4YA4PIr3/AOJEPha/8Ly6b4q1r+zLSb98pS6EUknlkMQqnPmdvl2tyRgbtpHhHiXUvFPj34q6EYNAn0DX12fY47wkBY4yZFkIdBnawlYnBBAChcqd3ocfwS8Oae82ueOfEt3qzq+Zri9n+zxMpUIgkYsXyDjB3j+EY9QDDh+KejeF9Ot/Cnwu0GfVpxki4lhc+c+FZn2KBJIxAYHOzbtGAVAFMn8MfGjx9byvqeox6PZyoSlpJP8AZ1eOUcoUiDMQAAMS8jOOTur0/wAD6p4Ae4vNJ8Gvpqz2qKtwtrFtaRUJUMXIBmAOfnBb7wOfmGe0oA8r8L/AXwpoaJLqqya1eK6vvnzHEpViRiJTggjAIcsDjsCRXqEEENrbxW9vFHDBEgSOONQqooGAABwABxipKKACiiigAooooAKKKKACiiigAooooAKp6rqtjoel3Gp6ncx21nbpvllfoo/mSTgADkkgDJNXK8T/AGitS1L+y9B8P2CyOmqXEheOEMXmaPYEQAH5gWkzjB5VcdKAPXNG1zS/EOnJf6RfwXtq2BvhfO0kA7WHVWwRlTgjPIrQrH8MeGNL8I6HDpGkQeVbx8szcvK56u57scD8gAAAANSORneZWhkjCPtVmK4kG0HcuCTjJI5wcqeMYJAMfxR4u0Xwdpb3+sXscICM0UAYGWcjA2xpnLHLL7DOSQOa8jvv2l7OO8kWw8MTz2oxsknvBE7cDOVCMBzn+I+vHSuk8VfCObxn8TYtc1nUo5dBit0RbNFKS/KSfK3D+AsWYvnd8xUAcMNDwN4903U/F+teCrHQY9LTR3mEP2dl8qRUm2OdoVdhLMDgZzlsnjkA6jwp4t0jxnow1TRppJIA/lyLJGUaOTarFDngkBhyCR6E1uVn2tho/hzTp/sdpY6XYpunm8mNII1wPmdsYA4AyT2HtXN6l8WvAelXCwXHiW0d2QODaq9wuMkctGrAHjpnPT1FAHYTzw2tvLcXEscMESF5JJGCqigZJJPAAHOakrh7H4weAdQvI7WHxHAkj5wZ4pIUGATy7qFHTueenWu4oAKKKKACiiigArx/4y+OriDyPA3h4wT6vq/+jXK7gWhSTCrHz8oaTd1J+VecDcrDrPiZ4+h8AeGheLFHcahcuYrO3dwAWxkuwzkovGcdyoyN2Rx/wb8A3cNxJ4+8QyxzapqqNPbxhEOxZTuMpIGA7A8BcYViD94hQDP+Emu+FPA6XPhzXEk0LxRv2X8l8xWKcq0jJtfcUUBGHPyhtwxuzmvdK5/xZ4L0Lxrpy2etWnm+VuMEyMUkhZhglWH4HBypIGQcCvL49M+KPwt2DTZv+Et8PR7V+zFWM0ajy1wq5LrwCqhS6gAsVFAHuFeF/GzxVfa5qlv8OPDkMlzeXDqb6NYuWPyyRoGPAAHzs3QAL8wAYVQ1X9oTWrpLjR9M8JyWOtSP9miLzmZ4pd23HleWCXzkAHvjIPQ9X8HfhxqnheXUfEHiU51u/wAx7Gl8x40LbnLuGKszsFPfG0c5YgAHonhzQLHwt4fs9F01ZBaWqFU8xtzMSSzMT6liTxgc8ADitSq9v9s8+7+0+R5Pmj7L5Wd3l7Fzvz/Fv39ONu3vmrFABRVe1v7O+8/7HdwXHkStBN5Mgfy5F+8jY6MMjIPIqxQB5fefB/8Atn4ijxR4h13+1rVZWZNNmtMIsYz5ceQ+NqkgkbcNg5HzGvUKKKACiiigAooooAK8z+OltrV98PksNEs7u7e5vYkuYbW3MzGIBn5ABIG9U5GOw74PplFAFPSdNh0bRrHS7dpGgsreO3jaQgsVRQoJwAM4HoKsRzLK8yKJAYn2NujZQTtDfKSMMMMORkZyOoIElFAHnfxi1XWrbwdJpGg6NqV/d6qjwPLaWxlWCLjfuwDyysVHA6scgqM7nw/8HW/gfwlbaTF81w2JryQOWEk5UByuQMLwAOBwBnnJPUVXf7Z/aMOzyPsPlSebuz5nmZTZt7bceZnPOduO9AHheofCjxf4o+KU0niK6kn8LxXslzC0t40imFnDeTGm4MhIwp4UDaSCcLn3yiub8c6p4j0fw1Jd+F9Hj1TUFcZiduEjALM20EFzxtCqcksDzjBAOkorzv4bfFBfGtxd6PqOnSabr1im6e3Iba4UhXYZGUIc4KNyMjluceiUARkTfaEZZIxAEYOhQli2RtIbOAAN2Rg5yORjmSiigDH0HwroXhj7Z/YumQWX2yXzp/KB+Zuw56KMnCjCjJwBk1sVHNCs6BHMgAdX+SRkOVYMOVIOMjkdCMg5BIrm/iPa/bPhr4ji+0TwbdPml3wPtY7FL7Sf7rbdrDupI70AdRRXlfwL8QzXnwtd9UuY1g0q4lt1nlcjbCqLJl2Y4AUOQOgCqB2rm9c+Kvinx1eXugfDjR53g+aJ9TGVfaRkMrHasOdsgBY7jxja3FAHvFFfPlyPif8ACW3j1/WPEVprOmG4jiuLKa/eVpAQ3CGVQVPU/ISeASrKpx7P4P8AEsPjDwnYa9BbyW6XaMTC5BKMrFGGR1G5Tg8ZGOB0oA3KKKKACiiigAooooAKKKKACiiigDH1/RtJ1uKGz1aWcRz74Ehjv5bcT7lJZCsbr5nyqxwc8Bu2auPpVi9vaW4to44rN0e2WL935JQYXZtxtG3KkDgqSpyCQTULaGUQ3b2slzPYu1xbRxsFYyeW6YGSFJKuy/Mcc54xkXKAMfUfDdnqfiPRdcmknW60jz/s6IwCN5qBG3AjJwBxgj8a1I4IYXmeKKNHmffKyqAXbaFy3qdqqMnsAO1SUUAFFFFABRRRQAUUUUARzTLAgdxIQXVPkjZzlmCjhQTjJ5PQDJOACaIIIbW3it7eKOGCJAkccahVRQMAADgADjFEM8NyheCWOVA7IWRgwDKxVhx3DAgjsQRUlABRRRQAUUUUAFFFFABRRRQBXv4bi4065hs7r7JdSROkNx5Yk8pyCFfaeGwcHB64r5U8C+E9K8bp4l8V+M9bks7O1fzriWB41d5ZGLFiuCQM8ABPmLYU5UivU/id8a7Pw39t0Pw+ftOtp+7e5wGhtW53f70i8fLjaCeSSpWuI+FXwas/E9nJrGv3E4tYbt4EtYMbLjYVy4mBIeMnenyemQ3FAFjUPHD+KLOw+F/w9jnaxmiNm1/qMzLLIiHcSpz8sexGyCOVYqEXAB9v8D6DeeGPBel6Nf3v226tItjzAkjkkhVzztUEKOnCjgdBoaNoel+HtOSw0iwgsrVcHZCmNxAA3MerNgDLHJOOTWhQAUUUUAFFFFABUcEK21vFAhkKRoEUySM7EAY5ZiSx9yST3qSigArw/wCM/wDaPizxz4Z+H1n+6hucXssvyt1LruwcfcRJWwG+bdjGQK9wrxfRrG3u/wBqjxDNPHvktNPSaA7iNjmKCMnjr8rsOfX1xQAXHxX0vS5Y/BXw30X7fdw/6PZFE3WzOGUtghwzLtMpMhI5XdllJYU3+EU/jK31PWPFHi6TWtehRrZY9KMRjt5Yw37ohtoJ5UlP3WCWyed1dR4l1nw58F7eC403wvJ5Gr3pN3Ja/KseBk8nIzgkpENq8Pgrznyz4F+NvDvg7+3v7f1D7H9q+z+T+5kk3bfM3fcU4xuXr60Ad/8As7a39u8DXekyXG+bTbs7ItmPLhkG5ecc5cSnqSPpivYK8L/Z7ghtdZ8b29vFdwwRXECRx3ihZ0UNOAJAOA4HBHrmvdKACs/U9d0fRPK/tbVbGw87Pl/a7hIt+MZxuIzjI6eorQrl/Enw78K+LtRjv9c0r7XdRxCFX+0Sx4QEkDCMB1Y/nQB458XPjJpHiPw/P4b8PRyXMFw6G4vZYyi7VKuBGpw2dwwSwGNpwDkEer/DKzh8PfCbQxcXcfkLZfbJJpMRrGsmZjkk4AUPjPtniuH+Nek6b4R+Fsdl4f0+00+C9vbe2uvJhUNPGiSOodsbmIZFO4nPXnk55Cy1LxH8XLLRfBGhrJYaDpllbR6jcuMhmRFBZ8HkblOyPPzEbjjHyAHq/gfxvqXjrxjrNxYLGvhKwTyLaRoGSS5mbYd2TkYUK5wNpAkTcMnjyz4O+Hre3+Kfizw1eN9rtY9PvLCY4MfmoJ4426HK5Geh4z1r6L0rSrHQ9Lt9M0y2jtrO3TZFEnRR/MknJJPJJJOSa+fPDlreaJ+1Rd2bXGPtN3dyyCFzteOWJ51VumcZQkdNy98A0AafghPBHhTVNS8Q+FfHV3NpdjZGXUtLuYAr3I+YIVZxGMhigGFJydu4eZirHwf0C58U+MdU+JmqLIiTXEy2EMrOzKW4LBzgMiITEOCODwuwV1ms/AzwNq+9orCfTZnlMrSWM5XOc5UK+5FXJ6KoxgAYHFegWFjb6Zp1tYWcfl2trEkMKbidqKAFGTycADrQBYooooAK8j8Ya/8AFxvEt/onhvQLRLTYstrqCAMfLyP+WkrCPeTkFCuQCcZADn1yigDw+L4IeIPEt4t3488Yz3m2WRhb2jM4UMAcozgLH82MqI8YUAe3rHh7wroXhSzNroemQWUbffKAl5MEkbnOWbG44yTjOBxWxVPVtSh0bRr7VLhZGgsreS4kWMAsVRSxAyQM4HqKAPnRPE+l237QniHxTq8/2K30jzwsK/vHunjQWoROnzNkv7YOTgFhIkHiv49+ILvN7JYeE7O4doGeAbYzjCLtB/eS7cE5YhdzYIDBW5/4TeAl+I3iW+vtZu5JLOydJrxS7GW6eQsQC3UAlWLNnPYcncvufw28b2GvS6p4dj0KDw/faPK0b6dDLGyY3EMU2hej5DYGPmU5O6gDyjxF4Z07wH8cPBWneHxPbRt9jMshmYvKzTtG5Y5/iUYIGF5PGDX0vXz/APFP/k4XwV/24/8ApW9fQFABRRRQBHOZlt5Wt445JwhMaSOUVmxwCwBIGe+Dj0NSVGZGFwkQhkKMjMZQV2qQRhTznJySMAj5TkjjMlABRRRQAUUUUAFFFFABRRRQAV4f44i/t79o7wjpSajiOyijuWjDbxFIjSTFSuflZ1jjBPXBU8gCvcK8b0KCGb9qTxO8sUbvDpiPEzKCUby7dcr6HazDI7EjvQB7JRXjfxZ+IHijwD410W4tPLl0Ge3zJauqYnkVj5g3YLqQrRkHpnHB5FdZ8RPiNZ+CvCUWq2vkX11fYXT0EoKSZXd5hwctGBgkr13KMjdkAHUazrml+HtOe/1e/gsrVcjfM+NxAJ2qOrNgHCjJOOBXzJoHjTxND468WxeA7KPUH1y9klSU27O0aea+yUZICD96CTIMDjPeug8L/DHX/ik6eK/HOrXcUE6KtvGkarLNEFOGUY2xJkgj5TuyxwMhjb0kJoP7VU+m6TBBZWM8XkyW8EKomz7IsuAAPl+dFbjH6mgAsfgx4x8Yyx3/AI98STxYlMi2gk+0OoZj5gXny4shVxs3DkZA24r0DRPgt4G0TyH/ALI+33EO799fyGXfnP3o+IzgHA+XsD15rqNZ8V+H/D28avrVjZSLEZvJmnUSMgzyqZ3N0IGAckYHNeL6p4s8XfGe8u9G8Ep/ZugRxLHezXbpG77wT8+3cwU7GUKmc5O44bAAOc+JOpaD4s8QWnhHwB4a015FuPmvLG1SNriQAjajKAPKAJJY8HGeFXc30f4a02bRvCukaXcNG09lZQ28jRklSyIFJGQDjI9BXP8AgH4Z6L4At5GszJdahOirPezAbiABlUA+4hYZxyemScDHaUAFFFFABRRWHpPjDQNc1nUNH07Uo5tQ052S5tyjIyFWKtjcBuAYYJXIGR6jIBoXmk6bqFxa3F7p9pcz2j77aSaFXaFsg5QkZU5UHI9B6Vcrh/ir43vPAXhKPU7C1guLqa7S2QTk7EyrMWIBBPCEYyOue2Dj+KPjGnhnwl4c1mXw/PJca3EZktXuVURIFUklwGznehHAyDztI20AeoUVT0nUodZ0ax1S3WRYL23juI1kADBXUMAcEjOD6mrlABRRRQAUUVl+JdSm0bwrq+qW6xtPZWU1xGsgJUsiFgDgg4yPUUAeJ/s0PcP/AMJFGbyf7LF5BW1yDHvffl8EZDYQDgjI6g4XH0BXh/7NemeV4e1zVvOz9pu0tvK2/d8pN27Oec+djGONvfPHuFABRXB+K/i/4Q8JXBtLi8kvrxX2yW1gqytFywO4khQQVwVzuGRxjmuDufE/xW8d2rXmkWcHhXw80Tlry6lVMRmIMZGkcb9uMlXjQAbuvy5AB7xRXy58LtEvvE3xSudY8O6hqVppNncJPd3F5db7i4Qvu8uTZt3mRkJI5AGcliBu+o6ACiiigAoorH8V6z/wj3hLVtXDwJJaWkksXnnCNIFOxTyM5baMA5OcDmgDYorh/hL4h1nxP8P7XU9cXddPLKi3GUH2hAxAfagATBymMfwZ712kc8MzzJFLG7wvslVWBKNtDYb0O1lOD2IPegDzvxP8bvCfhfXJtImW+vbiDiZrKNHSN+6Es6/MO+M46ZyCBX+GXxZ/4TzWNUtr2Kx03Z5X2C08/dNLlXMnJxvxsB+VRgHnPWuof4c+DpdcutZm8O2M99dZMzTR+YjE4Jby2ygYkZLAZOTzyc/PnxXM3hL4y3GpW+m2kbtcWuqWjkHDbB82VVgMNKrFsjcSuQRk5APp8PDYXFvaQ2UiR3Dyt5kMQ8tZCS5345BYl23EYJBBO5lDXKp6TqUOs6NY6pbrIsF7bx3EayABgrqGAOCRnB9TVygD5/tbDZ+1tP8AY7TbDFunm8mPCpvs/mdsdMu4yT1ZvU19AVz+i+C9C8P65q2s2Fptv9UlMs8zsWIzgsq5+6pbLEdyfQKB0FABRRRQAUUUUAfHHgKx8QeM8+BbHV/selTy/brlDEzKSu0EkouTgBSFdlQsq8hiK+l/+KW+EXgb/n20+3+jTXUxH4b5Gx7AAfwqvHjH7OGs/ZPFup6Q7wJHf2glXecO8kTcKvPPyvISME/LngA1v/En7Z8RPi5pfgK38+PTdP2z37rkD5lDs+RuHCFVQsow7kdGoA4TxHJrXjPwReeO/FmqSIiXAstFso4SkbuzhpCOMbAgcbsksUwW+QBvf/hLps2lfCvw/bztGzvbm4BQkjbK7SqOQOdrgH3z1615J4g/s74h+OdA+H3hL934W0nJne13CM4P7yQN8wbA+VXYcvI3JDgn6LgghtbeK3t4o4YIkCRxxqFVFAwAAOAAOMUASUUUUAFFFFABRRRQAUUUUAFFFFAFe+s0v7OS1lP7mXAkUoriRMjcjBgQVYZU8dCcEHBFiowJvtDs0kZgKKEQIQwbJ3EtnBBG3AwMYPJzxJQAUUUUAFFFFABRRRQAUUVHIZg8IijjZC+JSzlSq7Tyowdx3bRg44JOeMEAkooooAKKKKACiiigAooooAKKKKAPI/jzeabo3gO8hitLRdQ124hilkXaksixEPvbjLhdqpz03jnseMmuPiB4X0fwP4UW+sdCsdZ/0aP7FFI1zH5jR72m83lZAZScIVwSw4wuJPiRqUPxM+L2ieD7BZHtLC4e3uZEAjfduBuCpY4IRI+OOSGxuBXMfxg8YeILjxzo0elaLqul3Wl3c8Wn3bRtuvnYxqfLXbhlyMYBbcrjIGcUAdf/AMKg8Yf9FZ1z8pv/AI/R/wAXi8F/8+Pi7TIv+AXKwx/98sZHX/rqcjv/ABF1438XfDTwbBf+N5LHWNTvrtVt7OGRIJIY/Ly5ZlTa21toIVcAt99sgD2CgDy/RvjhoVxqKaV4jsL7w3qfAkjvkPloxI2gtgMMqwbLKqgZ56E+oVzfiXwD4X8X3EFxrukx3U8CFI5BI8bbSc4JRgSM5IBzjJx1NdJQAUUUUAFFFFABXjfwhghb4l/Eu4aKMzpqZRJCo3KrTTlgD1AJVSR32j0r2SvJ/wBn++uNT8G6zf3knmXV1rc80z7QNztHEWOBwMknpQBY8f8Axit/CusP4c0jS59U187EEe0iNHkXKDj5pG5Q7VAyGxuBGK5P4LaTNrmveLZPGOnyXOoIkKPb6jCQEWaSSdx5LDaAzhXA28HkYyc+6fYLP+0f7R+yQfbvK8j7T5Y8zy87tm7rtzzjpmvD/wBnT7Z/aPjL+0fP+3ebb/aPtGfM8zM27fnndnOc85oAj/ZsnmurjxbcXEsk08r2rySSMWZ2JmJJJ5JJ5zXvlfP/AOzL/wAzT/26f+1q+gKAKd/ftYJ5n2G7uI1Qu7W6q5UBlB+XO5jhmbCgkhGAyxVWsTzw2tvLcXEscMESF5JJGCqigZJJPAAHOaJ5ltreWdxIUjQuwjjZ2IAzwqglj7AEntXz5rHiTXfjprieHPDcc+neGodkl7POoye+ZApwcEHZGDyRuJ4ygBl/E/x5e/EmyvbPQNNkn0HSbiOd7hbaVpWwkoMrEDZHFjPDYbof7wX2v4W2Ol2Xw10I6THAsc9pHNO0Lbt85UeYWPdgwKnPTbt4AAGP4t8C6Xo/wS1vw/pA+y28FobppGXe8zxESlnPGWbywM9uMDAArn/ghqj+IvhVqfhqG9+xX1l5sEU8KMHhSYMyS5yMsHMmMEfcHTqQD1zUtW03RrdbjVNQtLGBnCLJdTLEpbBOAWIGcAnHsa8nv/ixDrniX7J8OvDUet68bcp/aVxEIkSEFXIy21ymSRhigDbcbsjJYfAdb+9+3eNPE2pa5PG4WJfNYAxK7EK7OWbDAgkKV2ksATw1eiOnh/4eeErq4t7OCw0yxiMrpCFUyEKAOSRvkbCqCxyxxzQB8+eK9R+Kmj+KtBs9Q8USPrl6kTQWNnN5aoWfCLKoVYWJfcp5YHbyduM/UdfMnw317Qx4s1H4jeNNetIbt7iSK2tAZZJVkdeXCLuYRKhMa5yOcZBVc/Sdjf2ep2cd5YXcF3ayZ2TQSCRGwSDhhwcEEfhQBYooooAKKKKACuf8d/8AJPPEv/YKuv8A0U1dBVPVtNh1nRr7S7hpFgvbeS3kaMgMFdSpIyCM4PoaAPH/ANmzUoZfCus6Wqyefb3ouHYgbSsiBVA5znMTZ47jr2yPgUZvEHxL8WeKVjjgglSQvAXLMrTzeYoBwAQBGwJ47cel/wCDPhHxX4T8S+J9H1GyktLOS3Vf7RjUEGVSfLaFmG1htd2OVOCFDAH5TT/Z3/4lHiHxZoF9+61NPK3Qfex5LyJJ8wyvDOo6854zzQBJfzNqf7WVna3wjngskCW6PGpCAWrSjtyRIxYE8g4x0GPfK8b07wJ4rf8AaDuPFuoWtpHpaPI0c8UwxInk+VGoXJbfgruyAMq2D0z7JQAUVGDN9odWjjEARSjhyWLZO4FcYAA24OTnJ4GOZKACiiigAooooAKKKKACiiigAooooAK8T+FlnNffGf4gazPdySPa3ElmEfLEq8zbfmJ4CrAFAx0I6Y59srx/4Qf8lD+J3/YVH/o24oA9I8T+GNL8XaHNpGrwebbycqy8PE46Oh7MMn8yCCCQfnjU/Cz+C7zSdC8f2c954Rt9Qkay1W0lYFQ4QsjKCdsZ25ZAA+d5Rmwd30/Ve+sLPU7OSzv7SC7tZMb4Z4xIjYIIyp4OCAfwoA5vwr8RvDni+31W4065kjg0t/8ASJLpPKXy8EiUEnAQhWPOCNpyBxXgHim51PxL8bdUv/Al5HcXcji2tpoLiNGY/ZSkhjLkZAVJPnHA+Ug5ZSTwHpwHirUPh/LqOpWJunltNU+wSxt9uaJyAsbPGDEiqZWYktvVSuMsFqnFfapLeWFx8OdA/wCRb3WzanY2nnT3jyh18+RGUsFZUYhSD5e4jPKgAHp/hn9n/TlxqHjK+n1XU5JZJJ44Z2EL7s43MQJGbJ3FsrycYOCTxl1pOkaf+0NoekeBreS3SyuIVuvKuiwZlJefDM5OBFlWXI5Vlx6+zzap401D4VjULHR47TxZPbrts3ZcRsXALYcgKdmXCsTtOFOSCDz/AMG/hi3gywk1fVo5E167RoXiLqVt4t/CjaSGLbVYnPHAAGDkA9UooooAKKKKACvnDxjav8IPjDp3iSx886RqOXmQytI7jgToS0m6RuVkBcgbmXrtr6Prj/iQ/g6TwvLZeM7yC3s5uYiT++VwQN8SgFiy7hnAPBO4bSaAM/xPceFviH8I5tTvL77FpE0X2iO8miG+1kRsfdPVgwKEKctkhT8wNeQeD/D2t/FPUdHsL5oH8K+Gv9HNwgl2XAyCyqzkSFnCrx8ojXGFXhTJrniLSPFXhi2+G/gXwxqWpQWbrJa3skpDJIJCGlKYxsYSH5nKAeZ91dorH8Xv4/8AAOl6DpN94kjs0+zs0FhpVx5L26/LnzfLVdxLZ+YlslXIJ5JAPrOiub8A6drWleCNMs/EVzJcasiO1xJJOZmyzswUuepVSF6kccEjBrpKACiiigArL8S6bNrPhXV9Lt2jWe9spreNpCQoZ0KgnAJxk+hrUooA+cPA/j5PhB4e1Hw74j0LVU1druW5hi8tVjcbFRfnLfdLRsNyhhjkZ6VcPi34n/FW4S18OWUnh7R5UZxeAuisFIzm525J3qQBGAfmIbIBI93vNJ03ULi1uL3T7S5ntH320k0Ku0LZByhIypyoOR6D0qn/AMJVoX/CUf8ACNf2nB/bPled9kyd23GcZ6bsfNtzu2/NjHNAHH+D/gp4W8KXlvqLifUtSh2Ok10RsjkAOWSMcDJORu3FcDByM1x/xq8V6prmuRfDbw7bfaZrjy2vAi5d34kWMZACqAFctn6lQrZ94rn9K8E+HdE8Q3uv6dp/k6nfeZ9on86Rt+9w7fKWKjLAHgCgA8F+E7PwV4XtdFs383ysvNOUCNNIxyzED8AM5IUKMnGa6CiigAooqvfts065bzZ4sROfMt4/MkTg8ou1tzDsNrZPGD0oAjj1KG5SZrNZLrybj7O/lAABgwDkMxAYJk7sE4KsvLArXB/HW+t7T4UalDPJsku5YIYBtJ3uJFkI46fKjHn09cV6RXi/7SN9bx+DdJsGkxdTah50abT8yJG4Y56cGRPz9jQB3nwx02HSvhl4dt4GkZHskuCXIJ3SjzWHAHG5yB7Y69a+dB4d8QeKvi/4s0nw7d/ZZpru+FzI1w0SGDziGV9uSyklRtwc5GRgEj6n0LTP7E8PaZpPned9htIrbzdu3fsQLuxk4zjOMmvlz4Xz+I9Zvdc0vw9LHDr2pW7y3Gr3TbnjjDpna/30LFm3MBISfLwF2lwAfR/gbwZY+BfDUej2MkkxLma4nfgzSkAFsZwowoAA6ADJJyT5H+0dpNv/AGj4b1aSGeOOTzLW7u4ozJtQFWRcEhd3zSkDI3YPOBwz9nS5ttLt9Zn1C8tLVNRuILWzE1wiNcSoGLIik5JHnR9B/EK9H+MegL4g+GWqrtj8+xT7dCzsyhTGCXPHUmPzAAeMkdOoAMf9n3Upr74ZC3lWMJYXstvEVByVIWXLc9d0jDjHAH1PqleP/s52ssPgG9uHuN0c+oOUhDowTCICTj5lY9wx6KpAAOW9Yvr+z0yzkvL+7gtLWPG+aeQRouSAMseBkkD8aALFFFRyGYPCIo42QviUs5Uqu08qMHcd20YOOCTnjBAJKKKKACiiigDzu3+E1jYfFRPG1hqEkILyzTWLR7w8siOrMr7sqCX3Ywec4IBAHN+Pfg3rer+KLvW/C2u/ZG1OJor+K7upV3AgAqGUMWjYAfIeBtAHGAvefEPxlN4E8NLrMWkSakguEilRZTGIlYN87NtbA3BV5HVhz67HhzX7HxT4fs9a01pDaXSFk8xdrKQSrKR6hgRxkccEjmgDL8C+BdL8B6GLCwHm3EmGurt1w9w47n0UZOF7Z7kknqKjE8LXD26yxmdEV3jDDcqsSFJHUAlWAPfafSpKACiiigAooooAKKKKACiiigAooooAr2du9tA0ckvmMZZJA2W4DOzAfMzHgEDrjjgKMKLFFFABRRRQAUUUUAFFFFABRRVOys5ra61GaW7knS6uBLFG2cQKIo02LyeNyM/GOXPHcgFyiiigAooooAKKKKACiiigAooooA8v8CfC+3+F39r6215PrN0bQiNLe1KSBFy7IibyHZiqY6crjPJrxz4o+Pde8U3ui3txoN34fSweYWrsz72mV1EhVyq8oyKMAZUg5PIA+r55ltreWdxIUjQuwjjZ2IAzwqglj7AEntXzh4g8Sw/Gn4jeGNN0rSbuXSbNw92k6iN/LaRfOLMrkBNioAcg5JAyStAHZ+GPhBrEni2HxR461/8AtW/tZfMt4IXcorBt6HcQu1VYsRGqhenOMrXsFfP99rOu/AzxbBYyP9u8E30sstraKQXgUsCyoWJYMm4cE7X3E8MxK++QTw3VvFcW8sc0EqB45I2DK6kZBBHBBHOaAJKKKKACiio5jMqAwRxu+9QQ7lRt3DccgHkLkgdyAMjOQASUUUUAFeP/ALOP/JPNQ/7Csn/oqKvYK8T0TVbH4ZfF7xfZ+ILmO3s9bT+1ba8fgEBpGKbV3HOXkUZIJMYwCXUUAewWGq2OpveJY3Mc5srg2txs5CShVYpnoSAwzjocg8ggcn408Sp8P7yDXv8AhHvtGmXm6PVr2ziXz0dQogLHI3Ly6/N0yuCOFbP+DMP2vw5qviZ7aCGTxBqtzfKEO50j3lRGzYGdrCTHb5s8EkV6JPBDdW8tvcRRzQSoUkjkUMrqRggg8EEcYoA8P/ZptoV0bX7pZIzPJcRRugmBYKqsVJTblQS7Ybcd2CMDblvdK5vwr4G0XwdcarPpMUiPqdx58wYgKgySsaKoAVF3NgYzzyTgY6SgAqva2FnY+f8AY7SC38+Vp5vJjCeZI33nbHVjgZJ5NWKKACqdnpOm6fcXVxZafaW092++5khhVGmbJOXIGWOWJyfU+tXKKACqeq6VY65pdxpmp20dzZ3CbJYn6MP5gg4II5BAIwRVyigD50+L3grwP4D8HWcGn6XIdavHEUFzJdSlgqYMkhGdhJ+VcYX/AFmQPlxXrfgfTU8B/DPS7LW7yC1+yxb7mWeRY0ieRyxQtuK8M+3Oece+K5/4q/DPWPGmo6Vq2h6z9kvrD5VinldI053CSMoCVkyACccgLyNvPinxMtNal1nTrTVfFkfiXXpXkjmsbEF47R9yoqIFAG9tvKhVIK8g5BIB9Z2N/Z6nZx3lhdwXdrJnZNBIJEbBIOGHBwQR+FWK4/4a+CP+EC8JLpUl19pupZTc3LqMIJGVVKpxnaAoGTyeTxnA7CgAooooAKKKKAMPxd4osfB3hq71i/kjAiQiGJn2meXB2xrwTkkdcHAyTwDXzR8O/F2veH9Z1vxifC13roukkF1exK8awsWEshLKpQDoSCvHGCBkH6P8a+CtN8eaNDpeqT3cMEVwtwrWrqrFgrLg7lYYw57elbGlaVY6HpdvpmmW0dtZ26bIok6KP5kk5JJ5JJJyTQBy/gH4maL4/t5FsxJa6hAitPZTEbgCBlkI++gY4zwemQMjPaV4P8dtH8J6PBDrdo/2DxV5qPAlhKkTyFnLmeVB83G18SLg7iAScDHpnw21rWvEHgPTdT16COK8nQkMoKmZAcLIykAKWAzgZBBBGAdoAOsooooAKKKKACiiigAooooAKKKKACiiigArxP4FabDo3ir4gaXbtI0FlexW8bSEFiqPOoJwAM4HoK9srxv4Tzw2vjr4p3FxLHDBFqe+SSRgqooluCSSeAAOc0AeyUUUUAeH+KNF8XeF/jRL4503S/7R0y58m3MUV8kHmNIiW6xvu5/1mxuhX7pyMHHKHxhd/Djw+ngrwmJG8RXV6094wZLv7JIxCrbJhAskoVUVztIDbgMk/J3/AO0VPND8ObVIpZESbU4klVWIDr5cjYb1G5VOD3AParnwy0jwV4P0PRJUurG31/WdPt5XN1dKJ5vN+YKik8KWO0BRztGckZoAk+FPw3vvCCT6zrOp3c2takhN5b+dviBLBgXPJeUHdl84+dgM/ePplFFABRRRQAUUUUAeB3/xm8UeNdU/sT4daLJHKyF/tNwEaUKNpJwx8uMZyuWLZ3LjBIFanh74CxT3h1Xx1q8+tX8n34kmfYcAqN0pxI/GwjG3G3HzCvYLWws7Hz/sdpBb+fK083kxhPMkb7ztjqxwMk8mpJ54bW3luLiWOGCJC8kkjBVRQMkkngADnNAHD+NvEmi/CrwdPNpdhptrd3Dn7HYxRiJZpTtDOVQchVwSeM4C5BIryzQIdL8J/ZPiR8SbqfUNa1b/AErTLSOPzWIGz96eiBgrqVUkBFxj5gAnH+LfEXiXxdrl948tLSePTLCUWdrdC3UC1Q7vLyecSDduLgna7rgrlK9b+Fvwq0Gbw1pfiPX9Lju9Tu7dXWGcIYEjwVjIjUBWLRlGJfcd3PDZoA7zwd8QPD/jiz83SbrbcLuMllOVWeMAgbigJyvK/MCRzjOcgdRXkfw++HOr+Fviv4l1ma2tLfR50lSyEDjDLJKsihUA+UIq7SCBzjbkc165QAUUUUAFFFRzzw2tvLcXEscMESF5JJGCqigZJJPAAHOaAJK8/wDHPwn0vxhqMWs215PpGuxbSl9bDO4qQVZ1yCWUDAYMpHGSQoAr+J/jd4O8PxTR2t7/AGvfJwsFl8yEldwJl+5tzgEqWIz0ODjhNU+NnxBit21a38Fx2eisiPHPd2lxIoVgACZQUUhieDgdQOepAPf4I2ht4onmkndECtLIFDOQPvHaAMnrwAPQCpK5/wAE+J/+Ex8IWOv/AGP7H9q8z9x5vmbdsjJ97Aznbnp3roKACiiigAooooAjjjZHmZppJA77lVguIxtA2rgA4yCecnLHnGAPC/jRNb6x8U/A/hq8td9qZY3mPmEeYk86xsnGCMCI8g/xdsc+8V4HfzNqf7WVna3wjngskCW6PGpCAWrSjtyRIxYE8g4x0GAD3yvGx8M/E3h5PH8egHRprDX7dxa25DRPGSx/dgDCKAkkqqc4yIydoyK9U1zWbPw9od7q9++y1tImlfBALY6KuSAWJwAM8kgV5/8ACLx5qnj288SXt/PBHbxSwi10+NObZCH5L7Rv3YHOTyrcKCAQDj/hZ8EtRsNcj1zxZD9maxlWS0s0mVi8i4IkZkJAUHGFzkkc4Aw3qnxM1Wx0n4c689/cxwC4sprWHd1kleNlVFHUkn8gCTgAmug1XVbHQ9LuNT1O5jtrO3TfLK/RR/MknAAHJJAGSa8nstSm+MHxBsruzW7tvCXhu4FxFcgFft10pUqCCcAAcjgsFJztMgAAM/4B399oeqa94E1axktry3c3wVl5U/IjgnOCCPKKkDBBY5IIq58UTb+MPih4Q8EJP5kccrXWowGYrGyYDbTtORJ5aSYyBgSLggMSI/HMemeDPjv4c8YajNHa6feW8y3MgEkjeakLR7ioBwCrwqNvoSQOpr/BfQLnxN4g1L4k68sjXc1w62PzOFBIKuwDdUVSI1+YgYYYyoIAPcIIIbW3it7eKOGCJAkccahVRQMAADgADjFV9RBkS3hWSSN5LiMhlSQj5G8whihG0FUIyx25IBDZ2tcrLurLV5vEFjdwavHBpcCOLiwFoGa5YggEylsqFO0gKOxyTkYANSiiigAoqveW73UQhEvlwvuWfaWV2QqRhHVlKNkqdwz0OMEgixQByfxO02HVfhl4it52kVEsnuAUIB3RDzVHIPG5AD7Z6da5f9n3Upr74ZC3lWMJYXstvEVByVIWXLc9d0jDjHAH1J8bvHq+GPDUmhw2kk15rFvJCJHRhFHERtc7ujPhuFB4yC3GA3nnwT0Xxjq8UEmn6z/Z/hmy1VLi5jRsPcyqqlkG0bipUICGYL82QGIIoA+kxBCtw9wsUYndFR5Ao3MqklQT1IBZiB23H1qSiigAooooAKKKKACiiigAooooAKKKKAKenXV3dJcG80+SyeO4kiQNKjiWMN8kilTwGXBwcEHIxwCblFFABRRRQAUUUUAFFFFABUYmVrh4AJN6IrkmNguGJAw2ME/KcgHI4zjIzJRQAUUUUAFFFFABRRRQAUUUUAFFFFABWfa6Fo9jqM+o2elWNvfT7vOuYbdEkk3Hc25gMnJAJz1NaFFAHjfx8ebWLLQfCOm2V3datfXouIVSI7CqI6kbzgZ+cE/3QCW2jGfUPDWmzaN4V0jS7ho2nsrKG3kaMkqWRApIyAcZHoK1KKACiiigAooooAr3UNxL5BtrryGjlV3BjDrKnRkI6jIOQQRhgpORlWsUUUARzzLbW8s7iQpGhdhHGzsQBnhVBLH2AJPasvXvCuheJ/sf9taZBe/Y5fOg80H5W7jjqpwMqcqcDIOBWxRQBHBBDa28VvbxRwwRIEjjjUKqKBgAAcAAcYrP8R6/Y+FvD95rWpNILS1QM/lruZiSFVQPUsQOcDnkgc1qVHPBDdW8tvcRRzQSoUkjkUMrqRggg8EEcYoA830T47+CNYuPImubvTHLokZv4QquWOPvIWCgcZLFQM/XHplcH4o+EPhDxHpb28Ok2ml3YRhBdWMCxGNjjllXAccAYbsTgqTmvNPAniHxB8LPHMHw/wDEC/a7C7liS2aIsVieUjDxFwu6MsSGHZgxHIIYA+h6KKKACio4YVgQohkILs/zyM5yzFjyxJxk8DoBgDAAFSUAFFFFABWHZ+DvDlh4gutetdHtI9UuX3yXOzLBsEErnhCQx3Fcbs85qxqXiXQdGuFt9U1vTbGdkDrHdXSRMVyRkBiDjIIz7GtSgAoqOQzB4RFHGyF8SlnKlV2nlRg7ju2jBxwSc8YMe+88jPkQed5uNvnHb5e/G7O372z5tuMbvl3Y+agCxRRRQBHDPDcoXgljlQOyFkYMAysVYcdwwII7EEVJRUcJmZCZ440fewARyw27jtOSByVwSOxJGTjJAJKjE8LXD26yxmdEV3jDDcqsSFJHUAlWAPfafSs/xHr9j4W8P3mtak0gtLVAz+Wu5mJIVVA9SxA5wOeSBzXi/wAF9F1rxL4x1L4iatPd26TO6wqhIjui2Qy/MSTFHhQB0yFwfkIoA9c17wP4a8T6jZ3+s6RBeXVpxE7lhxnO1gCA65/hbI5PHJz0FFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABXzB8QvC/i7TPHOvaLodjfS6T4ou4pc+UjpcSZMu3zMfJtfzTjKkKuWyOa+n6KAI4IIbW3it7eKOGCJAkccahVRQMAADgADjFSUUUAeP/tHf8k80/wD7Csf/AKKlrYHwx0Lxh4B8Nwa/Yz2+pWulW0AnjJinhwiFkIIwcEEYYHGWxgnNV5PH+o6n8dLLwpo8m/SLKKX+0zHErhpPLY8uM7VVvLX+E7yynPFeoUAeL/DDxPqnh3xpefDDxBP9o+x7hply/wB9kUB1Q43DaY/nGT8uNuTwB7RXkfg34Wa9Z/Ei58a+KdUtJrwvJJDDZO7rudWQgmQAhFU4VRntyAuD65QAUUVGIVW4ecGTe6KhBkYrhSSMLnAPzHJAyeM5wMAElFFFABXz58ZPGN94t8QR/Dvw1bSXLrcKLloZM+fKBny+DgImcsW6MnO3Zk+t/EPxBeeGfA2p6lp1vPPfLEVg8mIv5bEcyNhWAVBlyWG07cE8ivP/AIGeBXt7N/Gutj7RqepZe1adWMsSEtukJbq0mc5wfl53fORQBT+JuiQ/Dz4FW3hzT/LmS6vY4ru4eMK8rHdKX477o1UZ3YUAZOAa4CwT4oW3hP8At7TL27g0fRHEEaWUqrE6ozF5AkfyTIGzvkO7dk5LAMV9T/aD0T+1/D2gfZrfzdTfVUsrX59ufORsryQvLInJ6Y6jmuc8SeKL7xW9l8Lvh1JJPYW9ulrd6kX4liRQhy4GBEBjcwHzk7VBBAcA9b+HmuXHiHwNpl/fX9je37RAXT2TgqrkZCuB92TaV3LxhicDGK6iub8DeDLHwL4aj0exkkmJczXE78GaUgAtjOFGFAAHQAZJOSekoAKKKKACs/W9E07xHo8+k6tb/aLGfb5kW9k3bWDDlSCOQDwa0KKAOT0T4ZeC/D1x9o07w/aLPvR1km3TtGynKlDIWKEE5yuOg9BXJ/tDX1xafDWOGCTZHd6hFDONoO9AryAc9PmRTx6ema9Yrw/4peC/F3xB+Ium6bHafZNAtoj5d+7I6DO0yuQPmDHKIEJ52ZGBuIAPRPhjpsOlfDLw7bwNIyPZJcEuQTulHmsOAONzkD2x1611lRwQQ2tvFb28UcMESBI441CqigYAAHAAHGKkoAKKKKACiiigArxPwXqUOq/tL+MLiBZFRLJ7chwAd0TwRMeCeNyEj2x06V6x4l02bWfCur6XbtGs97ZTW8bSEhQzoVBOATjJ9DXhn7P+k3F34h8W39/c30GrxRC1kd8GRHldy7MHU5kDRD72ec5BoAt+JZ5vir8aYPCccsjeHdFcvexhiFkaM4lJHytkswhBBOMll4Jqp4Bv9C8H+OfHnhLU7ufQYb+V4bK4aQxeTGhl2kSv91tkisjHg465Iz3/AMOPhFZ/D/UbjUv7Wnv76aJrfPlCKMRko33csd2U67sYPTvXYeIfCuheK7MWuuaZBexr9wuCHjyQTtcYZc7RnBGcYPFAHzB8QfCngfRf7PsPB2sX2t6vdSgbIporiMIcgLmNRmQttwozwDkDK5+o/Ddo1l4a02CXTrTTpxbo09naIqRQysN0iqFJGNxboT9T1rL8PfDrwj4VvDeaPokEF0ekzs8rpwR8rOSVyGIO3Ge+a6igDzP44eEofEXgO41FIZH1DSEa4gKyBQI8r5wYHgjYu71ygx1IPQfDbxLD4r8B6bqMVvaWrqhgltbUjZAyHbtC/wAAKhWCnoGHXqeonghureW3uIo5oJUKSRyKGV1IwQQeCCOMV4X8NNcsPhx408T+B9bv/sVoLvztPku3jCYxnLyDADPH5RGcD5SOGOCAe8VXe4eK8WKSL9zLhYnQM5L4csGAXCKAowxPJOODjdJBPDdW8VxbyxzQSoHjkjYMrqRkEEcEEc5qnqVreXstpDDcfZ7VZVmuJI3IlbYyssa442sRhiT90FcfPuUA0KKKKAIzMq3CQESb3RnBEbFcKQDlsYB+YYBOTzjODiSozCrXCTkyb0RkAEjBcMQTlc4J+UYJGRzjGTmSgDj/AIpX2l2Xw110atJAsc9pJDAsy7t85U+WFHdgwDDHTbu4AJHhnw31X4kQeFfN0C5tLfw3ptxPPdXN7h40CosjxsvzSBMDOI1BzIxznke9/Ee2+1fDXxHHtgbGnzSYni8xfkUtwMjDcfK38LYODjB+ZNC1Lxf440TSvhzo6xixhd5H8sMoZS5cvO2T8is3AAHJXhm20AfR/wAPPiHD8QbfU7i30q7soLO48qOSYhlmUjI5HAcDll527l5Oa7Sub8DeDLHwL4aj0exkkmJczXE78GaUgAtjOFGFAAHQAZJOSekoAKKKKACiiigAooooAKKKKACiio554bW3luLiWOGCJC8kkjBVRQMkkngADnNAElRkzfaEVY4zAUYu5chg2RtAXGCCN2TkYwODnggmW5t4p0EgSRA6iSNkYAjPKsAVPsQCO9SUAFFFFABRRRQAUUUUAFRxxsjzM00kgd9yqwXEY2gbVwAcZBPOTljzjAElFABRRRQAUUUUAFFFFABRRUckbO8LLNJGEfcyqFxINpG1sgnGSDxg5Uc4yCASUVXvr6302zku7uTyrePBkkKkhASBubHRRnJY8KASSACaL26+xwLL9nnn3SxxbIE3MN7qm4j+6u7cx7KCe1AFiub0Xx/4U8Q6pNpmla5aXF5E5TygSpkI3Z8vcAJAApOUyMc9CK6AzKtwkBEm90ZwRGxXCkA5bGAfmGATk84zg48n8V/AjSL+4OqeFrqTQtUR/OjWMnyDICzAjHzRHcV5U4ULwlAHrlFeP/B3x7ruq6tqPg7xMPM1LSoiFnwGdvLk2SCV9xDMCyAEDnDEknk+wUAFFFFABRRRQAUUVyfjjwjqXiu3s10vxTqWgT27sWe0ZtsqsBwyqykkEDBzxluDngA6yq9/fW+madc395J5draxPNM+0naigljgcnAB6V8+a/4T8Y+D0a+8S/FHUrXSXuBb289tPdTyuzNwWj3KFAQM5wzY24G4kV6P8G9d8UeI/B0mqeJJY5xPcMbO4GxWeMfKwKIoAAZWwScnJ4AAJAOg8GeOdF8daW99o8sgMT7JrecBZYTzjcASMEDIIJB5HUEDpK+f9M/4sn8WpbC4/wCRZ8QY8mUfKlv85Cbnfr5e4hvm+64c84WvfBPC1w9ussZnRFd4ww3KrEhSR1AJVgD32n0oAkrxv4r+GJvFnxG8H2UPh+7ureN86jeLCViNuZFJRpgRgqqSnGQfnG3JavZKKACiiigDg/jPPNbfCTXnglkicpEhZGKkq0yKw47FSQR3BIqP4KPcS/CjR5bm8nuWbzQnnEHykWRkVFOM7QF4yTjOAcAAYfxY+K2i6LZa54RW3u7nVJ7JoGKqFiiMqY+ZickhWDcAg5AyOccR8JfAWv8Aiebw/rmr3ckPh3R3M2mxq6kyOJmZgFH3R5inczDJAUDjBUA9j+I/jr/hX/h631b+zvt/nXa23lef5WMo7bs7W/uYxjvXD+KPjRcajPpuh/D20+3avqMSuzyIG+zl0yEAB2+YucsSSibSDn5tvonj/QG8UeA9Z0eJZGnnty0CIyqXlQh41y3ABZVBzjgnkda8U/Z3sXk/4Sy/sY4Dq8NpFDZvcM3lqX8w4cL/AAlo4898DjrQBt2fwDWLw1rF9rlxJrHii4t7hoFS4YRCcgmNtzbWdywySxC/MQQcbjX/AGb9b1e4TV9Gl8ybSbVElidpBi2kZj8ir1IfDNwcAoeMvWx4Uj+KujeGvF134gF3cXbW7TadCDDcS/aGDZaPDkKi4U+Xgg/wDOVa58C/AuqeEdDvr/Vx5FxqvlMtoy4eFE34L+jHefl7YGeSQAD1iiiigAooooAKrwxPFeXJ2/uZdsgczs5L42sAhGEUBUI2nkliQDy1iuD+KnjtfBfh+KK3uo7bVtSfybOWWFpI4QCvmSvgHhVbIGGJJHysM0AeafEC+v8A4r/FCDwNpMnk6bpkrie42yMu9QBJI68D5CDGuerMfmw4x73pWlWOh6Xb6ZpltHbWdumyKJOij+ZJOSSeSSSck186fDPx54C+HvhyC6nt7678Q33mC8aCEMYEV/kQFyoCsNrfKTk/ePCge16J8TfBfiG4+z6d4gtGn3oixzboGkZjhQgkClySMYXPUeooA6yiiigAooooAKKKKACiio5zMtvK1vHHJOEJjSRyis2OAWAJAz3wcehoAkoqMQQrcPcLFGJ3RUeQKNzKpJUE9SAWYgdtx9akoAKKKKACio442R5maaSQO+5VYLiMbQNq4AOMgnnJyx5xgAhmWdC6CQAOyfPGyHKsVPDAHGRwehGCMgg0ASUUUUAFV7++t9M065v7yTy7W1ieaZ9pO1FBLHA5OAD0qxXnfxxnhh+EmrpLLGjzPAkSswBdvORsL6narHA7AntQB4Z4M8W+JLf4h6z4r8PeGp9S+2SyG6tYoZJvKjll8zbvQfK3yYDEY4J2mvrOCRpreKV4ZIHdAzRSFSyEj7p2kjI6cEj0JrxP4LfELwnZaPpfg9DPbalJud7ieBIo7idmzsDByS2CFUsBuCAcEha9g1zWbPw9od7q9++y1tImlfBALY6KuSAWJwAM8kgUAaFFcX8LvF+peN/Bw1fVNPjtJ/tDxKYlYRzKuPnQNk4ySp5PKHnsO0oAKKKKACiiigAqnquq2Oh6XcanqdzHbWdum+WV+ij+ZJOAAOSSAMk1crL1/wAOaR4p0ttN1qxju7QuH2MSpVh0KspBU9RkEcEjoTQB80fFD4pzeNnsEtNLkj8N29wJES7Qg3cyKNwZkPACyY2o2cPknJXb9D+DPCkPhjS3aURy6xfv9p1S8GM3Fw2SxBwMIGZtqgAAHpkknxz45WNvBL4O8DeHY/K27jHp0bFUJkZUiYlvlLFhL8xOeWJPzZP0PQAUUUUAFFFFABRRRQAUV5XrPxJ1dPjXpngvSrOOW0R0F7uwjvuiZ2KsSRsVGR8YDEoVBwefVKACiiigAooooAKKKKACiiigAor588S3t98a/iDB4d0mKRfDOj3BN3eRzfLIM7WkBGUJIVhEMEnLE8Fgvv8ABCttbxQIZCkaBFMkjOxAGOWYksfckk96AJKKjhnhuULwSxyoHZCyMGAZWKsOO4YEEdiCKkoAK8f+OPw3vPFVnb6/o0PnalYRGOa3XJeeHO4bBnG5SWO0DLbjySFB9gqOETKhE8kbvvYgohUbdx2jBJ5C4BPcgnAzgAHL/DJ9Sb4b6GmrWUlleQW/2doJImjZVjYxoSrcglVU++cjiusoooAKKKKACo4J4bq3iuLeWOaCVA8ckbBldSMggjggjnNSVHDBDbIUgijiQuzlUUKCzMWY8dyxJJ7kk0AeF/Fjw78Sdfl1SS4u7GDwzYWk10qW1w6JNGjFwsics821VPI2DHBBJzqfs3wQr4F1O4WKMTvqbI8gUbmVYoyoJ6kAsxA7bj616xq2mw6zo19pdw0iwXtvJbyNGQGCupUkZBGcH0NeP/s2XrSeFdZsDFGEhvRMJBMpZi6AEFOqgeWMMeGyQPumgD2yqZs1iv0ura3jDyO32lhKybgUA3lQMSP+7jUFsELnB/ha5RQAUUUUAFFFFABRRRQAUUUUAFFFFAEcEjTW8UrwyQO6BmikKlkJH3TtJGR04JHoTUlV0ut+ozWf2edfKijl85kxG+8uNqt3YbMkdgy+tWKACiiigAooooAKKKKACqckd8dZtpY5oxp628yzxEfM0paPy2HHQKJQeR94cHtcqOQTF4TFJGqB8yhkLFl2nhTkbTu2nJzwCMc5ABJRRRQAVHBG0NvFE80k7ogVpZAoZyB947QBk9eAB6AVHdPcL5CWyZZ5VDuVDLGg5YkblPIG0EZwzKSCAasUAFFFFABRRRQAVHBG0NvFE80k7ogVpZAoZyB947QBk9eAB6AVJRQBXv5ri3065ms7X7XdRxO8Nv5gj81wCVTceFycDJ6Zrwf/AIXL8R9I/wBO1/wN5emRf65vsVxbYzwv7xyyr8xXqDnp3zX0BXJ/EvX7bw58PtYvLho98tu9tBG6o/mSyAqo2Pw4GdxGD8qtwcYoA0PC+v6L4s0tNf0do3FwixSsVAlQrk+VJ3BUuxxnHzZGQ2TuV43+zrot9Y+Dr7VLieT7JqNwDa25OVAjyrSDngs3ykYB/dDqCMeyUAFFFFABRRRQAUV5v40+NPhrwn9qtLaT+1NXt5RE9nCWRVP8W6XaVGOhAyd3BA5I4CUfEn40ytazQf2B4Zby5sSQuiSxliUIYjdO2054Kodin5SRQBt/G/x54XuvCep+GINRkuNYS4iUw2+8JGysGbewwjAAEbcthscArleQ8LeIfiX8Q9GtvDfhy5tNMsNKt4ree7hcwsFC/uy75Z8nyiP3YHU7uDx634O+D3hbwjF5htv7Tv3iaKW5vFDAq6hXVY/uqp+brlsMwLEGvJPhr4osfhUnjiLWJI57+3uIbWG1gfm5lRplO0kZCA8liOARwSQCAdXq/wAO9L8GfDrxNH4s8WT31vqMsdxBK1t+9juxuO6NWdt8j5AY5UlQcsBkjM+BWu33ibx1e3+t6vJdahaaOtnbxyRZZ4BKpLFx1Ktt+9lm8zOeDR4S8L+Jvi14gi8RePI5P7Bt0b7NZujQLMJBuURBSCEG5T5hJLbVGWwSr/iPYf8ACuPi5o/j21tN+m3cp+1okeQkhUpLgDaAzIxZctkuHJ4oA6y4+K1zp3xrfwZf29oulu8UENwqv5qyyRIy7uSCCzbegxuBJ4OfVK+eNDsbfx/+0hqGt2cfn6RpsqzPMWOxnijWOMqyZBzIgdckblUn1FfQ9ABRRRQB538cYIZvhJq7yxRu8LwPEzKCUbzkXK+h2swyOxI71h+A/GOl+B/gJoWrat57QtLNDHHAm55HM8pwMkAcBjyR09cA9h8Tdc0vR/AOtxahfwW815p9xDbRO/zzOybQFXq3LrnA4zk4HNeQfB34dv4mzf8AieaebTdEu5La30e4LbUn+VpN6Nwq5Iyo+833uBhgD6PrwOSO++HH7Q0LLNdnRfE9xuZVG5ZXlJG1sgLlJmB4yVRhydxB94nnhtbeW4uJY4YIkLySSMFVFAySSeAAOc0QTw3VvFcW8sc0EqB45I2DK6kZBBHBBHOaAOL8CeH/ABroWo6v/wAJL4jg1exuJTJa/KxkDEnJ5wI1xj92u5QehXB3dxXi/jj41O14mg/D6H+1tTk8xZLiKBpQhAYYiUf6xhjdu5TAH3snGB4q+FdxH4X1Hxj458VX11fx6eXFv5Q/c3LnKRbgXHliRyuFCj5sgqKAPoeivP8A4M6zruvfDq1vddfzpBK8VtcMQXnhXChnOeWDB1ycE7QTknJ9AoAKKKKAMfxP4n0vwjoc2r6vP5VvHwqry8rnoiDuxwfyJJABI8A8HeG7z41eOdR8TeIZJ10a2lCiDcfmXJKW6OAAFUfeIw3zA4BfcPc/HPgyx8deGpNHvpJISHE1vOnJhlAIDYzhhhiCD1BOCDgjQ8OaBY+FvD9noumrILS1QqnmNuZiSWZifUsSeMDngAcUAR2vhPw3Y+f9j8P6Vb+fE0E3k2UaeZG33kbA5U4GQeDWHrHwn8EazbzRSeH7S1eRAolskEDRkBsMu3Az85PIIOF3A7RjH+J2k/ESbVNL1XwRqcmyBGiksFeNBubP7xvMOyQYwMN90gFRySND4d6/40v3vdK8Z6BJaXdkkZW/QKIp8qOOGIZ+pJTKjJBCEAMAc3L4M+Ivgi8abwXrv9s6RHFGkelaxPvcYBXapO1VVc7vlZOgBB2jNix+Ntvpd5HpPjrRL7QNTGRJIIjJAwAI8xcfNtZ1YDaHHT5jyR6xWXr/AIc0jxTpbabrVjHd2hcPsYlSrDoVZSCp6jII4JHQmgC5Y39nqdnHeWF3Bd2smdk0EgkRsEg4YcHBBH4VYrl/Afgiz8BaHPpVldT3Mct3Jcl5gARuwqrwOyqoJ7nJ4BwOooAKKKr/AGG3/tH7eY83Qi8lXZidqZyQoPC5OM4xu2rnO1cAEk5mW3la3jjknCExpI5RWbHALAEgZ74OPQ1JRUckEMzwvLFG7wvviZlBKNtK5X0O1mGR2JHegCvBYNbXETJfXZt47cQLbSMrqSD/AKwuwMjPjjJcg9SM81YhMzITPHGj72ACOWG3cdpyQOSuCR2JIycZMlFABRRRQAUUUUAFFRwTLc28U6CQJIgdRJGyMARnlWAKn2IBHepKACvB/wBpe+uI9O8O2CyYtZpZ5pE2j5nQIFOevAkf8/YV7xXgfxo1TV3+K/hDSdKe0hu40R7OeeIOEmnlMeWDBhgeWhB25ByRzjAB2msaH8Nvh5p2l6rqdhYx3WlRZs32ILm6dQq7ti7RLJnadxHyk7sr1rzi+/4Sn4/a5ILD/iWeFdPlARp843cAsQP9ZNtJO3O1Qcbhu3N2/hr4H6dBeLq/jC/n8Rau21n892MIYBcA5O6TG0jLHaVOCleqQQQ2tvFb28UcMESBI441CqigYAAHAAHGKAKeh6NZ+HtDstIsE2WtpEsSZABbHVmwACxOSTjkkmtCiigAooooAKKKp6tqUOjaNfapcLI0FlbyXEixgFiqKWIGSBnA9RQB53e/FDVIPjQvgi10WC5tT5cZk8/ZICyLK8uTxtVC3yYySvDc4r1CvA/gPpTeIPEviHx3qNtH58lw6QMm3YsshLzbQcspAZADno7DnnHvlAHB2vwu00fEi+8Z6ldyajcyuj2kE0SqtsyqFB44cgKApIG3GTubDDpNH8V+H/EErxaRrVjezJvLRQzqzgK20ttznbnGGxg5BBIIJ2K+fPjN4a03wJf6R4v8LXEej6o1wIhaWwVFbCHMiJ0AAwrjBVt4yBk7gD6DorL8NalNrPhXSNUuFjWe9sobiRYwQoZ0DEDJJxk+prz/AMW/Hjwz4duJbPTkk1q7RFYPayqLcknlTLk8heflVhnAyDnAB6pRXyxZ+OPiT8RvFGnxaPq/2a6EpZbSyDxQ2yKEzLNkEPGT2YvzuAUblDfT9hDcW+nW0N5dfa7qOJEmuPLEfmuAAz7RwuTk4HTNAFiiiigD5k0vxbpGjfHrxT4h8UzSMbJ7uOzMcZLGRGESIAuBnygy5bA7k55rY1Tx940+K2stpPw/iu9O0uFE+03LOsUg3MPmeQElACDhUJZgGPzfdX1vxPp/gexim1vxPp2hru4e6vbWJnkIXhQSCzttXhRknGAK8n8X65b3nib4dzeB9bgg0SbUDBBaWtuYoYJxKodnjUox3LNgodvyk4OJCaAPaPCun6ppXhfTrHWtR/tHUoYgs91j75zwMnlsDC7jy2MnkmtiiigAooooAKKKKACuH+Lfid/Cvw61C6tp/JvrnFpasN2Q79SpXG1ggdgcjBUdeh7ivF/j19sv9R8FaBB5EkOoag2YLjIjkkBjRN7L84XErg7SDhvUDAB2Hws8Cp4F8JR28ozqd5tnvWKrlXKj90CucqnIHJySxGN2BqXXjnRbbxvY+EBLJNq12juUiAKwBULjzDngsoJAGT0JwCCfL7mw+PWp6pdWL31pY2c7yp9oiaBYkTnGwqDMB2U43DIJwckd58Pvhxb+B/7Quri9/tXV76UvLqMsRWQocHZyzdWyxOfmJGc7RQB5p8OrOHwB8fNV8JRXd3cWlzbmOHOAC2xZ1MgzglU3ruA6noAxx73fw3Fxp1zDZ3X2S6kidIbjyxJ5TkEK+08Ng4OD1xXk/jl/C3gX4gReLRBfav4u1DbHZ6VFMAoyoh34VSwyoKjO7czHA4yuPp/xB+JWleOfD48WabBb6Zr8qW8FkFRPL3GNS4wWkVlLqSrnuwwONoB7JokGtR2/m67e2k926IDFZQGOCIgc7dxZ2JJI3EgEKuFU5zqVwfxM+JkPw6t9OZtLk1Ce+eQIgmESqqBdxLYY5y64GPXkY57ygAooooAKKKKACo554bW3luLiWOGCJC8kkjBVRQMkkngADnNSVx/xE8EXHjzQ4tLi12fS4RKJJlSISJOB0DDKng4I+bHqCQpUA5vXfjjpFtqiaT4X0y78TagXKlLTKodu7dtYKxcjbn5VKkHO7ivLPh34X8a6pq3ibSdC1WDw5HFdxRapHFIxePEkmFiYbmO3a4/1g3cAsQSa9rX4XabpOl2SeE7uTQtWskKJqccSyvMrY3idTgSg9QD90hSuAMVw/wAMPDniPwn8S/EV/wCLbGOMT2U1zLrBOID++UsQ4IRQ2C5VgGAUHCjIIBJrngHxZ4T0O98QSfFjVX/s6JrhI5w+yV15VDumKnc2FwQc5xg5xXYfB/xjqnjbwbLf6v5Bure7NrvhTZ5gWOM7mGcbiWOcYHoBXkmpal4j+PfjFdN01ZLHw7ZOHzIMrCpyPNkwcNKwyFQHjkA43ufovQ9Gs/D2h2WkWCbLW0iWJMgAtjqzYABYnJJxySTQBoUUUUAFFFFABRRRQAUUUUAFFFFABRRVexuvt1nHci3ngWTJVJ02PtycEr1XIwcHDDOCAcgAFiiiigAooooAKKKKACo5I2d4WWaSMI+5lULiQbSNrZBOMkHjByo5xkGSigAooooAKKKKACiiigAooooAKKKKACvlT4m/EbTfHPiW2g+06kvhuyuI1EMKKDcLlvNnG4ja+3aqKwPBJOwkqfquuPi+HPgLR/t1+/h3So45MzXD3cYkjjAySQJMrGoyfu4GAOwGADH8F/Ez4ez/AGXw1odx/Z6xym1sreaBo1mA5DK3I+ck43kOzHkZPPpFfLlzoem/Fn4pRweEdGj0/wAO2aRxXdzbxLbq0Ydi0gAXAdgSqAgk7QSAAwX6joAKKKKACiiigDn/APhBvC3/AAkP9v8A9g2P9p9fP8ofe37/ADNv3fM3c78bvetyeeG1t5bi4ljhgiQvJJIwVUUDJJJ4AA5zUleT+IvgPo/iPxRJrVzrmq/6TLJLdxsyOzZHyLGxHyKvTBDfKABjGaAI/Ffx30iwuDpfha1k13VHfyY2jB8gSEsoAx80p3BeFGGDcPXnngXRP7T+Nt3p3xIt/teryRGZYp38wNcAJIoPlnYVEQb5T8mBtx0Fe7+E/h74a8FbpNGsPLupIlilupZGkkkA9ycLk8kKACQOOBjyfQf+Kn/aevNd0X/TdJs/9feRf6tf9F8kfMeuXBAxnIBIyATQB9AV5X8Y9H8Va54a1WGCDw+NBtbf7WZLhpWuw0QLsyYXYhIBTHOQTyN2B6pRQB5P+z5eaXL8OmtbIbL6C7kN8pfJZ2+44GThSgVRwMlG44JPrFfP/wAL9W0vSvjl4l0fSdSgm0jU/Me1FvBtjeVW8wImAcKitMoIIVguR1WvoCgArH8T+J9L8I6HNq+rz+Vbx8Kq8vK56Ig7scH8iSQASNivD7D4PeJNb8aS3fjzWv7Y0W2lmMMT3EmZ94BBVFIEK5IyAese0ArhqAMzwrpepfHDxZL4m8SpGPDNg728GnLK2NxUHYCpBBG5GZzjcQoAxwmn+zXqfm+Htc0nycfZrtLnzd33vNTbtxjjHk5znnd2xz7RY2FnplnHZ2FpBaWsedkMEYjRckk4UcDJJP41538PPhTN8P8AxVqd7b63Jc6Xc2/lx2zIVbdvyC+DtJVRgNjne3C4+YAufG3/AJJDrv8A27/+lEdeSat4hu28B+Evhz4JuZLy41GyEt/5Do77pSXaDcp+QBjJvBAwoXJxuz7v4/0BvFHgPWdHiWRp57ctAiMql5UIeNctwAWVQc44J5HWvAP2dYIZviNdPLFG7w6ZK8TMoJRvMjXK+h2swyOxI70Aer/Dnwr4Q8Aapc6FDrNpe+KZEBnaQqkpjOWVETJwNq7mAJPRm4KY5f4tvq/jrx5pPw906yu4beF1u7q5aIFCrADzlzj5I1ZxncNzErjIGafxvuPD9r4t0ybRYp5PHSSxMn2MKwUhlMRlQq2+TgBVGDgjdkBAfcND0+40rQ7Kwu9Rn1K4t4ljku5wA8pHc4/rk+pY5JAJNJ02HRtGsdLt2kaCyt47eNpCCxVFCgnAAzgegq5RRQAUUUUAFFFFAHm+p/HPwNpuoxWa3895ulMUs1pAWjgwQNxY43LyTlN2QD7Z9EgnhureK4t5Y5oJUDxyRsGV1IyCCOCCOc14/wDHLRvDOj+CNQ1NNDtE1bU7iG3S6it1DB97SsxPYsokBYctkA5A46z4P2Nxp/wo0CG6j8uRonmA3A5SSR5EPHqrKfbPPNAHcUUUUAFFFFABVdrby/MezWCCaaVJJnMWfMxtVs4IyxRQoYnjC8EDFWKKAI454ZnmSKWN3hfZKqsCUbaGw3odrKcHsQe9SVHJMsTwowkJlfYu2NmAO0t8xAwowp5OBnA6kA030lJPEMOstczmSG0ktY4Pl8tQ7ozN93duPloPvYwOmSTQBoUUUUAFFFFAFOW8mZ54bS0kkljR8PNmKLzAqlVLEEkNv+8isBtYHkYMkNjbwXlzdxx/6Rc7RLIWLEhRhVGeijJIUYGWY4yxJsUUAFFFFABXzp411rxG/wC0fCvh+wtLvVNOt1t7OCU7VkVoGkYuS6jIEsmOR0HXv9F14P8AC++uNc+PnjHV/M+12oinhS6iUGPYJo1hG5eOUj49QpPPJoAuW2l614h8Y22n+J/ibd6drkluZ5tB0ZzB5CHcyxiVW2F1BBIIZio6lQHq5/wz5o9h/pOgeJNc03U0/wBTd+ajeXnhuEVG5UsOGHXv0PKeMdK8Zah8d76fwfbXdnKr29vJqMPmLASIY5D5znKYA25TGCFX5STzJcfF7x7oeuR+Dymh61rcd39ma7TJSZ327IxtMaqyszKSQOmCAVJYA2zoPxl8GX6JousR+KdPZ2crfOu7OwD5/MYOoz0CSEfLk4yQfbKKKACiiigArm/HfhRvGvhO50JdSksBO6M0qxLIGCsG2spwcZAPBByBzjIPSVx/xI8dW/gPwvLfZgk1KX93Y2srEea+Rk4HO1Qdx6dhkFhQB5f8KPEOqeEPHf8AwrO6XSry382Ytd2R5SUReYQWwN+NhQhhuB4zhQtfQFfLGjQ+IPhvo+i/E/7V9rbWJXgvrK8jZZJEdmcHeck7xHv38YJXhwTXv+pfEDw/o/hK08RahdfZ7e8tFura2cr58wZVIVUz8zfOoODgZySBzQB0FjYWemWcdnYWkFpax52QwRiNFySThRwMkk/jXy58XvHlj4l+INmkax6loOkOEEccu1bokgzYcDIDYCZBI+TcvWuo1Xxr43+LyXGi+ENGksNDlfyri9lYqWTdgh5PuqCroWjTc2AeWUkVf0i38M/CH4g6J4ZGjyanqGqJCx1mV18yB5C8IWOPb8iFgScNnDnJbaBQBXn8NfEv4s3Esmt3Enhrw67kx2MgO7bngGIbWkIaNTmQr97cgxxXonh74R+CvD1mYE0aDUJH+/PqSLcO2CSOCNq9cfKBnAzkjNdxRQBn6ZoWj6J5v9k6VY2HnY8z7JbpFvxnGdoGcZPX1NaFFFABRRRQB5X8Wdb+G8L2MHi63k1W8t3dY7OymPmwBlVmZwsiBQQExuOTngEZI881LxJo/iLxN8NZdE8MT6HajVVkcfZEigmkaWBW8plwJNpTaWwD93gdB7nqXgDwprHiBdd1HQ7S61AIEMkoLK4AIG9M7HIBwCwJGB6DHhnxb+KkF94t07/hGHgkbR/MUX8kEU6PIzIcxB1YDaYxiQcnJxxywB7PrXxAs9O8aaT4SsLf+0dXvZR58STBBaQ4LM7E9WCgsE6kDtld3YV5X8Kfh3NpLz+LvEscc/iTUnNwrFSDbrKoZgUKrslLMwbHQcDALZ9UoAKKKKACiiigArx/4v8A/JQ/hj/2FT/6Nt69grx/4v8A/JQ/hj/2FT/6Nt6APWL+G4uNOuYbO6+yXUkTpDceWJPKcghX2nhsHBweuK+fPCfxnufDPgPWNP12e7u/FFlcMtnDqCuSwYgFXfG7KNvYhyDjCg/3fouvnT9oXwTNDqkXi+xt5Ht7hFiv2UEiORcKjtzwGXC8AAFBk5egCPQ7zWPAnjSy1bxP4XvvEHiLX4luorqFnMtvGR+8jSExD98ij5lBAVdqjaCc6+qxJ43/AGmrKCBd1r4fijaeaCdTkxEyAnI7SyLGyjJ4PTnHrmh+IYbzQYrjUrm0tdQgsobjVLcuENkzxhyJFY5jH3j83YV5nc/G7QrPWG03wR4Wn1ma6leeY2sZt/PkKhmdVCM7tgNuLKD8vcc0AR+MrmHX/wBoHw9ps11INN8P25v7tlUItrIoMxZ3IwEIW3BJOBnAIJNZfiH4heKfiXrg0D4eJfWlhHL5dxqSExh1OCrs4XdCo2PjDbnBxjPy1Q+LXw41Sb/hIvH+o3sEEZ+zPBZLF+8w2yIpIQxVWXjlS4YgkbQQK9X+EKaWPhfosuk2f2aOWImfIw0k6kpI5OTnLIcZP3dowAAAAdB4V0/VNK8L6dY61qP9o6lDEFnusffOeBk8tgYXceWxk8k1sUUUAFFFFABRRRQBGBN9odmkjMBRQiBCGDZO4ls4II24GBjB5OePJ/Efgjxv4/8AFV5Y69qkel+E7ZwII7BiftyFww3KW++FABZxhWAKqQSa9I8Qx61Loky+HprSHVA8bQtdgmIgOpdWwCcFQw4554I6ixpMd9Do1jFqk0c+oJbxrdSxjCvKFG9hwOC2T0H0FAFfQPDmkeFtLXTdFsY7S0Dl9iksWY9SzMSWPQZJPAA6AVqUUUAFFFFABRRRQAUUUUAFFFFABRRUYmVrh4AJN6IrkmNguGJAw2ME/KcgHI4zjIyARu3m3ixJLPG0OJXAj+SVWDqFLFcHBG4hSGGFzw2GsUUUAFFFFABRRRQAUUUUAFFFFABRUcMbRIVeaSYl2bc4UEAsSF+UAYAOB3wBkk5JkoAKKKKACiiigAqnLPfF51t7KMiNHCNPPsEr7VKY2hiEJLAscEFOFYEGi80u0v7i1mukkkNs++NPNcRlsggsgO1yCoKlgdpGRg81coAjhkaVCzwyQkOy7XKkkBiA3ykjBAyO+CMgHIElRxwrE8zqZCZX3tukZgDtC/KCcKMKOBgZyepJMlABXP8AjHwjZ+NtD/si/vL62tTKsr/Y5Qhk25wrZUgrkg4x1VT2roKKAMPwv4R0XwdpaWGj2UcICKss5UGWcjJ3SPjLHLN7DOAAOK3KKKACiiigAooooAKKKKAI54Ibq3lt7iKOaCVCkkcihldSMEEHggjjFU9G0PS/D2nJYaRYQWVquDshTG4gAbmPVmwBljknHJqS81bTdPuLW3vdQtLae7fZbRzTKjTNkDCAnLHLAYHqPWrlABRXJ+KPiT4U8IO8Gq6rH9sVGb7HADLLkKGCkLwhIYY3lQc9cZNeZv8AFH4heO5bqLwB4c8ixTKi9mVWcFWB++5EQYqy5jwxGSQT1ABiaR4YtPAXx8ydR02y0+0eW4iXUZ3th9nkQKAjuGDkecyj5ssYHzt4r6Tr5M+K3hPxXp+s6PeeKNbtNT1DVEaFXVxFHCUYDblgqKmHVs4UZL5HG5vquwtfsOnW1n9onuPIiSLzrh98km0AbnbuxxknuaALFFFFABRRRQAVw+lfDPTtG+JV74wsrudPtkUnmWe5tvnOwLvuDcqcE7GBAY5GMKB3FFAGP/wiuhf8JR/wkv8AZkH9s+V5P2vB3bcYzjpux8u7G7b8uccVsUV43H+0DY2viWbS9e8N6lpECPtEsxzKgJG1pISoKjadxwWPTAbOaAPZKKjgnhureK4t5Y5oJUDxyRsGV1IyCCOCCOc1JQAUUUUAFFFFAHzx+0Da+JNZ8UaRp9tok9xYwxMbSa0hklaWRwS6HC4DAQ52jJ2gsTzheX8Tad4p1fxQfD/jLxdv1Oe0jubWwhYzRm8YBIrZkXbHDIQeX+6A2cndmvpPxd4osfB3hq71i/kjAiQiGJn2meXB2xrwTkkdcHAyTwDXj/wJ8KTaxql/8QdaEkl3JcSfZHOUDyPu86XaAAR8xUYOAd4xkDAB7R4bsL7TPDWm2Op30l9fwW6JcXDtuLvjnnAJGeASMkAE5OSdSiigCvDE8V5cnb+5l2yBzOzkvjawCEYRQFQjaeSWJAPLWKjjnhmeZIpY3eF9kqqwJRtobDeh2spwexB71JQBGDN9odWjjEARSjhyWLZO4FcYAA24OTnJ4GOZKKjjhWJ5nUyEyvvbdIzAHaF+UE4UYUcDAzk9SSQCvqM80KW6W0tolxNcRoi3LEB13bpAuOS/lLIQPUZPANXKjkghmeF5Yo3eF98TMoJRtpXK+h2swyOxI71JQBHHCsTzOpkJlfe26RmAO0L8oJwowo4GBnJ6kkkBma3ia4jjjnKAyJG5dVbHIDEAkZ74GfQVJRQAUVHBBDa28VvbxRwwRIEjjjUKqKBgAAcAAcYojmWV5kUSAxPsbdGygnaG+UkYYYYcjIzkdQQACSiiigAooooA8z+KXxWtvA6T6PBb3ba1cWXnWkyqnlRlmZAzEknKlS2NpBwBkZJFP4A+GH0XwNJqlzB5d1q0vmqTuDGBRiPKngZJdgR1V1OTxj1C6sLO+8j7ZaQXHkSrPD50Yfy5F+6656MMnBHIqxQB8wX2t+IvEvxo8QweB7j7BcalttJJLtI4JlSNFjcguPMTBUttTD4UHblePW/h/wDCDRfAlx/aBnk1LVthQXMqBViBJz5ac7SVIBJJPBxgEg+UWtsvxI+PmuS2Gr3elFUm+y3unFpSTEghV/MTAVGA3E5AIOwHLA16H4I+JGo2nii58DePJoE16GXZb3se0R3W4BlU4AAYhgV4GQQCAw+YA9YooooAKw9cuvEcWqaNb6Hp9pNaTXBOo3VzLgW8K4yFUEFnbJAPIBHIwcjcooAp6tqUOjaNfapcLI0FlbyXEixgFiqKWIGSBnA9RXgHhrTdS+O3jGfXfETSQeHdNcJDZRlgrZ58pXwBnABkf73KgBQV2/RdV7Gws9Ms47OwtILS1jzshgjEaLkknCjgZJJ/GgDx/wDaI+x2fw80jToPIg26hH5FsmFxGkUinao/hXco44GR6io9H+CEOujRdX8T6xqVxGmmWkY0ySMRNBsjTMRbqEB3jaFVvmyW3ZJy/jfpeqeJfid4U8PoJ1sbmIJDIlvvCO8hEzjGN21FjYjPAGeM5r6AoAr2NhZ6ZZx2dhaQWlrHnZDBGI0XJJOFHAyST+NeD/G7/iQ/FPwf4ouvnsY/KzHFzJ+4n8x+DgciRcc9Qc4r6ArwP9oqFtV1nwho9kY5tQmeZEtxIobMjRKmcngMwIBOBwfQ0Ae+UUUUAFFFFABWX4j1yHw14fvNZuLW7uYLRA8kdpGHk25AJAJAwAdxOeACe1alFAHz5qvjXxv8XkuNF8IaNJYaHK/lXF7KxUsm7BDyfdUFXQtGm5sA8spIrP8AD3hCH4efGnwpo97qEcxvLLzbwuwWF5iZfLRQcZAkjiK55LqrAA4A+j4IIbW3it7eKOGCJAkccahVRQMAADgADjFeP+KIIbn9p3wkk8UcqDTC4V1DAMouWU89wwBB7EA0AeyUUUUAFFFFABRRRQAV4v8AtF2vleHtB1yC4nhvrHUPKgeJ9u3ehctkchgYVwQRjn2x7RXl/wAftM+3/C+e587y/wCz7uG527c+ZkmLbnPH+tznn7uO+QAeoVj+J/E+l+EdDm1fV5/Kt4+FVeXlc9EQd2OD+RJIAJFfwnf/APFvND1HUbv/AJhVvPcXNxJ/0yVmd2P4kk1HeReEPiHpc+my3Gm63aRujyJb3KuYm52ncjZQnDDIIyNw6ZoA+fLvSLPxtLq3xJ1vRp9D8Lx7Q8WnEPPeTMwQuhfC/fbLNgA4xgtuYej6rZeNvDn2Kw+FmgaUPDX7u8W4iuY5Wui2SwdpXBKkbeVydoXDj7q4f7Q+vrp2l6P4Q05o7eB0+0T28KsgWJPlhUYwuzIc7ecGNTxgZ9c8Cf8AJPPDX/YKtf8A0UtAHjHxE8feKZvAmpeHfF3g+fTbqfykXUICWtnkEqShQeR9xW6OxyvQc7fT/hbrnh+98DaFp2k39i11Bp8ZntIXVZEcACRmj6jLk5YjktnJyCfP/i/fXHjf4gaJ8ONMk2qkqzXj7QdjlSc4bGdkRZ+G+bfjqK7DxD8FPC2r3g1HTBPoOpR/PFNppEaLIANjeX0G0qD8mwnJyc8gA9A8m4Wfel1ujaXe6Sxg7U2Y2IRjHzANlt3Vh0I22K8XitfjF4FlVo7iDxdpEXmFonfM/lhg2SWxIZCMgAGUDpg4WtCx+PWhR3ken+JdI1XQb8Z+0JPCXSHgsucYkORt/g/i9OaAPWKK4/TPip4G1fzfs3iaxj8rG77Wxts5zjHmhd3TtnHGeorqLG/s9Ts47ywu4Lu1kzsmgkEiNgkHDDg4II/CgCxWX4j1+x8LeH7zWtSaQWlqgZ/LXczEkKqgepYgc4HPJA5q59ut/wC0fsAkzdCLzmRVJ2pnALEcLk5xnG7a2M7WxJPBDdW8tvcRRzQSoUkjkUMrqRggg8EEcYoA8Tt/jV4p8XXkll4H8GefJHLzPdyl0EeGK7wNixsduRlyOCBk81kaZ45+JWl/FDRvDOsarBqM00sAvbOG0Q+SsgBYMyRr8yId5KkqMckgEV3/AMUvH9n8PfC66dpMkEGszRLHYW0cQKwRg7d5XgKoAIXsWA4IDYp/Cb4ZroFunifXTJdeJL9POLXAbdahxkg7ufNOTvY88lRxksAeqUUUUAFFFFABRRRQAUUUUAFFFFABVOyhuYrrUXnMZjluA9vtkdiE8qNTuDHCncr8LgYwfvFiblFABRRRQAUUUUAFFFFABRRRQAUUUUARmCFrhLhoozOiMiSFRuVWILAHqASqkjvtHpUlFFABRRRQAUUUUARwiZUInkjd97EFEKjbuO0YJPIXAJ7kE4GcCSivn/xNq1/8MfjydXnmnGga55bXGZJHRk2hGJJDZaNvnCrnClVG0NigD3TUtW03RrdbjVNQtLGBnCLJdTLEpbBOAWIGcAnHsasQTw3VvFcW8sc0EqB45I2DK6kZBBHBBHOa+ZNYubj4165retSTT6X4e8O6e8sYCCZ8DLcxmRR5jhXJIOBsVSTgMez/AGb59Xm8NaulzLI+kw3CJZKzAhJMFpQvcD5ozjpkkjktQB7ZRRXi/wAFtQ8QeK/EPiLxVfajff2RJLJDa2E1000cbu4kIUE8eWu1R8oyH4xgigD2ivP/AIw+Mv8AhEPA1x9mm8vU9Qza2m1sOmR88gwwYbV6MM4Zkz1rz/wRdX/hL9oTV/C0lxPPY38tw6RG7kdELoJ1kYNnfJsUISeeT8xxyfG7/iffFPwf4XuvksZPKzJFxJ+/n8t+TkcCNccdSc5oA9I+EkWur8OtPuPEGoz3t1d5uYjOwd44X5RS+SXyPmyeRv28ba7io55Ght5ZUhkndELLFGVDOQPujcQMnpyQPUipKACiiigAooooA+eNb0dPiN+0hPpd4/2zSNOiUTCCVV2RJGCyFhyf37lWx8w3EZGONPxj8RfG+ueMdV8D+DtHkgliRomndSlwAOWlVywSNGBAVjyQykEFlAsfAGCbVrjxV4wvIrQT6je7AY1O6NsmWUDOSEJkj4yc7OegNHxM+JviMeJR4a8ALJdXdohlv5rO2+1OrA7THtKsAFyu488kLlSCCAcZ44+FVn4L8PadrniHWL7Ub681WJNQe3x/q3Rnl2F8l5Mq2HYgHPKiu/8AFHj288Cad4O8JeFRY6xqdzaW8UcswPlyR4EcTLhgv7xgSDvwAvOcg1h/F6/1rU/gjoF94hsY7HVJ9TR5rdFKhBsn2cEkg7dpIJyCSDjoJPhl4b1Hx340k+JniS1ghjba9lFCqmKaRAYt2GZmXZ5YIzgliCDgYIAftF/Y/wC0fBv9o+f9h824+0fZ8eZ5eYd2zPG7GcZ4zXvFeL/tI2NvJ4N0m/aPN1DqHkxvuPyo8blhjpyY0/L3NewWF9b6np1tf2cnmWt1Ek0L7SNyMAVODyMgjrQBYqOYzKgMEcbvvUEO5Ubdw3HIB5C5IHcgDIzkSUUAFFeT658QviBoPi29tj8Pp9Q0gbvsjWYkd3UNgO0iB1GdrHYVDDcuemTseG/ivp2qajbaPr2mX3hvW7jHk2eoRMqzZLBdjkDOduPmC5Y7RuNAHaalq2m6NbrcapqFpYwM4RZLqZYlLYJwCxAzgE49jVyvN/jd4YTxB8Oru6jg8y+0r/S4WG0EIP8AWgk/w7MsQCMlF64wdj4W6zZ638NdCls3z9mtI7OZCRuSSJQjAgE4zgMM87WU4GaAOwrD8UeEdF8Y6W9hrFlHMCjLFOFAlgJwd0b4ypyq+xxggjityigDyf4HTf2fZ+JvCT3M9zJoeqyRrK4whjYlRtGTty0UjFeg3dSSa9YrD8PeFNN8NXGr3FkJGn1W9e9uZJdpbcxzsBAB2AkkA5xuPPNblABRRRQAUUUUAc/4r8F6F41s7e21u085beUSxOjFHXkblDDnawGCPx4IBG5BBDa28VvbxRwwRIEjjjUKqKBgAAcAAcYqSigAooooAr+dt1HyHuYP3kW+KDGJDtOHbOfmX54xwBgnkncALFFFABUckEMzwvLFG7wvviZlBKNtK5X0O1mGR2JHeiGCG2QpBFHEhdnKooUFmYsx47liST3JJomEzIBBJGj71JLoWG3cNwwCOSuQD2JBwcYIBJRUYnha4e3WWMzoiu8YYblViQpI6gEqwB77T6VJQAUUUUAFFFFABRRRQAUUUUAFY/inw3Z+LvDl3od/JPHa3Wze8DAONrq4wSCOqjtWxRQBx/gj4a+H/AX2mTSknmurjh7q7ZXkCcfICFAC5GeBycZJwMeWfEnUrTxB8c/C1joCxzapYXEMdzdIHmVWWXfsaMEAiMBmYhh95gSNnH0HXP6b4H8NaRrl3rdlpECandStNJdOWkcO27cVLE7M72ztxnOOwoA6CiiigAooooAKKKKACiiigDx/xPf+Kfhf4tm8SNd32ueD9Ql/0u2lkLvYMzceXnhVycL0U8I2DsY4fi1IfGHx/wDCVxoV7aXtpbJbGW6glEkSNHJNMULpkBykbEA9fpk17pf2NvqenXNheR+Za3UTwzJuI3IwIYZHIyCeleX+Dvgvb+DfiL/bdtd/atMitGFqs7kTxTthSTtAVl2F+ePvYxxuoA9YooooAKKKKACiiigAr5/+Kf8AycL4K/7cf/St69Y8Q/EXwj4VvBZ6xrcEF0esKK8rpwD8yoCVyGBG7Ge2ak0dvDPjew03xXDpdpdO6Bra4urRTPCUc/KCQSpVw3Q4zkgnrQB0lFFFABRRRQAUUUUAFYfjLSm1zwVremRW0dzPcWUqQRPtw0u0+X97gENtIJ6EA8YrcooA8z+BGtzax8MreGfzC+nXElmJHkLl1ADr16ALIEA5wFH0Hea5rNn4e0O91e/fZa2kTSvggFsdFXJALE4AGeSQK8E8C+L5vh98UtU8EPp8i6Le6w8VtCVIkt2dwkTgtyyMvl5yTxhgeob3PxJ4W0bxdp0dhrln9rtY5RMqea8eHAIByhB6MfzoA+fL/wAL3N/8KfFHxE8RRxyatrDwy2iuj5tYjcIMrvPAZcBeuIwuGwxFd3bfEyz+H3gbw/Z6paX100nh+2urGbaAlxIRgwbgoC7AUOeTtPOWxv6D4n+Ftd8R+ErPwz4ZisbazllVLkyOI0ihjUsihQh+XcqfdwRhRgqWI7DQtM/sTw9pmk+d532G0itvN27d+xAu7GTjOM4yaAPN/hL4W8QRa5rvjLxZZ/Z9T1baLdHlYvHE3zspQk7V4iUKx3L5eMDv6xRRQAVXvrCz1Ozks7+0gu7WTG+GeMSI2CCMqeDggH8KsUUAc/8A8IJ4P/6FTQ//AAXQ/wDxNamlaVY6HpdvpmmW0dtZ26bIok6KP5kk5JJ5JJJyTVyigCnpN5NqOjWN7cWklnPcW8csltJndCzKCUOQDkE46Dp0FXKz9Z1zS/D2nPf6vfwWVquRvmfG4gE7VHVmwDhRknHArj/+F2/Dz/oYf/JK4/8AjdAHJ/EX4ReKvFHjxvEWj65aRIEiMAuJpY3tWQdEKq2BuG/Ixyx47mn8M9S8SWvxh1bwx/wlM+vaZZxP9qlvzJu3R4U+UGLFWWR9p52soJ5+XHuFhfW+p6dbX9nJ5lrdRJNC+0jcjAFTg8jII614J8CrK+vPiX4s1m6lkZ4kkhuPtMPkztLLNuy0YyEP7p9y54JAGaAPa/E/ifS/COhzavq8/lW8fCqvLyueiIO7HB/IkkAEg8MeJtO8XaHDq+lmc2svA86FoyGH3hyMNg5BKkrkEAnFeIeK7y4+MfxVt/C+kXvmeHNNxJcSRTgI4UgSzLlfmb5xGv3h/EPlZjXqeueJ/Dnwr0vRrCXTru20mVzbxSWkG+K3xg5kOc5OWbjczbXPJoA7Sio4J4bq3iuLeWOaCVA8ckbBldSMggjggjnNR3119hs5Lk2886x4LJAm99uRkherYGTgZY4wATgEAsUUUUAFFFFABRRRQBHPBDdW8tvcRRzQSoUkjkUMrqRggg8EEcYqSo4I2ht4onmkndECtLIFDOQPvHaAMnrwAPQCpKACiiigAooooAKKKKACiiigAooooAKKjkExeExSRqgfMoZCxZdp4U5G07tpyc8AjHORJQAVHNPDbIHnljiQuqBnYKCzMFUc9yxAA7kgVJRQAUUUUAFeb/G7wwniD4dXd1HB5l9pX+lwsNoIQf60En+HZliARkovXGD6RUc8EN1by29xFHNBKhSSORQyupGCCDwQRxigD5Qi8W+HLP4P6P4WjmuxPe6n9o15LaPLeSsnRWk+UOVWErt4+Q7sZOe3+CHxG01H0zwPBoUls8qSub0Tq5mmCl2LqEXA2qQDliAqLz1HUSfs++CHvYZ1GpRxo+5rdbkbJBvLbWJUtjBCcMDhRzuyx9I0rSrHQ9Lt9M0y2jtrO3TZFEnRR/MknJJPJJJOSaAMfxL4+8L+ELiC313Vo7WedC8cYjeRtoOMkIpIGcgE4zg46GuD/Z3KQeDdXsGnge6t9VkMiRTLJgGOMBsqSCpKNhhwdpwTXUeN/ht4W8W6jba5r8k8P2CLErrcCON4VJfEhI4UZblSpwx54GPnzQfC03iPx1JomjXkll4X1e9ldE+1mL7RZwykghHyzkDIUsp+ZW/uOVAO4+J8EMP7RPhB4oo0eZ7B5WVQC7faWXLep2qoyewA7Vb0p38SftUXt3a3nnWukRSAiUsNgWIQuiAjtLIx7A/MQTnmv8U/+ThfBX/bj/6VvWx4y+GU+kajrvizS/HU/huxupUuLyOPzUUZI3sWWTLsSzsq7eWYKMZzQB7RUckbO8LLNJGEfcyqFxINpG1sgnGSDxg5Uc4yD8kaDputeEvHnhePwpqsd1qmqWVtcSR+WdkQmG5oZlUsSgUByeCBhgBgGvrugAooqOeZba3lncSFI0LsI42diAM8KoJY+wBJ7UASVh+NJ5rXwL4huLeWSGeLTLl45I2KsjCJiCCOQQec1sQyNKhZ4ZISHZdrlSSAxAb5SRggZHfBGQDkDi/jBfXGn/CjX5rWTy5GiSEnaDlJJEjcc+qsw9s8c0AY/wAAdM+wfC+C587zP7Qu5rnbtx5eCItuc8/6rOePvY7ZPH674o1FPHOp+GfhNpXlanLdy3Or33lq7zzKTvXM2VSNWJ5OAWOFxn5+8+Gui33/AAo/TtLaeTT7u7spzFcRnLQiZnaOQbSOdrq2Mg/Q1JpmmeFvgp4GluLib0NxclR517Ng7UVc/XaucKMkn7zEA80+JGpa5B4K0TRPiKsj3ba68hubARZns4lAZ1wQA584hQVXhRuAPX0P4Gag998L7KNtO+xR2kskEbAsVuADuMo3erMwOCRuVsYHyjxjT5/+Fq/HS3j8RJi1nlkjNtbXXmIkcUbssayAkFSU5ZMA7mK4zx9Djx14H02zhii8SaHFbx7IY4oLuIhASFUBVPCjI7YUDJwATQBj/G3/AJJDrv8A27/+lEdanwx1KHVfhl4duIFkVEsktyHAB3RDymPBPG5CR7Y6dKseJp4dd+HOuvo8seoJc6ZdJA1owlEreW64Xbncd3GB34rk/gDqf2/4XwW3k+X/AGfdzW27dnzMkS7sY4/1uMc/dz3wAD1Ciiq99f2emWcl5f3cFpax43zTyCNFyQBljwMkgfjQBJPPDa28txcSxwwRIXkkkYKqKBkkk8AAc5r588MO+t38PxU+JV5Yx6TF+60wKWR0lSfdHsjjGWVT5owxZuCSCBurr9W+OGnXd5BpHgiwn1/V7v5YMo0MKMQ3LbgGO3AJGFXaSd4wa4j4ReEtL8R+IdTtfGUk9/q+g7LaDTruffHFHG5BxhjvVWG3bjYAw+9vGADq/Cfxgm8f+PD4di8ORnQbi3mWUzZkcKAfnkGNgRhtQoc8uPmPQnw4M3gb4g+LvB13HHbaKiPrFpK7lY4YcqudzjLDayAsWwphbrkmvWIYNN0LSykEVpp+n2yM5VFWKKJeWY8YCjqSfqa+aPiP4vu/iJql9d+GdPji0nRbdhPqjKkMssbbkwZGwQjb3VYQcvuYlSflUA9D+Amq67q+na/PqGqX2paYl2kdlPfOGkLAEvkFmZflMR25Kgk4J5Neoafrml6reX9pYX8FzcafKIbqON8mJ8Zwf1H1Vh1UgZ/gabS7jwNos2i2sFpYSWiPHbwSeYsRIyyFv4mDbgxPJYHPOa8n+HH2P/hozxn9h8/yfKut3n43eZ58XmYx/Dv3be+3Gec0Ae8UUUUAFV7++t9M065v7yTy7W1ieaZ9pO1FBLHA5OAD0qxXh/7QGs3l5LoXgnS333WpSiWaAEoXywSFSxIUqX3kg9CinjuAeieB/iHovj+3vJdJS7iezdVmiuowrAMDtYbSQQdrDrn5TkDjPWVz/g/wfpfgzQ7fT9PtoFmWJEubpItr3LjOXY5JPJYgEnbnA4roKACq9/fW+madc395J5draxPNM+0naigljgcnAB6VYqOeCG6t5be4ijmglQpJHIoZXUjBBB4II4xQBz/gzxzovjrS3vtHlkBifZNbzgLLCecbgCRggZBBIPI6ggdJXz/pvw4+F3i7xbq+j6bc65p9/p8skclirrsCxMsbOjMr5Usf4m3cngDFe8WFr9h062s/tE9x5ESRedcPvkk2gDc7d2OMk9zQBYooooAKKKKAK81mkt5bXWdk0G4BgiksjD5kJIJCkhGO0jJRcnAwbFFFABRVexuJbuzjnmsp7KRs5gnKF0wSOSjMvPXgnr68VJBMtzbxToJAkiB1EkbIwBGeVYAqfYgEd6AJKKjMELXCXDRRmdEZEkKjcqsQWAPUAlVJHfaPSpKACiio55Ght5ZUhkndELLFGVDOQPujcQMnpyQPUigCSio4DM1vE1xHHHOUBkSNy6q2OQGIBIz3wM+gr5g1PVdf+BniXW9A0q5knsL63E1g1xtYRliAJtoyN6hZE/hDEKxBAC0AfTf2+z/tH+zvtcH27yvP+zeYPM8vO3ft67c8Z6ZqxXzB4k+GFx4R8DR+JtVvtVm8YXF2JYxaSB1gfBmZpH5ZmVI5GLqeGx1ALH3vwBqWr6x4D0bUddWMahc24kcoAA6knY+AcAsm1iBjBJ4HQAGxqWrabo1utxqmoWljAzhFkupliUtgnALEDOATj2NXK8r0PxV4c+OGl6zoN9o13BFbuHidhuKqchJVcDEcvX5DnIzguNwHN+GNR1H4WfFWHwJe6hPceGr7nTvO2kxtKfkPygn76tGR8q5YvgZoA94oory+++OGhWPj6TwybC+njjlFq13Ahc/aN4UoIsbmUc8jJJGApGCQD1CiiigAooqvY39nqdnHeWF3Bd2smdk0EgkRsEg4YcHBBH4UAWKKKKACsfxD4q0LwpZi61zU4LKNvuByS8mCAdqDLNjcM4BxnJ4rYr5Y1VYvE3xo1Gbx/cwadY6fKy3Fobh1/wBHjRigiLAF1YhCQoDv5xZFGSVAPovw14w0Dxhbzz6DqUd4kDhJQEZGQkZGVYA4PODjBwfQ1uV84fCPSX1b4r3HiTwvps+leFbXfCySzsd4MeBHkk72LbZCuSF45+7n6PoAKKKKACiivK/EnjL4kXGqaloPhrwPJDPA7iPU7iYNFJF0V0LBU3ncrbSzYwQVODgAp+L/AIafDXw3Z6n4k18XxjmljkCNeO7vIDuZI8nc7SYO7cxIySCgGRh+EfDutePNUtNd0XVLvwz4P0y4EelWMWS5EeAzhSNhL75dzsZDksp3jNcx4h8F+I9Z+KWkeGNW8QXfiGd0S71Awv8ALp6yP++ChiVQBQpHyrncgC8gH6L0Y+H9J2eGNInsYZLCIH+z4ZlMkScHcy53c7gSx6lskkmgDP0zx7o+r+OdS8JWYnkvtPiMs0wCGE4KBlDBidwLgEEDBVh2rqK8P+Bcyap4y8e6vLawR3U12jgpIs3liSSZmRZF4ZcheRw20H0r3CgAooooAKKKKACiiigD50+MltD4Y+Mfh3xTJayCzle3uJ3jYM0skEg3gKTwRH5Q7A/XNfRdc34z8DaL460tLHWIpAYn3w3EBCywnjO0kEYIGCCCDweoBG5YWNvpmnW1hZx+Xa2sSQwpuJ2ooAUZPJwAOtAFiiiigDD1/wAY+HPCyM2taxaWjhA/ks+6VlLbQVjXLsM55APQ+hqTw94q0LxXZm60PU4L2NfvhCQ8eSQNyHDLnacZAzjI4rx/xLpGheKv2i9P0pNGnMlri51e5JLpPthRolZDuVYxiNCcDd5hHXBNfwDpmnXf7QmtX3hSbydAsomZjaK3kSsyKpjznAUyF3UcqfK+UYAIAPoCiiigAqnqWrabo1utxqmoWljAzhFkupliUtgnALEDOATj2NXK8L+P2lwxaz4V8Qakl3c6LHcfZr63WUbQu4PhFyCHdRICwP8AAvIwMgHpnjTwBoXjyzgg1iOcSW+77PPBKUeLcVLYBypzsA+YH2weap6b8JfAelXDT2/hq0d2QoRdM9wuMg8LIzAHjrjPX1NdRpWq2OuaXb6nplzHc2dwm+KVOjD+YIOQQeQQQcEVn+MNAm8U+E7/AESDUZNPe8RUNyiliq7gWGAy5DKCpGejHr0oA871P4haz418cxeFPh7fwW0NtmW/1ho0lUopCt5asCGUFgMj7zEYIUFjxnwKmvrbwr8QJ9LEh1COyie1Ece9jKEnKYXB3HdjjBzXofhvTtC+B/g22OuXeZtQuwl3exQFlWQxsyrwN5jAQgcE7mJwoY44/wDZl/5mn/t0/wDa1AFD4NfEHwj4K8G6mur3PlanLdtKI4rZ3kmjWNdi7gNv3t+AzAAknjOauQSa18ePGMUrwyWfgjS7gM0UhI84j+E7SN0rKccHEatwcn5/X9a8AeFPEOqQ6nquh2lxeROH80gqZCNuPM2kCQAKBh8jHHQmtS6v9H8OadB9su7HS7FNsEPnSJBGuB8qLnAHAOAOw9qALkEENrbxW9vFHDBEgSOONQqooGAABwABxipK8f8AEn7Q3hrTPLTQ7WfWpGwWb5raNRzkZddxYYHG3GD1yMV6Z4c1yHxL4fs9Zt7W7toLtC8cd3GEk25IBIBIwQNwOeQQe9AGhJPDC8KSyxo8z7IlZgC7bS2F9TtVjgdgT2qSqd5Dc3FxaxoY0tFfzJ28x1kJUgoq7SOC3LEkghdpUhyVuUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQBGDN9odWjjEARSjhyWLZO4FcYAA24OTnJ4GOZKKKACiiigAooooAK4/xN8UPCPhLUTp2q6ptvh5ZeCGJ5GRXIGWIGBgHcQTu28gHIBPiJovizXtDisvCmswaXMZQbiR2eN2QcgLIgJXkDIA59QAQ3lmu/CDRfA3wr1XV7+eS71xLdF88IJIoXdwhWONsAgh9u9slcb1CsAKAPd9K1Wx1zS7fU9MuY7mzuE3xSp0YfzBByCDyCCDgirlfPHwk8ZaT4Os9RvPEM0Gjabq/lz6bZwNLOnyF45XCbpHTLKoy+N23jIXj0fxp8UtO8P+C4Nb0Zf7VkvomksjGjGIKCqtJKR91VZ0UqcMWYLwckAHP/GXRPHPirUdM0DQ7f8A4kFxt+0TBwqmbLH97zu8tVUMOMFiPvNtAsaB8HP+EW8beHdX0u9gexsYpPtscse2R5mg8sujYJKscNsZsId2372B0Hwq8Xax428JSavrNnBbSG7eKDyInRJI1VfmG5jn5i4yDj5cdQa7igD5/wDi1/o/xu8PatL8tjpVpZXt7L18qEXhUtjq3LqMAE89Kqa5f33xz+INtpGjG7Xwnprq1zKW8tSMndLyvDsuVRWBPBOFBfHX/FP4R6l498VabqllqVpbQJbra3KzK25FDs29McMcOflJX7o5547jwN4MsfAvhqPR7GSSYlzNcTvwZpSAC2M4UYUAAdABkk5JALEHg7w5a+IItdt9HtIdSitxbRzRpt2RgbQAo+UEL8u7Gdvy5xxW5RRQAUUUUAFef/G3/kkOu/8Abv8A+lEdegVw/wAYLG41D4Ua/Dax+ZIsSTEbgMJHIkjnn0VWPvjjmgDU8FzLbfDTw9O4kKR6PbOwjjZ2IEKnhVBLH2AJPavI9J8MeIPjF40n1jxlBPpuiaZL5MelNuRwSFfy8HDDKsjPIQC2QFwMbPQPBPify/gbY699jz/Z2lSfuPN/1n2ZWT72ON3l56HGe+K3PAPiWbxf4I0zXbi3jt57lHEkcZJXcjshIzyASucc4zjJxmgDL8XfCfwt4x+yNeQT2c1rElvDJYyCPbCu7bGFIKBQWPRc8AZxxXjFj4f0LxF+0JHo2naL9n0TTJTFNDHGWD/Z0ILS7iwKtIoUnjcGGfmYk/RfiXUptG8K6vqlusbT2VlNcRrICVLIhYA4IOMj1FeR/s5+GbOHQ73xM43X88r2aETBgkI2MQVB+Vi3XdzhVIABywBsa5peqfCm8vfEvhSy+2+HJ90up6GH2C3fH+vhIB2rwNwAOB22gGPP/ZuvreTwbq1gsmbqHUPOkTaflR40CnPTkxv+XuK9oqvDYWdveXN5DaQR3V1t+0TJGA8u0YXcw5bA4GelAFiuD8d/CzTfH+qWN5qOqalAlqhj+zwOuwqdxJUMDtcsUy3OQgGOhHeVj3+p6i3he5vtJ0ueXU/KcQWVwFRhMCVAfcyrtDckhsFRlS2RkAk0Dw5pHhbS103RbGO0tA5fYpLFmPUszElj0GSTwAOgFeN+J5U+GPx5tfE07eRoWuROtyYIF4O0BwVBycOIpWYAE7iBuOc6Fj8TfiZqFnHYQ/DedNXfOLqeOWG2GCTyrgY+UY5k5P8A3zXIfE2DxJZaHHqPjzWILm/1DcthocMchgtsYJk3JIiiSPzCoLCTOQMsMlQDU1/X9a+N/iVvDHhhpLXwvbOGvL11IEoB4dxwcZHyR8Ekbmxj5PY9H8F6FofhJ/DNpaf8S2WJ4rgMxDz712uzsMEsR3GMcAYAAEfw+ghtvhz4bSCKOJDplu5VFCgs0YZjx3LEknuSTXQTxtNbyxJNJA7oVWWMKWQkfeG4EZHXkEeoNAHifhbxTL8N/CXivwtey/adX8PyyPp0BRM3EMi70YIr7mUfNLJySiN14wJP2fPBM2laXdeKNQt5Iri/QRWauCp+z8MX68h2C4yAcICDhqxPGfhDS/h58QPDfiLVZb7XtIvJWg1B9YH2nYduwMz7TuwrblQrn9ycH+79D0AFFFFABXgejCHxn+07qd1LJI9voqOYoblA43QhYcKMkKBKzSAjuM4BPHvleH/s9WdxqH/CS+K78wTXV/diLztgEgfmSXoAFVjJGcDqV6DAoA9wqnqWrabo1utxqmoWljAzhFkupliUtgnALEDOATj2NXK8v+PmjXmr/DVpbNPM/s+7S8mQAljGFdGIAB6bwxzgBVY54oA9QorzfwN428NaV8HtF1S81L7NZ20SWcxnkaWRZ14ZAoLNzyyqOiEHAUcZY+MGpeKtZfSPAHhyS/IdUbU77clvD8x+Z1UZCFVJGWVieNpIwQDn/hlBNbftE+NEnikicpeOFdSpKtcxsp57FSCD3BBr3yvlCBPinefFXUb6ys57PxS8TyzRgRxIYARF8okOySMEKActnaGySM16PoPxX13wpeRaL8UNMnspG3mPVhECkmArBSsYKtjdgsmcZUFc5agD2iiiigAooooAKKKKACiiigAooqvN9sN5bCHyBajcbhnyXPGFVQOBknJYk4242nduUAsVGIVW4ecGTe6KhBkYrhSSMLnAPzHJAyeM5wMSUUAFeP8Axr+GeqeMpdP1bQbeCW+tomhnjefY8qbgUC7vk+UtITkr178CvYKKAPnTxH4G+IfxN+IN5DrUUemafYOEhkYs1ukTEf6k4HmuV+Yn5eQA2z5VHsfgXwjceDdDGm3Gv32rYwFNzgJEBwFjXllXaFG0sR8uQBk11FcX8Q5/Glrb6ZceEpdNhgiuN+pSXzKqpCBnLFuBEBuLlfn+7t70AeYfArQIdI+Jfiy3bUY5J9MSSxSMqFadfOw0oG4kAGJQRz/rBz6v+Kf/ACcL4K/7cf8A0remfDK91LS/jn4gsLjSo7k6pcXbSX627R+UscsmXTO7ETSLsIyfmCjcSuC/4p/8nC+Cv+3H/wBK3oA7z4xeOV8HeDpILeWRNW1NHgsygYbBwJJNwI2lVbg5zuK8EA4PhN8OYfA/h9Li9tox4gu0/wBLk3h/LXOREpxgADBbGct3IC47i80nTdQuLW4vdPtLme0ffbSTQq7QtkHKEjKnKg5HoPSrlABRRRQAUUUUAFFFFABXD+PPhboXj2WC6vWntr+Ly4xdQuSTCrEtHtPy87mw2Mg4PIG09xRQBT0rSrHQ9Lt9M0y2jtrO3TZFEnRR/MknJJPJJJOSauUUUAFFFFABVe/vrfTNOub+8k8u1tYnmmfaTtRQSxwOTgA9KsV88fED44f8JFZ3PhnwjYTyLfZtWu5Ey8ysSpSKLBPzgrgn5vmI2g4IANv4ObZ08Q/EvxNdR20+oXBgWe5dooo4tyk7XdsFC2yMZ+75W0HkiuQvPEJk/aKn8S+H7OTWrSO4S3DW6yFGY2pjJ3IjkgbJG+VW3CNiMjmur8K/AXztH07/AITLV76f7PiSLSoJsQ225tzoW5zu+Xds285wW4atST4R2nhO68TeIvD0kgd9HuItOsIYXeW2mMQAeOUuXLkq2MAH5yAfUA88+Dng7xL4h0PXJdJ8TT6BYyyxQySwQq7zumWwGDq8e0MvQjdvxzgivQ/AXw18ZeF/Hl1rGp+KY72wnRknzJJJLeADbF5gcYUqMHIZiMbRwxNWPhJZL4B+E0uoeIpZNPSS4lvLhLuFomtxxGFKnkk+WCOMneAAeM954b8U6N4u06S/0O8+12scphZ/KePDgAkYcA9GH50AbFFFFABRRRQAVHBBDa28VvbxRwwRIEjjjUKqKBgAAcAAcYqSigAqvdQ3EvkG2uvIaOVXcGMOsqdGQjqMg5BBGGCk5GVaxRQAUUUUAeL3Hwo8ar8Rda13TfFsFla6r5sb3QVpLmOF8ERqpGBtKqoYOCAoIx92vUPDHhjS/COhw6RpEHlW8fLM3Lyuerue7HA/IAAAADYooAKKKKACqeq6VY65pdxpmp20dzZ3CbJYn6MP5gg4II5BAIwRVyigDwNNH8X/AAOv7u90qCTxB4Snd3ktlZhJBhMiR8KQhABBcAqwX5gpK49I8L/FXwh4sRFtNUjtbt3VBZ3xWGUszEKFBOHJx0Qt1GcE4rtK4vxR8KvCHixHa70uO1u3dnN5YhYZSzMCxYgYcnHVw3U4wTmgCP4vaN/bfwv1qJUgM1tELyN5h9zyiHYqcHDFA6j/AHsZAJrwT4O+OdF8C3us32sS6kTLbokNvagMkx387gSBvAOVJIAHmDqQD6n/AMM4+D/+glrn/f8Ah/8AjVamkfBjSPDvxDsvE+i3clraWyMP7OZDICzRshKyM2QPmBwQ3OecEAAHJp8Q/id8QJbVPB/h/wDsaxfDNfzKJUIDFGPmyIEKgkZVVL/IcZ5FF98PvDvgPSZPFvxB1KfxFq7YEUU7SGGW4EYZIycMzf6tl3P8m08pXpnjn4g6L4F0uSe+njmvygNvp6SASzE5AOOqplTlyMDBxk4B830r4c+K/iRqlvr3xHupLazhfdbaSihCY2+Yr8pzEMlVOcyEKQSpCmgDmPhn8PLvx/4lPi7WLK0sdDS4EqW1vapHFcsp4jSPGPKGMMTndgjJYsy/TdRwQQ2tvFb28UcMESBI441CqigYAAHAAHGKBGwuHlM0hRkVREQu1SCcsOM5OQDkkfKMAc5AJKKKKACiq9/DcXGnXMNndfZLqSJ0huPLEnlOQQr7Tw2Dg4PXFSQRtDbxRPNJO6IFaWQKGcgfeO0AZPXgAegFABDMs6F0EgAdk+eNkOVYqeGAOMjg9CMEZBBogMzW8TXEccc5QGRI3LqrY5AYgEjPfAz6CpKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAqMGb7Q6tHGIAilHDksWydwK4wABtwcnOTwMcyUUAFFFFABWH4w8NQ+MPCd/oM9xJbpdooEyAEoysHU4PUblGRxkZ5HWtyigD54n+EnhXwv/wj2keJNT8zVdT1vEU0MEpWa2XA8lgHATcSmXGSrSYGVUtXJ6D4YsfHfjy1tPBOn6lp2h21ukWoXk9xiVkYFZXYjcqvIpZAi5BwTgLuAufEXU/F2tfGHWNG0+GeK7uYhpUVrAyM0lqdsmC4Awr/AOsbJ+VWKsdoNe7/AA38C2/gPwvFY4gk1KX95fXUSkea+TgZPO1Qdo6dzgFjQB1FhY2+madbWFnH5draxJDCm4naigBRk8nAA61Yrzf456Zeal8L71rOadfsksdzNDCpbz4wcMrYP3V3CQk5A8vPuNT4VeKF8WfD7TrtpJHu7ZBZ3ZkdnYyxgAsWIG4spV+/3sZJBoA7SsPw14w0Dxhbzz6DqUd4kDhJQEZGQkZGVYA4PODjBwfQ15v8RvjOunvbaP4HntNU1S5cxPLCrTiIso2CPA2yOS3GCwBUgqc8SfCH4R3ng68fXdbusalJF5cVpbTHZGrAFvMIwHbPG3lRtzljgqAewUUUUAFFFFABVe/sbfU9OubC8j8y1uonhmTcRuRgQwyORkE9KsUUAfNGvaF8RdG8OS/DO30qfU9Lku0ktdQtbf5GhLs2x2wQn7zax3MCu08shBr3vwdoC+FvB2laKqxh7W3VZfLZmVpT80jAtzguWPbr0HStyigDk/iXoF94o+Hmr6PpixteTpG0SO20OUkV9uegJCkDOBkjJA5ri/gv8LdR8HS3Oua43k6lcRNbJZo6uI49wJZmGQWJQYAOAOuScL7BRQAUUUUAFV/3Wnad/wAt3htov9ueQqo/4E7tge7E+pNWKKAI4J4bq3iuLeWOaCVA8ckbBldSMggjggjnNZ+peGtB1m4W41TRNNvp1QIsl1apKwXJOAWBOMknHua1KKAK4s0is4bW0P2OGHYI1gRQFRSPkAIICkDbwOAeCDgixRRQB5X+0FJYp8Mit3DJJO97EtmyniOXDEs3I48sSDvyw47jc+EOs/238L9FlZ4DNbRGzkSE/c8olFDDJwxQIx/3s4AIrqNb0TTvEejz6Tq1v9osZ9vmRb2TdtYMOVII5APBrH8B+CLPwFoc+lWV1Pcxy3clyXmABG7CqvA7KqgnucngHAAOkEjG4eIwyBFRWEpK7WJJyo5zkYBOQB8wwTziSiq8Nmlt9njtj5FrBEYktYkVYwPl28YyNoXAAIGGOQeMAGX40nmtfAviG4t5ZIZ4tMuXjkjYqyMImIII5BB5zXB/s82NxafDWSaePZHd6hLNAdwO9AqRk8dPmRhz6emK7jx3/wAk88S/9gq6/wDRTVz/AMEv+SQ6F/28f+lElAHoFRzwQ3VvLb3EUc0EqFJI5FDK6kYIIPBBHGKkooA+SJvAaad8XLbwPrFxfDRpbtjatHMqEpKvyOpkAQsSqK2ByUKqCQor6n0bQ9L8PaclhpFhBZWq4OyFMbiABuY9WbAGWOSccmvJ/wBoOzTT9O0DxXZnydXsNQSKGZUU5GGkXdkHdtaMEA8Dc3BzXoGh+LPM+HVl4p8SpBpSvaLcXGH3Iqn7rLjJ+cFSF5b5gvJoA8v+EFr9v+MnjrXLW4glsUlniDo+7zPOuC6MpHBXETc57jGe3pHiu28P+PdD1bwxFfaVe6mkUhjgNyrPbTrlVdgpLJtcgHjuQQckH5c8F6l4vCX2g+EFu/tGpPC0z2YYSqsbED5wf3abpBubjtkgEg9R42+GUPw78OadKddkk8RXryhkjYQwC3EB85Qx5Y5YLyQXD7QmaAPW/gX4n1nxN4NupNZnnu5LW78iK6l2fMgjT5cj5mYdSzDneOSc49Qrz/4M+GLjwv8ADq1hvYJ7e+vJXu7iCbGYy2FUYH3fkRCQeQSc46D0CgAoqOCCG1t4re3ijhgiQJHHGoVUUDAAA4AA4xRDPDcoXgljlQOyFkYMAysVYcdwwII7EEUASUUUUAFFFFABUZM32hFWOMwFGLuXIYNkbQFxggjdk5GMDg54kqMyMLhIhDIUZGYygrtUgjCnnOTkkYBHynJHGQCSiis/XLfUbvQ72DSL37FqTRN9mnIUhJByu4MrDaTgHgnBOOcGgDn/ABd8TvC3gvdFqV95t8Mf6DaASTfw9RkBOGDfMVyM4zXN/D/42WPjbxB/YtxpUmm3cqFrXE/nLKVBZlJ2rtIUZHY4PIOAafhn4JaFpEA1zxpcf2lqaeZcXjz3Ra0DBy/mEsqs3yj5t5KnLZBrzCy+JulwfFDWvHV5pk91dN8mmWq/ulAwI98j7m2sIgRgK4LMfu4BoA+r6+f/AB94zvPiT4vs/AngvVfIt285Lm784pDdsI2JXKAsYwquP7rlumAGPUeDPjjpev6PrNzq9t/Z91pkUl2YkbcssAbChWbA8zLImDjczAjqQuX8C9MXUNZ8R+MV0CPSrS+cQ6ekTN5apuJlVATyNyx5OAMghQoBUAGJ4F0+z8BftF3fhqwE9xazWhtklnkG9MwpcFjhQDyhXGB19uZ/in/ycL4K/wC3H/0rerGoWF5pf7VVhqN3aTpZ6hn7LKkZcSYtPLONucYbrn7o+Y4Xmq/xT/5OF8Ff9uP/AKVvQB0mq+C/H/hO9uL/AMCeIJL6xmuPPfR9SfzCMvuKo8hOQzPIWO5Gxjlm5qTw18cNOnvF0jxhYT+HdXXar+ejCEsQuCcjdHncThhtCjJeugtfiVZ33xQn8FWenz3HkRMZr+Fw8ccijLKwHRRkKWJyH+Xb3roNf8K6F4oihj1vTIL1Yd/lGQHKb1KtgjkZB/MKeqggA2KKKKACiiigAooooAKKKKACiiigAooooAK8j1yNfFn7Q2jaWZo5rPw5ZG+mgYMhSckEFSBljlrZsZ24U98g+uV86aF8QBafEHxlNpljJr3ijVr02mkmKGPyBDGSqEyhgdm0KTgAERAluSygHf8Ahj4rKLh9F8e28fhzXkdtomVo7edMt86OxIA+UjJYhiBtJzgZeq/G19V1EaP8PdEn1y/bB+0SxMsKAlBu28Nt+YqWYoFODkiqcfwZ1fxpqk2v/ELWpFu50xHZacRi2Xgqm9gwAXLgqAcn5t5JOaelR+Ofgtm2m02DW/CIlMk09hABNEvzlnYDDBtoBYvuUBVUOOtAHGfEXS/H8ml6U/jLWJLjUL64EVlosC7i23cC5WICPfl0AxuZhJjI24r6D+H/AIOt/A/hK20mL5rhsTXkgcsJJyoDlcgYXgAcDgDPOSfGPCuq2fxM+M3/AAkmt6pY2dnpssaaZY3DiGaf5m8hVAb5mDfO3LfMQuNrcfR9ABRRRQAUVHJGzvCyzSRhH3MqhcSDaRtbIJxkg8YOVHOMgyUAFFFRzzw2tvLcXEscMESF5JJGCqigZJJPAAHOaACcTNbyrbyRxzlCI3kQuqtjglQQSM9sjPqKkqOOCGF5niijR5n3ysqgF22hct6naqjJ7ADtUlABRRRQAUUUUAFFFFABRRRQAUUUUAFFFcf4i+KPg7wx5kd9rUEt0nmA2tofOk3p1QhchGzx85XnPPBwAdB/YWj/ANsf2v8A2VY/2n/z+/Z0877u37+N33eOvTitCuH8EfFXw/491G5sNMivoLqCLzil3Eq70yASCrMOCV64+8MZ5x3FABVez+2GIte+QsjbSI4ckR/KNy7jjf8ANuw21eCBtyMmxRQAVTvNRWzuLW3+zXc8ty+1BDAzKoBG5nf7iAA5+YgnBChjxVfUb69F7b2GmQxtcF45Z3uY5ViW334k2uqFGlxkBCwPO48DB1KACiiigAqvY39nqdnHeWF3Bd2smdk0EgkRsEg4YcHBBH4VYooAKKKKACiiigAooooAKKKKACiiigAooooAr2d19sgaX7PPBtlki2TptY7HZNwH91tu5T3Ug96sVHBPDdW8VxbyxzQSoHjkjYMrqRkEEcEEc5qSgAooooAKKKKACiq9rf2d95/2O7guPIlaCbyZA/lyL95Gx0YZGQeRVigDPm0PS7jXLbW5bCB9TtomhhuinzojdQD+ePTcwGNzZ0KKKAOf8d/8k88S/wDYKuv/AEU1fPEHw78V2Hw1fW9O8TzwaJdxQzzWa/aYgY5FCzvJGoOVTjkBg8alugAPu/xN1zS9H8A63FqF/BbzXmn3ENtE7/PM7JtAVercuucDjOTgc18+San441L4MGF7OO28JWSCLzobaJPtTeemN+WDYDE/PGvLAhskswAPQ/CWieEfhfa+FNYv5rG6u9a3KdWmneP7OskQZTFEy/6vojSNtZfMycKxVbnibxlfeLfib4f8M+CtftI4LVxe3l0lzmKcqd3l4UgygKCSiMQ28527CV8k8ZfDtPDPgHwt4itpp7j+1Ig927lQkbugkjRV+993eCcnO3Py5Ar0fwh4Dt9P/aG1V9Hg+yaRoMUbhN5ky81uAEyzbud0jZ5xtxxkUAe8UUUUAFFFFABRRRQAVj+KtP1TVfC+o2Oi6j/Z2pTRFYLrH3DnkZHK5GV3Dlc5HIFbFed/Enxf4r0K4tNH8NeHZLu41VPJtNRDhlinycqUxjIQbgWIXqSCEagDj/Afjvx5o/jew8DeMNOkupZ0/dyuU89ECZD+YG2SoAjZPLE7uSRtPulfNEM/iXwn8U/DMmp+KvtviLVpYrfVrTasv2OCSdGEO45Ubsk7UC7e3DAn6XoAKKKKACiiigCvf31vpmnXN/eSeXa2sTzTPtJ2ooJY4HJwAelcv4A+Iml/EGzvZbCGe3ms5dksMw5CMW8t8jj5gpyM8EEcjDHP+NOt/wBifC/VNlx5NxfbbKL5N2/efnXoQMxiTk49jnFcX4W8UaL8Hfhbpa38kl7q2sodTSyt3BwJEGwsSBsTaqAk7ju3bQwBwAe4Tzw2tvLcXEscMESF5JJGCqigZJJPAAHOa8z8Q/HPw1p0ostCjn8Qam8vkx29orKhfcFxvIO7OTt2B847ZBrzS5ufFfxL+J8fh7xHeXeiafcvGsulRXAHlReU1wimMkbnKry7KdrEZA+VK938KeA/Dngy3CaNp0cc5TbJdyfPPJwucueQCVB2jC55AFAHkfi/xT8VdS8CanrNxYweGNJhlR12NJFfOhl2BAc5GCVJJEe4dMgkV6Z8K9V1/XPAdnqfiK5tLm5uHd4Zbfblos8b9nyhwdwwOgABwwaq/wAZ4Jrn4Sa8kEUkrhInKopYhVmRmPHYKCSewBNR/BL/AJJDoX/bx/6USUAegUUUUAc/47/5J54l/wCwVdf+imrn/gl/ySHQv+3j/wBKJK6Dx3/yTzxL/wBgq6/9FNXJ/AbUob74V2dvEsgewuJreUsBgsXMuV56bZFHOOQfqQD0gQQrcPcLFGJ3RUeQKNzKpJUE9SAWYgdtx9akoooA4P4ueDL7xv4KNhpkkYvLa4W7iifgTFVZdm7OFJDnBPGQAcA5HhFteeI/HQttG120u4vDvhSyP2+C0/cNCsMbLubeGBnwpUKQM4YDYC7D6zrn/Hf/ACTzxL/2Crr/ANFNQB4x8Fda1TS2Uaf4Y+1eHr+7itLm/tYf38Nz5UeS48xiYVO5iTgDzCw258us9bhPij+0TGtzF9q0iyleNBCFljMEG4qWbbgxyScnOeJdoPSuf8I+K9dl8DXfgLwpoc9xfalK73d0HEuI3CxsAm0CNcbFLsxAyx4yNv0H8M/AMPgDw0bNpY7jULlxLeXCIAC2MBFOMlF5xnuWOBuwADtKKKKACo44VieZ1MhMr723SMwB2hflBOFGFHAwM5PUkmSigAoqOCRpreKV4ZIHdAzRSFSyEj7p2kjI6cEj0JqSgAooooAKKKKACiiigArzvWfDPg/4b6XrHjTTdGtLXUre3laGRvMdBK/CqqZwgLMq/KFwGIyBmvRK4P4ueDL7xv4KNhpkkYvLa4W7iifgTFVZdm7OFJDnBPGQAcA5AB8+eFdA1n4n+IdO0xp57vTbHEl5fSxJHJEkj+ZKDJhi7F2k2bi2SS2FBbH1npWlWOh6Xb6ZpltHbWdumyKJOij+ZJOSSeSSSck1zfw38C2/gPwvFY4gk1KX95fXUSkea+TgZPO1Qdo6dzgFjVjxH8QPD/hTXNL0nV7ryJtR3FZMrsgAwA0vOVVjkBsY4bJABNAHUV438d/COtasmja/4espJrzTnZZWtFP2oAspjZdo3EKwY8HKl8gY3EeyVnvrmlx64uiSX8EepvEJo7V32vIh38qD97/VvnGcYycZFAHB/CD4Zt4F0ua+1MxvrV8iiVVCsLZBz5YbqSTgsQcEhQM7dx9MoooAKKKKACiiigAooooAKKKKACiiigAooooA8j+K2geOtWh1e5g1+0sPC1pZSTG2ty4nm2QsWV8KNwZiVxv27cHaSOdT4LeFbPRPh/peovpkEOrX0TTS3OA0jxu25Bu5wpQRnaMDPUZzUfx51KGx+Fd5byrIXv7iG3iKgYDBxLluem2Nhxnkj6jvNC0z+xPD2maT53nfYbSK283bt37EC7sZOM4zjJoAjfxLoMbuj63pqujyoym6QFWiXdKDzwUU5Yfwjk4rxPxt4xf4ua5YeB/CHnyaY8qTX2oBGUFB1JQlf3aZyd2NzhQMYBbuPE/wR8J+KNcm1eZr6yuJ+ZlspERJH7uQyN8x74xnrjJJPaaB4c0jwtpa6botjHaWgcvsUlizHqWZiSx6DJJ4AHQCgDm0+EPgYaHa6TLoME0dvg/aGJSeRucl5U2s2ck4zt6YAAGOX/4UR/ZX/IqeNdc0bzf+Pn59/m4+7/qzH0y3XPXt39gooA8ns9O+N2mSmeXWfDmrK22PyJkKhcsAXykcZ+UZJ5PGcAnAqv8A8Lh13wv+58e+Cr6x2/Ib2wAeGSQ/MqruO37mc4kY5U8dcewVHPBDdW8tvcRRzQSoUkjkUMrqRggg8EEcYoAr6Vqtjrml2+p6Zcx3NncJvilTow/mCDkEHkEEHBFWI42R5maaSQO+5VYLiMbQNq4AOMgnnJyx5xgDP0Pw5pHhq3ubfRrGOzguLhrmSOMnb5jAAkAnCjCgbRgDHAFalAEc08NsgeeWOJC6oGdgoLMwVRz3LEADuSBRHMsrzIokBifY26NlBO0N8pIwwww5GRnI6ggY+ueD9A8R3Ftdappsct3bOrw3UbtFPGVJK4kQhwAWJxnGeetblABRRRQAUUUUAFFFFABRRRQAUUUUAFeT+Ntd+K3/AAlF9onhTQ4PsPlRzW2o+Wu7bhdw3yP5e7duG0ru284/ir1SSNneFlmkjCPuZVC4kG0ja2QTjJB4wcqOcZBkoA8Li+FfxD8WXG/xr4wkj0+5SN7mytZ2bJBU+WYwFiUjH3huG4A4bOa888SeGdOvviVH4L8AidP3QsLx7iZlWeaNi8ruSeVGxSQABujyqn5c+9/Fnxy3gfwc89nLGurXj+RZhgrbT1aTaTyFXvgjcyZBBrn/AID+Bm8PeGn16/ijF/qyI8OCrGO2wGXnGQWJ3EZIwEzggigDvPB/g/S/Bmh2+n6fbQLMsSJc3SRbXuXGcuxySeSxAJO3OBxXQUUUAFFFFAHD/EjVNG0aDRb/AFHW/wCybqHUInhdC++eIOpniIjVmaNkHI4UsI9x6V2FnfW+oQNNayeZGsskJO0jDxu0bjn0ZWHvjjii6sLO+8j7ZaQXHkSrPD50Yfy5F+6656MMnBHIqQQQrcPcLFGJ3RUeQKNzKpJUE9SAWYgdtx9aAI3m33i28NzAJIsSXEJG5/LYOFxgjbll4Ygg7GGM8ixUccEMLzPFFGjzPvlZVALttC5b1O1VGT2AHaq95qcFjcWsE0d2z3L7IzDaSyqDkD52RSEHI5YgdfQ4ALlFRwzw3KF4JY5UDshZGDAMrFWHHcMCCOxBFSUAV726+xwLL9nnn3SxxbIE3MN7qm4j+6u7cx7KCe1WKKjE8LXD26yxmdEV3jDDcqsSFJHUAlWAPfafSgCSiiigAooooAKKKKACiiigAqOeRobeWVIZJ3RCyxRlQzkD7o3EDJ6ckD1IqSigAoqnI8OmJCsNlJ5U1xh/s0QIjaRiS7KOcFzyQDgtubADMLlABRRRQAUVHJPDC8KSyxo8z7IlZgC7bS2F9TtVjgdgT2ooAkooooAKKKKAOL+I3g7SPEXhrVryfR473VodMnS0kRCZQwG9Au3kneowPdh0ZgfLE1LS/wDhl+10a41axtb65wUhmm+fZ9vOX2DLlQFYnap+6eDijxPo/jX4mfEXU9KsdWnPhiK7ayllTdHbQ+TsZ1aLd88is4AJ++RkEKvyX/BPwH8+8v8AUfHpnuroXbhIUucpcrjmZpAd53M2QPlYbfmznAAOMuPidosfwy8PaBp2k3cWpaVcJMyyyBrWU4cS7+dzpIJHDR4UYkIyQMNH4I8CeKfilrFz4om1v7Jsu/3mosS03nBQw8tFIxt/d91CgjbnbgfS9hY+H/B+nW1hZx2OlWssqQwpuWPzpSAFGTy8hCjrljjvXkfh68034e/HXXtGudYj0/Qbq3a4treW5VbeOaTy3IKrhIyArqu4A7Qo53LkA9wgjaG3iieaSd0QK0sgUM5A+8doAyevAA9AKkoooAKKKKACiiigArm/G3jTTfBHh+fUr145JwhNtZ+cqSXDZUYUHkgFgWIBwOcHpXSV5P8AEr4S6p4+8UWN6niHyNNjiaN4Jo9/2c46xKMBtxxu3EEY6kYVQDH+CWgy6pqM/j7xDe/atZ1LzhZpMULGNSqSTKOowT5YAChV46MuPcK+XPEPwY1/TPHkFn4Sg1J7MPC0Oo3DKghchm3eYhzhTGTnapB2jBLIX9rTxt4W8FaHa6drnjOC/vLTFrPMziadpFznfHGGZcYIy2TwNzFjkgHcUV43J+0Jpt88Nr4c8MazqmoSPgWzKqEqFJJXYZCSMdNvTJzxzzE/7RuvWviCWO48NWkNnE5SSykkdbhGAwQZDwCG7eX04680AfRdFV7C6+3adbXn2ee38+JJfJuE2SR7gDtdezDOCOxqxQB4v8d5v7Y1Hwh4NS5gi/tLUFeVsb5IckRI23I+U+ZJ16lOCMGtz4p+IvBHhq402/13S49Q16F1uLCOHCzqYyxRncEFYt+eDkEkkK2044v4j6u8fxyt7rSNCn1/UtI0pQLWKJnWGcs7JI6hCWVRKjfKV+Yr8wIxWh4U8Ep4bvLj4g/FTUYF1ZpS8MdzKrCNwCQ3y5DyYX5ETO0AYGcBADlNG8e6rqfxw0C88V6LHZahG6WIjigkikjEiyqgKO/QtcKxJGcKMdw2x+0Jr+tTa9pvhCxaQ2dzbx3DW8CkvcytI6qpxywBQEKO5yckLh/jPxJo/jeDRvHvhyPzLjwvdxzajaXSpbyeSX3RhpSdp+aIhVUucy9AeCz4c6Zc/Ef4qXPxBvI7SKzt3D/ZhE7gShDHHHuZQpdFVJCy5wWTABOFAPX9W0W+b4aX2hJPJqGoHR5LNZpDta4l8kpuJY8Fm55Pfk964f8AZ1nmm+HN0kssjpDqcqRKzEhF8uNsL6DczHA7knvXrleJ/s5Xk0WjeIdBuLSSCexvVlk8zKsGdShQqRlSph/XoMcgHtlFFFAEc8EN1by29xFHNBKhSSORQyupGCCDwQRxivF/2eJL6xt/FPhy9hjifTb1GdQcsJWDI6kgkEDyRjHvyeMe2V4HJGvwx/aBuNTvZo4dD1y3ubh7q4DHYCDK6qQAC4lQAKNx2uo5ZhQB75RVPStVsdc0u31PTLmO5s7hN8UqdGH8wQcgg8ggg4Iq5QAVz/jv/knniX/sFXX/AKKaugrg/jPPNbfCTXnglkicpEhZGKkq0yKw47FSQR3BIoA5v9nH/knmof8AYVk/9FRV7BXifh3wFc+LPgR4cto7u70vVrN5b2wl3vGoczOyMyjsVIKuBlc5XIJDbHwT+IGr+NtL1O31ry5rvTnjP2pVCGVZN+AyqAAV2EZGMgjjIJIB6pRRRQAUUUUARmNjcJKJpAioymIBdrEkYY8ZyMEDBA+Y5B4xJRRQBHJBDM8LyxRu8L74mZQSjbSuV9DtZhkdiR3qSiigAooooAKKKKACiio5xM1vKtvJHHOUIjeRC6q2OCVBBIz2yM+ooAkrxP466bDrPir4f6XcNIsF7ey28jRkBgrvApIyCM4Poa9srwvwhoFj478VfE3TPEKyXkUep7baR2zJakvKu6JjnYdsUQ44IjUEEDFAHoHjb4n+HPBNvOl1dx3OqIhMenQtmRmwpAcjIjBDA5bqMkBsYryTQ/C3iz4za5ZeNdWvoNLsYZVgja0V4plSL5swZBz85Ybyxw27g7QtSeCfhz4ctPjTrvhjUraTVINOt0u7Rp32qOYmAkQDDnEgHJCnDZU7ht6NPG/9iftF61p2s6zPDpEtokUEc91ttoJPJik3EMwVc7XGRzl/c0AewWN/Z6nZx3lhdwXdrJnZNBIJEbBIOGHBwQR+FWK+fPhJ4x8L+APh9LcazrEZvNQuJZ47K2d5pAqAKFaMfLG7EEgnbuBXJwvy+56JreneI9Hg1bSbj7RYz7vLl2Mm7axU8MARyCORQBoUUUUAFFFFABRRRQAUUUUAFFFFABRRRQB438XNUh13xj4R8EWjx3Ej6nHc38cUQlkgUYCkhgyY2PKxVlPCgkbTz7JXzh4curzW/wBqi7vGt8/Zru7ikMKHakcUTwKzdcZwgJ6bm7ZAr6PoAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACo554bW3luLiWOGCJC8kkjBVRQMkkngADnNV9W02HWdGvtLuGkWC9t5LeRoyAwV1KkjIIzg+hrxv8A4UFrH9j/ANkf8LEvv7M/58vsr+T97d9zztv3uenXmgDmLP7Z8dviqZ5fPi8Nadtk8ibJCQggFMpgLJKQSecgA4LbAK+l68L0DwZ8XPAOlrFot54fvrSFy/8AZqqAZ2bglnaNCSMg5Mg4UDkALRrfxc8f+Crfb4s8JabFPeo4sHhucKrKPmLqruWALJxlO/PPAB7Z9vs/7R/s77XB9u8rz/s3mDzPLzt37eu3PGemasV80eDr3wrp3iP/AISb4g6/rln4yhu2lltZ7aWIYKDZkIhYrtPTKjHy7Sv3vovTdW03WbdrjS9QtL6BXKNJazLKobAOCVJGcEHHuKAJLy1+1xBRcT28i7jHLC+CjFSucHKtjcSAwZcgHGQKL6yi1CzktZnnSN8ZME7wuMEHh0IYdOx56dKsUUAZ9lo9vp+o3t3bPOi3e0vbeaTCjgsWdE6Izl8tj7xAJGck6FFRzQQ3KBJ4o5UDq4V1DAMrBlPPcMAQexANAElZ+nWKaSv2K3jneGWW5ummdlIR5JTIVPQ8mRsYB4Xk5xm5HGyPMzTSSB33KrBcRjaBtXABxkE85OWPOMAEEjTW8UrwyQO6BmikKlkJH3TtJGR04JHoTQBj22o32o69ZS2ttdx6KbKdpZLiDyS0/mRiNdj4lBCrKeVCkMpyeMblFFABUcxmVAYI43feoIdyo27huOQDyFyQO5AGRnIkooArw3sU/wBn2JOPPiMyb4HTCjbw2QNjfMPlbDdePlOLFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFAEZhVrhJyZN6IyACRguGIJyucE/KMEjI5xjJyVJRQAUUUUAFZfiTW4fDnhrUtZn8spZ27yhHkEYkYD5U3HoWbCjg8kcGtSo54Ibq3lt7iKOaCVCkkcihldSMEEHggjjFAHzB8JfG1l4JsLi1g0y71PxBrlxClrbx3ESQMocogZ8lo3LNJwy8jYeAc10+nfFzxpafE2z0jXtNtILTVri3VNPLL5tlHKdqZZeQ/zKzLIMnHAQEVuazpnhr4E6c/iXSNGnvrq+uzaYmvWXyonBk2L8pBUGIYyC3PLGsP4PaRfeN/Gd98TNYvIxPFcPBFbwJtBfygvPoixuoHJJPJPHzAHOeKtC8c/FTxzqMcJsbm30q7NkUguwIbLn5sq+2RuQdzhPmKHbkKALDfB/QvDvxF8OaHrOuz6nHqXmO1ta2hRxsxsD7XZljb58uBxsPQZZbnhrXdI8A/HPxlP4olksTO85hlGZF2ySrKoKorHLKVYHIxggjJ4w/FPxHl8T/FXRtc8G208d+toNPtkvUQHzpDKgIG4r/y2GCxxkcjHUA97sfF3hTSfEC+Bob2O0u9Pt4IoYZmKqwIASJHY/O4UpxyTuGMkNjrK8b8M/Ba+bxKfFHjTXZLzWkvY7qL7E/yEoQw3l0BxkABVChQvB5AX2SgAooqnquq2Oh6XcanqdzHbWdum+WV+ij+ZJOAAOSSAMk0AXKK8z1L48+A7G3WW3vrvUHLhTFa2jqwGD8x8zYMcY655HHXHLp40+Lnjh0Phrw/Hoenu8Tpd3CAny3XrvlGJE6tmOMkDb1z8wB7JrOuaX4e057/AFe/gsrVcjfM+NxAJ2qOrNgHCjJOOBXlet/Hy0lv/wCy/BmiXeu3kiOI5NjqpbZuBWMKXcD5twIT7vBwcg0T4B2kt/8A2p4z1u7128kRDJHvdVLbNpDSFi7gfLtIKfd5GDgdZ4rnt/hh8M7+98MaZY2/2PySkLRnY5Z44yz4IZm2n7xOTgZJoA4efwP8WvGuw+I/E0Gj2E3nCWytHO6NGyNhSPCyKcAfNI2FJ5zkHqNG+BngbSNjS2E+pTJKJVkvpy2MYwpVNqMuR0ZTnJByOK6Twh4uh8T+CLXxPcQx6dBKkryLJOGWJY3ZSS5CjGEznAxWppmu6Prfm/2Tqtjf+TjzPslwkuzOcZ2k4zg9fQ0AWLWws7Hz/sdpBb+fK083kxhPMkb7ztjqxwMk8mo5NJ02bVIdUl0+0fUIU2RXbQqZUXnhXxkD5m4B7n1q5RQAUUUUAeJ+DdShPxN+J/jDUVk36OjW/l24GGhjLAnDHl9tsncDJb1GMvQfDeo/HTUbzxJ4qkvtP0SH9zpVtasoXr85VmBzjaAzbfmY4BATaNz4Tzw2vjr4p3FxLHDBFqe+SSRgqooluCSSeAAOc1X8U/EDUvH3iC58A+BPskkFxbyxXuqTKzR7cfNsKggJj5N5ByXG3HDEA4zxNDZ2Won4S+BLbzPtd3Gmp6hORLJcSAhgpZAdscXVsAEFW4GGL/QfhHwvY+DvDVpo9hHGBEgM0qptM8uBukbknJI6ZOBgDgCvnz4Xz+Gvh3401eTxhPPYanZxRwW8dxaMxV3GZSNgfpgBXDDcjk4+bA7/AFz432+oxXul+BdJ1XWdT8pvLuILY7IgVx5oUqzNtdlGGQA+vTIB7BXj/wAIP+Sh/E7/ALCo/wDRtxWPpvxW8beEvE2l+HfH+nWI+17M3TTRxOiPLt812QmPaoDDGF+6CT3PQeBPsei/G3x/ocXnvJd+TqCu+CBkb5ATx/FcDAx0HJz1APWKKKKACuH+Jvw7t/iDoccQm+z6nZ7nspmJ2AtjcjgfwttXkDIwCM8qe4ooA5P4aaBfeF/h5pGj6msa3kCSNKiNuCF5GfbnoSAwBxkZBwSOa6yiigArL1/w5pHinS203WrGO7tC4fYxKlWHQqykFT1GQRwSOhNHiXUptG8K6vqlusbT2VlNcRrICVLIhYA4IOMj1FeZ+Dvj3oN/ofmeK7qDTNTWVkMcEEzpInBVxhW29SMFj93PfAAO88R6XqVr8PrzSfCKRw3kVkLaxV5WXYoAXCvnIcLnaxP3tpJxmuf+EHw/m8CeGpjqHl/2tqDrLchGJESqPkjznBK5YkjuxGSACe8sb+z1OzjvLC7gu7WTOyaCQSI2CQcMODggj8KsUAFFFFABRRRQAUUUUAFFFFABRRRQAUUVz+q/8JF/wkNl9l/5An7vzfsnl/afN3nPmeb8v2fZ12fvc428ZoA2JobiS8tpI7ryrePcZYhGCZSRhQWPRRkkgDJIXkAENJBBDa28VvbxRwwRIEjjjUKqKBgAAcAAcYqSqeralDo2jX2qXCyNBZW8lxIsYBYqiliBkgZwPUUAXK8f+EH/ACUP4nf9hUf+jbiq9z8ff7V3Wvg3wlquqX3lO7CWP/VYwFbZFvLLk88r2GeeOI8NaFrPiT4ua7oPifVJ/D9xfxPPf2WmTJELolQREApKt8khY53kgPnks1AHR3PjDRfCvx813VWe71Oe8T+zRZadCJXVwlrt5LAMSyyoVHIKcjnjhPF+vaxrXjTU5ruyn8ITapaIJI7gOhmjQbG82RtreWUD/LGpDmNF2M2Grv8AwxomheCfj9ryPbwaZpFppSz2kt45CR7zAhdZJD3ZpEznuy+1cx8a/iToXjOLT9N0OP7RHaStK99LblCcqMLGSdwU5O4FRkouMgUAdn4O+B3hTUtG0rX9S+1zve263b2Ucpjt1Eq7lQdZMKGAyZMkrk9cV7JY2FnplnHZ2FpBaWsedkMEYjRckk4UcDJJP415n8JR8RL55Nc8V6lHLpN/biW2t2Me8syxFJFCLhEKAjbkckkrk5Ppn2+z/tH+zvtcH27yvP8As3mDzPLzt37eu3PGemaALFFFFABRRUZkYXCRCGQoyMxlBXapBGFPOcnJIwCPlOSOMgElFFFABRRRQAVHNPDbIHnljiQuqBnYKCzMFUc9yxAA7kgVJRQAUUUUAef6B8LLPQfiVqni9b77T9t8147aa3Ba3kkYMzLJn/fUfKDtfGTzn0CiigAooooAK4//AIWp4G/tj+y/+EmsftH9/cfJ+7u/12PL6f7XXjrxXYV5+/wU8Ay3l1cy6Hva4lMuxbiSNIsgZVFRgAuQTjtuIGBgAA2L74j+CtPs5LqbxRpTxpjIguVmc5IHCISx69hx16Vwev8A7RXhzT3aLRdPu9WdXA8xj9niZSuSVLAvkHAwUHfnpm5/wzz4K/tH7T5uq+T5vmfZPtC+XtznZnZv244+9ux3zzXlmp+BNMl/aDTwlp1rHDpYuIGaB5pCDEIVllXdktkgPjnqRyOwB9TwTLc28U6CQJIgdRJGyMARnlWAKn2IBHepKKKACiiigAooooAKKKKACiiigAooooAK8/8AiD4Q8Wa3qOn6p4S8UT6ZdW+EktZbh0tpACWD7VBBbJwQykMMdMYb0CigDzPw9qvxgvtZgi1nw/4f0/Tw6tPK0hZim4blTZK/z7SSMjHHJ9fTKK4f4ifE3S/h9ZxCWP7bqc+DDYpJsJTOC7Ng7V4IHByeAOGIAOg17xVoXhj7H/bWpwWX2yXyYPNJ+Zu546KMjLHCjIyRkVy+mfDuzvfHMvjjVtZ/t5pMSaUhjAhtY8lk24Yh8Aja3AyS2CxBHiHxH8e+MPF/h63XV/Df9m6I92tzZ3H2WZd+UfYvmsdr5RicgDOMjAr6b8NabNo3hXSNLuGjaeysobeRoySpZECkjIBxkegoAsalpOm6zbrb6pp9pfQK4dY7qFZVDYIyAwIzgkZ9zXn+t/A7wpqFx9u0n7XoWoI7zRTWEpCrKTlW2HO0KwyAhT0yOMemUUAeRyeFfi54eSFdF8Z2ms2lsnm+TqMIWWdtxYx7mDEg8AEyLjOMqADUcPxsvNE1G3sPHPhG+0XfmI3iZeN5FKh2VSozGMk5VnOMY3ZzXsFV76ws9Ts5LO/tILu1kxvhnjEiNggjKng4IB/CgDP8PeKtC8V2ZutD1OC9jX74QkPHkkDchwy52nGQM4yOK2K8v8XfB3Trzdq3g8/2B4hjx5MtpO1vCc7VbIQHb8gYfIFyWO7Oa2PhlrXjHWtDkk8YaN9hmTaLedl8p7gcqxeInKMCuc4UHeMAAcgHcVXext5NRhv2jzdQxSQxvuPyo5QsMdOTGn5e5qScTNbyrbyRxzlCI3kQuqtjglQQSM9sjPqKr6TJfTaNYy6pDHBqD28bXUUZyqSlRvUcngNkdT9TQBcooooAKKKKACiozMq3CQESb3RnBEbFcKQDlsYB+YYBOTzjODgMKtcJOTJvRGQASMFwxBOVzgn5RgkZHOMZOQAEytcPABJvRFckxsFwxIGGxgn5TkA5HGcZGZKKKACiiigAooooAKKKKACo4J4bq3iuLeWOaCVA8ckbBldSMggjggjnNSUUAFFFFAEZgha4S4aKMzojIkhUblViCwB6gEqpI77R6UUGCFrhLhoozOiMiSFRuVWILAHqASqkjvtHpRQBJRRRQAVn65qFxpWh3t/aadPqVxbxNJHaQEB5SOwz/TJ9AxwDoUUAfLmj2Xib48eMYbrW5ZLXR7RCGkt4WEEQG3dHETkea25SSxJxzyFVa9X0z4L6RofjWPXtF1XUtNtERP8AQLaUgOysrFWkYktE20ZQ9T/EBgD0yigDn/Engfw14u8s65pEF3JHgLNlo5ABnC70Ibb8xO3OMnOM15vd+IJbn9qOy0xLeBI7e0Fs7vEkjti3lmDKxXdH/rcEKedvORwPaK8D8IT6b4t/aZ1rWbSWRoLK3d7d0ZSsrIkdsTkZBQhnYEEZ+U+ooA94knhheFJZY0eZ9kSswBdtpbC+p2qxwOwJ7VJRUYjYXDymaQoyKoiIXapBOWHGcnIBySPlGAOcgElV76ws9Ts5LO/tILu1kxvhnjEiNggjKng4IB/CrFFAHL6N8OfB2gbDp3h2xSRJRMk00fnSI4xgq8m5lxgEYPB5611FV1huG8sy3WGSV3IhjCrIh3BUYNuPAKkkEZZc8AlasUAFc/47/wCSeeJf+wVdf+imroKjnghureW3uIo5oJUKSRyKGV1IwQQeCCOMUAfGFnJ4jtfBUEF4bu38HXt68r4HlpczRqCUEgRjltgVcgpuXOPkYjqPH/hiH4UXHhK40TUZIfES27yXjxzh8SAj5wpAOxi0iYIwypggndnqPjtomkaWngXRoPL0vSY3uIi6RlxBGWh3PtHLkZLHnLHPOTUepX1v8Rv2jtI/siTzLHSfLLXkKmVHEDNKW7YUuwjDZI5BBOQKAPoeiiigAooooA8X8H2aWPx+8deHwfP0y/tDd3ME6K4kdzGxB4+6PPkGO4POcZr0TQtL8IeDriPQdITTdPvLhN62wlX7ROoLHJ3Eu4Hz4JzgA4wBXn/iSFfDf7RvhvWCZLWz1q3a1mkWRiLicK0YRlBJxk23YLkA9QSK+s20Pin9p3TLcWsksWh2SSXRZggVlDSxuuDkgPNCMeucjGTQB65qehaPrflf2tpVjf8Ak58v7XbpLszjONwOM4HT0FcX488XaL8JfDSppWk2kN5fO5tLS3gEUTOAoaR9oAwAU46twBgZK+gTmZbeVreOOScITGkjlFZscAsASBnvg49DXl/gz4f6vqPiV/G3xA8ubW9+LKwDBorJVJ2kYJGR1UAnGdxJc5UA5vwH8KtU8R3mpa98SbXz/t+yRLec7Z2fGQ5ZCGiUKSvlce6jYtdZ4E+EcPgPxrfatY6lJPp81kLeGGdQZVYsrOWYYBA2DGBzuIONuW9MooAKKKrpK91Fa3Fu3lwviR1mgZXZCpwMEqUbJUncD0IwCcgAsUVXV7iLy0lTz2klcF4VCLEnzFSwZsnACqSM5Y5wBnbIJla4eACTeiK5JjYLhiQMNjBPynIByOM4yMgElFRzQQ3KBJ4o5UDq4V1DAMrBlPPcMAQexANR3919h065vPs89x5ETy+Tbpvkk2gnai92OMAdzQB4BrPi7xL8Wt8elyf8I14NtpTDf6lc3KxqwbI/eHK7sqQPKUkbnG5sFSMuKb4LWWiWukXgu9RnkuLkf2naRyq9ujviN5CwTcVj2fKqMAVfC/N82H4r8W3Hje1mmnjsfDnhyWWe5ht4YBI11fxxAtuZFDGR/OQFm2qFPcrz3/xk07wt4Z+HVl4ctruCPVYvLkt1ngFxc3EY2xtmQjMWQqfNwCIdijaMAA9Q8D6fpfhj4daXDa6j5+mQ2n2kXs48oMj5laQg42L8xOD0HUnGa4fxH8dtOsfFul6Z4eh/tu1aVor428bF2YsFRYDkB2zk9CrZUBuSR554Q8E+NPih4ftYbvU49O8L2txK8ANuqq0jlmYxxIFDAMSMkgLuYL0K17n4R+GPhbwXtl02x82+Gf8ATrsiSb+LocAJwxX5QuRjOaAOwooqOGFYEKIZCC7P88jOcsxY8sScZPA6AYAwABQBJRRRQAUUUUAFFFFAEZnhW4S3aWMTujOkZYbmVSAxA6kAsoJ7bh61H9vs/wC0f7O+1wfbvK8/7N5g8zy87d+3rtzxnpmqc9y1rrMuzSJJ3lsjItxAF3SGJv8AUsW2gE+blMtg5kztxlpLvRrO+1jTtTuE3XGneabc4HytIu1jnG77uRjO05yQSqlQDQrj9Q+Gnh3U/Fth4luRfPf2UpmQPeSOjNu3KCGJ2qrZYKhVeeQRxXYUUAUxpdousvqypILx7dbZ2Er7WjViygpnaSCzYbGRuIzg1YjnhmeZIpY3eF9kqqwJRtobDeh2spwexB71JRQAV8kax4j1Tx18VU17wNpF9a6mNm0xP5jsVPliWT+GNShRWByg5yTk5+j/AIjaz/YHw617UQ88ci2jRRSQHDpJJ+7RgcjGGdTkcjHHNeOeAfGei/DP4byTJHHq+vXrrez2tpj/AEeAsI0E0yhgoHXafmDTBSoyTQB09r8HNa8S6oupfEfxLJqZifMVlZsVix8nfaoQMFIZUVSeDuzWH4jfwP4g+Betaz4Y8MwWH2GWG0Saa0iWcESQ5O8FmOVfBJbJ5z79h4j+Iml+Ivgl4h1/SYZ3hMT2EkM48t43kKx84yDgSq/BOemQc48YksfGM/weuZEjsdP8K2ksUzrA2Hvnfy1yWG7zNrYLbiNrEr/yzCRgHWW3xT1dfB3hfwd4Knk1PxFJbxxy3X2UL5GOkKqw2kqo2tIfl2ru6klej+G/wYvNI1iLxR4p1Gd9ZWX7RHDb3JOGZTu86Tq7EscgHacHJcMRXQfAyws7f4W6XeQ2kEd1deb9omSMB5ds8gXcw5bA4GeldJ4X8Z2Pii91uwgjkhvNHvZLS5ibkEB2VHVsYIYIeOoII6YJAOkqOCeG6t4ri3ljmglQPHJGwZXUjIII4II5zUlFABVex+2fY4zf+QLo5LrBnYuSSFBPLYGBuwN2M7VztFiigAqOaeG2QPPLHEhdUDOwUFmYKo57liAB3JAqSo4IVtreKBDIUjQIpkkZ2IAxyzElj7kknvQBJRRRQAUUUUAFFFFABRRRQAVh+KvFukeDNLi1HWZpIreW4S3UpGXO5s84HYKGY+ynGTgHcrzv44wQzfCTV3lijd4XgeJmUEo3nIuV9DtZhkdiR3oA9AgnhureK4t5Y5oJUDxyRsGV1IyCCOCCOc1JXB/CafTbX4P6LcQyyQ2cVvK80l2yrsYSOZSSOAgbeQT/AA4zzmjW/jH4I0O48iTWI7tzbvODYYnUkHAj3KcB25wCQBj5iuRkA7yvH/h54L12P4q+KPFXiW08qQSvFZ+YwmDbyCGikODtSMKgO0cOV+Uqy16R4Y8T6X4u0OHV9In823k4ZW4eJx1Rx2YZH5ggkEE7FABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAVw9/8MfBX/CUXPi7UbGDd5TvcRXBX7JuwS0zoRjdjOSTt/ixu+au4ooA+TPjV47sfGviWzXR7qS40uxt9qM0OwNK5y7LkBsYEY5xypwOcn1PwtceLPhlod7qvxD16C60mSIPFC9w9xeLcnGIlLDByA2QGIBXIIG5jyHxZX4fwxeH4tKubGXTYpb6W4s9EuIzK0kiqVbcAyou9VU55C4CqQuBY0HRPEnxv1zTvEfim3gt/DllmEQwPJELjG4sY1Jbq2xXYFcgYByvAB3/AMHr/Xdd0fV/EWq3c7WOqahLNp9nNIJfs8YYghX67c/KFIAHl5A+avSKjgghtbeK3t4o4YIkCRxxqFVFAwAAOAAOMVJQAUUUUARzzw2tvLcXEscMESF5JJGCqigZJJPAAHOakoooAKKKKACiiigAooqvfrv065Xyp5cxOPLt5PLkfg8I25drHsdy4POR1oAsUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAVHNCs6BHMgAdX+SRkOVYMOVIOMjkdCMg5BIqSigAorD8XeHW8VeGrvR11S704XCFWlttuWGD8rZGShOMgFSQMZAJz4Z4Y17xr8HLyHSfEllv8LvqH2c3LhnSLI3M8LLztIYPtK/NtcAK2/AB7X450vxHrHhqS08L6xHpeoM4zK68PGQVZdwBKHncGUZBUDjORy/wj+GE3gG31C41SS0n1S6cIsluSypCADgFkVgSxJYdDtT0r0iCeG6t4ri3ljmglQPHJGwZXUjIII4II5zUlABRRRQAUUUUAFFFFABRRRQBj+J/DGl+LtDm0jV4PNt5OVZeHicdHQ9mGT+ZBBBINPwZ4G0XwLpb2OjxSEyvvmuJyGlmPONxAAwAcAAADk9SSekrH0rxVoWt6je6dp2pwTX1jLJDcW2SsiMhCsdpwSoJA3DK570AbFFFFABRRRQBy/j/AMF2/jzwu+jzz/ZpBKk0FxsL+U6nBO0Mu7Kll5P8WeoFYfw/+HmpeG/Euu+IvEOqx6rq1+/lRXKhlIhyD8w6AthPlAIQIADg4r0SigAorg9L+MvgPVbhbdNdjt5Xd1UXcTwrhSfmLsNgDAZGSDyAQDxXeUAFFFFABRRRQAUUUUAFRzzw2tvLcXEscMESF5JJGCqigZJJPAAHOakrwvxp4R8dfET4gtoGqzR6d4dtEe5tbm3gd4JFyQhbnDT4YKVJXADFRg/MAc58UvF0/j3xppuieFrOe6ZbQxW0nlSwSu1wFZ2XLL+7MQAy6hdryE5G1h3/AIO+B2l6Ref2v4muf7e1aXc8izrvgDuBuJDZMjZ3/M3XOdoIBrjPAGhaQf2jdSj0KKSx0/RUnKW75csyqLdxuLEgF3ZgSTwAMDPHufiHxVoXhSzF1rmpwWUbfcDkl5MEA7UGWbG4ZwDjOTxQBsUV4fL8b/EHiW8a08B+Dp7zbLGpuLtWcKGBGHVCFj+bOGMmMKSfaQfDX4l+Krd38U+O5LES26xm0swWVlYHekqIY0yM4JG/PTOAMgHtlFeRwfs6+DYbiKV7vWZ0RwzRSXEYVwD907YwcHpwQfQivLNEtZbH4tQWfwquL64aDdFcTX7o8Miq5EjM0fBtyAhB+8Tjb822gD6vooooAKKKKACiozMq3CQESb3RnBEbFcKQDlsYB+YYBOTzjODggnhureK4t5Y5oJUDxyRsGV1IyCCOCCOc0ASUUUUAFFFFAEc88Nrby3FxLHDBEheSSRgqooGSSTwABzmgwq1wk5Mm9EZABIwXDEE5XOCflGCRkc4xk5z31LTbzXpfD86xvdw28N+I5QpDL5jBWUE5JR4wSccEpzk1oTzLbW8s7iQpGhdhHGzsQBnhVBLH2AJPagDxf4/3Pia8t9L8PaVpF3Pp94/mPNahpGnlQMRCUXsFHmcg5wCMbDXml/qXh+DwhpXgzRLP+z9avpUj13UNUjWNoXEgDQsxUlYw6I/GNqoucsXx9R+I9fsfC3h+81rUmkFpaoGfy13MxJCqoHqWIHOBzyQOa8Q+G/g9filqmt+MvG+myTx3TolpsdoInIyrbQpDEIFRAc4PzZ3MCQAT/EV9N0r4FWugeFNWtNStLO4gi1GSykWYbW3uXfBbyw0ygjnqdoOOK6DxZBNc/sxW6QRSSuNHsHKopYhVMLMeOwUEk9gCaueIfgd4Vu/C9/Y6BpsGn6lLskgupZZZNjqTgZZiQrAspxnqDglRXnni/wAAePtM8EXU/iPxfaDR9MsooIrO2klMcgV1WNGRY1BPPDsCchckD5lAK9j8Tr6bwdo3gHwBpN3DqEtukMl0ZP3nmtuabyxk7QWJbzCw2gtwuAR6/wDCv4eL4A8PyxXDxy6tePuvJYpGaMhS3lqmQMAK2TxnLHkjGK/wV0rRbL4b6df6TbSRy36b7uabHmSyozI3I/gDBto9Dk8lifQIJGmt4pXhkgd0DNFIVLISPunaSMjpwSPQmgCSo5oIblAk8UcqB1cK6hgGVgynnuGAIPYgGpKKACiiigAooooAKKKKACiiigAooooAKpi8W9GoW9hcRi7tX8h2kiZlilMauuRldw2yIeCOuMg9LlFAHkfhj432JN/pvjWKPR9W055FuXiG+BisgTagDM5fLcgbhhS2ccDiPiR8b9N8WeGLzw/pej3awXaRlrq6kVGRlkD4Ea7gRhRzuHU8cc9n8Zfhtca35Hinw9ZwSavZfNcweSHa8RcbTtOVdlxjaQdynHO1VPP6l420vxd+zzrsNjp0GmXWn/ZkurK2i2QoXuEIeMDjaxDHHUHOc8MwBJoPw88UfEyytdR8ZarJp2g7EfT9K08IqeSUJjKKMpGFDgAsGcgEHGAT5xoXg+x8c/E19H8OJJHocLhnmeb5zbRlUeXLLne5+YLtwC4GAoJHoFx8UF1LwLovgLwnp0mratfaPHYTuQyLA5iCOoBA3EKHJbIVeDlhkD0P4Q+BbjwN4SeHURB/ad7L59x5agmMbQFiLj723k+gLtjPUgHWaB4c0jwtpa6botjHaWgcvsUlizHqWZiSx6DJJ4AHQCtSo54Ibq3lt7iKOaCVCkkcihldSMEEHggjjFRwr9l+z2ccU7wrEQJnk37du0AMzMXZiCTnn7pyckZALFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFV7m8S33RqPOujE8sdqjqJJQuM7QxA6soySACwyRmrFRxwrE8zqZCZX3tukZgDtC/KCcKMKOBgZyepJIBJWfreiad4j0efSdWt/tFjPt8yLeybtrBhypBHIB4NaFRzzw2tvLcXEscMESF5JJGCqigZJJPAAHOaAPmz4tfCrw/4F8G2V/pUt9JdPqAhd7mVW3I0bHGAoHBj4x/ebOeMe7+BP+SeeGv8AsFWv/opa8n+LvxY8Lav4V1Xwvpc897dTeWBcwxjyFZJlZhuJBPCHBUFTkYPpkeA/Dfj3xhZz6fF46nsNE0OWSxt7qxmLiZwRwpVkZ4wuCrMeAVCjGcAH0fRXzh8OY/EGlfG2+8PaT4jn1LT7eWaXVJZ0YJKQFErGNnz5nmbU3gk9/mXIP0fQAUUUUAFV7O1+xwNF9onn3SyS7533MN7s+0H+6u7ao7KAO1WKjmnhtkDzyxxIXVAzsFBZmCqOe5YgAdyQKAJKKKKACiqepTzRW6x2ktpHeTuIrf7UxClsEnCjlyFV22gjO0jKjLCxAJlt4luJI5JwgEjxoUVmxyQpJIGe2Tj1NAElFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFRwQrbW8UCGQpGgRTJIzsQBjlmJLH3JJPeiaNpUCpNJCQ6tuQKSQGBK/MCMEDB74JwQcESUAFFFFABRRRQAUUUUAFFFFAGX4j1+x8LeH7zWtSaQWlqgZ/LXczEkKqgepYgc4HPJA5rj9I8UeCPjNpd3pM1pJKIHWVrK9AjlAHSVCjE4ySpKtkZweGGfQJ4Ibq3lt7iKOaCVCkkcihldSMEEHggjjFeB+IrDTh+0T4Y0fwtaWOlzWXlyXjWkbW+7G6V0IX5TmEY4HPmFWJAwoB75BBDa28VvbxRwwRIEjjjUKqKBgAAcAAcYqSiigAooooAKKKKACiiigAry//AIWVrFj8aP8AhDdX0+xttMuOLO53urtlNyNubCvuYFNoAwxwGbb83qFcn8QfBmi+MvDU0GsSR2ptkaaHUDgG1IGSxJIGzA+YEgEDqCAQAdZXg/wO+x698Q/G/iiDz08yU+RG+B+7nleQ7gM/MPLXoccnrxVe2vtB8Hf26bz4tz6xq+o6U1raTKZpI4Sd+xmeMy/MrEkYIKhmODuFdZ4KFp4M+C01z4Zkj8Q34Rpn/s5Hm8y6kChVMeQwCgxhh8rbVJKgnFAHP/FvxXr2q+PNJ8C+E7+7tLsOpuZLaR4z5jgFQxVdwREO8kEjDcjKV7ZYQ3Fvp1tDeXX2u6jiRJrjyxH5rgAM+0cLk5OB0zXmfwe8DX2kpfeLPEcUi+ItXd3dXO0xxOwc7kAAV2YbiOcAKPlO4V6pQAUUUUAFFFeL+IfjPea5eDQ/hpp0+p38nD3j2x2RqQAHVTjGGcZaQBRt5DA5AB1nin4b+ALu31TUtasbSxN04kuNQM/keXJgqHDE7VJL5IxhmwWDHFef6R8Rbnwjpf8AwiPhZ7vx5eWqSlLqG2dUtEGERAq7mlRWydwYDDKqtjGKniL4XXCeC9V8YfEHxHfT6yloXihjYMkErFtkROGypd0GECquWwSPmr0f4Jf8kh0L/t4/9KJKAMPwjc/GHWPFlpf65HaaVoLoJZbZoYyCu0DYq7jKrnOfmYbTnIONh9coooAKKKKACiiigAqnq2pQ6No19qlwsjQWVvJcSLGAWKopYgZIGcD1FXK8P+PPxG/s6zl8G6bzdXcStezpLgwxk58sBTncwHIbjY3Q7sgA848C+KNd0Se/s9G0qe68aa3LGI7q7jEjeQ6M5cbsEMSySbmLIygEgBcn1fwb8Fbc+VrvjuafWNdlxJJDcTmSOMjbtDnrIwC4OSUwcYIAY7nww8E6R4H8HWuq3NvHbapPZCXULu5BjaNTmQowY4jCAgNjGdgJHHHiHgvxV4m0LWdc8PfDyGTVLS8uG+yvNE0jRIGKJPj5UQkMm5nXHC5AAxQB9XwQQ2tvFb28UcMESBI441CqigYAAHAAHGKkrwfRfjhqPhqz1bSPHlhO+v6fnydiKhuGJGEbaNq9QQ4G0pyMnG/rPhZ8SNa8f3GpNd6BHa6fA7GK9ilO0EldsJBHzuFJJYYHT5RkZAPTKp2ek6bp9xdXFlp9pbT3b77mSGFUaZsk5cgZY5YnJ9T61cooAKKKKACiiigAqvfW8t3ZyQQ3s9lI2MTwBC6YIPAdWXnpyD19easUUARwRtDbxRPNJO6IFaWQKGcgfeO0AZPXgAegFSUUUAFFFV7FLiOzjjun8yZMoZCwYyAEhXbCqAzDBICgAkgZAzQBJPBDdW8tvcRRzQSoUkjkUMrqRggg8EEcYogghtbeK3t4o4YIkCRxxqFVFAwAAOAAOMVJRQBn63omneI9Hn0nVrf7RYz7fMi3sm7awYcqQRyAeDUmlaVY6HpdvpmmW0dtZ26bIok6KP5kk5JJ5JJJyTViGNokKvNJMS7NucKCAWJC/KAMAHA74AySckyUAFef/G3/AJJDrv8A27/+lEdegV538cZ4YfhJq6Syxo8zwJErMAXbzkbC+p2qxwOwJ7UASfBL/kkOhf8Abx/6USV6BXH/AAr0z+yPhf4etvO83faC53bduPOJl24yem/Ge+M8dK7CgAqMwq1wk5Mm9EZABIwXDEE5XOCflGCRkc4xk5kooArzXiQXltbyDb9o3CNy6gFwM7ACdxYqHYYB4RskcZsVHNG0qBUmkhIdW3IFJIDAlfmBGCBg98E4IOCJKACiiigAooooAKKKKACiiigAooooAK8H/aB8E6Xb6HB4n07TfJvhdiO8e2j2o6PuJkkAH3t+Bv4zvwc/Lj3ivP8A42/8kh13/t3/APSiOgC58LvDmkaF4F0q402xjgn1Gyt7m7kBLNLIYlJJJJOMkkKOBk4Aya7Suf8AAn/JPPDX/YKtf/RS10FABRRRQAVHNI0SBkhkmJdV2oVBALAFvmIGADk98A4BOAZKKACiiigAoqvcWv2ie0l+0TxfZpTLsifasuUZNrj+Jfn3Y/vKp7UfYovP87fPu83zsee+3ds2Y25xtxztxt3fNjdzQBYooooAjghW2t4oEMhSNAimSRnYgDHLMSWPuSSe9E0bSoFSaSEh1bcgUkgMCV+YEYIGD3wTgg4IkooAKKKKAI554bW3luLiWOGCJC8kkjBVRQMkkngADnNSVXvrX7bZyW4uJ7dmwVmgfa6MCCCOxwQOCCp6EEEgyQCZbeJbiSOScIBI8aFFZsckKSSBntk49TQBJXB/FTwLqXjrw/FaaXq8llPE+Wt5JWW3uVJU4kCgnKlQynBwcjHOR3lFAHh+lfADUdE+2/2d438n7daSWVx/xKlbfC+Ny8yHGcDkYPvXQWvw48QeE/hrP4b8Ia5ANSu7tpbm/ug0O1GXafKCBijYVBk56sQQcY9Qqv8AbYv7R+w7J/O8rzt3kP5e3OMeZjZuz/DndjnGOaAOT+G3w/tPAPh9YB5cmqXCKb+5jZ9srAsVADHAChyuQBnGSB0HaUUUAFFFFABRRRQBXtbX7J54FxPKssrSqsz7/L3clVJ525yQCTjOBhQqixRUcE8N1bxXFvLHNBKgeOSNgyupGQQRwQRzmgCvJczLrNtarHIYJLeaR3EJKhlaMKC+7Ckh2wu07sE5G3DXKKr2Nw9zZxyyxeVNysiANhXUkMFLKpZcg4bA3DBHBFAFiiiigAooooAKKKKACiiigAooooAKKKjkMweERRxshfEpZypVdp5UYO47towccEnPGCAR2v2w+ebzyBmVvJWHJ2xjhdxPVjgscAAbtvO3c1iiigAooooAKKKKACiiigAooooAr38Nxcadcw2d19kupInSG48sSeU5BCvtPDYODg9cV8sT6T49+Et0njB7mBpLy7ms7h5MyNKRKSd4kUMVk8osHU5K85UkZ+q554bW3luLiWOGCJC8kkjBVRQMkkngADnNef8AiP4rfDdLe807UtStNURUDPax2xuY5iAHVQ20xsc46tgHqRjgA3Ph74puPGfgux1u6sfsc025GVWBSQqSpdOSQpIPDcjBHIwx6ivD7r9ovw3YadBBofh2+k8rbGsE3l20ccYGBtKF+mAAuAMd+MGRfjJ42vHsruw+GWpPp7oXfCTSGdWUFDHIIwFHfOGyDxjrQB7ZRXg99+0XeaZeSWd/4GntLqPG+Ge+MbrkAjKmHIyCD+NeweFfENv4r8L6drlquyO7iDlMk+W4OHTJAztYMM45xkcUAbFFFFABRRRQB4vqPiX4zal4hvtA07w9Y6cqy/JqIiZo44t4wwlkOyT5SMgJuxu+QEYEb/A/WvEeqPc+NPGl3fwJcStFFADnY3Rl35WEk4yiqQAoAPTHtleX/FL4pf8ACL7fD/h9ftnia62oiRp5n2bd90led0hyNqe4J4wGAPMPiFY+HdD8Zf8ACD+GPDVjL9t+yR3Ll5DdpM0m4RxTSsyx7kKAsFI+c5zjA9r+GfgGHwB4aNm0sdxqFy4lvLhEABbGAinGSi84z3LHA3YHiGl+B9X8A/GnwjpdnfR3d/OkV1O0cYVUjYus6DefmAjST5sAnPABxXu8fjm2f4mzeC2gjjkSy+0rcNcpmR8g+WsYyc7SW5IOFJ27cMQDrKKKKACiiigArPisNH0T7dqMVpY2HnZnvblY0i34yxeRuM4yxyfU1oVHPBDdW8tvcRRzQSoUkjkUMrqRggg8EEcYoA8f+OPiOHUfhDZXmjX0c+n6pexI0iAESRhXfHIypDxrkcEFSD3Fdx8MdNh0r4ZeHbeBpGR7JLglyCd0o81hwBxucge2OvWvEPiF4K0DT/iNo/h7wQLuy1p0RisMrSpFKZAUZm3GSIqm+Rjg4CxkDksO3sPiR4r8DXv9n/EzTJHs2cRwa1ZQhkZi7cvtwuNoJAAVwF5QlsgA9korH8PeKtC8V2ZutD1OC9jX74QkPHkkDchwy52nGQM4yOK2KACiiigAooooAr399b6Zp1zf3knl2trE80z7SdqKCWOBycAHpXyB4esb/wCKnxFMN7Hvm1G7+1Xl0jSZtoFzvVCdwVcFVXcDgiMZAJz9j1z+g+B/DXhjUby/0bSILO6u+JXQseM52qCSEXP8K4HA44GADYvr+z0yzkvL+7gtLWPG+aeQRouSAMseBkkD8a+XNE8T3dl8SNftPhhp1pOdZd1s5LqBEkhUKzt5OSqomcsFYdEQEZGK9j+Jnw41Tx7qOk7Nc8nSLeVPtOnkbMjJ3yq4BzJtIVQy4HJyMkHzzSrNbP8AaWmsfDdvHJp9g9xPHaxSt9ngle02yZKhhGDLtQ4HBAXHAFAHZ+Gvgfp0F4ur+ML+fxFq7bWfz3YwhgFwDk7pMbSMsdpU4KV6pBBDa28VvbxRwwRIEjjjUKqKBgAAcAAcYrzfwR8U5tW8QHwj4p0uTS/FCPIpjjQ+RJtG4YOSQduT3UhchvmAr0ygAooooAKKKKACiiigAooooAKKKKACiiigAooooAK5P4jTeK7bwdcT+DhGdTjdXYGMPIYhkt5akEM/Tgg5G7Hzba6yuP8AG/xK8P8AgL7NHqrzzXVxylraKryBOfnILABcjHJ5OcA4OACn8OfiTD42S50+8s5NP17T0AvbWTCgtuKsUUnfgEDcCPlLAZPU+cfGrxfD4sez8EaZp92urR6x5bx3KiIlguyMrngpJ5pIYkcLkjDA16H4DvPB3i7XNS8b6EJ11a4iS0vIZ3w8QHQlASBuCJyCR8nGDvz2l5pOm6hcWtxe6faXM9o++2kmhV2hbIOUJGVOVByPQelAEehaZ/Ynh7TNJ87zvsNpFbebt279iBd2MnGcZxk1oUUUAFFFFABRRRQAUUUUARkzfaEVY4zAUYu5chg2RtAXGCCN2TkYwODniSio5p4bZA88scSF1QM7BQWZgqjnuWIAHckCgCSiiigAooooAKKKKACvP/jb/wAkh13/ALd//SiOvQK4f4wWNxqHwo1+G1j8yRYkmI3AYSORJHPPoqsffHHNAFj4V2t5Z/C/w9FfXHnzNaCVX3lsRuS8a5P91GVcdBjA4ArsK8/+C2t/238L9L33HnXFjuspfk27Nh+RegBxGY+Rn3Oc0eMvjD4W8Iebbfaf7S1NMr9jtGDbGG4Ykf7qYZcEcsMg7TQB6BRXzBq3xA8e+JfG/hmJ7qfw3b6jLC1hFbE4MUs21ZZFJ/e/d6MArAZCgNk/T9ABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQBHDMs6F0EgAdk+eNkOVYqeGAOMjg9CMEZBBohmWdC6CQAOyfPGyHKsVPDAHGRwehGCMgg1JRQAUUUUAFFFFABRRRQAUUUUAFFFFABVO1muWv76C5EexHR7YxxuP3TIB8zEbS+9ZOFPC7M4zzcooAKKjgEy28S3Ekck4QCR40KKzY5IUkkDPbJx6mpKACiiigAoqvfX9nplnJeX93BaWseN808gjRckAZY8DJIH40UAWKKx/EGhy67BBANSntbdZUa4t1jR47uMOrNHICN21lVlwGAIc7gw4rYoA8nv8A4r+LLTUbm2i+FeuSxxSvGsm5zvAJAPyRMvP+yzD0JHNU54fjh4nt5YHOjeGkVCjGOQbpwwxwymUqVx1BQ/NxnHHslFAHkZ+CtjNbpd+NfGOs6ylk7Tbrm58qBIsAsG3lyoO07mDLxjpjNbng7wp8MdU0P7X4c0XSr+xeVgZJ4DO6uMAqfOBdeADtOOuQOcnuL+xt9T065sLyPzLW6ieGZNxG5GBDDI5GQT0r5k8A/FC58MeDtR0rw/4fkutYuNTNzHEkTyW8ET+XGBgMZGO7agBPV1+YnggH0/BBDa28VvbxRwwRIEjjjUKqKBgAAcAAcYqSvD/AnxR8S6d4og8MfEO2njutR8p7K4e3WN1MgARGVAAVJ4zjKtkN32e4UAV76ws9Ts5LO/tILu1kxvhnjEiNggjKng4IB/Co3I023tLey02SSAOkCx2ojRbePGNxDMo2KABhcnpgGrlFABRRRQBHPMttbyzuJCkaF2EcbOxAGeFUEsfYAk9qkoqMwq1wk5Mm9EZABIwXDEE5XOCflGCRkc4xk5AOb+IHjG38D+ErnVpfmuGzDZxlCwknKkoGwRheCTyOAcc4B4PwFocPhayuviF8RrqODXr52ZJb6Qb4YymQqpjiVlDDYuSFAUBfmWrnxV+GfiTxn4h0rVtD1mC1+xRbVinlkj8mQPuEkZQN8x4BOARsXk9sPTfgJqWs3DXnjzxTd306oYoltZ2lYLkEZllUnGS/yhe4OeooA5Dx98RV1P4s6drvgp5Lq4trIWNtI1sx8yV/MGUQ8kjzgACOWHQjr6P8MfhbfaVqkfi/xbeXdz4iZGVYZ5fMMB+aPc0gdvMJjwB2UEjBIBHMfEPwjoHgnx/8PbnR7KS1ge9VZYYVaUsI5433AYLu581h1JIVQBxivoOgAqOeZba3lncSFI0LsI42diAM8KoJY+wBJ7VJVczXD2c0kVrtuF3iOKeQKHIJCksu7CtgHOCQDyM5FAEkE8N1bxXFvLHNBKgeOSNgyupGQQRwQRzmpKz9Msry0n1KW8v/ALV9quzNCgQotvHsRFjALN/cLEjALMxwM1oUAFFFFAHl/gb4NW/hPxRL4j1HW59Y1L5jFI8Zi2u4Id2+di7EMRye5OCcEemTwQ3VvLb3EUc0EqFJI5FDK6kYIIPBBHGKkooA8z8T/ClRcJrXgK4j8Oa8jruMLNHbzplfkdFBAHyg4CkMQdwOcj0yiigAooooAKKKKACiiigCOeeG1t5bi4ljhgiQvJJIwVUUDJJJ4AA5zXz58Dof+El+J3ibxa9tAkY8yRYnO94ZLiQsNpx2VZFLcH5umCce5+I9AsfFPh+80XUlkNpdIFfy22spBDKwPqGAPORxyCOKp+DvB2l+B9D/ALJ0nz2haVppJJ33PI5wMnAAHAUcAdPXJIB5n+0JZNp9v4d8W2UscGoWF6IUcQqzMSDIhLHqEaM4Ugj5z759ognhureK4t5Y5oJUDxyRsGV1IyCCOCCOc186fFmx1Txt8bdM8Kxxz28KxRxQu7ZRkYGSWdVO0cDKkA8+TjOeB9D2Fjb6Zp1tYWcfl2trEkMKbidqKAFGTycADrQBHAkMeqXgS9keWRI5JLVpQwi+8odVPKhtmMZ2koSACXJuVXt7G3tpZJYo/wB9Lw8rsXdhuZgpY5O0F3wucLnAAHFWKACiiigAooooAKKKKACiiigAooooAKKKKACuH+I1t4O07Tv+Er8S6VY3l1p8Tx2i3P8Ay3dgdsOMEPk5xlW25ZuME13FeF/Gr4e+NPFviWzvNIijv9Pjt/Ljt1mWI27ZyxYO4DFsj5lxwoBHygsAV/gDoN9da9rXjSe0jsbO7R4LaGGHZE5eQO/ljOQiFAo4I5IzlSK98r5s1Hx748+G2t2+n6xr2jahHZPHG2kWsKIGgKZBDJCojAHABIYEA7ChBP0XYTXFxp1tNeWv2S6kiR5rfzBJ5TkAsm4cNg5GR1xQBYooooAKKKKACiiigAoqOeRobeWVIZJ3RCyxRlQzkD7o3EDJ6ckD1IqSgAooooAz7TUPtGsajY58z7L5Tb0jwib1/wBWW3HMg27yMLhZY+DnJ0KjhghtkKQRRxIXZyqKFBZmLMeO5Ykk9ySakoAKKKKAPO/i/wDECbwJ4ahGn+X/AGtqDtFbF1JESqPnkxjBK5UAHuwOCAQa/wAKPDPjLSbdtU8T+JLu8S9t1ZdNuWklaEkKyszSco4y6sgGOmScDHCeLbb/AIS/9pzT9GvVgFrZeSgVot4ljSM3LK4JwdxZl9MEcHHP0PQAVj+J/E+l+EdDm1fV5/Kt4+FVeXlc9EQd2OD+RJIAJHm/jD42vZ65ceG/CGiT6trMMrwuzxMyB0wWCxr88mAHB+7jbkbhXKaV4DtNc8eW9n8SPFsl74knTzV0i3Z3KLjzfLeUDZGMeZ+7TGAQVbBGQATVvGnxSt7vQ/A+mWmgeD43eBiqLApVhu2uVBOSQSViHHmYbIOT6X4N+D3hbwh5Vz9m/tLU0w32y7UNsYbTmNPuphlyDywyRuNaHxE1i48G/DPUr/REgt5rOKKK2URDZEGdIxtXp8obgdOBwRxVj4d+JLzxd4E03XL+OCO6uvN3pApCDbK6DAJJ6KO9AHk/xT/5OF8Ff9uP/pW9fQFfP/xT/wCThfBX/bj/AOlb19AUAFFFFABRRRQBHNI0SBkhkmJdV2oVBALAFvmIGADk98A4BOAZKKjmhWdAjmQAOr/JIyHKsGHKkHGRyOhGQcgkUASUUUUAFFFV7z7YIg1l5DSLuJjmyBJ8p2ruGdnzbcttbgEbcnIALFFFFABRRRQAV5/8WLLxzqGh2tr4KfYzyn7YYZxDOFGCuxyQAuQd2DuPygcbq9Arwf4r395pnxw8FXkt3PaabH5H75pDHCuZyJvm6DKFA/8AskZ4xQBc+Gnibxh4f8Yw+CPHIu5ri+tzPYSSzxzNEF8xm3upJYNtYDLErsAAwcjc+KXxet/A+3TNKSC911trPHJkx2yHnMmCCWI6KCODuPGA0fxO+GuveKfEul+IPDOrR6fqFtbtbySSXDxFVySpQopIJ3yBuemPerHwu+E1p4Itxf6olpd+IN77bmIuVhjYAbU3YGcAkttB+cr06gHoGk3k2o6NY3txaSWc9xbxyyW0md0LMoJQ5AOQTjoOnQVcrxvx74u1rxP41tfAHgu9khIdW1TUbNiXtgG+Zd2QFCDBbDZYkJkHKt7JQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUVHHGyPMzTSSB33KrBcRjaBtXABxkE85OWPOMASUAFRwwrAhRDIQXZ/nkZzlmLHliTjJ4HQDAGAAKkrLsob6TW9Rur0yJBG4gsY1k+RoikbNIyg8uZN65boEG0DcxcA1KKKKACiiigAooooAKKKKACvI9V+B+kWHh/WW8Mz6lFqjul3YK12dsE0RZkCYKjJDMgdyxXdkHrn1yufh8aaFN40ufCQu9urwRLKYnUqHyNxVSfvMF2sQOzcZw20A8Q8GWHin4ofErTdf8AENp9nh8O+XBczrGYWeaFmdUKnOZN5y4AUBf7pK5+j64fXfiPb6B8StH8I3Vl+71KJHF75p+R3Z0RNgU5yyKM5GN2TwK7igAooooAKKKKACiiigAoorh/iJ8TdL+H1nEJY/tupz4MNikmwlM4Ls2DtXggcHJ4A4YgA8//AGl7G4k07w7frHm1hlnhkfcPldwhUY68iN/y9xVyX4w+K/Erzz+BvCkbaXaI73F9qzBEwqqTlt6ohXJJG9iRg8YNch4g0DxNrngjX/HfjlbtLiF1XTNNmZo44RI4idvJ6oBlCvILFMuGB+bfks/G/if4ZeG9G8GW+mw+HbuyiguriGUxSh1G2fzAwXCGQvnYHLbGJJD7SAdJ8F/H/iXxnZ3MOtWHnw2u7OrjbGJHJBWIoAAWALElegC5GTk+sVy/w98J/wDCFeC7HRpHgkuk3SXM0KbRJIxJPu2BhQx5IUcDoOooAKKKKACo454ZnmSKWN3hfZKqsCUbaGw3odrKcHsQe9cX8U/HSeBfCUlxEc6neboLJQy5Vyp/ekNnKpwTwckqDjdkcn+zvpOm2fhO71G31OO41C/dTdWquubVUaRYwyg5Bb5mycZBGBwSQD0Dx/c6vY+A9ZvdCuo7XULW3NwkzqGAVCHcYIIJKKwGR1I6dRyfgf4m3GqfCXUfFGtRwfatJ82KRvMES3ToisvbCM5dVwM/N0HIUeoV8geH9P8AGOt2d98MdNtoI/K1Bru/3TbChQrEwdg21o1bacAMcgEZxQB7P8F9e8Y+Kf7a1zX5c6VdSg2aGHC7xwwiOciNQqggg5bJzuD7vWKz9D0az8PaHZaRYJstbSJYkyAC2OrNgAFicknHJJNaFABRXH/FO/1HTPhnrd5pV39kvI4l2zCRYyql1D7WboxUsBj5skbfmxWh4FmvLnwD4fuL+5+03UunwSPMQQW3ICN2SSWwRls8nJwM4AB0FFFFABRRWfa67o99qM+nWeq2NxfQbvOtobhHkj2na25QcjBIBz0NAGhRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUVh+I9c03wT4YvNZuLWT7HbuHkjtI13M0kgBIBIBJZ9xOeck8mgDcoryeH403GufZz4S8C65q8bymGSaUCGON/lwN6h1H3sncVwMHoeJGuvjRriXsUOn+H/AA6m8GCSeXzpQu4nAKmRCQAASyDO7gDsAeqVT1LVtN0a3W41TULSxgZwiyXUyxKWwTgFiBnAJx7GuHHw41/ULh59e+I/iCdwipENL26eqgEk7lXcGJyOcA8d+MZej/BPwFfyvrJv77xDDdbz501+JEkct8z74gpZshgcsepyM9ADDs774UfDPWbrW49du/EGtTJvV/NW8kG9iHKuoVA5ySxZt2On3sN0lv8AFvUde+xt4U8A65qUNxv/ANIu9trD8vpL86HkMOSOQByTXcWPhPw3pl5HeWHh/SrS6jzsmgso43XIIOGAyMgkfjWoZ4VuEt2ljE7ozpGWG5lUgMQOpALKCe24etAHkd1cfHPVNOg8qy0PSJmu1jbySrSKmOXbe0ieXk87cvkcDHWO58IfGjUbC5trvxzpsQZAY/sq+UzMHU4MiQqyDGTkZzgDGCSPZKKAPnDUPB3xh0XxbYWNj4j1XUIZ5S0F8LyVoIwrZzOrEheACVIYNnaN5yK+h7D7Z/Z1t/aPkfbvKT7R9nz5fmYG7ZnnbnOM84qxRQAUVXR7w3jK8EAtRnbIJiXPCYyu3AyTJn5jjap53ELYoAKKKKACiiigAooooAKKr3V/Z2PkfbLuC38+VYIfOkCeZI33UXPVjg4A5NWKAPnz4dQrqf7SHim6vjJPPZPevbu8jEoRMsQ78gRsVAPAGMdBj6Dr5/8AhZ/ycL41/wC37/0rSuo+JkXxO1bxDa6J4VX7FpE0QZ76CcI2Q67jI+A0e3IwqZLLuPzcqoB6Bb/8I3/wlF39m/sr/hIfKH2ryvL+1+Xhcb8fPtxs68fd9q8b+P0E2h+KvCvjCzikknt32EyKTArROJYgcYOWLScZ5C8YwTWZ8PPhfZ6l4+TUrCe+GhaJLG6X0yjGp3EbnLQEDaId6H+JzjHOW+X0P486bDffCu8uJWkD2FxDcRBSMFi4iw3HTbIx4xyB9CAV/jXr9jL8GnngaSaDWHtltJFXAIJEwZgcEArGe2ckcdcdB8JdNm0r4V+H7edo2d7c3AKEkbZXaVRyBztcA++evWvEPEOrr49svhz4I07+0oJ4beGK9je3bahKRqsuzPzgRq8gbpscHIywH1HQB4P8cfseg/EPwR4on89/LlHnxpg/u4JUkG0HHzHzG6nHA6c17xXhf7S2mzS6NoGqK0fkW9xLbupJ3FpFVlI4xjETZ57jr290oAKKKp3+qWmmpuuHkL7C4hgieaVlDKpKxoC7AF1yQDjcM4oAuUUUUAFFFFABRRRQAUUVn6nrNrpHlfaYr6Tzc7fslhPc4xjOfKRtvXvjPOOhoA0KKw7PxVZ3+qQWEFjrIMqO3nz6VcQxIVwdrPIigEgnHb5SMglQdygAooooAK5fx14F0vx5oZsL8eVcR5a1u0XL27nuPVTgZXvjsQCOoooA8T0f4oa14BuIfDfxH067PluYoNaiBdZ0BXDHIHmAK2SwJboCu7Jo8W/Hyxkt5dL8FW93fatO6w21y1v+7yw6oh+d3BO0KVAzzyBhvZL6ws9Ts5LO/tILu1kxvhnjEiNggjKng4IB/Cub0D4aeD/C+qLqej6LHBeKhRZWmklKA9du9iAccZHOCR0JoAw/hP8ADu48H2d1q2tzfafEOp4e5ZiHMIyWKb+SzEnLnOCQOu3cfSKr317Fp9nJdTJO8aYyIIHmc5IHCICx69hx16VYoAKKKKACiiqcg1IapC8Ulo2nlNssTIyyq3J3q+SGH3RsKjqW3cbSAXKKKKACiiigAooooAKKrulwdRhdXxaiKQSLuHLkptONuTgB+dwxno2QVsUAFFFFABRRRQAUUUUAFFFFABRRRQAUVHBG0NvFE80k7ogVpZAoZyB947QBk9eAB6AVJQAUUUUAFFFFABRRRQAV4Pb2Nxd/tbXc0Ee+O0iE053AbENmsYPPX5nUcevpmtzxX8d9IsLg6X4WtZNd1R38mNowfIEhLKAMfNKdwXhRhg3D15Zb+A/iLeeObPWpNGvtPuNXu3umntJ/I8hXO6TLjc0HyuRhwW6jaxyKAO3XUf7X/avjDahBJDp8T29uo5zi2YvGCoPzB3kJ3EY2sM5AWveK+aNH8LeBF8fa1ZanZ7/D3h3ShDf37yzokt6rorSEA5VmPmKIwSDs+XOa6f8AZ/1PUZpdd061hvpfCsMpbT57tlzAxYny8AcsysGYKdqkZx8+SAe4UUUUAFFFFABRRRQAVw/jf4VeH/Huo21/qct9BdQReSHtJVXemSQCGVhwS3TH3jnPGO4ooA8r1r4MTaskNq3j3xPJp7ODeW19dG4EyhlIC/dCkYPJVucHHHPeeFvDdn4R8OWmh2Ek8lra79jzsC53OznJAA6se1WNT13R9E8r+1tVsbDzs+X9ruEi34xnG4jOMjp6itCgAooooAKKK8b+OPijWoTp3gvRrSTfrqeU85BTeWkVVijk3AZPIcMMbXX1oAwNAgh+MPxpv9bu4o7jw7o6BIY5FBWVQWEQIO1sM3mS4YHGNh4IqfWRD4M/ad0y6ikkS31pEMsNsgQbpg0OGGQGBlVZCT3OcEjn0z4b+BbfwH4XiscQSalL+8vrqJSPNfJwMnnaoO0dO5wCxryTxlJfa9+07pFhFDGx064s1QKdpMSAXDsxJwSAz9McADBPUA9P+JnxAh8IaWNPsPMuPEmooYtPtYFDurN8qyFcHgN0GDuIwBjcR5R8B4b62+LmvQaoZDqEdlcJdGSTexlE8QfLZO47s85Oa9r0XwBoWgeLdW8S2Mc4v9Tz5oeUsibmDPtH+0wDHJOMcYHFeOalp3j74efEbWdd0XwvHqQ1K4umjuYoZbkPFLIkm1ljYFCpAAyBkl+WABAB9F0V8/8A/C0/i/8A9CF/5R7v/wCLrUm1/wCOup6WPsvhjTbEzorpMhjWVBwfuSzEA44IZcjJ6HoAdJ8dZriP4UakkFr50cssCTv5gXyU8xSHwfvfMFXA/v56A11HgT/knnhr/sFWv/opa8vvvg3418XRSP4u8ebpDKHW2ghaWAYUKGC5jVW5YcL75JJr1Twf4ah8H+E7DQYLiS4S0RgZnABdmYuxwOg3McDnAxyetAG5XifxE8PeL/BusyeO/C+talfQK5kv7G6maVUj3OwAQYDQKHYbeqZLA9SvtlZfiXTZtZ8K6vpdu0az3tlNbxtISFDOhUE4BOMn0NAHiGrat4v+N9xp+laVpl3ovheRFluruZGMcrKRv+fAEgVshUHUjc2MfJzbeFrfwZ8cvDGh+H76+vLqCW1N84UqwdmzKAFHEZiIJGWwrMCTg10/w4tNZ+EfhfxnrfiTRZ41i+yrFEJEPnPlhgMCRtzMmWGcfMBkqRWJ8Lr3xNqPxSHiy68MXepJqzun21kZYLUM4DSJI4YYRFdAu7OPlB7EA+k7+6+w6dc3n2ee48iJ5fJt03ySbQTtRe7HGAO5rm/h544h8f8AhptWisZLJ47h7eWFpA4DAK2VbAyNrL1A5yO2T1leH/Ai4/sTxD4v8Fy33nfYbtpLdfK279jmKWTPOM4h+UsfbuaANz4bfEbWte8X6z4S1+1tGv8ATnuXa8tWKoQkyp5YQjJALHDEg4AyCck9Z468daX4D0M39+fNuJMra2iNh7hx2HooyMt2z3JAPkHw6vrfTPjr4+v7yTy7W1i1GaZ9pO1FuULHA5OAD0qTwJos3xf8eX3jfxHZxto9s4htrMzF08xQpWMqeqKp3N90MzdCCy0Ab/gPxF8WfEHiCw1jUdLtF8M36ZMbbIViTHEiDJlJO3IDZDbuwIZfZKKKACiiigAooooAjgjaG3iieaSd0QK0sgUM5A+8doAyevAA9AKkoooAKKjnjaa3liSaSB3QqssYUshI+8NwIyOvII9QakoAKjnghureW3uIo5oJUKSRyKGV1IwQQeCCOMUAzfaHVo4xAEUo4cli2TuBXGAANuDk5yeBjmSgCOCCG1t4re3ijhgiQJHHGoVUUDAAA4AA4xUlFFABXg/wz0/XfAvxh1bwZGJ59Ekie6XzJAFWPjy5xlRubkRNswCxPUIMe8V8yfGWCbxf8abPw/pcUhvEt4LJi6nbuYtLv+XJ2KsgJOONrcYGaAPf4/GXhmXVJtMXX9NF/FcfZWt2uVVzLx8ignLHLAcZ5yOoIGhZ6XaWNxdXEKSGe6fdLJLK8jHkkKCxJVAWYhBhV3HAGTXzp8T/AAX8PfBmh2emie+i8TLaKwNsjOlyTkebIrttRS0bDCNld+drAV6f8EfE+qeKPAJm1ef7RcWd21os7ffkRURgXP8AE3zkZ74GcnJIB6RRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRUcc8MzzJFLG7wvslVWBKNtDYb0O1lOD2IPepKAPn/wCFn/JwvjX/ALfv/StK9A+JfxE8NeFNOuNH1eGe+ur60YfYIQy+ZE4dctJwFUlSpIJYZyAa4OOZvDv7VcyKI9Ps9UTY26NY0uA8Ab5SRyWnQcjksCOSSK9ov/Dei6nqlnqd9pdpPf2Th7e4eIF0I3Y56kAsSAeAcEcgEAHgknxT+IWm+MtEsf7Bg02zuNkdnoBhWHzIjI0SKWYbo242g/Kvyqdu08+r/GCG3n+FGvpdXX2aMRI4fyy+XWRCiYH95gq57bsngV5/4B0b/hYHxc1r4gXCQS6RaXbRWLAYE0iKqRsUYEjEe1yTtIcqR0IGR8QfFdx8UL6/sdFtp7vwrodpJfzXVuoRnlEL7HYyhdqqxK7B8zKJCN3G0AsfAe2/t7xpd+IJVgdtP0qCzZWiwYpMCKNozls/uoDub5TlyAMc17PqHjHS9K8W2Hh2/wDPtrjUIi9rcyJiCV92PKD5/wBZ0OMfxKM5YA8H+z3pTad4K1KWa2jSeXU5E89NrCZEVFG2Rch0DeYAQSM7sd66T4peDrjxd4XU6V+713TpVutPmVxG4cHlQ+MrkcjBHzKhJAFAHL/tHf8AJPNP/wCwrH/6Klr1iwvrfU9Otr+zk8y1uokmhfaRuRgCpweRkEda8H8VeJLP4ifs9vqVxdZ1fSJbd7qJGAPnb/K3MNo+V1dnG0AA8ZO0ivYPAn/JPPDX/YKtf/RS0AdBRRRQAUUUUAFFFFABRRRQAUVj6Xo97aS2lxf69fahcQ2jQSB1jihldmDGUxoow3AUcnC+pLMdigAooqOQzB4RFHGyF8SlnKlV2nlRg7ju2jBxwSc8YIBJVe/vrfTNOub+8k8u1tYnmmfaTtRQSxwOTgA9KsVj6h4q0LStcsNFvtTgg1LUM/ZoHJy/YZPRcngZxuIwMnigDP8ACfxC8NeNd0ejX/mXUcSyy2ssbRyRg+xGGweCVJAJHPIz1FfP/jnwO/gHxp4T1X4d6ROL6eWWHySWmhLgDAYuTt3I0ufmGFQkbdpavoCgAqOOeGZ5kiljd4X2SqrAlG2hsN6HaynB7EHvUleD/s3X95ef8JV9qu5590tvOfNkLZkfzd78/wATbVyepwM9KAPeKKKKACiufsfHPhbU9cj0Ww16xu7+SIzJHBKJAyjOQGHylhgnbndgZxjmugoAKKKKACio4Y2iQq80kxLs25woIBYkL8oAwAcDvgDJJyTJQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUVGRN9oRlkjEARg6FCWLZG0hs4AA3ZGDnI5GOSgCSiiigAqvf2NvqenXNheR+Za3UTwzJuI3IwIYZHIyCelWKKAOT8GfDnw54FR20i2ka7lTZLeXD75XXcTjOAFHThQM7VzkjNY/j34qr4F1S1t5fDupXdmzqtzfBGiijLchY2Zdsr7QxwCBxjOd23c8deOtL8B6Gb+/Pm3EmVtbRGw9w47D0UZGW7Z7kgHwzXtC8R+OfAd1498ZatJp0VqjyafbeRuSWNyPLVIxgxhnIUSMXLAqTgKGYAw/DvhTxL8R/EOtSaNc30OgahqDNfXlyyorje0imSNCFkkGc7VGAzD7oINfTfhfQNF8J6WmgaOsaC3RZZVLAyuWyPNk7ksUYZxj5cDAXA8n07x/bfC34P6VpczWl14k2SmKyiuEmWMPI0iSSNGxAQo6MADltwxgZK5nwf8J+Kdd8dr4/1954413yLLdIQ940kRUbBxiMK4IYfLwFUYztAPoeiiigAooooAKKKpx6tps2qTaXFqFo+oQpvltFmUyovHLJnIHzLyR3HrQBcoqva39nfef8AY7uC48iVoJvJkD+XIv3kbHRhkZB5FSTzw2tvLcXEscMESF5JJGCqigZJJPAAHOaAPG/2kbG3k8G6TftHm6h1DyY33H5UeNywx05Mafl7mrGr/Eq38BfCXwxHavBNrtxpVmba1kUuoTYm55AGBC4DAc8t0BAbGX8VvF3gDXL/AEeG/wBfu7+z064Z7rTdKTzFudyAqTLuVMLgglSW+dgNpyRgadbePPilrdx4ktrCSws5nkGn30V2ls1m6psUCXY0zxfeDIgCs7MflO7AB634b8bJZ/DrRtY8balY2F9dWjTnfIqGdByrKgJ3MUKMVUdWxgdKj8MfF7wp4s8QPounzXaXZdlt/OgKrchQzFkIzgbVz8+08jjOQPENf+EOpQeILDwvoOk6ldzo5N3rlzA0VrJvCsAvVVSNQcnJZmZhjhRXufw7+GWl/D6zlMUn23U58ia+ePYSmchFXJ2rwCeTk8k8KAAdxUckEMzwvLFG7wvviZlBKNtK5X0O1mGR2JHepKKAMPxhoE3inwnf6JBqMmnveIqG5RSxVdwLDAZchlBUjPRj16V5X8F/hVrPhjXLnXvEdrBbzJE0FrbkpK4LYJlDKSF4BUY5O5s4H3vcKKACiiigAooooAKKKKACiivG/Gfx3tLG9TSfBtrHreoSP5fnYcxCTeFCIq4MpPOCpA5UgtyAAewTwQ3VvLb3EUc0EqFJI5FDK6kYIIPBBHGKIIIbW3it7eKOGCJAkccahVRQMAADgADjFeAWHjv4m+GfH+gaF4rurSc6q8KvbPDETCkk/l7t0QUb8ISOWGGGRngfQdAHmfxc8J6br9vp97r/AIuk0bRbJy01syqVnbBPycg+btDgcP8A7K/e3eefAW/+0fFPxD9lu76WxubSacfa5N0kuJ02PLjhpMO2T6s2OtX/ABNp+o+I/wBpDSNH8SjdpI/0ixtlkWSNoUjZ+QV/jeIhwRnHy5ICmrHw2utHvP2gvE8ugW8EGmLp7xQpboixnY8CMyBPl2syswI6g570Acxoeg3nif4o/EnRrC9+xXV3FfIkxJA4vIyVbHO1gCp68MeD0PZ6DqnxI0Dw1a+GPD/w6jt59LRFmuL29EkUxcFmZPmQEsxLHa7Bc7SBxjP+HkLaT+0h4ttb8xwT3CXTwo0i5kDzRyrt55Jj+bHUAHOMGvdLk28+6wln2SXETkIkxjkKDAZlKkMMb1+YdCw5BxQB86at8QPih4N8Qafp+oa3puralOil9JitlkaNnACJJ5aKd5LZARz0HYjd9HwGZreJriOOOcoDIkbl1VscgMQCRnvgZ9BXH+Ffhd4c8J6pLq0C3d/q0juxv9Qm82Ub8bsYAGTz82N3zMM4OK7SgAooooAKKKKACiiigAooooAKKKKACiiigCMxsbhJRNIEVGUxALtYkjDHjORggYIHzHIPGKc2h6Xca5ba3LYQPqdtE0MN0U+dEbqAfzx6bmAxubOhRQB4v8ZfhbrvjPxDpmraK0Ev7pbKeKVwnkqHZhLk/eX5yCANwwMBsnHqHhXw9b+FPC+naHatvjtIghfBHmOTl3wScbmLHGeM4HFaF9f2emWcl5f3cFpax43zTyCNFyQBljwMkgfjVigAqMTwtcPbrLGZ0RXeMMNyqxIUkdQCVYA99p9KkooAKKKp6bZzWFu1vJdyXMaOfIaXJkWPAwruSS5ByAx5Ixu3MCzAFyiiigAooooAKKKKACiio54VubeWBzIEkQoxjkZGAIxwykFT7ggjtQBJRRRQB4f+0Bo15Zy6F420tNl1psoimnALlMMHhYqQVCh94JPUuo57dJqfxWUXvgOfR7eO40vxLcPBL56sksRDxx8c4BVnbPBB28HndXol/Y2+p6dc2F5H5lrdRPDMm4jcjAhhkcjIJ6V5HpXwItNH+JtvrltdRnQbZ/tUNm5cypMDlU3d0VsMGJJOApB5YgGHN8IviLJplt4PPiSxPhVJWcFBtKqJNw3KFDMx3s4QsVyvLDCmug8c/DH+x/hVeaP4HscySSxTagpPmT3scYJwCRywYK4Vdo4YKMttPsFRzzw2tvLcXEscMESF5JJGCqigZJJPAAHOaAPE/A/xf8HeF9D0vwte2eq6XNZReVdvPb5SOcZMuQGZ+ZN3G3jPQAce0WN/Z6nZx3lhdwXdrJnZNBIJEbBIOGHBwQR+FeCfEnxLN8V/EFp4F8IW8d5Bb3Hnz6gSfL3KCpYMOBEoc5bncSAueN/s/g/w1D4P8J2GgwXElwlojAzOAC7MxdjgdBuY4HOBjk9aAPM/i38Lby9s9Q1rwm08c11iTVNLgchL7adwkCDhpAcnb/F1HzZ3+ifD9b6P4faBDqVhJYXcNlHC9vI2WUINqk+hZQG2nlc4PIrpKKACiiigAooqvdQ3E3kC3uvs6rKrykRhmdBzsBPC5OATg/LuAwSGUAkkkZHhVYZJA77WZSuIxtJ3Nkg4yAOMnLDjGSCeFbm3lgcyBJEKMY5GRgCMcMpBU+4II7VJRQBXews5Lxbx7SBrpMbZjGC64DgYbrwJJAP99vU1YoooAKKKKACiiigArwv4sSWJ+NngGKOGQagtxbNPKT8rRG5HlqOeoYSk8D7w5Pb3Svnj4xX1vpnx18J395J5draxWc0z7SdqLcyFjgcnAB6UAfQ9U9N1bTdZt2uNL1C0voFco0lrMsqhsA4JUkZwQce4rwDxj8XvF+vWGqzeFLKSx8O2TtFLq0asWkG/yxh2ACFhJGdqjzF65xnGX4L+C/iTU/C9r4o0zXf7J1ObL2cRWSJvLJ27jKvzLuXcRhWBUrz83AB7n8TtSh0r4ZeIridZGR7J7cBACd0o8pTyRxucE+2evSuT/Z80b+z/AIdNqLpB5mpXckqyIPnMafuwrHHZlkIHIG71Jrm/jZqutaf8MtA0PX7mOTWry4Z7qWxysEyRA/ezgkkvE2NuNykjGBUev/GSLw94N0vR/BEn9pfY7SKzuNXmtHSOJhGVQKjY/eHYzjdlQFxhudoB6R4w+K3hbwjZ3G/UIL7Uo96JYWsgdzIpAKuRkR4J53c8HAJGK80MPxP+MFwk5Mnhvw26MgAkeNZoXIJyud05KMMEhYztONpJyfBP4cw66lx4x8UW0eoJcu32NLpxKJW3MJZZFIO47uBuPXccfdNfQdAHgc/wLu9H8S+FT4feR4re4FxqOrSXCB0ZShASIqQB8rFOHyXIfgAn3yiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigCvbyvJPdq7ZWOUKg8ho9o2KcbicSckncuBzt6qasUUUAFFFFAHzxrPgv4pp8UH8T/2ZY69Jayk2ck0ka2+zB2BYmkDJt3ZAzw4zlj8xp/EOH4s6p4T1XUPFJtNM0W3eB2sIJExKSwQbdhdmAYhiHfGSCM4GPpOvP/jb/wAkh13/ALd//SiOgDk/hB8LfCl14T03xJf28erXl0jNtny0ERDOhURsoBOODuDDcuVOME+2V438MfH/AIU8L/CTRItY1y0gnV5kaBSZZUJmkYbo0BYDHOSMcj1FeieG/HHhrxd5g0PV4LuSPJaHDRyADGW2OA235gN2MZOM5oA6CiiigArxf4neLfiZbaje6JoXhqeOxlixHqNhDLcyMpJ+ZXUARtjgqVLKckNyrV7RRQB88at4e+M/iqKVNf1KDRdNgtG855L2OCB12qr+Z5JYtuALHcNo+bG0HFcX8MPCM2ufFK1GlzSXOl6Tei5k1AQFVaONy0ZKsQVMhUALyRknB2muw+OvxEh1K4uvB1lJIkFm6vczRMGFxMCP3RwwARQSTkMfMQDaNu6vT/hHJ4Ul8FBvCEN3DZi4ZbhbskymcKoLNyVyVCH5Pl56A5AAPP8AVfhT4w8EeJbjWfhpcRmC5Tyfs7tGZYUY7mXMw2sgKLg53cgYOCxIPhP4/wDG1xFcePfEskFmziV7KOXzGVlO0YjXEKEpuO5d2M8gkmvfK5vx54rh8GeDr/WXMZnRNlrG+P3kzcIMZBIz8xAOdqsR0oA+dPiTZ+DtI1zS/B2hGC2tLWVf7V1Up9omEpwjbjjcdigsUQhSzEbQy11nx80200fS/Bvh/Tmj07SVeceXl/KTHlAO4UEsRvck4LHc3Uk54ceG4NMmtJdY8vXvEHiCytbrTrGYyhHluZmy8kokQ5Cpg5PLTDqFLG54kl8VeIPiV4dsfGTTxLqXkrFbyQRJ9nhuW2OEjy4VgSyhn/efIhOCAAAd34k+PUFq9lo/gSyk1ScOkInvY5ZBKNoCqgLCV3JOCWwcjo27I9ssJri4062mvLX7JdSRI81v5gk8pyAWTcOGwcjI64rL0Dwd4c8LIq6Lo9paOEKecqbpWUtuIaRsuwzjgk9B6CtygAooooAKKKKAOP8AiFdeNbHR0vPB1vY3DQZluoZkZ5pFVkIWJRwcgOGH3iPu/Nio/hn4+h8f+GjeNFHb6hbOIry3RwQGxkOozkI3OM9wwyduT2leP+FI/AXwc/tkT+MoLmS8u1iaEESSQCPftRki3NuGWDMQBnAwp6gHsFFeT/8AC1PEvieDPgTwNfXUbxeZHf6mVhhyH2sANwV/TiQHOeMKc0734a/EbxVcXr+I/HcdjBKhjS00sSNA0bFtyMmYxgA4y28kcE8cgHslFcf8O/AFv8PtDlsIr+e9mnlMs0r5RCeg2x5IXgDJ5J7nAUDsKAPM/jJB4v1PS9L0Pw1FJ9k1S4NtqFxGrExq20KHK5KxHLl2A6KATg4bQ8IeAfDnwt0a+1ESySzrbmS91CZMsI0XcwRVGVTKltoyTxktgY7yuX8f+Dv+E68Lvov9pz6fulSXzIl3K20/ddcjcvOcZGGCntggHzpB4+0jVfilF4y8WRXc8EaCWzsbBy32WRHxEjbyoIABkOw4LMDjllHsek/Hnwdf6HPfXcs9jd28XmSWMibnkPyjETD5XyzYGSp4LEKoJFjwL8GfD/g2UXtwf7W1MYK3FzEoSIhtwaNOdrcL8xJPy8EZIryCXTf+Fp/H6/8AssMEumpdq9yRcZSS1hKRMysuCd4UY2/3xzgFqAPR/g74bvNSvNR+IXiS1zquqSl7LzVOYYSPvR7mJCsCEXIyEQYJVufSNP8ACuhaVrl/rVjpkEGpahj7TOgOX7nA6Lk8nGNxGTk81sUUAeP/ABC8G+INL8d2vxE8H2MF/fQxMt1ZybmLERMnmAbhu+Qhdq4OVUgNk4r+DfDfjnxJ8TLPx14vtYNMjtLTZbWyKAXV0dQoXcWTHmMx3ndk4xj7vqHiTxTo3hHTo7/XLz7JaySiFX8p5MuQSBhAT0U/lWpBPDdW8VxbyxzQSoHjkjYMrqRkEEcEEc5oAkooooAKKKKACiiigAooooAKKKKACiiigAooooAK8X0XxVrum/tF6t4ZvtTn1Cwvs+VG5CpbYhEybV5xhcocY3Z3HJFe0V4P8XftmhfF/wAI+KpfPttIi+zwTXseSF2zO0iHbzzGx4x8w3AZwaAOw+Otjb3fwo1KaePfJaSwTQHcRscyLGTx1+V2HPr64rQ+D99cah8KNAmupPMkWJ4QdoGEjkeNBx6Kqj3xzzXnfxT+LGgeKfDE3hXw2l3ql3qLwqsscLIqkSBtoVgHZyVUYC4+brkYqT4Z+OZ/B+rQeAvEvh+fSZLi7k+yOHldFaSTCIquW/dltyh1YqeCcks5APeKKKKACiiigDLg0s22sxTW6RwWEFkLaOCOWQL97IAiBESBQMbtpY7sZULhtSiigAooooAr3119is5LgW89wy4CwwJud2JAAHYZJHJIUdSQASJIDM1vE1xHHHOUBkSNy6q2OQGIBIz3wM+gokghmeF5Yo3eF98TMoJRtpXK+h2swyOxI71JQAUUUUAFFFFABRRRQAVyfjj4f6b4+t7O31S+1KCC1dnWO0mVVdiAMsGVgSACAe25vWstvilbw/FeTwNcaVOG+RYryFjLudo1kw0YXKrgnLZOMZOBkj0CgDi4I/B/we8KxRPNJYafLcBWlkEkzSzlPvHaDglY88ALxwBmussL631PTra/s5PMtbqJJoX2kbkYAqcHkZBHWvP/AI62Nvd/CjUpp498lpLBNAdxGxzIsZPHX5XYc+vrisPSfF0Pwp+B/hqa9hjvry7TfbWsU4TesrNNkkjICq4BIU/MQOhzQB7JRXL+CNW8U6zp1zeeJtEg0nzJd1lCspaTySAQJF7MM4zwSc5Rcc9RQAVGRN9oRlkjEARg6FCWLZG0hs4AA3ZGDnI5GOZKKACiiigAooooAKKKKACiiigAooooAK+bPi9NbeKPjdovhyQSRwRPa2NxIkaK5MrhiVfBJAWRcBuAd2BySfpOvO7z4YtN8YLXxzbatJAgTdcQeWrMziMRBVJGAjJndnkY4PzAoAcv8edKsdD+FGj6ZpltHbWdvqcaRRJ0UeVN+JJOSSeSSSck16p4TsbjTPBuh2F5H5d1a6fbwzJuB2usahhkcHBB6V4p+0zMrXHhqACTeiXLkmNguGMQGGxgn5TkA5HGcZGfoOgDL1/w5pHinS203WrGO7tC4fYxKlWHQqykFT1GQRwSOhNeJ/F3RtC+Hfw1t/D2gJ9nm1a7Q3RcGSS7jhXJLORxhzEcDaMscDBavoCvnj4z6rea78VfD/h7R7CDVLrTNsgtJISwkmkKuySZIBj2JGT0ADNk8cAHtfg3Sm0PwVommS20dtPb2USTxJtwsu0eZ93gktuJI6kk85rcryPQvjNNZ6zHoHj/AEWTQtSd9q3IBFu3zMNx3HKplQocF1JycqBXrEE8N1bxXFvLHNBKgeOSNgyupGQQRwQRzmgCSiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAqvdfbD5As/IGZV85psnbGOW2gdWOAoyQBu3c7drZbaFqB1u1v/APhJNSe3huHlaxdIREysjqEyiK+FLqRuZvujIJww3KACiiigAooooAKKKKACiiigCnquq2Oh6XcanqdzHbWdum+WV+ij+ZJOAAOSSAMk14n4i8aX/wAXYNc8MeGYLG00K2i8671fU3kRGRHRlIIXEWSrH585VSflIIrsPir8ONU+IH9lR2WufZLW3l/f2swzHg9ZVCjLSAZADHGDwV53SeK/AevzeHzo3g3XbTR9JFl9j/sqSzVo5AxYSMZiGdSVY9ASSM5yxNAHnHwT+G/hfxZ4Vv8AVNdsZLydb028amd41RVRGyNhBJJc5yT0GMc5r/Ei8h8B/F7TpPAlpHa6m1vGLm1tMNFMzthYTAo4LKFJAPO5GADfMe70nRNL+AvgbWNUubv+07q4lTaQvkNOcYjiCl2HBLsSOdpY4O0Vxnwn8L61418eP8Q9cjjFmtxJNH5yFxPLgqojDEkJGcYbJwUVRkglQD6LooooAK8z+MvxBXwf4abT7CeP+2tRQxxqJGV4IiCGmG3kEHheRzyM7SK9Mryvx18IT4y+IOl6497GNNRES/tpWkLMqEkCPBwAwO0gFdvLDcSaAPIPAXw8XVvBXijxRrFnIbO10yc6du3KJJ1VmMikMCQhXGCCpLHupFZek6Dea54Nu47S7gtdMSWK4Euo6oYIFuljkV4FDKsbTSDa4POxAql+TX0v428M3l58L77w54aHkTLaR29rH5xXMaFcx7if4kUp8xwc8nBJrzO1+Depa38GtF0+OSTSdYS4kvZrW6kYRTM52gyKMlHESpjA4+YEAsSADpP2e7+8vvh5c/bLue48jUGgh86Qv5caxRbUXPRRk4A4Fc3rk83xh+LNtoljLJceD9HdXu5IWJglYZLHI2nLHMSkE4AZ14LVsa/Mvwl+E1h4X0sSSeJNVQwQm1jZmknfaJZAyhTld4VD97/VjBAOO0+G/gW38B+F4rHEEmpS/vL66iUjzXycDJ52qDtHTucAsaAPHPHWueI7f45apY+HbWO61h3tU0+Z4/MltcW/zCLcdiowlkLBgV53cEbhx/je28V+GPFVjqPiXV5JvEmyG/tsETLApeRtpJ+VSjrwiqyfMcEAYPq/ge5h1L40+N9duLqSK8sXlt3tbdQfNt4yI1Ijw7yH90pbZtKsEA3CTavmHinxrpHj74l2mq6xBJaaLA8FusYQsz26zAuZCrZUlXkPyZIwqjP3qAPpP4daxqmv/D/R9U1pNl/cREyHyvL3gMwV9v8AtKFbjg7sgAYrqKz9DvtL1HQ7K60SSCTTHiUWxgXagQcBQvG3GMbcDGMYGK0KACiiigDl774j+CtPs5LqbxRpTxpjIguVmc5IHCISx69hx16Vxc/xjvvEdxLp3w78NXerXaOVe7u18u3jAOVY/MOGVXxvaM5A4J4rqIfhL4Dg1Q6inhq0M5dn2OzvFls5/dMxTHPA24HGMYFdhBBDa28VvbxRwwRIEjjjUKqKBgAAcAAcYoA8nj+Gvivxmk1x4/8AFN3DBcJxo+kyBIoxuDqHJBVipLDox+VTvOK5vVT4a+HvxQsvDF54U8OHw7feXMLm4gae4iDgx5eSZyFUSISccBOepNe6arqtjoel3Gp6ncx21nbpvllfoo/mSTgADkkgDJNfKHxM8ew/EzWdOXTdBkt54HkgicMJJ7pXZfLUqq5BBzhQW5c49wD67ooooAKKKKACiiigCvf2v27Trmz+0T2/nxPF51u+ySPcCNyN2YZyD2Ncf8Nfhxb/AA706+hW9+3XV5KryXHlGL5FGFTbuYcEuc993sK7iigDj/DXxE0vxL4t13w3DDPBfaVK6fvBkTIjBHcEdMOcYPUFT3IXsK+f/E+i+X8dJrrwLc48T20X224sprPFszGP518wdGkVlzuAXMhPmhiBWhrN/wDHjV9Oezi0Gx03fkNNYzwrIVIIIDPM23rnK4YEDBFAEnx/8Z6LF4ffwmI47vVpnjmOMH7EAQwYnHDsuVAGDtYkkAgN6B8MtN1LSfhvodlqzSG8S3yyyFt0asxZEO4AgqpVcdtuBwK4/wAE/DDw54X8QQTeI9YtNX8YTuLyFJp8MjYYsyIzbpTuDnzGH8IICkE12mmePdH1fxzqXhKzE8l9p8RlmmAQwnBQMoYMTuBcAggYKsO1AHUUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAVj+J/E+l+EdDm1fV5/Kt4+FVeXlc9EQd2OD+RJIAJGxVPVdKsdc0u40zU7aO5s7hNksT9GH8wQcEEcggEYIoA8fm+O194gvRpngTwpd3946KRJd9IzvAJZEJGzBA3l1ALc8Dk0T4W+K/F1x9v+Jut3ctvvSWPSYbkbd6nblwg8tAVH/LPk7ydynOfZLGws9Ms47OwtILS1jzshgjEaLkknCjgZJJ/GsfxZ400LwVpy3mtXflebuEEKKXkmZRkhVH4DJwoJGSMigDxTwt4g8MwXvia68ZeHtN06TQNTS5sEsrNY5YpQ7gW4lhCrKVKjAb72GY5VSVk+G/h7xB8SfHMXxA8TN/oFrLvtlIZVkdCdiRAHiONuSecsCDuJcjk/hN4CX4jeJb6+1m7kks7J0mvFLsZbp5CxALdQCVYs2c9hydy/Vf+h6Xp3/LCzsbWL2jjhjUfgFUAfQAUAWKK8n8E/GJ/F/xFv9Bi0vfph3myu7dWJVE/jm3Ywr8Y4G0sqkHOR6xQAUUUUAFFFFAEZM32hFWOMwFGLuXIYNkbQFxggjdk5GMDg54koooAKKKKACiiigAooooAKKK8r8U/Hfwpo9vcwaTcyanqASVIjDCWgSVRhd7EruQt3QtkA+2QDjPgdD/wkvxO8TeLXtoEjHmSLE53vDJcSFhtOOyrIpbg/N0wTj6Hr5/+A3w3s7izi8Z6tD50glYadC+Ci7TgzHnlgwYAEDbt3ckqR74I2Fw8pmkKMiqIiF2qQTlhxnJyAckj5RgDnIB4P8VvjJoOoaJqHhzRY5L24+0RD7W0aNany3WQlQ2fMG5AuCu0gk5Ixmx4D+F994suLDxt491GTUnnTz7ewlO9SjHfGXOcBPmZhEoxyM/xJVf442fhDwv4a/s7StH0a31jU7hJHEdsolihUcuhH+rBZEXHAbL4BO4jQ8HfE7V9K1vw54G1bwtJC5srW2XybgSzxYQjzJIwPkBUIxRtrIoZjuyBQB7ZRRRQAUUUUAFFFFABRRRQAVHNMsCB3EhBdU+SNnOWYKOFBOMnk9AMk4AJqveWc13cWpF3JDbRP5kkUWVaZgQUBcHIQHJKgfNwCdu5XuUAFFV7izSeWOYHyriPhZ0RS4QsrOgLA4Vtig49OCCARYoAKr2d9b38RltZPNh+UrKqnZIGUMGRujqQw+ZSR1GcggE14kF5bW8g2/aNwjcuoBcDOwAncWKh2GAeEbJHGbFAEck8MLwpLLGjzPsiVmALttLYX1O1WOB2BPapKjnhW5t5YHMgSRCjGORkYAjHDKQVPuCCO1BjY3CSiaQIqMpiAXaxJGGPGcjBAwQPmOQeMAEd1YWd95H2y0guPIlWeHzow/lyL911z0YZOCORViiigCOeeG1t5bi4ljhgiQvJJIwVUUDJJJ4AA5zXgfwpS48a/GHXfHFxZzyWEfmi0uZQI/Lc7UjTCnDMIMg9cZBJyQTofFXxn4p1G8uPAOleFr63k1GUW8F60hBu4wAz+Xt+UKcjcSxAQncFJO30j4f+DrfwP4SttJi+a4bE15IHLCScqA5XIGF4AHA4AzzkkA0PE/hjS/F2hzaRq8Hm28nKsvDxOOjoezDJ/MgggkHyPStVvvgt41t/Cmp3Ml54S1N99jdTfKbMs2DljhcAkFwDjDBxtJKn3SuP+J3hH/hNPA17psS7r6L/AEmy5x++QHC/eA+YFkyTgbs9qAOworzf4HeIbjX/AIa2yXS/vNNlNgHyPnRFUpwAMYV1XvnbknJr0igAooooAjEbC4eUzSFGRVERC7VIJyw4zk5AOSR8owBzmSiigAooooAKKjmmWBA7iQguqfJGznLMFHCgnGTyegGScAE1JQAUUUUAFFFFABRRRQAUUUUAFRzTw2yB55Y4kLqgZ2CgszBVHPcsQAO5IFSVTn02G51Szv5WkZ7RJBFGSCgZ9o34x98KGUEdBI4/ioAsGFWuEnJk3ojIAJGC4YgnK5wT8owSMjnGMnMlFFABRRRQAUVHJIyPCqwySB32sylcRjaTubJBxkAcZOWHGMkFAElFFRxxsjzM00kgd9yqwXEY2gbVwAcZBPOTljzjAABJRRRQAUUUUAeT+IfhBeeK/iKNV1zX573w6v71LF3IeNjgGJQAFWM7QSwwxxg8/PXqkEENrbxW9vFHDBEgSOONQqooGAABwABxiuf1/wAf+FPC7tFrGuWkE6uEaBSZZUJXcN0aAsBjnJGOR6irHhrxhoHjC3nn0HUo7xIHCSgIyMhIyMqwBwecHGDg+hoA3KKKKACiiigAqnq2pQ6No19qlwsjQWVvJcSLGAWKopYgZIGcD1FXKjnghureW3uIo5oJUKSRyKGV1IwQQeCCOMUAeL/CnRJvHmqT/EfxX5l1ercGLTrd4ysEKpgh4weoViVX0ZWYkvyPbKr2NhZ6ZZx2dhaQWlrHnZDBGI0XJJOFHAyST+NWKAPD/gXdWet+MvHuuQW+PtN2ksDyoPMSOWSZyuRnGcLkA4yo64FZH7QOm+E9F07SLLTNJsbLV5JWlJs4UixAAQd6rjOW27SQfuPgjnMf7Ouv6Lp9/faLK12usao4aPKgwMkSFgoI5D/NKTnjCjnPB2/FXwEvPEvjnUdW/wCEj8qxvMzbp4zNMkhOPLAyo8sD7pzkABcHG6gDuPAiWHgfwhoPhbV9XsYNXMW/7LNcRpIXlkZtiruO7DMUBGdxXj0ruK+SPiH8PtH8M65ofhvw/qU+p67dYju4ZGQBZH2CPHACbiWO1mJA2knBBP1PoWmf2J4e0zSfO877DaRW3m7du/YgXdjJxnGcZNAGhRRRQAUUV438atW8X3l7Z+DPDWmXbxalb+bcXECMTKu/YY92MIgypck9HUEgE7gDnPG+t3nxg+IFt4H0C4g/sS0l82S+jQyqzKp3ykgfdXcyKAQrMwO7DLtj8YRQx/G7wL4a0rzLpNDSwt3/AHAMq7HDkuwUFgIgjH+FcsRj5q9j8C+BdL8B6GLCwHm3EmGurt1w9w47n0UZOF7Z7kknyDwRN/wl37Sur6yLmDy7H7RJE0A3JPGgFsmDnurK24ZBxwADwAfQ9FFFABRRRQAUUUUAFFFeZ+J/jb4c0a4TT9FWTxDqkjqkcFg2YyxK4HmAEEkNwEDcjBxQBl+If+TofCf/AGCpP/Qbqs+31zS/D37SHi+/1e/gsrVdKQb5nxuIjtjtUdWbAOFGSccCuY8Y/wDCa+IfE3w/n8X+RpS6hdrHawaduintcyxhnYtkrIQyEcnbtGQpyDr+DvBuj2nx+8SaHew/2xawaf54fVVS4d5HMDs7Ergtl25xnmgDAl8c2X/C+7/xNoOjz+JPOiVLGKAyRPvECK7hdhY4VZBgr3z2BqmnirxXoHxB1jxbYeH9Ngu9QvZdINhuEv78GMuqojh3fITc4G0s/qwFdR4e1yHQPjP4y8R6haxrafZ75RDpcYmf9zNGpaSNCTEW2Fi8mxSWJyAa5/wZ4g8Xv418Ra34J8LR6gmsXsxW5vbZsQruaTYZQ6ohIZSQW5IX2yAe1/C3xpqnjjwu2parpP2KRZWjSaMYhuQD1QElhj7pzkZHBzlV7iq9hNcXGnW015a/ZLqSJHmt/MEnlOQCybhw2DkZHXFWKACiiigAooooAKKKKACiiigAooooAKKKKAOf8aeK7fwV4Xutbubae5WLCpFCpO52OFDNghFz1Y/QZJAPzx4U8YaP4j8aXGt+M9K1XxHrU+Y7LTbOySeCKMAniNny2BuwuCByxLMcj6nooA+UPCGr+Lj4+8R/8IR4asbG/uopRJYOiA2KhwDtMhXaysRlfuk/wYUBe/uvg74x8ZadBc+LfHM6XUu2WSwFv5kMDgbRgLIqbgvUqvUtyepyPh5rlhovx18bHU7+xsbWWW8Alu3jjy4uRhQ7cjILHaDzgEg7Rj0uD4z/AA+ubiKBPEUYeRwimS2mRQSccsyAKPckAd6ANjwZ4G0XwLpb2OjxSEyvvmuJyGlmPONxAAwAcAAADk9SSekqOCeG6t4ri3ljmglQPHJGwZXUjIII4II5zUlABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABXmfxa0XQdK+HniDWYPDujNqDoAbh7JC+6WRUZ9wAO/5ywOeuDzXplef/G3/AJJDrv8A27/+lEdAB8Ev+SQ6F/28f+lElYfxW8Q+P/7Ug8O+D9F1KOKZB5upwQ7hJvyu1X5EQHdmKsCARtAy258Ev+SQ6F/28f8ApRJXoFAHzJ46+G6/D7wVHr99qd3f+KLrU4St9DM0Yt32u7EE5aQllJ3kqc7SMYYN1/wo8H32reJ2+Imspdoklusemx303m3Djy1j853VUBBQEDK5YNuPQM1P44eOPB2veEn0ay1f7bqcF3FNCtmN8WdpBLSY2sux2Hykndt9GrmPE/iX4naB4B0e01BoPD9isUNrbR28gjvLlFQEMfmZ02BFDY2cyAEHOAAfT9FcX8NtD8V6J4fVPFeuyahcSorJbyAO1qcsWUzZJkJ3L14XbgEjmu0oAKKKKACiiigAooooAjjghheZ4oo0eZ98rKoBdtoXLep2qoyewA7VJRRQAVGJGNw8RhkCKisJSV2sSTlRznIwCcgD5hgnnElV7i6+zz2kX2eeX7TKYt8SbliwjPuc/wAK/Jtz/eZR3oAkmghuUCTxRyoHVwrqGAZWDKee4YAg9iAaI5lleZFEgMT7G3RsoJ2hvlJGGGGHIyM5HUECSo5IVleF2MgMT712yMoJ2lfmAOGGGPByM4PUAgAkooooAKKKKACiiigArL8Sw31z4V1eDSzINQkspktTHJsYSlCEw2RtO7HORitSigDg/hD4Su/B3gOKy1GGSDUJ7iS4uYWkRwjE7FClcjGxEPU8k/Qd5RRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABVdG868ZklnVYMxPEY9qOxCMGBK5bA4BU7fmYHJHy2KjhkaVCzwyQkOy7XKkkBiA3ykjBAyO+CMgHIABJRRRQAUUUUARwQrbW8UCGQpGgRTJIzsQBjlmJLH3JJPeipKKACiiigAooooA5/wAY+MdL8D6H/a2ree0LSrDHHAm55HOTgZIA4DHkjp64B8Qk1P4o/GPfHpsP9keHpdy7gzQwuv7wYaTG+bIyjBQVyBlV6173r/hzSPFOltputWMd3aFw+xiVKsOhVlIKnqMgjgkdCauf6Hpenf8ALCzsbWL2jjhjUfgFUAfQAUAeJj4MeC/Afh/UNd8XXd3rEFsmdkaNAoyVCgKjbi5bjJcL83IGM1Q/Z10KafWda8VCKO2s9jWUMEeSu5mWRgCzFgEAQfNnO/rkGsfUtS8R/Hvxium6aslj4dsnD5kGVhU5HmyYOGlYZCoDxyAcb3P0P4c0Cx8LeH7PRdNWQWlqhVPMbczEkszE+pYk8YHPAA4oA1KKKKACiiigAooooAKKKKAPG/hZ8IL7wX4x1LVdUntLiKFGt7BlTLOG2kzc/wCrO3KY5PL84wW9koooA4fwv8L9H8NeKNS8RtPPqOp3krSJPdqm6HeMyYCgLuZi3zADCkKAPmLdxRRQAUUUUAFFFFAHH6r8QtGj8Da94j0i/gu49M8yASeW7RtcgAIvAG5SzoNynGGznHNcP+zho32Twlqeruk6SX92Il3jCPHEvDLxz8zyAnJHy44INch8Qvh/4x0rXL7RPDFrfT+Gdbu1uktbQZhjl4yrgALEoY8Zwu1UJJKfL7/4V8PW/hTwvp2h2rb47SIIXwR5jk5d8EnG5ixxnjOBxQBsUUUUAFFFFABRRRQAVz/hvwP4a8I+YdD0iC0kkyGmy0khBxld7ktt+UHbnGRnGa6CvP8AxNpPxO1DxQX0DxHpWl6F+7QKYBLMBgb3IaMgtktgBgCAvQ5NAHJ/HXUodG8VfD/VLhZGgsr2W4kWMAsVR4GIGSBnA9RWRY6f448WfE+78ZeEoLvQtL1RIoTf6jBFuEIiiJYRMWLglAVK8Hj5hzjLi8EPF+0JpOlJrt9q9xafZ77ULy5iZ3jeNA+1iT91tsYBydvmKvzFefpegDyPTPg3c+HdL1u8sNXj1TxRqCNEl3qIdINknEgkjBcSkgs3zhgWCfLwSaf7N0Nwvg3Vpmut1q+obI7fywPLcRoWfd1O4Mgx22e5rU8c/Gqx8PapJoGg2Mmsa4HEO1OYo5TkBPlyzuG2gooHUjcGBFeKfDbwZ4k8ceda6Trv9n2Om3cF1IGnkHlyNuAljReDIAh5JU9Bn0APr+io4I2ht4onmkndECtLIFDOQPvHaAMnrwAPQCpKACiiigAooooAKjhkaVCzwyQkOy7XKkkBiA3ykjBAyO+CMgHIElFAEcEjTW8UrwyQO6BmikKlkJH3TtJGR04JHoTUlFFABRRRQAUUUUAFeR/E74vTeHdUk8LeHLKS616RFQy7SRBI+0oETB8xyrZA6AlfvcqPXK8r8W/CvV7zxvL428LeII7DWiihIrqAPHu2eUxDYOB5fYo3OeRngA4jwt8PrG/+MupWHiW4j1C5Wylur61PUzuI1d2IQLsYzPJHsO5Rs3bGBFeh/EPw58P9E8DandajoOlWqmIpAbaCO3mebGUWN1UkMSPQjAJYFQa8Mh8S654P+MR17xlbyXGsWiMbiFDEC7NbFIxlPkAwyZIzgZ4J4rv9E8GeJvi9qmn+KvHEkdtoapm206DdGZE45VSSVRzyXLbiAMYUqwAOg/Z4jvh8PriW6muzbteutpFKP3aoAuWj4zguXB5IypwAd2fXKjgghtbeK3t4o4YIkCRxxqFVFAwAAOAAOMVJQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAV5/wDG3/kkOu/9u/8A6UR16BXF/FrTZtV+FfiC3gaNXS3FwS5IG2J1lYcA87UIHvjp1oA8s8IfF+z8H+BNA8P2GgarqmpNnYsiCBJvMlcnyiC5fDkoPlGcHoRtrTg8A+PPifbxXXjzWJNL09LgT2+mQ26BijDJ4B+QhTtXzN7D5sgc7u0+CX/JIdC/7eP/AEokr0CgDj9H8E+Dvh/pL39vpsEX2CJ55dQmj82cBY8O27BYZUHKpgcnA5ry/wAC6JrHxQ+Io+Imt2/2bSLWUGygZ3Ido+EWM5Hyow3M3Cs+Rt5YL6J8XtA1/wATeA5dL8PLHJPJcRtPAzKpmiBztUtwCG2N1HCnnsfJNc1j4neD/hrp6SpY+HtNglbSlhtIgtzMdrZlJO4DJRzvQqSWLDIKtQB9L0Vyfw21rWvEHgPTdT16COK8nQkMoKmZAcLIykAKWAzgZBBBGAdo6ygAooooAKKKKACiiigCOOeGZ5kiljd4X2SqrAlG2hsN6HaynB7EHvUlRxwQwvM8UUaPM++VlUAu20LlvU7VUZPYAdqkoAKKKKACiq81hZ3F5bXk1pBJdWu77PM8YLxbhhtrHlcjg461YoAKKKKACiiigDwf4R395a/GTxnoEd3O2mCW7nEEshk/eJcKgfc2WLbSQTnLcZzgY94r5/t/itcaf8VfEc+v63BZ6Tp8s1slhBp4ea9ERkWOMSBcjBJfLOBubAADHbB4i/aL1iDUZLXSfDsFn5Eskcg1Pe8hwcDKKV8thg5UlueM8cgH0PRVewmuLjTraa8tfsl1JEjzW/mCTynIBZNw4bByMjrirFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABUYkY3DxGGQIqKwlJXaxJOVHOcjAJyAPmGCecSVl6New6qJb9dKu7GdkiRzeW4ildfLWVR3JCmVlI7OJB7kA1KKKKACq97Y2+oQLDdR+ZGsscwG4jDxusiHj0ZVPvjnirFFABRRRQAUUUUAFFV7O+t7+Iy2snmw/KVlVTskDKGDI3R1IYfMpI6jOQQLFAHL/EDxjb+B/CVzq0vzXDZhs4yhYSTlSUDYIwvBJ5HAOOcA/KmuXvijSPD9to1/fXa2etIutTRSFD57SEhXLqzM4IRWwxHzDlcgMfTPi49x4++LmjeB7JPL+x/I8xUZBlVZJHGWAZVjVSBwSQw5yK+g4IIbW3it7eKOGCJAkccahVRQMAADgADjFAHzRoHxD8VWnhpdN+HvgeSDTI0MUlytvLeym4I+aRpFVUL4KHBUgYHG3Cjq/B/wAXNd0fXLfw98SrX7FJcRJJBfywiEjfkr5oHyhTnbuAGwqQwzuK+seJ/E+l+EdDm1fV5/Kt4+FVeXlc9EQd2OD+RJIAJHgnhaDxH8YfilbeLriKOy0vSbiJlbblUWN/MSBTwXck5Zu27PHyqQD6TooooAKKKKACiiigDP13U/7E8Panq3k+d9htJbnyt23fsQttzg4zjGcGvN4fj94Wl8JXOrGKePUodqDS3I3yyMuRtYcGMEMC5AIxyuWUN6xXypr3h7QfDX7QMdlqltJB4fkvYrhVdEWLa4DY+YBPIEhKn0RWHUUASXXj/wCK1j5Hja8knt9MnlW3hhmiVLaRW/eqqxH5ipUECUfMRxvr6T8NalNrPhXSNUuFjWe9sobiRYwQoZ0DEDJJxk+prxv4nQ/8Jl8RfDvw80i2nFhpuyTUEtD5aQRts/hI2Dy4sbW5H73aBng+6QQQ2tvFb28UcMESBI441CqigYAAHAAHGKAJKKKKACio4JGmt4pXhkgd0DNFIVLISPunaSMjpwSPQmpKACiuD8e/EmHwne2ug6fZyX/iTUUUWNucLEGd9iGRyRwW3cD+7yVBBrvKACuL8ffEnTfh6+lDUbO7uEv3lGbfbmNUUZOGIydzIMccFjngA9pXgf7TMLNb+GpwY9iPcoQZFDZYREYXOSPlOSBgcZxkZAPfKKjgkaa3ileGSB3QM0UhUshI+6dpIyOnBI9CakoAKKKKACiiigAooqOeCG6t5be4ijmglQpJHIoZXUjBBB4II4xQB8oeA/iBc6X4l8QaxaeHpNV8Uay7G1itw+yIMXll+QZZhuEZ29cK3zDv6foPxa8R2fjy18KeN9I02yuLx0RHsptxgZwdgdVaTJZtgAyuA245BFU/C/jj4SeDvEOpW2n6XfaLcLuglu7u3lfdsfGwAs8iZPOCq/dG7kAV554b+IelL8VL3x14kTUjOXdrS2so43ADIYwrsSmQkeFGBljySMEMAen6FMsX7UnidGEhMumIi7Y2YA+Xbt8xAwowp5OBnA6kA5/7NOmzRaNr+qM0fkXFxFbooJ3Bo1ZmJ4xjEq457Hp3yJfiRovhb4weL/EN5pmsm7nt47ayt5IRBuAjTJkD4dAxjjKnBO1iSucCuf8AAPhLxp4+8HSaRY6raWHheK9UTqyKrSycMx+Rd0hUFTh2A+4Afl+UA7fxr8dL2z8VQ6X4KTTdYgZFjZmt5ZGa43suxNrKHBATBUEHPBNeweHNSvtX8P2eoalpUmlXc6F3spJN7RDJ25OByVwcEAjODyKw/Avw30LwHZj7DD52pSRCO5v5M75ecnAyQi5/hH91cliM12FABRRRQAUUUUAFFFFAEZnhW4S3aWMTujOkZYbmVSAxA6kAsoJ7bh61JRRQAUUUUAFFFFABUc88Nrby3FxLHDBEheSSRgqooGSSTwABzmpKjnghureW3uIo5oJUKSRyKGV1IwQQeCCOMUAV49J02HVJtUi0+0TUJk2S3awqJXXjhnxkj5V4J7D0q5WXYeJNF1PVLzTLHVLSe/snKXFukoLoRtzx1IBYAkcA5B5BA1KACiiigAorHvvFnhvTLySzv/EGlWl1HjfDPexxuuQCMqTkZBB/Gubn+M/w+triWB/EUZeNyjGO2mdSQccMqEMPcEg9qAO8oqOCeG6t4ri3ljmglQPHJGwZXUjIII4II5zUlABRRRQAUUUUAFFFFABRRRQAVz/jv/knniX/ALBV1/6Kaugqvf2NvqenXNheR+Za3UTwzJuI3IwIYZHIyCelAHif7N+t6ldaXq+jS+XJp9i6SxO0jb42kz8iryNnyM3BGCTw275fdK8H/ZusLyz/AOEq+1Wk8G2W3gPmxlcSJ5u9Of4l3LkdRkZ617xQAV8+ahCvxb+PU2j3pkGh6EkiPbmRkMgjYK+MEjLSsASNpMajowFfQdfP/wAdNB8BaTvv3ini8S326RLeymCrKTgedMpDBVBB+7tLsW68soB6ovxA8C6c9lpsPiPRokdCkCQToYo1RRwWX5IxjAAYjPQZrrK+ePhZ8EbiS8j1vxjZeVbx7XttNlwTKSAQ0o7KM/cPJIIYADDfQ9ABRRRQAUUUUAU9UihuLBoLjzDBM6RyIkAl8xWcAoylWGxgdrHHCljlcZFyio4BMtvEtxJHJOEAkeNCis2OSFJJAz2ycepoAkooooAKKKKACiiigAooooA878Z+GPH914lTVvB/iyOzie38mWzveYo8EHci+WwJPcsNw6BiDtXn5PBHxg1l4bbWPHtpa2Yfe0unApKCFOPuRxlhk8gsB35IFeyV4/8AF74j3umXieD/AA5PBBq11FvuL2S6jhFshBOwOzAJIQM5YjAK7clgVAPOPDd1qPgvUdW8PeHbeDXPEJ1CSzsZXRQ2nuDJG0saPnLOkZLkfIirFvZslR6n4X+G/hnwCia/4s1O0utandTJfalMoiScsXzEZMHfkZ3kljtJG3JFeefAnXvDXhWz8Q6vruqWNrMfIiiV1ZpwmW3bQFJZSSmQucbMsAACbFxqeo/Hzxzaabbw32n+FrDMlxtZWxy22RuABI4wgXL7fmIyN1AH0fRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAVGJGNw8RhkCKisJSV2sSTlRznIwCcgD5hgnnElRyTwwvCkssaPM+yJWYAu20thfU7VY4HYE9qAJKKKKACiiigCOATLbxLcSRyThAJHjQorNjkhSSQM9snHqaK5e/ufEOneOpLi20CTUdFn0yNGktTbrOtwkshAJkkQlNrk4OQCRjGWyUAdZRRRQAVn67qf9ieHtT1byfO+w2ktz5W7bv2IW25wcZxjODWhXN+PvDU3i/wRqehW9xHbz3KIY5JASu5HVwDjkAlcZ5xnODjFAHjHwL1nRYtZ8R+I/Eeuabb6tduFDXVwIGYOxeVgDtQhmCdMkbTwoI3dv42+Ofhzw9bz22izR6xqmwiPyTut42wpBeQHDDDZwmeVIJXrUemfBDR7j4f6boevxQRanbSvLNf6YqLJLln2qZHj3Mu114I6qPSuo8O/C7wd4Y8uSx0WCW6TyyLq7HnSb06OC2QjZ5+QLzjjgYAPING8AeMfi7qKeIfF9/PY6RLie1jHO5GIBWGMn90pVR87Dn5Ww+Sa+g9K0qx0PS7fTNMto7azt02RRJ0UfzJJySTySSTkmrlFABRRRQAUUUUAFFFFABXD/Eb4a6X4907zJE8nV7eJxaXStsycHakh2nMe4g9MjnBGTnuKy9S8S6Do1wtvqmt6bYzsgdY7q6SJiuSMgMQcZBGfY0AfNFn+z743ubi6imGm2iQvtjlmuSVnGT8ybFYgcA/MFPI4649r+EPga88DeEnt9Slzf3kv2iaFJCyQfKAEHO0sMcsoGemWCqa9AooAKKKKACiisfxZfXGmeDdcv7OTy7q10+4mhfaDtdY2KnB4OCB1oA8b+Gsi+Nvjr4k8W+THNZ2iMtrOhZApOIom2k7iWhSTORgHPAOK98rx/8AZ20T7D4Gu9Wkt9k2pXZ2S78+ZDGNq8Z4w5lHQE/TFewUAFeL/tF2vleHtB1yC4nhvrHUPKgeJ9u3ehctkchgYVwQRjn2x7JPPDa28txcSxwwRIXkkkYKqKBkkk8AAc5r58+OnxC8NeJPD1romjX/ANtuodQ82V4o28tAiOn3yAGyX4K5BAJz0yAdH4L+H/iq6t/D3iO4+JGszQSpbX0ljIZWV1IVzESZsEEfLnH4dq9krxv4U/F/RdSstI8KX0Emn38FvHaW8juGiuCiKoG7gq7EHCkY4ADEkCvZKACiiigAooooAKKKKAPnjxJquu/HDxRH4Y0iwn0vRNNlEl7LewgSRSAFSZBnhhl1WMHJOSTgfJ7XpXg3wzob28umaBpttPbpsinS2XzVG3b/AKzG4kjIJJycnOc1uUUAV/sFn/aP9o/ZIPt3leR9p8seZ5ed2zd1255x0zXk/wCzj/yTzUP+wrJ/6Kir2CvH/wBnH/knmof9hWT/ANFRUAewUUUUAFFFFABRRRQAUUUUAFFFFABRRXkc/wAWfGVtcSwP8J9ZLxuUYxyyOpIOOGWEhh7gkHtQB65RXj//AAmPxi1T/TNJ8B2NrYyf6uG/lxMuODu3SRnqCRlBwR16k/4Qb4ta1/o2ufEGCytV/eLJpkZEhccAHYkR24J/iPIHHcAHrk88Nrby3FxLHDBEheSSRgqooGSSTwABzmuT1P4qeBtI8r7T4msZPNzt+yMbnGMZz5Qbb174zzjoa4+z/Z/0u4vDf+J/EOq63fGVXeRm8sSoAAEfO9zwMZDjjAGMZrY8RfDnwdoHw88RnTvDtikiafczJNNH50iOIjgq8m5lxgEYPB560AeKfBzUtS8NazqGvW/hrWdYgayktI/sFq0i+dujcKzAYUYXk8kZHBr1P/hOfi1rX+k6H8PoLK1X920epyESFxySN7xHbgj+E8g89gfs4/8AJPNQ/wCwrJ/6Kir2CgDyOfwl8YNauJbfUvG2m6fp87ln/syM+ZEM7lVDsR8ZAHMmcZyT0Mf/AAo681T9x4o8fa5q9ivzpBuK7ZOgbMjyDoWHQHnr2PsFc/4x0LVPEeh/2fpPiCfQ5mlV5LqCPc7IM/ICGUrk7TkHtjoTQB53f+CPg98Pksxr7Rvdo4dDdzyTSyfMxBaGPgp8pXJTacYOSeeA1e+HxQ1T+wfAPgjTbDT2eIS3506OOWPqSZJEBEScHgZZtnBO4pXp+gfAjw5p96upa5dXev6gXMkrXR2xSSF928pyWPYhmYNk5HPHqEEENrbxW9vFHDBEgSOONQqooGAABwABxigDD8F+FLfwV4XtdEtrme5WLLPLMxO52OWKrkhFz0UfU5JJPQUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUV5v8AGXx5ceCfC8CaZP5Or38uy3fYG8tEIMj4ZSp6quDj7+R0oA9IrPutC0e+1GDUbzSrG4voNvk3M1ujyR7TuXaxGRgkkY6Go/Dd5qWoeGtNvNYtI7TUJ7dJJ7dN2I2IzjDAFT6qc7TkZOMnUoAKKKKACiiigAooooAKz4dHt7bU7m/tnnhmu5VlulEpZJisfljKtkLwFyU2k7FySBg6FFABRUckbO8LLNJGEfcyqFxINpG1sgnGSDxg5Uc4yDJQAUUUUAFFFFABRRRQAVzfiXwD4X8X3EFxrukx3U8CFI5BI8bbSc4JRgSM5IBzjJx1NbGq6rY6Hpdxqep3MdtZ26b5ZX6KP5kk4AA5JIAyTXl+pfHzSHv107wtompeIbxnARYkMSyLsLMU+VnJGOQUHQnOByAcB8DfBXhfxlb62uu6XJdz2jwmNzdOihXD8BUKnOUOSSc8YAwc/SdjYWemWcdnYWkFpax52QwRiNFySThRwMkk/jXyp4a8X6z8J9U1dG0/TYbie9jjvNHmWfzY403MDG/KBCHIDFnJ+U4I5P1PpOpQ6zo1jqlusiwXtvHcRrIAGCuoYA4JGcH1NAFyiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiq9rdfa/PIt54lilaJWmTZ5m3gsoPO3OQCQM4yMqVYgAlrs1Ga8+0Tt5sUcXks+Y02FzuVezHfgnuFX0oqxRQAUUUUAFFZ+uazZ+HtDvdXv32WtpE0r4IBbHRVyQCxOABnkkCuT8A/FbRfH9xJY2lvd2moQ26zywzKCpGQG2ODyFYgchScg464AO8rl/H/jH/hBfC761/Zk+obZUi8uJtqruP3nbB2rxjODlio75HUVl+JNEh8R+GtS0afywl5bvEHeMSCNiPlfaepVsMORyByKADw5r9j4p8P2etaa0htLpCyeYu1lIJVlI9QwI4yOOCRzWpXg/wCz/qd5peo6z4LvYYFmglnuZFViZIZIzFE6sQCjKSRgq2QUbPDKa94oAKKKr21z5m2CdoEvliSSeCKXf5e7IHJAJUlWAYgZ2njgigCxRRRQAUUVHDPDcoXgljlQOyFkYMAysVYcdwwII7EEUAef/EzSfH+tXGnWHhDU47GwmSQXkwfyWjZSrKTICXwcYARfXcSG48k8XfBez8EfD+91rU9d8/Ux5CW8ESiOPzGYCRctlpPl3EY2nCkkdh9J6bqtjq9u09hcxzojmOQLw0TgAlHU8o4yMqwBHcCuf+IfgeHx/wCGl0mW+ksnjuEuIpljDgMAy4ZcjI2s3QjnB7YIBn/BiCa2+EmgpPFJE5SVwrqVJVpnZTz2KkEHuCDXeV8weKPC3jj4Z+HtN1668YzvdW92tla21tPLJHFEU34y+BtzCoMezaQq5z0r1+L4u+FbDSdJPiDWILXU7rT7e7nghglkEZkjDY+UNt65AJzgg9wSAegUVHBPDdW8VxbyxzQSoHjkjYMrqRkEEcEEc5qSgArz/wCNv/JIdd/7d/8A0ojr0CvP/jb/AMkh13/t3/8ASiOgDY+HFjb6f8NfDkNrH5cbafDMRuJy8iiRzz6szH2zxxWh4q1DVNK8L6jfaLp39o6lDEWgtc/fOeTgctgZbaOWxgckVc0nTYdG0ax0u3aRoLK3jt42kILFUUKCcADOB6CrlAHgcHw88f8AxNuIr/x1qsmmaWXEsenRjDAZyMRj5UO13Xc+6QYwwNeoaR8NvCmi+GrvQbfSo5LO9RVvDOS73BAwGZuoIPzDbgKSSoBrrK4P4leM77QbKLR/Dccd34ov0dra2HzPHEqMzyhMYYgIQqn7x6BtpUgHknjVNL8b/HfRPD+lWfm2Nh5Wm3MCjyU2QyO0ypyMKqbhxj7p254J+l68D8HN4Z+COl3Fz4lv47jxReogl0+zVZpbZOD5eeikhwzFmAbaNu7bk+gfDn4oWPxDS5ih067sry1QPMjDzIgCxA2ygAZwAcMFPXGQpIAO8ooooAKKKKACiiigArP0T+2P7Hg/t/7D/afzed9g3+T947du/wCb7u3Oe+ay/HNr4oufDUh8IahHZ6tE4lUPEjidQDmPLghScgg+qgEgEkcH4P8AjI8MtvonxBsZ9D1PykMd3dQtElySxXcylR5f+99zhzlcAUAewV4/+zj/AMk81D/sKyf+ioq6D4q+Gdd1nTtK1XwoNviHSbvzbdxMI28txtkUbjsOSEJDcFVYd8HL+AWk6lo3gW+t9U0+7sZ21OR1juoWiYr5UQyAwBxkEZ9jQB6pRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQB5/8SofiOfsM3gS6gEY3Jc2/lxeYSeQ+6X5SoxjAwQT/ABA/Lxcl78f30uG0XTLSOdH3Neq1p5sg5+VgXKY5HRQflHPXPulcf4/8cxeDrOyt4YvP1fVJfs9hC0bsm7Kgu+wFiq7l+VQWbIAHUgA+dNPuNS8D+E9UNv45k03WDcTWcmgQRtIdysiSMX+5G+OQ69QuFfOQvt/wSn8X3Xg5rjxPLJNbSuH06S5ZmuHjOSxcnkoTgoTzjP8ADsryz4f6D4Y1LxlrOqePNd0qaeC78+Im/hW2vXaSYOxXjK7kDBflyGUkFWGfW9f+NvgjQXaJdQk1OdXCtHpyCUAFc7t5IQjoDhicnGODgA9Eorl/Bfj/AELx5Zzz6PJOJLfb9ogniKPFuLBckZU52E/KT74PFdRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFfPmpR2PxF/aSWxuprRtP0hBF5bjabkw5Zotrghz5rOCMAGNGI6ZPs/jHX18LeDtV1pmjD2tuzReYrMrSn5Y1IXnBcqO3XqOteN/CD4f6X4w8Da3qPiS1nubrVLtkS7mH7xQoB82KQjO4uz7jkhimCDgggHpHxK+I9v8O9OsZmsvt11eSskdv5pi+RRln3bWHBKDHfd7Gus0nUodZ0ax1S3WRYL23juI1kADBXUMAcEjOD6mvnDxd4W1/wdrPhtfFFzd694O0ZxJDcRWSybE3JuhkVm4DMEQb3I2kbc4KD0/4Q+J/FHjBNc1nWkkTSZrgf2UjIgCLufeqsFUuF+RdxHJB7hqAPTKKKKACiiigAooooAKjjkZ3mVoZIwj7VZiuJBtB3Lgk4ySOcHKnjGCZKjgghtbeK3t4o4YIkCRxxqFVFAwAAOAAOMUASUUUUAFFFFABRRRQAUUUUAU9V0qx1zS7jTNTto7mzuE2SxP0YfzBBwQRyCARgiuXv7DSPhj4I1G/8OaZptoLVFlkWdyv2hVfJRpTli5DOqZzhmUYxxXUarqtjoel3Gp6ncx21nbpvllfoo/mSTgADkkgDJNfPl9f6j8a9ckvL+7/sLwDpUo3zTyLGCxwBlj8pmbcB3WMN3J+cAx9GsH+L/wAXk1u+0G+h0K7lAujCWeNDFbjCNMFAG4quehw+AQcGvqOCCG1t4re3ijhgiQJHHGoVUUDAAA4AA4xVOxsNO8N6HHZ2Fp9nsLKI7IYI2chRknCjLOx5PdmJ7k143f8AxJ8X/ETWdR0D4d2ccemqixSarLujkjBbBkDEgICM4G0yYBYAHhQD2jUtW03RrdbjVNQtLGBnCLJdTLEpbBOAWIGcAnHsaNN1bTdZt2uNL1C0voFco0lrMsqhsA4JUkZwQce4rw+f4SeFPBujS+JviFrN3qdxvLzRRyFVuJXX/Vj/AJaSPu3HduXOMsAA1UPgd4G1pfFn/CVCK70vRUR1hiuCd94ki/KvRQyAFW34wSq4HXaAfRdFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUARyTLE8KMJCZX2LtjZgDtLfMQMKMKeTgZwOpAMlV7e6+0T3cX2eeL7NKIt8qbVlyivuQ/xL8+3P95WHarFABRRRQAUUUUAFFFFABRRRQB5f8ftT+wfC+e28nzP7Qu4bbdux5eCZd2Mc/wCqxjj72e2DzfhfxfoPwo+FeiPqGnxtr2p273At7VUEs8ZeRoXlcdE2soBOSMkBTtYDQ/aQjY+BdMlE0gRdTVTEAu1iYpMMeM5GCBggfMcg8YPB/wAJNK1mHSPFGutaXIuLe3vY9PtNPjtYA7QoGEgXJkBKq20bV3bztw5FAHpD+LtFtvCtp4jv72Ow0+6t0uI2umCsQybwoAJ3Ptz8q5JwcZrzPU/jx/amoxaL4E0OfU9TnlMUUl2uyM4I+YKDuKlQxyxTaME9wMd/gx4x8U+LboeMPEk8+k20pkt7nzN5nDsCwijJxBwMHjAIUAOBkemXq+FPhJ4Ovb+ysLSyiRCEjDESXcvzFIy53OxJJwTnaMngA0AfPCXetaDceKvFl3LHb31ze3GlXVpYXxhntrmQmUP8oY+UHjOMOCxTGSu4H1v4HX3j2/s7iXxHJPLonlA2ct+p8+R2O4MjH5njKsfmYn+EKcBgOA+HmjReINW1zxx41T7L4aSU6jKkgdLa5uDI+3CkESqhMi7ck5ZV+bcQdjxP8UPFPxC1ybwz8PIJ0tR84uoWMU8qpyzbyQIoyduM4Y8AkbtlAHvem6tpus27XGl6haX0CuUaS1mWVQ2AcEqSM4IOPcVJeN5UQuTLOkdvulkSGPzDKoU/LtClj1BAX5iVA5GQeL+GHw2h+HulzhryS61C+SM3hGPKVk3YEYwDj5yMnrjOF6V3lABRRRQBXmS4e8tvLfZbpueUhhlzjCoVKnK8liQykFFHIJqxVO602G6v7G9ZpEns3co0ZA3qyFWRuMlCdrY/vRof4asTwrc28sDmQJIhRjHIyMARjhlIKn3BBHagCOH7Yby5M3kC1G0W6pkueMszE8DJOAoBxtzuO7atiqdlDY6Vb2Wj2pjhSG3CW9uZMt5UYVeMnJC5QE89RnrVygDg/iZ8M4fiLb6crapJp89i8hRxCJVZXC7gVypzlFwc+vBzx4pa/Dew8Q/FCfwnoTXzaBpUrDUNQkWNpFfGHXdsTqybFU7sEO4BBavqeigAooooAK8/+Nv/ACSHXf8At3/9KI69Arl/iPY2+ofDXxHDdR+ZGunzTAbiMPGpkQ8ejKp98c8UAanhrUptZ8K6Rqlwsaz3tlDcSLGCFDOgYgZJOMn1NalcX8JdSm1X4V+H7idY1dLc24CAgbYnaJTyTztQE++enSu0oAK8X1v4Japr3xKn8R3Xify7WWVbgSW8Pl3MRVgEjTBwNqKoEmc5GSp617RXB/EzxDr1jpY0XwxoupX2rakhiW6ghcRWit8u8yjAV/T5ht+8xAADAHjnjzSPC1neQeAfAWjfb9dmljjvbwkTPuQH92GbIRs/NIybFXbg8bgvv/gvwnZ+CvC9rotm/m+Vl5pygRppGOWYgfgBnJChRk4zWH8M/hnY+ANLLuY7nWrhALq7A4A6+XHnkID36sRk9AF7ygAooooAKKKKACiiigDP1PXdH0Tyv7W1WxsPOz5f2u4SLfjGcbiM4yOnqKw7zS/BfxR0a1uJktNYs4n3RSRSsrRMVBKkqQynBUlDjtkZAqTxj4A0Lx1/Z39tRzt9glMkfkylNynG5G/2W2rnGGGOCOc+T+DNLt9L/aEuLfwfZT2+gwRT2t6zuXRjGi+YqmQbvlmaEEAse+drYAB7ppWl2mi6Xb6bYJIlpbJsiR5XkKr2G5yTgdAM8DAHAFXKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigCnqepQ6VapcTrIyPcQW4CAE7pZViU8kcbnBPtnr0rm/G/wANfD/j37NJqqTw3Vvwl1aMqSFOfkJKkFcnPI4OcEZOewrj/iVa+Lrzwk0Xgy48rUjKBIFdEd4SrKwRm4VslWzlSNpwc4BAPANA0XwFpeteNbPxWs8lrpN2FsXFyFmkEczqYggZS7OAoJC4Ubjuj4NSaB4Om+LOqKui6PaeGfDenoU85UM7mRvmIaRsPM5JHBICLjoSN2Xpvg3w1o95q8XjrW57ObTZZLc2FlGwnmcBWieMuhDRuBJyQuP3ZJw4r6b8B643iLwdYaidCk0WJ02wWjFSoiHCMmAPkK4xlV9gRgkAueGPDGl+EdDh0jSIPKt4+WZuXlc9Xc92OB+QAAAAGxRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAeL/Hiz8U+IP7G8PaP4fnvLGWUXDXcKl8TDcgRscRqA+SzcHPBG1s+uaTpsOjaNY6XbtI0Flbx28bSEFiqKFBOABnA9BViAzNbxNcRxxzlAZEjcuqtjkBiASM98DPoKkoA4/4k+FtU8Z+F00PTb6Czjnu4jevMu7MCkkhRjlgwQgZXO3GQDXBr4C8V/Cx7LU/Bd3d65YIhOqaTM4USttBaSNOgJCgADc4IUfOCQPbKKAOD8DfFnw544eOzgeSy1ZkLGyuBy2FBbY44cDJ9GwpO0AV3lcf4u+GPhbxpul1Kx8q+OP8ATrQiOb+HqcEPwoX5g2BnGKw/CXhn4h+FfE8UF74kj1/w3M7CVrpmNxGTHkON2SBvULtDsMMTtBJIAPTKKr3PmxbrmPz5fKifFpFsHnNwRy2MNwQPmVfmOexFigAooooAKKKKACiiigAooooAKKKKACiiigD58+O3jBb7xLYeCl1KSx02N431WZUZgC5UruQAFgi4fAJDFh3UV6vH8OfDkPgObwdFbSJpkyfvXV8SvJkHzWbHL7lU8jHAGNoxWH4r+DWheLfGlv4hurieNTj7dapnF1tACYbOY+AAcdQONpyx9IoA+f8Awp8RNU+Ft5ceDvHsM8lvZxF7C6iHmEoAdqKTjfG2MKeNp+VsAfJ2nw6+Jug+L/Euq6To2hSacmw3/nFUU3DEqsjSKvR9zLzltwGSRjFdh4l8H6B4wt4INe02O8SBy8RLsjISMHDKQcHjIzg4HoK5Pwx8KLPwFrkOq+G7ie5kl/0a8TUpwALdvmZo9kf+sDKhAPBG4cEggA7zUtJ03WbdbfVNPtL6BXDrHdQrKobBGQGBGcEjPuauUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAV7eG4jnu3nuvOjllDwJ5YXyU2KCmR975gzZP9/HQCrFFFABRRRQAUUUUAFFFFABRRXP+LPGmheCtOW81q78rzdwghRS8kzKMkKo/AZOFBIyRkUAeX/tKaZ5vh7Q9W87H2a7e28rb97zU3bs54x5OMY53dsc+oeBP+SeeGv8AsFWv/opa+cPF9z41+InhzU/G1832Pw1aSxrbWTSsEb5/LBjXGHZS+GkOM5IHTavv/wAK9T/tf4X+HrnyfK2Wgttu7dnySYt2cDrszjtnHPWgDsK8H+KfiLxTp3xQs4rPQZ723Gnywafa7DNDfeYAZ/MjQbpFCgAxk8FEfocN7xRQB8oeCPDyfF/xzcnU2g0uwtbTeLTTAsSoMgBIY2J2qWZnYgEbic4Lg19P6JomneHNHg0nSbf7PYwbvLi3s+3cxY8sSTySeTXh/wANv9B/aM8XQXn+jzT/AGzyY5vkaTdOki7QeuUBYY6rz0r1zX/H/hTwu7RaxrlpBOrhGgUmWVCV3DdGgLAY5yRjkeooA6SivJ/+GhvBX9o/ZvK1XyfN8v7X9nXy9ucb8b9+3HP3d2O2eK9I0bXNL8Q6cl/pF/Be2rYG+F87SQDtYdVbBGVOCM8igDQqOaNpUCpNJCQ6tuQKSQGBK/MCMEDB74JwQcESUUAV7ya4giElva/adu4vGsgVyApICZ+UsWCjDMo5JzxgyQTLc28U6CQJIgdRJGyMARnlWAKn2IBHepKKAMe+0LQvEksF1qelQXzWvmwxC9tyQmWAchHGOTGMNjkcqcNzl+MviHovgS40uLWUuwmou6pLDGHWIKU3M/IOPnB+UE8Hj13NT13R9E8r+1tVsbDzs+X9ruEi34xnG4jOMjp6iq/ifwxpfi7Q5tI1eDzbeTlWXh4nHR0PZhk/mQQQSCAXNK1Wx1zS7fU9MuY7mzuE3xSp0YfzBByCDyCCDgirlfP/AOzrriWGh+Jo9Sv4LXTLWW3mDzusaRvJvRiXOOuyMcntx157zVfjf4D0xLjZqsl9PA+zybS3di53YO12AQjvndggcZ4yAegTzw2tvLcXEscMESF5JJGCqigZJJPAAHOakrP0bXNL8Q6cl/pF/Be2rYG+F87SQDtYdVbBGVOCM8ipNNgFnbtZpFJHBbuUh3LGq+XgFQipgBFB2AEA/Jzn7xALlFFFAHifwtvJvAvjzV/hxf2kkNvcXEt5pdxNkvOoAAyVG0gxx7s/Lgoynk4HommePdH1fxzqXhKzE8l9p8RlmmAQwnBQMoYMTuBcAggYKsO1V/G/w70vxr9mupJp9P1ez5tNRtDtkjIyVB/vKGw2Mgg5wVycyeB/AVj4It7xobu7v9Q1B1lvr26fLTOAeQOw3M55yfmOWOBQB1lFFYeh+MfDniW4ubfRtYtLye3dlkjjf5vlIBYA8smWA3jKnPBNAG5RRRQAUUUUAFFFFABRRRQAV4X48+EmgeEdGv8Axb4f1nUtBuLJN8axSNIuSuwRoRiRS7MoLFiBuPGOnuleZ+Jfh5r3jfxjAfEmq2jeErNzLBYWYeOSZu3m5yM4JUsG6D5QhckAGh8INd1rxF8PrS/1tI94cwW0ysWaeKMKnmOSxJcur5JxnGcc5PeVj3Wu+G/DnkadearpWl7Il8m2muI4NsY+VdqkjC8EDHHHtVOP4g+DZXmRfFWjAxPsbdexqCdob5SThhhhyMjOR1BAAOkoqvY39nqdnHeWF3Bd2smdk0EgkRsEg4YcHBBH4VYoAKKKKACiiigAooooAKKKKACiiigAooooAK4f4ifE3S/h9ZxCWP7bqc+DDYpJsJTOC7Ng7V4IHByeAOGI7iuLm+FfhSfxqPFb2Ugvw6zeWkhSLz1YMJtq4O/I552nkkEkmgDxD4aQQ/FH4vXuqeKYo7p1t2vTAqhYmZGjjRGXuiqRwTztG4nnP1HXgf7P0i654q8YeI7uGNdQmdG3RlgqCZ5HkUAk8bkTrkjHXk598oAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiub1Lxtpml+LNN8OT2+pNeag7JFIllIYgVUN9/GGGGGSm4Lzu2gZrpKACiuL8fXnjTS30rVPCtpHqFpavK2pad8oe4j2grsJGcjDY2nOWX5WGRXB3Xj/wCKviXUYNL0LwbPoC3G1Dd31tI/kkHLOZHQIF2jGCjHrjJIAAPTPFfjzw54Mty+s6jHHOU3R2kfzzycNjCDkAlSNxwueCRXk978fte1O3vZfC3g+SSCzcvNdTh51SHDYMixgCMkDOS5A2t16jpPB3wXt7O8/tvxpd/8JBrb7g6zuZ4AMBVJ8wbpGCjq3AzgDKhqk+Mmp+Jrqwj8JeGtB1K6fU0UXN/DGwijQvjy944BbHzFiAEPOd2VAOw8DeM7Hx14aj1ixjkhIcw3ED8mGUAErnGGGGBBHUEZAOQOkrm/AfhSHwZ4OsNGQRmdE33UiY/eTNy5zgEjPygkZ2qoPSukoAjmmWBA7iQguqfJGznLMFHCgnGTyegGScAE1JRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRVORIb66hMV7IHsLjMscEowWMRGyUem2RXwcchD9QCxDI0qFnhkhIdl2uVJIDEBvlJGCBkd8EZAOQJKKKACiiigAqOCeG6t4ri3ljmglQPHJGwZXUjIII4II5zUlFABRRRQBXv4bi4065hs7r7JdSROkNx5Yk8pyCFfaeGwcHB64rw/QPgdrGr65Ne/EPWZ9Qjttkdusd48puFGHOXcblj5ZdowxJYggAFveKKAOb8YeFIfEfgO/8ADdqI7VJLdUtlTCJGyENGOAcJuVQQB0ziuL+BWneKdF0PVtI8QafPaWtrd4tPPzksc+YE5wYwQpBUbSXYgnnHrFFABXL+MfiB4f8AA9n5urXW64baY7KAq08gJI3BCRheG+YkDjGc4B6ivnDxp8F/Gz+KLrXtOu4NeaW7EqfaXjE2MZHmJIBEVXAXA4Ix8oHAAOD8Z+K9V1rxYnjS00270A3qbba4glkBlKKI3ZZeATj5TsAAGARnJPs+gfs6+HNPdZda1C71Z1cny1H2eJlK4AYKS+QcnIcduOucOH4VePviBqNvefELWPstva5RIU8t5CuVJ2rH+7XcMjectlRlSAK9I8f/ABJt/A8tlZJpF9qep3/FrbwoVSQ7lXbvwctzwqhj0yBuBIAeJ9L+HfhHQ5tX1fw3ocVvHwqrp0JeVz0RBt5Y4P5EkgAkeSfAyC71n4pazr+nRR6dpaJK09tGqMoWZyY4B0IAK7tyj/lljjdVyx+H/jn4r6tHq3jm4n0rTYc/Z7cwhH2mQ7kSI8x8DG9wWOE++Onu+jaHpfh7TksNIsILK1XB2QpjcQANzHqzYAyxyTjk0AaFFFFABRRRQB5P+0D4et9T+H/9sM2y60mVXjOCdySMsbJ1wMko2cH7mOMmpNP+Lei6R8IdI1y8upLy/wDs4tRavKDPPcxqFfcckgZw5Y87XU4ywB9I1bTYdZ0a+0u4aRYL23kt5GjIDBXUqSMgjOD6Gvmjw74QtNB8HeIr3xx4Ru8aPcJL8weJr1nzFHGkoIAiVizMUzu3IckIFcA0ND8Ia/8AF+3ubn/RPC/hNLhpbK0tLNRG8uAhIVdnmEKuDIx65CgDcF6vxX4A8LeAvAN1baV4Yn1zV9T3WNtJJGLicSMkjCQfL8uxQx+RQTtXP94emeHNf0XVPB1nrWmtHa6P9nLJ5iiFbdEyrKR0UJtI44+Xgkc1qWN/Z6nZx3lhdwXdrJnZNBIJEbBIOGHBwQR+FAHn/wAF/BmqeDPCVzDrUXkX11dtKYBceYI0ChVyASgYkMSV6grk8YHaQaKIPEt5rf2+7d7q3jt2tmEflIsZYqVwgfOXkPLH759FxqUUAFFFFABRRRQAVydn8NvCmm+LIPEmn6VHZ38KOqrbkpFllCbvLHyghdw4AHzsSCcEdZXifwj8YeK7zx/r3hTxFqUeoCxSdzJsGVlScK2xgAShLnAI4AUDaBigD2yiiigAooooAKKK8r1v4Z+MtY8S6hfx/EnUrGznffBBbrIoiGSPL2LIq4VQnzDliWJAPJAPRNT13R9E8r+1tVsbDzs+X9ruEi34xnG4jOMjp6ivP3+OOhXl5dWXhzRtc166iiMkf2KzJR+B1yd6ruIUsU47A8ZNG+AfgrSNRS8lW+1LZgrDfSq0YYEEEqiru6Yw2VIJyDXomm6TpujW7W+l6faWMDOXaO1hWJS2AMkKAM4AGfYUAeXzal8ZfFDj+zNK03wtZuizxS3ciyykbR+7bhiCSS2DGpG3BOesd78IfFPiPa3ib4i31xDP5ZvbG3gKQtt25VBvCDkcN5fX5iua6j4seG7zxH4Gm/sySdNT02VdQs/IYh2kjB4G0Fi20ttAwd23mpPhb4zbxx4Kg1CeORby3f7Jds23EkqqpLrgAYYMDjAwSRyACQCTRvhb4K0TTks4vD1jdYwWmvoVuJHbABJZwcZxnC4XJOAM1oan4G8Laxo8Wk3ug2LWMOfJijiEXk5YM2wpgpkgE7SM9810FFAHL+CPAel+AtOubLSri+mjuJfNc3c2/BwBwoAUdOoGTxkkAY6iiigAooooAKKKKACiiigAooooAKKKKACiiigAoorH8Vf27/wi+o/8I15H9s+Ufsvn427s84zxuxnbu+XdjPGaAPF/2Zf+Zp/7dP8A2tX0BXmfwV8CX3grw1eNrFrHb6pfXG51WbeViQYRWwSucmQ8Z4YZPGB6ZQAUUUUAFFFFABRRRQBHNBDcoEnijlQOrhXUMAysGU89wwBB7EA1JRRQAUUUUAFFFFABRRUc5mW3la3jjknCExpI5RWbHALAEgZ74OPQ0AY8l/rU9vcXtlYxhLZ7lEsbhSs14UBVNrkhYQzqcEhwUKt8uSBsGeFbhLdpYxO6M6RlhuZVIDEDqQCygntuHrUlFABRXBz/ABn+H1tcSwP4ijLxuUYx20zqSDjhlQhh7gkHtXN/8NHeD/8AoG65/wB+If8A47QB7BRWXoHiPSPFOlrqWi30d3aFym9QVKsOoZWAKnocEDgg9CK1KACo54Ibq3lt7iKOaCVCkkcihldSMEEHggjjFSUUAFFV4bG3t7y5uoo9k1ztMxDHDlRgMV6bsYBbGSFUEkKuLFABRRRQBT1aG5udGvoLIxi7kt5EhMkjoocqQuWQh1GccqQR25q5RRQAUUUUAFFFFABRRRQAUUUUAFFFFAGX4k1uHw54a1LWZ/LKWdu8oR5BGJGA+VNx6Fmwo4PJHBrz/wCA1trX/CESanqmryXtvqNw81tDIS7QkOwkYueSXcE46cZzlmrqPiH4Nm8d+Gl0aLV5NNQ3CSyusRkEqqG+Rl3Lkbircnqo49NywOl6Z9m8P2c8EclraIYbPzsyLAuEVsE7iowBuPfvmgDQooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAqnaxQwX98qeZ5k7pcSEwBV5QRgBwoDnEXOSzDIzhdoq5UcgmLwmKSNUD5lDIWLLtPCnI2ndtOTngEY5yACSiiigAooooAKKKjnnhtbeW4uJY4YIkLySSMFVFAySSeAAOc0ASUUUUAFFFFABRRRQAUVn6zrml+HtOe/1e/gsrVcjfM+NxAJ2qOrNgHCjJOOBXJ6B8XvCnibxYvh7S5ruSeRC0Nw0BWKYhdxVc/MCBu+8oHynnpkA7yiiigAqMib7QjLJGIAjB0KEsWyNpDZwABuyMHORyMcyUUAFFFFABRRRQAV5n8edShsfhXeW8qyF7+4ht4ioGAwcS5bnptjYcZ5I+o9Mrm/FvgTQPGyWi65ayTG0ctCyTMhUFlLrwcEMEAPcDOCDzQB5vY/Cm+8TfCvwrp8Pi67sbP7F581qtvuimMz+eNyh1JKlgPmJHyggKc5k8I/DbxF4B+K6to0k8vhG5iIuJJLiNmOI22iRcKdwk6FV4VsZ5YV7JBBDa28VvbxRwwRIEjjjUKqKBgAAcAAcYqSgAqOeeG1t5bi4ljhgiQvJJIwVUUDJJJ4AA5zUlFAGH4hg0PXfCcyapFJqGj3KRuy2iyymVdyshXycuwztOV7c9M1sTGZUBgjjd96gh3KjbuG45APIXJA7kAZGciSigCnpepQ6tYLdwrIgLvG8cgAaORHKOhwSMqysuQSDjIJGDVyiigCnq2pQ6No19qlwsjQWVvJcSLGAWKopYgZIGcD1FeL/AHQr69vda8d6o8hn1B3giYrsExZw8smNoGNwUAqcZDjHAr3SigCvNY28/2gtHskuIhDJNExjkKDdgb1wwxvYjB4LEjBotftg88XnkHEreS0ORujPK7gejDJU4JB27uN21bFFABRVea3e4+0RSy4tZYhGFiLRyKTuDHzFbIyCuMAFSCcnIxJDBDbIUgijiQuzlUUKCzMWY8dyxJJ7kk0ASUVHHCsTzOpkJlfe26RmAO0L8oJwowo4GBnJ6kkgM32h1aOMQBFKOHJYtk7gVxgADbg5OcngY5AJKKKKACvkTwF8M9S8W2+trf6pJoWl6c4N29zC23zkDZDKSqgopfcScrvHHzHH13Xgf7Q+savDe6Po0s8lt4bvE8yZ4FDPLIr/MCu4bgilGCkqCW6kgbQDU+DvxN1TX9RPhTUY/7TktIpHXWIpMb4kKqpdXCs2c43feOV3Lnc1e0V4P+zdodxb2eta3c2GyG58qG0unQZcKX8wKeu3OzPYlcclTj3igAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooqv9ti/tH7Dsn87yvO3eQ/l7c4x5mNm7P8Od2OcY5oAsUUUUAcPffB/wAA6heSXU3hyBJHxkQSyQoMADhEYKOnYc9etcf8RNV+H/w4s4tJt/BulXmrvaB7aOWwjdEXOxWlkYbm6MeCWO05K5DV7Bf31vpmnXN/eSeXa2sTzTPtJ2ooJY4HJwAeleAfBfSX8ceOda8ca/D9okglDQCSNmjE7HIKMxP+qVVAU52h0PBAoA7f4PfDO88CWdzfand7r/UIoxJaRsdlvgscEhtsjcjnHy4YKSCSfUKp6rqtjoel3Gp6ncx21nbpvllfoo/mSTgADkkgDJNeASeIfGnxt8QXem6BcyaT4XidI7kh1VliYON0mDukLgMPLB2fdB6byAe52Pizw3qd5HZ2HiDSru6kzshgvY5HbAJOFBycAE/hWxXzx8QfhH4W8FfC+4vlup5dZilh8u5mmCeczFVaNY+m3G9wOWGPvEA16n8JtVvta+GGi3+pXMl1dukiPNJyzBJXRcnudqjk8nqcnmgDtKjgghtbeK3t4o4YIkCRxxqFVFAwAAOAAOMVJRQAUUVHNBDcoEnijlQOrhXUMAysGU89wwBB7EA0ASUUUUAFFFFABRRRQAVHBBDa28VvbxRwwRIEjjjUKqKBgAAcAAcYqSigAooooAKKK5fx/wCDv+E68Lvov9pz6fulSXzIl3K20/ddcjcvOcZGGCntggHUV4n8KrxfFvxe8a+LYriMwKi2kCpEyiWJmxG/zHIO23XII5LHpjFc5r3wgvPhv4c1HxNZeNr6GS3iCEWVqYXk3OqqpYTcKWK564xnBIxXV/s/+EdS0Pw/Prl1NGkGsIrJatAwkVULbH3EgYYM5xtOQUIbqKAPZKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKr3tr9sgWL7RPBtljl3wPtY7HV9pP91tu1h3Ukd6ALFFFFABRRRQAUUUUARwQQ2tvFb28UcMESBI441CqigYAAHAAHGKKkooAKKKKACvP/jF4pvfCPgYX+mXk9pfyXccMDxRRyKSQxIcOD8u1W6c7gvbNdxY39nqdnHeWF3Bd2smdk0EgkRsEg4YcHBBH4V4/+0Jf2d98PLb7HdwXHka2sE3kyB/LkWKXcjY6MMjIPIoAwPCvwY1Txvp2neIvGXiS+kW4iDRQeZ50xgI3RnzXJC5Lbtu08HsScez+G/A/hrwj5h0PSILSSTIabLSSEHGV3uS235QducZGcZo8Cf8AJPPDX/YKtf8A0UtWPE/ifS/COhzavq8/lW8fCqvLyueiIO7HB/IkkAEgAsazrml+HtOe/wBXv4LK1XI3zPjcQCdqjqzYBwoyTjgVT8KeLdI8Z6MNU0aaSSAP5ciyRlGjk2qxQ54JAYcgkehNfNHjCfxH8QdEv/Hesyx6fo9q62+lWbtxKzOAyRA43EKGZnxyUwBhSE9j+AOmfYPhfBc+d5n9oXc1zt248vBEW3Oef9VnPH3sdskA9QooooAKKKKACiiigArz/wCKuleOdU0e3TwXf/Z9u77XDHMIZpvmTZskIG3GHJ+ZcjjnpXoFFAHifw88XeJtB+IbfDjxVex6nIqO0V4rNK4cxrMFaRyCU27+qk5IGdoGPYNV1Wx0PS7jU9TuY7azt03yyv0UfzJJwABySQBkmvmDxLr+r+Cfjhq/iDVdOknv1eZtPEjCONo2UxQudqnegj4wCpJHLAgirHi7QPEeoaDd+MviPqN3ZeY5TS9KiXcTLJGSoC7sQINqlgfnIRs4bBIB9B+FPGmheNbO4udEu/OW3lMUqOpR15O1ip52sBkH8OCCB0FeT/AHww+i+BpNUuYPLutWl81SdwYwKMR5U8DJLsCOqupyeMesUAFFFFABUc0ENygSeKOVA6uFdQwDKwZTz3DAEHsQDUlFABRWP4h8VaF4Usxda5qcFlG33A5JeTBAO1BlmxuGcA4zk8V5mfj9Dqdwlv4W8H6zrE4RnmjICMiggAgRiQkZOCTjHHXPAB7JRXnfgT4oTeLvEt94f1Hw3d6JqFrbi48ueQsduVBDBlUqfnQjg5BPTjPolABRRRQAUUUUAFFFFABWXr/hzSPFOltputWMd3aFw+xiVKsOhVlIKnqMgjgkdCa4fxz418R+GfiX4U0u1gtDourOlu7SpuZpGmVXIwwIKqUxxj5zndgbfSJ54bW3luLiWOGCJC8kkjBVRQMkkngADnNAEdhY2+madbWFnH5draxJDCm4naigBRk8nAA61Yryv4dfFybx7411XSl02O30+K3NxZyFj5u1WVSJByCW3g8Y24x83WvVKACioxPC1w9ussZnRFd4ww3KrEhSR1AJVgD32n0qSgAoqOONkeZmmkkDvuVWC4jG0DauADjIJ5ycsecYADBC1wlw0UZnRGRJCo3KrEFgD1AJVSR32j0oAp2utWV5qM9hG06XUO4lJ7aSHeFOGaMuoEigkZZMgbl5+YZ0Kr3r3CQKbVN8nmxgjaG+Quoc4LL0Xcc546gMflNigAooooAKKKKACiiigAooooAKKK8L+Luva1f/ABN8LeGfC93JDqVs4n8yGYsqSyHA8xFBwEjDMcg/JK3GCcgHulFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABVe3uvtE93F9nni+zSiLfKm1Zcor7kP8S/Ptz/AHlYdqsUUAFFFFAHP+O/+SeeJf8AsFXX/opq83/Z/vrfTPhZrN/eSeXa2uoTzTPtJ2osERY4HJwAeleuatpsOs6NfaXcNIsF7byW8jRkBgrqVJGQRnB9DXyR4fm1XxB4KtPAmhCSSe91h7q9VI5CI4tsCRvIVBHlbixbIOCinjjIB3n/ABPfj94o/wCW+meC9Ol9tztj8Q0xB91jU9yfnj8XDSPhl8bvD8ulSSaJpZsoDe/ZkMoePe6MGUnkFY0yeTkbwGfr7f4R8L2Pg7w1aaPYRxgRIDNKqbTPLgbpG5JySOmTgYA4AryPU7r+1/2r9Ns7y3gkh0+IRQgpnOLZ51Y5z8wdyQRjG1e4zQBiTvq/x78eWY+xXdl4TsXkVbhYhmNcKW3OeDK+IxtGdoIOGAYt9F2Fjb6Zp1tYWcfl2trEkMKbidqKAFGTycADrRY2FnplnHZ2FpBaWsedkMEYjRckk4UcDJJP41YoAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACo5xM1vKtvJHHOUIjeRC6q2OCVBBIz2yM+oqSigD5U+JR+Ikus6Z4N8SavaarPdvFcWsVrHGimRmeJAW8tCDncOeOR+H0X4J/tFPCWn2uqaJ/Y91aRLbG1WdZk2ooCsjBmO0jHDHcMEHP3j4/8L/8AirPih4l+Iuo/u9MtPM+z3F1+68rI2rkj5DsgBDZJxuU85zXYeLvjp4W8ObrfTX/tu+GPktJAIV+6eZcEHgn7oblSDigD0yeeG1t5bi4ljhgiQvJJIwVUUDJJJ4AA5zXH6X8RrHxF4xXQ/DlrJqlpCjtqGpxttgtSMhFBI/eFmX+E4wcgsA2PP/8AhB/H/wAVHtrrxxfR6No6Os0Wm28eH+6nO3J2kqX5kZmQ7hsAOK9c8MeGNL8I6HDpGkQeVbx8szcvK56u57scD8gAAAAADYooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACqerR302jX0WlzRwag9vItrLIMqkpU7GPB4DYPQ/Q1cooAKKr2c1xPAz3Vr9mkEsiBPMD5RXYI+R/eUK2O27B5FWKACiiigAooooAKKKKACqerabDrOjX2l3DSLBe28lvI0ZAYK6lSRkEZwfQ1cooA+XPBWja14v+Fs2h+E9Vks7uDU2bVLR5Si3EUyKivvCjCKqPmPLbsMeuxak+InwpsPhv4S03WbfUZ77U/7QSJzNDH5DDbI4/dEN/cUEMzA88c4Enwv0CaTxf468BHUZFtJbK4tnnRShaSKYRpJgMDj52ym7DBiDkVc+JXgi48B/CC10uXXZ9UhOtxyQq8QjSAGGXIUZY8nJPzY9ACWLAHtek6c118NLHTLK5ksHl0eO3hnjnWZoCYQqsJE2hyvB3LgHGRjNfNl7rGqap45ll+IqX2v6R4flltL77DFtjjJLopBTywu6QLySpYKBzgCvpfwJ/wAk88Nf9gq1/wDRS1zfxb+IGm+EPDVzp7eXcatqNu8VvalVcKrAqZJFYEbBzwQdxGMY3EAHmHjzWrT4o+MfDHg7wjeRnRUQD9zC8SxNyGPlvtBEcSZUAD7zAHnFfRdhY2+madbWFnH5draxJDCm4naigBRk8nAA614B4Z+C9x4h8G2/iJ7v+x9flihm0wWjhYVRI4xHJIFBYSPtLllbguGxnK1P/wAJ38Ufhv8A6N4o0T+2NMtut/hvmT/Vp+/UFRlgD+8Xed3OMjAB9AUV5/4b+M/grxH5i/2l/ZcyZPl6ntg3KMchtxQ8npu3cE4wM16BQAUUUUAFFFFABRRRQBXmsLO4vLa8mtIJLq13fZ5njBeLcMNtY8rkcHHWuH8V/Cqz8a+LbfVtb1i+m023iCxaWmERW3AsQw52sBgjG7/bAAA9Ari/ip4tm8GeA7zUbOaOLUJXS3sy8ZceYx5OOmQgdhu4yoznoQDtKK4/4X+IdU8U/D/TtX1dYPtUu9PMhOPNCMU3suAFYlTkDI7jGdo7CgAooooAK8T8U/FPxRrfiq58I/D3S5Pt9q8sdzczIhZWjfDFNx2Kny43P13gAKcZ9sqvDYWdveXN5DaQR3V1t+0TJGA8u0YXcw5bA4GelAHkfhf4FquqJrnjfVpNc1IOrmEu0kTFcgeY7/NKMBOMKBjB3CvXLGws9Ms47OwtILS1jzshgjEaLkknCjgZJJ/GvN/G/wARNU8PfFPwx4asYYDa3/lC6Mw3bhLOIwVxgqyhGwckHfyOBWf8adS8QWniHwTZeHtWn0+6vruSIFZmWN3LwhPMUZDKC3Qg8E8HNAHsFFcH4r+L/hDwlcG0uLyS+vFfbJbWCrK0XLA7iSFBBXBXO4ZHGOa5eH4q+N/FaE+C/AcjQF2eK91CQiKWJWK8fcUPnHAdsYYc4yAD2SivB/FWl/Fr/hF9R1rXPGFjpNrbxG8W0spTC6vniDzEUHoxUDe+5go5zuHefCPxnfeN/BQv9TjjF5bXDWksqcCYqqtv24wpIcZA4yCRgHAAO8ooooAKKr2F9b6np1tf2cnmWt1Ek0L7SNyMAVODyMgjrXl/in432ejatd6DYaDqs+uwXaW6QTQBUn/eKDtIYt86ElDtOdynBHFAGP8AtI6NeXeh6Lq8Kb7WwlliuMAkp5uzaxwMBcpgkkcso5zWfdeL7j4yeKINKtop9P8AA+n7bvVpbghPMRRuxMwYBVJXaAGOOXOduF0H8W/GrxFZ3TaR4Sg0uEylYnmjEc8YBBAxO4DcEAtswecYI4rzfD34lXPga4s9T8WaVo9jFEJVsLeJLaFVYMZVmeJFCqAzEgb1Y5z60AR/s5aQs1x4h8RmwjgR3W0tmjkYqgJLyRgFicD9zy2T7n5q98rxv4D+Nb7XtLfQG0a0gs9It0UXds2wEnAVWjOSXYiVy4OMjkAnJ9koAKKKKACiiigAoqvNDcSXltJHdeVbx7jLEIwTKSMKCx6KMkkAZJC8gAhrFABRRRQAUVXhe8a8uVmggS1Xb9nkSYs8nHzblKgJg9MM2evHSrFABRRRQAUUUUAFcnYfDzRdP8eXnjFHu5tSukK7biQSJESFBZNw3KcLj72AGKgAYA6yigAorz/4weNJfBngtpbC7+z6vdypFZkKjkYIZ2Kv1UKCpODguvTOa6Twdq99r/g7StX1KzjtLu8t1meKN9y4P3WHoGXDYJJG7BJIoA3KKKKACiiigAooooAKKKjnkaG3llSGSd0QssUZUM5A+6NxAyenJA9SKAJKKKKACiiigAooooAKK5fxj4/0LwL/AGd/bUk6/b5THH5MRfaoxudv9ldy5xljngHnHUUAFcH4K+FekeBvEuratp80jpeII7aF85tY87nTdu+cFguCRkBQMnJJ7yigArwe3sbi7/a2u5oI98dpEJpzuA2IbNYweevzOo49fTNe8V434Xnhuf2nfFrwSxyoNMCFkYMAyi2Vhx3DAgjsQRQB7JRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAef8AjHSfide655vhTxHpVjpnlKBDPAN4fncSTG+e3IK+mONzeUfE2++J3hrQ47LxL4tsbi31TdF9nsogrui4L5YQpheVBG7ndjBGa+l6+dNEkm+LPx6/tqMyDRdFdJIZFBH7uJsxDJTq8mX2tg7S4B+UUAafhf8AZzs0+x3viPU55cxJJNp8MYj2SfKWRpAzblHzKdu0nqCK9U8KeA/Dngy3CaNp0cc5TbJdyfPPJwucueQCVB2jC55AFdJRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUVXvrd7mzkiil8qbho3JbCupBUsFZSy5AyuRuGQeCaAJIYIbZCkEUcSF2cqihQWZizHjuWJJPckmpKjgMzW8TXEccc5QGRI3LqrY5AYgEjPfAz6CpKACiiigAooooAKKKKACiivJ/i18SfEHhHUbLQ9B0jzLrUYgYL1kaTLkshjjQDDSA+WeSfvAFTkUAcAuuS/Cf4yeJ9W1zTZ3/ALSiupbGKGRD5qyXG6NmOfkU+WQeCw/u1JrOveP/ABX8NfFGt+Ioo7TQZLe2FpbC38oPIZ4CHjyC7JtycliCX4zg7er+HfwU+w3kuu+NxBqepT5dbSU+eiM4y7SluJJMkjuo5OWJBXtPi1ps2q/CvxBbwNGrpbi4JckDbE6ysOAedqED3x060AaHw+nhufhz4beCWOVBpluhZGDAMsYVhx3DAgjsQRXL6b8CPBGnay2oG2u7tN5eOzupg0EZ3BhgAAsBjGHLAgnOetWPgdPDN8JNISKWN3hedJVVgSjec7Yb0O1lOD2IPevRKACiiigDi/FHwq8IeLEdrvS47W7d2c3liFhlLMwLFiBhycdXDdTjBOa4O2+H3xO8AQKvhDxLBq1hHsc6fcqI9x3ksiK5Kqp3Ekq6E5PcCvcKKAOb8DeIdS8S+Go7zWNFu9I1BHMU9vcQtGGYAHegbnYc9+hBGTjJ6SiigAooooAz9c0+41XQ72wtNRn024uImjju4AC8RPcZ/pg+hU4I8f8ADPjrxB8NtRHhz4mGdrOXzJLHVyzXGQCcgsMs6ngjI3ruUEbSNvuFcv46vvB1toZi8ZyWP2GXJSK5Xe7H7paNRl9wD/eQZXOcjrQBqaB4j0jxTpa6lot9Hd2hcpvUFSrDqGVgCp6HBA4IPQivEPihdX3xK+Jth4G0V5Hs9PcG8li+dI3JHmSNh9pEakKAdp3F16sKNV+JGgWPhq48GfCnTNS+2XD/ALq4tIWyQRukdd2ZWfaCuSAVAyCAq1h+CPAXxWXR7mz0k/8ACPWN7+9kmuStvM7IwAXcqmdehIBwuN397kA+h5dQ8P8AhHTrGwutRsdLtY4hDapd3Sx5SMAYBc5bA2+vUZ61w+p/H7wNYeV9muL7Ut+d32S1K+XjGM+aU657Z6HOOM5cP7OugzIZtY17Wb3UJHZ57lHRBIxYnOGVznnkljk5PfFdppnwr8DaR5v2bwzYyebjd9rU3OMZxjzS23r2xnjPQUAeX/8ADQHiTXv9F8L+C/Nvk/eON0l5+7HB+SNUI5K/NnHbHNVIPj342s/EEWm6t4WtGn3hGsI4JoLhmYfIBuZiCSVONpyDx1zX0Hf2v27Trmz+0T2/nxPF51u+ySPcCNyN2YZyD2Ncf4O+FuheEbz+1S0+p66+5pdSvHLOWcDeVHRcnccnLfMwLEGgDh/i54o13wb8TvDWrR319DoTRIJreGUFJ9kjecPKJwW2OgBIHUYORx7ZBPDdW8VxbyxzQSoHjkjYMrqRkEEcEEc5rj/iZ4Bh8f8AhoWayx2+oWzmWzuHQEBsYKMcZCNxnHcKcHbg+CW+uePb77H8Kbi/g0dk32Uhu3KNKD8yxvJ8x2kYRAmAysq/MCKAOo8Z+J7PUPj94fvvDtnB4nktLRIVtoJQUebMrAiTBX5N6vu5C7eSCDjH8Uaf4x+IHxH03wx4m1HSrO9G1orW2G9bZHh8yU/LklgIhlXflmXb8h3DQsfC+j/C/wCOnhi1k1XFr/Z73Fzd3siRIJGjnQ46BVJUYBJPOMmt/wCFmk3Hi/4i618Sb+GdbRpZI9KM0YQuDmMH5Tg7IwEPBBLHncpoA9E8L/Dbwp4QdJ9K0qP7YqKv2ycmWXIUqWBbhCQxzsCg56YwK6ieeG1t5bi4ljhgiQvJJIwVUUDJJJ4AA5zQJ4WuHt1ljM6IrvGGG5VYkKSOoBKsAe+0+lc/418IQ+N9Gh0i71C7tLP7QstwLVgGmVVbCEnIxuKNyD9wcZwQAeOeJNY1T46eKI/DfhxPI8NafKJp7+WLqcFRIQeRkFgkfBOSWxzs9v8ADHhjS/COhw6RpEHlW8fLM3Lyuerue7HA/IAAAAA8MeGNL8I6HDpGkQeVbx8szcvK56u57scD8gAAAANigCOeeG1t5bi4ljhgiQvJJIwVUUDJJJ4AA5zQIIVuHuFijE7oqPIFG5lUkqCepALMQO24+tE8EN1by29xFHNBKhSSORQyupGCCDwQRxipKACiiigAr5EvH8b+PvilqtjbvdwanO81nPCtyfLtbYPhondODEvQ8YY84LNz7f8AEjxNr89x/wAIn4OEa6hM8EN5feeoNoJi21VUEvv2o7sQpKRjdjncvnnw8OhfDb4q6/p2oazYva6fp8qtqJJQzMTAxiCb2BZSrgKvzE7s54AAPV7Wz0v4P/C+cqftEenxNLJIU2NdTscDO0HG5iqAnO1duSQM11Gh6zZ+IdDstXsH32t3EsqZIJXPVWwSAwOQRnggivnzXNS1f49+MbbS9BW7svDtiivM9yBtjY5zK6qcFyMqibj0JyoL4+j4IIbW3it7eKOGCJAkccahVRQMAADgADjFAElFFFABRRRQBn6zpn9q6c8Mc32a7TMlpdhdzW02CFkAyM4yQVzhlLKcqxB0KKp6YLtbVxeySPL9onKl0RT5fmt5YwpIwE2gHqQAWAJIABcooooA4vw94v1rVfHmveH7/wAOyWtnp7t5OoK5dHAEZVGONu9lk8zGQQCARkEntKKKACiio5oIblAk8UcqB1cK6hgGVgynnuGAIPYgGgCSvH/2ftY1TWfD2rzarr8+oyJdqiW9zJ5kkA2A79xJba+cAHgGNsck13HjrxdceDdDOpW+gX2rYyWFtgJEByWkbllXaGO4KR8uCRkV88eC/F3iXTPFviXSfCugQW+p65LIILOTahsXRnfaA21fkQyDawHIXjgqQD6vorL8OWur2fh+zg17UI9Q1RUJubmOIRqzEk4AAAwAQucDOM4GcVzfxb8Tv4V+HWoXVtP5N9c4tLVhuyHfqVK42sEDsDkYKjr0IB5ZqUc3xl+NK2sIkm8L6O4jlkQkxGNSSxyHxmVgVDKQSgU4+Q19F15X8KrLQfh/8PtOuNW1XTbK81tBetJc3CR71IBRFLbSQqspI5wztg4NWNb+O/gjR7jyIbm71Nw7pIbCEMqFTj7zlQwPOCpYHH0yAemUV4vD+0Xo8V5cxap4d1W0jG17Ypsd5Y2G5WZWK7MqUYYLA7uuME+keE/GmheNdOa80W783ytonhdSkkLMMgMp/EZGVJBwTg0AbkcbI8zNNJIHfcqsFxGNoG1cAHGQTzk5Y84wBJRRQBy+k6j4xuPFs9rq2g2NlokdpmO6gvPPMs+5eASEYLtLDBjHK9cEVT1STxdJ4l1NrWHUoNNtbeE2AtDZypeS5Jk8xZSrgfMo2h0yI3IYFlrsBGwuHlM0hRkVREQu1SCcsOM5OQDkkfKMAc5jhv7O4vLmzhu4JLq12/aIUkBeLcMruUcrkcjPWgDL0q/197fRk1fRY47i5t3a/ktbhWjtJQFKqQxBYNlvu7tpAGWB31sRwQwvM8UUaPM++VlUAu20LlvU7VUZPYAdqkqvvuI58OnnRyy4QxKF8lNmcuS3zfMCMqP41GMAtQBYooooAKKKKACiiigD5/8AiP8A8VP+0F4Z0KD/AE2Gz8j7VZy/6tfnM0vyt8pzCFJxnIAHJGK+gK8L8e6VfeBfi3a/Ex7aTUNFd1S6WDh7YmHyOc8EEcg8An5TtyCTUv2hJtSuF0/wb4Yu728lQGI3Slm3AksBDESWG0E5DjvxgcgHsGv+I9I8LaW2pa1fR2loHCb2BYsx6BVUEsepwAeAT0BrP8GeOdF8daW99o8sgMT7JrecBZYTzjcASMEDIIJB5HUEDyfRPhZ4v8c6pp+u/EXVJHsdnm/2eXZZR0AQooCQhgoLbfm4wQGJK+2aNoel+HtOSw0iwgsrVcHZCmNxAA3MerNgDLHJOOTQBcnnhtbeW4uJY4YIkLySSMFVFAySSeAAOc14H8Kij/tAeL5Yp4J4ZoruaOWCZZUdHuI2UhlJB4I78dDggivoCvC/hr4B1rwN8Y9ThNjJJo72UogvnYlTEZEKDcFwZflAKHb/ABMMgDcAe6UUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFZ+uazZ+HtDvdXv32WtpE0r4IBbHRVyQCxOABnkkCgDzv45+NofD3g6bRba4j/tTVU8rywQWjtzkSOQQRggFBnB+YkH5TXEeDfiAuheGrHwt8O/D13q+tTolzfT3AYxJO4jDnAwdgJ8vJKKu1Tlskmn4W0S8+N3xFvfEevW88OhW+F8uNyU+XGy3VicjIJdio7nhS4I+h9G0PS/D2nJYaRYQWVquDshTG4gAbmPVmwBljknHJoA+aPirp/xCtvD2lXHjbxBY3EcsuYdPhdVkVymWZlRFVtv3SwLYL8HDc+hwfs3+FVt4luNV1mScIBI8ckSKzY5IUoSBntk49TWH+0Rpmo6j4h8J21tN5/2zzba1s9qriYvGC28n+LcgweBsz3Ndx4Qk8c+FZZbbx5qWlT6FBaB01dpwpSTciLE7NtJ4ydzKSSw+YnigAvvgV4Bu7OSCHS57KRsYngu5C6YIPAdmXnpyD19eay4fhF4j8POW8JfEHUrS3t0Y2dhdx+bEGKnIbnZgsSc+WcZzgkZPrEE8N1bxXFvLHNBKgeOSNgyupGQQRwQRzmpKAPA9X+Kvjf4c6paaV4lPh/XSEZpmspik+TyFfAAjIDIRmLDL0JOSPcNJ1KHWdGsdUt1kWC9t47iNZAAwV1DAHBIzg+pqvN4a0G51QapPommy6gHVxdvao0oZcbTvIzkYGDnjArUoAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiio5jMqAwRxu+9QQ7lRt3DccgHkLkgdyAMjOQASUVHBG0NvFE80k7ogVpZAoZyB947QBk9eAB6AVJQAUUUUAFFFFABRRRQAUUUUAFZfiXTZtZ8K6vpdu0az3tlNbxtISFDOhUE4BOMn0NalFAHjfwAW+0my8SeF9TsJLS8069SaXe3JMqbcY6YxECGBIYOMccn2SivM9J+J19N8YNQ8EarpMdum9lsZYpNzYWMybpDnBDoNwwAVyFIPJAB6ZRRRQAUUUUAFFFc3458KzeMfDUmkwazd6U7uCZrckh1wVZHUEb0KseMjnB5xggFjX/GPhzwsjNrWsWlo4QP5LPulZS20FY1y7DOeQD0Poa831D48LqFxNYeCvDOpa3dokh8wxMFUAgLIEQM7ISRkHYeg4J4uaN+z54O0/Y+otfapJ5QR1mm8qMvxl1EeGHQ4BY4B7nmvQL6az8I+HJJbDRZ5LW1xssNJtQXO5xnZGMDqxY/iaAPK5vD3xo8YOH1LWrTw3afaFzbWk210UKAWUxbiwOWO1pOSOwwa1NH+AXhq3le78QXd9rt9NvM7zStEjuzZ34U792ODlznJOOmLHgX43aF4uvBp19D/Y+pSyiO2hklMiT5HGJNoAbIxtOM5XBJOB6hQBT03SdN0a3a30vT7SxgZy7R2sKxKWwBkhQBnAAz7CsO4+IXhq18aR+E5r/wAvVn42PGyorkKyIXIxuYNxjPTGQSAfP/D3x5i1j4inQ5bGBdIu7v7NYXiM4fuFZlK8+Y2zAwuzdznGak+P/hGxuvBz+I7aytItQs7iNrm5C7ZJYmxHtJA+chjHjd0CnBHQgHslFcv8OdZ/t/4daFqJeeSRrRYpZJzl3kj/AHbsTk5yyMcnk555rqKACiiigAryP4z/AA/h1DS5vGOj+Za69piCd5IGCGaNMEsTkYdFG4MOcLt5+Xb65RQB8meOvFvhzx1N4a1zU5ruHUBb/Z9Vs7GPcEVJgQUZ8AFkeVhy+CEU92r0ez03x/4nSDRtDs5PAng6FHW3fZtu2VWGN67vMDswLHlchmyz8bvTF0Twt4X1HU/Ext7HTbi82/bL6VxGpyR3Y7V3NjOMbmwTk10FAHifh74ETeH/AIkQaxaaxJFotk6y24WUm6kYKMo5CKoQkuDjOV+XHJI9soooAKKKKACiiigAooooA8r8Q/AXwzr/AIgn1c3upWr3dw09zFFIpViwO7ZuUlSXIY5LDqABkbfKPAnw80XXfi5rnhy/e7fT9Ke4aNVkCtKIp1QK5A6FSc7dp9CK+q6+dPh7q2m6N8ffGdxqmoWljAz3yLJdTLEpb7UhwCxAzgE49jQB73o2h6X4e05LDSLCCytVwdkKY3EADcx6s2AMsck45NaFeR6l8fNIe/XTvC2ial4hvGcBFiQxLIuwsxT5WckY5BQdCc4HNzQ/jC9xrllpPibwlqvh6bUJVhspJ0Zklc8YO5EI5KDgN97naOaAPUKKKKACiiigAqvNf2dveW1nNdwR3V1u+zwvIA8u0Zbap5bA5OOlWKr/AGCz/tH+0fskH27yvI+0+WPM8vO7Zu67c846ZoAr6dqX9pT3TwSWM1inl+RPbXXms+5A53qFAThlIwzblYHjIrQqOCZbm3inQSBJEDqJI2RgCM8qwBU+xAI71JQBHNG0qBUmkhIdW3IFJIDAlfmBGCBg98E4IOCJKKKAI4IIbW3it7eKOGCJAkccahVRQMAADgADjFSVj+JPFOjeEdOjv9cvPslrJKIVfynky5BIGEBPRT+Veb/8NHeD/wDoG65/34h/+O0AewV5P/wgGu/8NDf8Jf5cH9jeV5nm+aN277P5Ozb13Z+b+7t754rrPBnxG8OeOkddIuZFu4k3y2dwmyVF3EZxkhh05UnG5c4JxXWUAFYfivwlpHjPRjpeswySQB/MjaOQo0cm1lDjHBIDHggj1BrcooA8nsf2efBVpeRzzS6rexrnME9woR8gjkoitx14I6enFbmmeKvBHhXxZH8P9Phj065CIQFiCRPKyrtRnPLSsu07jnPA3FuK7DVtSh0bRr7VLhZGgsreS4kWMAsVRSxAyQM4HqK8P+AOkW2taprXjTULyO61o3DxmEogMZkw7TYHILEsowAMBxznAAPeJ5Ght5ZUhkndELLFGVDOQPujcQMnpyQPUivA/ht4R13U/iu/jG88Nf8ACN6ZB5vk2YtxCu4xiNUCHa33XLFwu0srdM8fQFFABRRRQAVGIIVuHuFijE7oqPIFG5lUkqCepALMQO24+tSUUAFFFRzQrOgRzIAHV/kkZDlWDDlSDjI5HQjIOQSKAAQqtw84Mm90VCDIxXCkkYXOAfmOSBk8ZzgYkoooAKKKKACiiigAqOGCG2QpBFHEhdnKooUFmYsx47liST3JJqSigAoorn/Gniyz8FeF7rWrxPN8rCQwBwjTSMcKoJ/EnGSFDHBxigDoKKw/CXiqx8Z+H4tZ06G7it5HZAt1FsbKnBx1DD3UkdR1BA3KACiiigAooooAKKKKACiiigAooooAKKKKACiiigAri/if4Hm8feFU0u3vo7SeC4F1G0kZZXZUdQhwcqCX+9g4x0NdpRQBz/gvwnZ+CvC9rotm/m+Vl5pygRppGOWYgfgBnJChRk4zXQUVXv7G31PTrmwvI/MtbqJ4Zk3EbkYEMMjkZBPSgDw/9oK80uPxD4Mj1IfaLWGWWW9tYn/eNAXizxkEbgrgHI5B54rnNa1rxH8ePEsOlaTYSWug2NwHaQHmKNyq+bMC4RnADlUXnBcAtyaseO/hd4c8L+OvB1pZLdyWetamY7m2mmyqR+bENiEAMBiQjJJPA5zzX0XY2FnplnHZ2FpBaWsedkMEYjRckk4UcDJJP40AFhY2+madbWFnH5draxJDCm4naigBRk8nAA61YoooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACio55ltreWdxIUjQuwjjZ2IAzwqglj7AEntUlABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRXB6v8VtF8P+PP+EX1i3u7EOkRi1CZQIGLgnJOchAdq7+Ru3A7QuSAd5Xz/dFLH9raCa5ngjjm2hCZl6tZ+Wqnn5WLcBTgnK4GGGdT4nfEnxG3iyTwD4Ps5BqEqLFLcr/rdzqr/ujnCAIeXbpkkbdu4+YXfg3UrL4kaTpHje7kubzWrdQZlma4khklV4YS5JXcY5ApIDEYXAJHFAH13RXzJ4i8FeI/g4+nXvhvxZJcXeo3H2cWcMGx5m2kKRCWcSgbiOR8pZcctx7v4F8W2njPwra6pbzRyThEjvFjjdFjuNis6DdyQC3XJHuaAOkooooAKKKKACiiigDwf9obw9b2MWleMLBvsmprdrbyywgq8h2lo33A8MnlkA4ycjn5QK5jV/Enj0wW3gq/12C+bxR5L293sKIIJ3BVkkG1wrMzoyPH8qqAowcD2P4ueDL7xv4KNhpkkYvLa4W7iifgTFVZdm7OFJDnBPGQAcA5Eem/DPS7yLwbq2tW86azoWn20JjSf5C8agqGxnOx9xG0jPfcOKAPHPi74K0z4eW/hNtEEkGoFJDcXscsitLLEIsSAFjsO4scL0z7Cve/iPY2+ofDXxHDdR+ZGunzTAbiMPGpkQ8ejKp98c8Vx/xr8Aa744/4R/8AsWOB/s0ssc/myhPLWTZh+eqjYc4y3IwDzjuPHf8AyTzxL/2Crr/0U1AHH/AHU/t/wvgtvJ8v+z7ua23bs+ZkiXdjHH+txjn7ue+B6hXj/wCzj/yTzUP+wrJ/6Kir2CgAooooAKKKKAPE/wBpPUoYvCujaWyyefcXpuEYAbQsaFWB5znMq447Hp39M8DaJ/wjngbRdJa3+zzQWiefFv37ZmG6TnJz85Y8HHpxivEPjXr1vdfFrQtNn1Se2sNL8k3MtupWS0eRwzujBclhGImGM4I4GcivoPTdW03WbdrjS9QtL6BXKNJazLKobAOCVJGcEHHuKAJLW/s77z/sd3BceRK0E3kyB/LkX7yNjowyMg8irFeX/DH4fXngrxb4tlltvJ024lQaYY7kujQ7nbBUnO5QUXLDP3sEgknQ0r4pW+r/ABQvfBlrpU8sdr5inUImJVXQDeHQqNihtybsnLbccNkAHoFFFFABRRRQAUUUUAef+Ofixpfg/UYtGtrOfV9dl2hLG2ONpYgKrtgkMwOQoVieMgBgT45ZeHNS8RfHLTIfH1jG763bm9e3QtEBH9ncomAQ6FDGEIPOUOS2cnr/ABF8PvFfg3x5e+OvB0Fpq4meWeSzuYw8sbSBmk2jgkZ6bGD/ADbcMMlucPjXWPEPx+8NX58Nz6Zf2/l2MtjPvkdY3L75CNqkYjlZuRgbcnIoA+i9N0nTdGt2t9L0+0sYGcu0drCsSlsAZIUAZwAM+wrxv9pHWbNPD2k6Hv3X0t39s2KQdkaI6ZYZyMl+OMHa3PFekeOvHWl+A9DN/fnzbiTK2tojYe4cdh6KMjLds9yQD5n8PvBmteOvEsPxF8aySAB1m0yzGUGFO5GAzlYlPKjq5+Ykg5cA9g8NQ31t4V0iDVDIdQjsoUujJJvYyhAHy2TuO7POTmtSiigAooooAjmjaVAqTSQkOrbkCkkBgSvzAjBAwe+CcEHBAYIWuEuGijM6IyJIVG5VYgsAeoBKqSO+0elSUUAFFFFABRRRQBT1LSdN1m3W31TT7S+gVw6x3UKyqGwRkBgRnBIz7mjTdJ03RrdrfS9PtLGBnLtHawrEpbAGSFAGcADPsKuUUAfPniaFdQ/ak0qDRjJaXkT2730rSMolKR+Y+0gngwBUxgAnIPBJP0HXhfi74L+Kr3x5d+JPDniOOF7lzKJbm5ljnhZgVKKyKfkC8DkYX5ccZOYg8SfC7x9ocV34vn1VtQ8ubXIrlZDBbQu8cPmtI7YOCNokO0jYoIw20gHv99f2emWcl5f3cFpax43zTyCNFyQBljwMkgfjVivn+1u9R+LnxhnsrifzvCOh3bXAgQq8Emz5EywTa/mMpbDZ+QyBT6/QFABXz5BpS2H7TMWl+E7aTRre2QPfm23SRyoU859yHhEbckYHRTtI5wK+g68T+LCWPgvxKnjnR/EEdj4kZI0l0t/3g1BMhfmXdlU2o2T0JRcbX5IB7ZRXN+A/EV94q8HWGsajpcmn3E6ZMbfdkHaRBnIRuoDc/UYY9JQAUUUUAFFFFABRRRQAUUUUAFFFFABUc5mW3la3jjknCExpI5RWbHALAEgZ74OPQ1JRQB5P8KvilrHjHxDquh6/p8FnfWsXmokELx7dr7JFkDuSGBZMDHZs9q9Yr5k8P/Eyx8HTeMdetRHdXniG4F1p1oTkwgzXIPn44UqNrbQSSHXBwSV3/DnxV8aaH4qs4viBYSW2l605Fs01utt9k+crnnB2Akbg53BdrZ5+cA98ryf496H4g17wlYxaNYfbbeC7865ihRnnztKoUUfeX523YBP3T0DGvWK5/wAR+NNC8J3ml22s3f2ZtSlaKF2U7F2gZZ26KuSoyf7wPQMQAeIa7c/Ey48DDU2b/hDvD2kWkEcVosssU0rKPK4wGl5bACuVXBQ5bBevY/hpr994o+HmkaxqbRteTpIsrou0OUkZN2OgJCgnGBknAA4rc1vRNO8R6PPpOrW/2ixn2+ZFvZN21gw5UgjkA8Gsu81Xwp8OfD9rbXNzaaTp8KbLeDkswBAO1Bl3OWBYgE85Pc0AdJRXkcH7RXg2a4iie01mBHcK0slvGVQE/eO2QnA68An0Br1iCeG6t4ri3ljmglQPHJGwZXUjIII4II5zQBJRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAeP/ABf/AOSh/DH/ALCp/wDRtvXsFY+seFtG17UdLv8AU7Pz7rS5fOs38118p8qc4UgHlF656VsUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUVHPG01vLEk0kDuhVZYwpZCR94bgRkdeQR6g0QCZbeJbiSOScIBI8aFFZsckKSSBntk49TQBJRRRQAUUVHHMsrzIokBifY26NlBO0N8pIwwww5GRnI6ggAElFFFABRRRQAUUUUAFFFFABXL+OvAul+PNDNhfjyriPLWt2i5e3c9x6qcDK98diAR1FRzSNEgZIZJiXVdqFQQCwBb5iBgA5PfAOATgEA+WNa0/xp8INbhvpYLS9t47gLYarNArltqKgQsD5iAxAx+Wx2gbinKhxc1rxtpfjD4neHPFaadqstppWnpc6hbWUXmS27wySPyThTGGMZLcfK38LcD6bnghureW3uIo5oJUKSRyKGV1IwQQeCCOMV8oa5oniPxr8YLbwrrP9m6deQItnGtnHi3t7eOMyDy1HJBXLAEg5YA7RwADP8SeJ9X+J3jGRLLTo7m6v0htrSLyB5lvGuXMatkjG4ktKcEhc/u1LJX038P/AAdb+B/CVtpMXzXDYmvJA5YSTlQHK5AwvAA4HAGeckyeDPA2i+BdLex0eKQmV981xOQ0sx5xuIAGADgAAAcnqST0lABRRRQAUUUUAFFFeP8Axl+JF5ovkeFfDM2/W775J2t8tNbq2AioAOJHzxzuA5AyysAD2CivL/hf4V+IGgajNc+K9e+22NzaDFpLeSXEkM2VI5YYGAXB2sQTjrgEeoUAZ+t63p3hzR59W1a4+z2MG3zJdjPt3MFHCgk8kDgV4/8AFD4u+FdX+H+o6XoGsTz395siAhgli2puBfczBflKhlIGc7sYxnEn7SepQxeFdG0tlk8+4vTcIwA2hY0KsDznOZVxx2PTv3nh74d+H9L0nQhdaFpTatpsUJa6ht1BadI9pfdgFuSWG7vhsZAIAKfwe8OTeGvhvYW95YyWd/cPJc3UchO7czYUkZ+U+WsY28YxyAc13lFFABRRRQAVn65rNn4e0O91e/fZa2kTSvggFsdFXJALE4AGeSQK0K83+Kfh/wAS+LZdF0GwtM+HpruOTVbiG6WOZUDAEbWwCoBLD7+WUcDaNwByfwa0C58V6prnj3xCskh1F5LaKBmfypUbiQFGyHiA2ooJYDYwIyqkbGvfCO70XVLrxP8ADrUpNJ1Yo5+wbUMEu7GUTdwgOCQGDLu242AAj1DStKsdD0u30zTLaO2s7dNkUSdFH8ySckk8kkk5Jq5QB5fo3xK1HXrXXPDVzp/9jeOrS0lFtaM6lLiYREhoy/y/e+baSRtIYMwyRc+FPwzXwDpc8l+bS41q5ciS4gDEJFxtjUtg4yNxOBkkA52g1seOfh9ovjrS5IL6COG/CAW+oJGDLCRkgZ6smWOUJwcnGDgjg9G8eeIPhxrCeHfiZP59nLEHsdZhRpFIVQCjEKGfsCSN4Y5OVYMAD2iio4J4bq3iuLeWOaCVA8ckbBldSMggjggjnNSUAFFFFABRRRQAVn65Nqlvod7LolrBdamkTG2hnk2I79gT/TIz0yudw0KKAPL9M+E76t4hl8S/EC8g1nUnwIbKEMLS1CuSqqCQXXGPlIA5fcHJ3V6hRRQAUVl6lPr0Vwq6Xpum3MGwFnutQeBg2TwFWFwRjHOe5445uWE1xcadbTXlr9kupIkea38wSeU5ALJuHDYORkdcUAWKKKKACiiigAooooAKKKKACiiigAr5w8b+CfiV4x8c6xB/Z+3TJ7uFopJJkEKwxmSOFwSxYYVpGZF5y5JX5lr6PooAx/DHhjS/COhw6RpEHlW8fLM3Lyuerue7HA/IAAAADYoooAw/FfhiHxbox0u41HUrGBnzI1hOImlXaylGyCChDcjHOBXF6N8A/BWkail5Kt9qWzBWG+lVowwIIJVFXd0xhsqQTkGvUKKAI4IIbW3it7eKOGCJAkccahVRQMAADgADjFSUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAfMn7PnhTTdc8QX2s3okefR3he2j+UxsziQZYEE5UqGUgjBGa6v9pTTPN8PaHq3nY+zXb23lbfveam7dnPGPJxjHO7tjnM/ZmnhW48S27Sxid0tnSMsNzKplDEDqQCygntuHrU/wC0b4osZLLT/C8Ekct4lwLy52vzAAhVFYYxlhIT1yAo4wwNAHsnhvxTo3i7TpL/AEO8+12scphZ/KePDgAkYcA9GH515f8AtH6N9r8JaZq6JO8lhdmJtgyiRyryzccfMkYByB82OSRXpHgvwnZ+CvC9rotm/m+Vl5pygRppGOWYgfgBnJChRk4zVjxXo3/CQ+EtW0gJA8l3aSRReeMoshU7GPBxhtpyBkYyOaAPM/E3xZu9K+FGlalpbx3+rXdvbwXN8oQxWtw8W5gwHBl+VjsAwvBYAFVfnPh/8HX8ZWdt4s8ZapfXH2zEiWzs3myopAVpJG+bayqcBf4SpDDpXjaw61qN7ZeHHN288VwbW2sZ5CohldwGQKxAQluvTnrX23/Ymnf8I9/YH2f/AIln2T7F5G9v9Ts2bd2d33eM5z70AeAa3ob/ABF8R23hLwRYQWPg7SpSJdSt0YwSyhEDyMeBLIowq8lmyW3bWyPoewsbfTNOtrCzj8u1tYkhhTcTtRQAoyeTgAdap6B4c0jwtpa6botjHaWgcvsUlizHqWZiSx6DJJ4AHQCtSgAooooAKKKKACiiigAooooAKKKKACiiigAooooAK8j1zxz8T/D/AIllWTwPHqOim4mW2NijvLJEpIRmZGcISCp+ZBnkADt65WH4u8SL4T8NXesNYXd8YEJWC2jZiTgnLEAhEABJc8ADucAgHm8Hx7Wx1mKw8V+EtS0FJUDLJIzOwBbG4oyIdnDcjJ+XAB7eyV8qeGvFWpX3xj0jWvGGjSXtzqTwpp6ThoY7ZZJAI5YlIIZFBbb6k7t27mvqugAorH8Q+KtC8KWYutc1OCyjb7gckvJggHagyzY3DOAcZyeKuaVqtjrml2+p6Zcx3NncJvilTow/mCDkEHkEEHBFAFyiiigAooooAKKKKACiiigAooooAKKKKACoxMrXDwASb0RXJMbBcMSBhsYJ+U5AORxnGRmvqV61lbr5MUc13M4itoHmWLzXIJxk9goZjgMdqsQrEYNygCOGNokKvNJMS7NucKCAWJC/KAMAHA74AySckyUUUAFFFFABRRRQAUUUUAFRzzw2tvLcXEscMESF5JJGCqigZJJPAAHOaJpGiQMkMkxLqu1CoIBYAt8xAwAcnvgHAJwDHa2NvZeebePa08rTSsWLM7nuSeTgAKPRVVRgAAAEkE8N1bxXFvLHNBKgeOSNgyupGQQRwQRzmipKKACiiigAooooAK+f/wDm7z/P/PhX0BXg+q6bqmkftPWWuT6TfSabeyxxQXEEO9GLWpiOSOBtKszA8hVLYxjIB7xRRVO81bTdPuLW3vdQtLae7fZbRzTKjTNkDCAnLHLAYHqPWgC5RRRQAUUUUAU9W1KHRtGvtUuFkaCyt5LiRYwCxVFLEDJAzgeorwv4JaVeeLvGmsfELWDvkSV47fDEgTOPm25YkKkbBArAjDjByteqfE3RJvEPw31zTrfzPPa382NY4zI0jRsJAgUcksU2/j0PSuD+APi7QpPC8HhjdBa6zHLNJ5WCGu1yG8zO0AsAdu3JbbHnoOAD2iiiigD54+OmpXCfFPwtatZ/2na2sUVxHp3lg/aHechk+6Sd4jRcYP05OfU/hz8RrH4h6XczwWslneWjhbm2Zt4QNnYyvgBgQp7Agg8YwToeNNa0XwtozeJNVgtJLiwRxYmUDzGldSPLjbBKl8YJA6Ak8A14p8MtZs/hZ4G1TX9bf/TtW8ptP0rIWaeNQ+yXqSsbFn+YqBhMjduUEA+j6K+dLz9oTxVDcWuoDwxaW+i3L5gEyylplQgSBJshSc5GQh25GQcc/QdhNcXGnW015a/ZLqSJHmt/MEnlOQCybhw2DkZHXFAFiiiigAoorxv4V+MNf8WfEbxU51KS88NRPI9vuRVCFpAsO0EBwDHG3HTOSfmOSAeyUUUUAFZ+s6HpfiHTnsNXsIL21bJ2TJnaSCNynqrYJwwwRng1z/ij4k6F4S8Q6bod9HfTX1/tKJaW5k2Kz7FJGcnJDcIGb5TxyM9hQB4edH8Y/BeWa90h5/EnhFIneayll2PZgMSCo5xjdlmRcH5yyrhWHqHhPxpoXjXTmvNFu/N8raJ4XUpJCzDIDKfxGRlSQcE4NdBXk978MvEFp8YV8YeHtTsbOwuJY3vbf5o3dPlEybQpV923dkkfM2eCA1AHrFFFFABRRWP4q0H/AISfwvqOi/bZ7L7ZEY/PgPzLznkd1OMMvGVJGRnNAGxXnev/ABq8IeHfEraHdS3c0sbhLi4toleKBicEMd2SV6kKGx06ggeUD4ieI/h74a8QeAtYkkk1a1RLfTbmJtwhjcfMfMDBgAjK0fGVJwcABRoHwjovg79n641y7stNu9c1a3iaKW7USbBMRtWHIBV1iYvxzuUnJVRgA9/0rVbHXNLt9T0y5jubO4TfFKnRh/MEHIIPIIIOCKuV5/8ABbTNR0v4X6XHqM27z91zbxbVHkwyHcq5BO7OS+TyN+3+GvQKACiiigAooooAKKKKACiiigAooooAKKKKACiiubtfHOi3Pje+8IGWSHVrREcJKAFnDIHPlnPJVSCQcHqRkAkAGxHq2mzapNpcWoWj6hCm+W0WZTKi8csmcgfMvJHcetXK+f8A/m7z/P8Az4V6J4q+Kui6DexaPphj1rxBPcJbRafbzBQHZymHlwVQggjB5yRkAHIAMf4yfEPWvAT6A2jpaMLp52mW4jLBwiqAvBBAzJnjByq84yD6pXh/x9urOx8Q+BLzUbf7RYwXc0txDsD+ZGrwFl2ng5AIweDXonxGs9f1TwdcaR4ct45LvUXW0llklVFt4Hz5jncDuG35cKN3zZHIoA4v4c694g8c/ErXtf8A7UnHhiy32lnBGrJDOC3yHay8sFG9jkOCyD7p217BWH4R8L2Pg7w1aaPYRxgRIDNKqbTPLgbpG5JySOmTgYA4ArcoAKKKjEKrcPODJvdFQgyMVwpJGFzgH5jkgZPGc4GACSiiigCvfWUWoWclrM86RvjJgneFxgg8OhDDp2PPTpVfUv7RjltLiw/fKsqxz2p2gPG7KGcMeQ0Yyw6hgGXGSrLoUUAFRwyNKhZ4ZISHZdrlSSAxAb5SRggZHfBGQDkCSigAooooAKKKKAPmjwBq/wDYfxD+I15pVrBHDa6fqF3bQy2vl7PKlBRNpCui4OCg29BkZUY4u/0Bf+ED0jWJ1u7/AMReJNTla2dWaQmKMlHVh1MryuD/ABZAHIJIP1GPh74aXXNZ1cWG241i0a0vFSRlR0f/AFhABGGf5ckf3cjBLE+Gal4Z1/w18V/B3hhjd3mh2OppPpDmBc+U0qSTZZBklSDuz0A3YVSKAPpuiio5o2lQKk0kJDq25ApJAYEr8wIwQMHvgnBBwQAeb/EL4Rw+L9Zt9f0vUpNK1qN4Q0yKNpVW+/8ALhvNAIwd38Crx94eaeA9K1SL9oSeztvEV9qf2DzBqGoMdjzIiBWR1kLFlEmyPv0DKRgMPZ/id4u/4QvwNe6lE22+l/0ay4z++cHDfdI+UBnwRg7cd64/4A+Df7F8LyeIbyHbfar/AKneuGjtwflxlQRvOW4JDKIzQB7BRRRQAUUVyfgvxo3jK41xodPji0/Tr1rO3vY7pZluypOWAAG0bdh7g7+DwaAOsooooAKKKKACiiigAooooAKKKKACiiuf8Y6FqniPQ/7P0nxBPoczSq8l1BHudkGfkBDKVydpyD2x0JoA6Cs/XJtUt9DvZdEtYLrU0iY20M8mxHfsCf6ZGemVzuHkdz4Q+Mvhy3uU0HxjHq9uHDxrdbWuJCQoOPOVgoHPHmYwCepxXUfDf4kzeK73UdA1yzjsfEmmvILiGDJidVfYSpy2CrEKRk54IJBIUA8s1y48fXHxD+H/APwnFlBa+XqEMdr5JjPmsJYvMdtjN8x+TPReOAOa+l6+dPifdeNLr4r+Gjc6faWVvHqYi0ITSq6SsJY8ySFDvAY+WSOMDAAyCT9F0AfPHxx0TQvDFnLcpbz3uu+IruSRr29cyG3hQoxSPkBMHy1X5Sdhdc4xXtfg7QF8LeDtK0VVjD2tuqy+WzMrSn5pGBbnBcse3XoOleMfEjUYdE+Ouna74r067Oj2VvG2nG0iDG5ZPmGSZFAKSuSfZUBXDZO3N+0B9s+0T+HvBeq6nY2sQkup3by/I+8fmCLIAuFzuJHQ8cZoA9oorx/4d/Gi88b+NJdHm0L7PayxGS3e3YyGDaPm85jgFSeAwAwSq4Ocj2CgAooooAKKKKACiiigAooooAKKz7m51Tz2Sy06B403gvc3XlbjsBQoFR8qWJUltpXaSAwxmxvvP7R2eRB9h8rPnecfM8zP3dm3G3HO7dnPG3vQBYooooAKKKKACiiigAooooAKz4dYt7nU7mwtknmmtJViumERVIS0fmDLNgNwVyE3Eb1yADkaFY+j+b/aetGTRJ9PWS7Ei3Ek6SC8xGkfmBVYlOIwNpA4CnqWAANiiiigAoqvfW8t3ZyQQ3s9lI2MTwBC6YIPAdWXnpyD19eaKALFFFFAHmfxF+LK+D9ZsdD0nT49X1ad1863SRt0QLLtTCqcu6lsDOR8pIIIB9IgMzW8TXEccc5QGRI3LqrY5AYgEjPfAz6CvA/g9b2/jH4q+J/Gcss8n2aVms0nJ3qJi4QkhsfLGpTbyPm4xtFe8X1/Z6ZZyXl/dwWlrHjfNPII0XJAGWPAySB+NAFio554bW3luLiWOGCJC8kkjBVRQMkkngADnNeL+KP2itIsXe38N6fJqb7GAupyYYgxUbSFI3uASQQdn3eCc5rmLDwp8Q/jK/8AaHiDUpNM0XeHhSWJlQgqzK0MPAcAOB5jHJDcM2CKAN/xX8drm+vzovw+06TULiVMJeGB3YnYxby4cZJXg7m4+VvlI5PmHxM8MeL9GuNO1TxhqMd5eamkhRRO0jQbSrMh4CqAZOAhKjnHFfT/AIU8B+HPBluE0bTo45ym2S7k+eeThc5c8gEqDtGFzyAK8g/aZghW48NXCxRid0uUeQKNzKpiKgnqQCzEDtuPrQB9B0UUUAFFFFABXzZ+0hBpEPiXSHtoo01aa3d71lUgvHkLEW7E/LIM9cAA8Ba+k6+cPCnn/E74/XGvSf6TpGlymaJ/3qoscZK24XP3WLYk2nAOJDjqKAPo+iiigD5Y8VeJJfGnjTxD4i0SOe5g0u0UWd0qp5tpCoOZUhJV2zKc+byYlcttDBNvT/Cv4b+EW+H6eM/EsP23/WXREu/y7aOFmB+RT+8zsJIIIIwNvXdgeGdQuPEOj/FbxVZ6dANSntAqx5CrFbTM5mHG0MwRA24jJZM8liDT0k+I/itcWPhTQY5NH8L6dbxxTRq+6NFyCZJioUSysylgMDnJwPnagDpPCtnqnxe+Kv8AwmVwfI0TR7uMW4KeW5RC0kSDAZWYNtaTLceZwQCuPf7W/s77z/sd3BceRK0E3kyB/LkX7yNjowyMg8ivN/Fmp6P8F/hqthocPl3dxuhtPmQyPMV+a4kyPm28E8EZ2LhVIx0Hwx8I/wDCF+BrLTZV230v+k3vOf3zgZX7xHygKmQcHbnvQB2FFFFAFe/vE0/Trm9lGY7eJ5WG9UyFBJ+ZyFHTqxAHcgV80fAvxl/YusJ4ds9A+2X2r3a+befbPL8uFFJ+4VIOwea3BBbOPSvV/jrY2938KNSmnj3yWksE0B3EbHMixk8dfldhz6+uKp/Ajw5pFn4BsNegsY11S+SVLi5JLMyrM4AGThRhVyFxnAznAoA9UooooA+f/BcKa5+014hvri6nmk077S8TGNY+VK2+wgZyqqxAOQTtVjgkiuo8S/HDToLxtI8H2E/iLV23KnkIxhDANkjA3SY2g4UbSpyHrL1P4T+BfGfjzUJbDxLJHdpcSvqmnW8qO/mEKSULZKgO3zHDjJKjaRgdZc+C9C8A/DzxBL4etPst9HpVyft+4m5LCLr5nVeUVsLhQRkAUAR/B3xnrXjbwrcX2sx2m+2uBbJNDlWlKopYumMA/MDlTg5I2rjn0SvJ/wBnmxuLT4ayTTx7I7vUJZoDuB3oFSMnjp8yMOfT0xXqgmVrh4AJN6IrkmNguGJAw2ME/KcgHI4zjIyASUUUUAFFFFAHnfxW+Ga+PtLgksDaW+tWzgR3E4YB4ud0bFcnGTuBwcEEDG4mvOPin4R8b3Nv4P8ADljZXd/p9rpkFuy2iloBdqNjsxwNo27cM+AAWxj5q+i6KAPE/hT8OfHXh3VNPvNa1qS10m2SU/2Ot48gLNuADIp8sDLF8gtzjjJJHtlFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABXh/xQtpfD3xm8FeJNNXN1fyi1kghiQPLtZUb5mIBZ0m2AtjG0fMOMe4V4f8fby30/xD4EvbwTm1t7uaWYW7lJCivAW2MCCGwDggjB7igC5omlLe/tP+JL+W2kkSwsonjmG7bFK8MKDJHGShlwD6E9sjQ+G+kfDdvFmtz+EbCSS60x0j+2SSGSJRIpUiAsxJGY3yxGTubDFSBXmkTeLviV4v8AEz+DZb620TXNi3l1fxpEm2OMARNJGpxjdtCqSzKw3ZG419B+EfC9j4O8NWmj2EcYESAzSqm0zy4G6RuSckjpk4GAOAKAPO/2jv8Aknmn/wDYVj/9FS16JBqtjpfgWLWEubu/0+10wXS3EnzT3ESxbt53bcuyjPOOTzivK/2jhqlzp3huws4J5rW4u5A6RQ790+FES5AzuIaXCjrzwccXNc8RfFDxd4altdL+H0dhaX9vNbz/AG68XzdrAplVYxlCPm+8pzkHp1APUPDmv2Pinw/Z61prSG0ukLJ5i7WUglWUj1DAjjI44JHNaleZ/BjSPGHh3w/daL4nsI7W0tnVtPxJG7EOXaRSUY8BsEZ5+Y8kYA9MoAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD5013UofjD8adM0OFZH8P6W8gkdAJFlVTmR9yEFUkKpGDu4yrDBbFel+LvhxcanqNprfhfXJ9B1extEtLdIQBbNEhZ1iZAOFLFc/eXC42Guk0DwfoHha4v59E02Oye/cPcCN22sQWIwpJCgbm4UAc+wrcoA8bk+KfivwK8Nn4/8ADEksAfyxq+nMCkwCkKdp+UuxRmI3IQDnYMYPYaJ8WPBGuW/mw+ILS1dURpIr9xbshYfd+fAYjBB2lgPXkZ7SuP1P4V+BtX8r7T4ZsY/Kzt+yKbbOcZz5RXd075xzjqaAOP8AiP8AFazmsx4Y8F6h9t1/UZUtY7izkASDeV6Sn5SzbtoKn5ckllKjPcfDzwwnhHwNpml+R5N0IhLeA7SxnYZfLLw2D8oPPyqoycVHoHw08H+F9UXU9H0WOC8VCiytNJKUB67d7EA44yOcEjoTXWUAFFFFABRRRQAUUUUAFFFFABRRRQBXv/tn9nXP9neR9u8p/s/2jPl+Zg7d+OducZxzirFFFAHL+OvHWl+A9DN/fnzbiTK2tojYe4cdh6KMjLds9yQD5n8EbOHQPCet+P8AxHdxxpfOcXdxh38tGO9t+SxLyHG3GSY165FUNE03UvjJ8Tb7UPElndr4Z0l5oIrOZGhCNnCxEBgVl6O5BPKhTgFQPd54NNtdGlt7iK0h0uK3KSRyKqwJCFwQQflCBeMdMUAfLHxC8Zt8WfGuj6fpUcltZ70tLRbvaD5srAM7bQSBnYMZbhMjkkV9T6TZzado1jZXF3JeT29vHFJcyZ3TMqgFzkk5JGep69TXi/xZ0nQfCfirwHq1pp+m6XZwanvu2tYUjYqrxNkog3OAFfkA4yB1YZ90oAjnnhtbeW4uJY4YIkLySSMFVFAySSeAAOc18+eM/EPiD4x65J4Z8FL5vh628p7i6y0SSlsfNKWAIVTnCAEnYzANgbe78V/Ce78Z+IDcaz4x1KTRQ+6PTI4kTy+Gxhh8pILH5ihbb8pJ613GgeHNI8LaWum6LYx2loHL7FJYsx6lmYksegySeAB0AoAy/AvgXS/AehiwsB5txJhrq7dcPcOO59FGThe2e5JJ6iiigAooooAKKKKACiiigAorn/E3iX+wZ9ItIFsZb7VLv7NBFeXv2ZeEZi2Qjk8hUwF+9Io7itwwq1wk5Mm9EZABIwXDEE5XOCflGCRkc4xk5ACCCG1t4re3ijhgiQJHHGoVUUDAAA4AA4xUlFFABRRRQAUUUUAFFFRxmYvMJY41QPiIq5Ysu0csMDad24YGeADnnAAJKKKKACq99YWep2clnf2kF3ayY3wzxiRGwQRlTwcEA/hViigCOCCG1t4re3ijhgiQJHHGoVUUDAAA4AA4xUlV7+xt9T065sLyPzLW6ieGZNxG5GBDDI5GQT0qxQAUUUUAFZ+u6n/Ynh7U9W8nzvsNpLc+Vu279iFtucHGcYzg1oVwfxnnmtvhJrzwSyROUiQsjFSVaZFYcdipII7gkUAYf7PNjcWnw1kmnj2R3eoSzQHcDvQKkZPHT5kYc+npiuk+I3w8h+IWl21pLqt3YPbOXi8sB4mY4GXj43EKGCkEY3HrnFU/gl/ySHQv+3j/ANKJK4/x1428e654v1Lwf4H06eOO02xXF3HEUky0bE5kkwsSnnaeCSgKsQwFAHcQ+CdL8B+DdXk8Jabs1dNPm8m68vzrmWUR/LyQc5ZFOwDbnotcf+znrP27wzrGnSvPJdWt3HK0khyPLaJY41Bzn5RARjoBtx7HgDw7440X4gJDrHjyx1OOCJze6Z/aktxMEK/K3luvy/MYzu44PXBwfHGuda8IW+veBbe3u49Wv72O3upIHP7+JAwWONNgciQybt2fmUqMYJyAfZdfP/7TX/Mrf9vf/tGvaPCulXmieF9O07Ub+e/voYh9ouZ5jKzyE7mwxAJUEkLnnaBXmf7SEELeBdMuGijM6amqJIVG5VaKQsAeoBKqSO+0elAHslFU9J1KHWdGsdUt1kWC9t47iNZAAwV1DAHBIzg+pq5QAUUUUAV7+1+3adc2f2ie38+J4vOt32SR7gRuRuzDOQexrwTw7/b/AMCdU1GPWNGk1HwvcvuOp2MKl1K4CMxzlQS4XY5AySUJ53fQdeT/AB68Y3HhzwlDpNl8txrPmwvIUDBYAoEgGTwx3qBweC3Q4NAHomgeI9I8U6WupaLfR3doXKb1BUqw6hlYAqehwQOCD0IrUrwP9maCZbfxLcNFIIHe2RJCp2syiUsAehIDKSO24ete+UAfPn7M08K3HiW3aWMTuls6RlhuZVMoYgdSAWUE9tw9a9z1G+0vw9p19q19JBZWq/vrqcrjcQAoJxyzYCqOpOFA7CvL/FnwavG8UL4q8Eat/ZusvdtcTLcyHywzDLMhCseTuyrAqQ5HAG085e/Dz4q+O9Uj0vxjqscOk2ryOl0ghZHYcArHHtLE9i4XaC3QnaQB/hr+0fjF8VV8TTfbrXw1okqyWSNtwJEKsqem5iN743EDC5+4a+gKx/DHhjS/COhw6RpEHlW8fLM3Lyuerue7HA/IAAAADYoAKKKKAPL/AI/an9g+F89t5Pmf2hdw227djy8Ey7sY5/1WMcfez2wes+H0ENt8OfDaQRRxIdMt3KooUFmjDMeO5Ykk9ySa83/aT1KGLwro2lssnn3F6bhGAG0LGhVgec5zKuOOx6d/aIIIbW3it7eKOGCJAkccahVRQMAADgADjFAElFeR+PNaufF3xB0/4a2EEcun74rrXHYP/qlIk8rcB8gKhDuB5Z0XK859coA5MeBvDMPxBt/EyRRwawLeXy4IisauSSJJyigF3/e7SxJHzDIzg1c8d/8AJPPEv/YKuv8A0U1ef+BvC3jbUviVL4u8eWcEMlraNDYqksf7ssxwFEZOVVWkHzkn5x1IyOg+Nv8AySHXf+3f/wBKI6AD4Jf8kh0L/t4/9KJK9Arm/h9BDbfDnw2kEUcSHTLdyqKFBZowzHjuWJJPckmukoAKKKKACvK9S/aC8EWNwsVudS1BCgYy2tsFUHJ+U+YyHPGemORz1x6pXL6Z8OfB2j6jLf2Ph2xjupJRMHaPzPLcEkGMNkR4J/gx0HoMAHnafHvUry3u7/S/h/qV5pdu7hrxZ22qqjOXKxMqEKQSNxxnqetekeBvGdj468NR6xYxyQkOYbiB+TDKACVzjDDDAgjqCMgHIEfxF8Q3HhX4f6xrFmubqGIJCcj5HdljV+QQdpYNgjnGO9cP8FLWz8HfDUarr1xY6Z/a12ZYprpxCzR7QI1ZnxnO2R1AJG1sjqaAPYKKKKACiiigAooooAKKKKACiiigAooooAKKKKACuP8AGPw20LxzqOnXmsSXx+w5Cww3BWORSQSGUg4zjBK7WI6nhcdhRQBT0rSrHQ9Lt9M0y2jtrO3TZFEnRR/MknJJPJJJOSauVzfjnxnY+BfDUmsX0ckxLiG3gTgzSkEhc4wowpJJ6AHAJwDH4A8Y/wDCdeF01r+zJ9P3SvF5crblbafvI2BuXnGcDDBh2yQDqKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAK8v+Oni7/hHPAz6bbti+1ndbJx92EAea3KkHghMZB/eZH3a9Qr5/wBXt7jxx+01bQW0sBtdA8mR5oSGwkJEjBvm5bzX8s4+7nkfKaAPRPhV4CbwL4aEc93dyXl6kc13byOpiglwchAMjOCFZsndsB4GBXeUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAVn65o1n4h0O90i/Tfa3cTRPgAlc9GXIIDA4IOOCAa0KKAPlzxlqRtPhzp/w8128jg17RtYWN3d5JkNqY3KTbgpwgWVVCDLAKPlHQfUdcX4q+FfhTxhqkWp6nZSLeK6GWW3kMZnRc/I/YgjA3DDYAAYAV2lABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRUc8y21vLO4kKRoXYRxs7EAZ4VQSx9gCT2qSgAooooAKKKKACiiigAooooAKKKKACiiigAooooAK8/+Nv/ACSHXf8At3/9KI69Arz/AONv/JIdd/7d/wD0ojoAPgl/ySHQv+3j/wBKJKp/FbVPH8KQaX4P0eSS3vUEMuowNuljkdiNqjI8sBV5kYYG8YKkZq58Ev8AkkOhf9vH/pRJWh8StK8U6x4Sa18JX/2O/EokkKzGJ5I1VjsRwPlYts7qOoJAJyAeQXXwuf4fajB418TeI4LuOz23pjDMs13fg71hDMG3KzDJkxuxuJVRlhY+EWl3HxD+IGp+PdfG+S0lQwotuBC8pUhQCc/6pVQgfeyUYnP3i5+EHxF8cbtR8Xa/BFcCJzb20r+Z5cnChdsYEcasFBLJuPTKk5wB/jJ4S8FmysNCsdI0zTvOummhlhldIsvIy/vJXyo3HHBb5RyecgHt/wDwlWhf8JR/wjX9pwf2z5XnfZMndtxnGem7Hzbc7tvzYxzXm/7R3/JPNP8A+wrH/wCipa5/QLHVPjZ4Sm1S/jg0/wAQ6PKkWm63bN5ZmdVDFJFXkYJVty4wZMqBhg0Hij4iXGr/AAt8ReFPFcP2DxZY+QkkbgKLsCeM71xxuxyQOCPmX5chQD2fwJ/yTzw1/wBgq1/9FLXQVx/wr1P+1/hf4eufJ8rZaC227t2fJJi3ZwOuzOO2cc9a7CgAooooAK8j+LPwj1Lx5rNrq+l6laQzxW6WrW90rKpUM7bw67jnLgbdvqc9q9cooAw/B/hqHwf4TsNBguJLhLRGBmcAF2Zi7HA6DcxwOcDHJ61w/wDwsy+8VfEa28MeChaS2Fq6zanqUpyGiSRN4h7EEfJuwcl8jaBvNf47+K5rDw/beFtLMkmqa04Ro4MmQQ5xgBTuy7YQDBDASCus+G/gW38B+F4rHEEmpS/vL66iUjzXycDJ52qDtHTucAsaAI/+Fr+C18S3OgzazHb3du7Ru9wjRReYpcOm9gACuzqcA7l2knIHaV4P+0fYaFb6Tpl4tpBHrt1dn98kZDywrHh9zDhsHyQN3I7cZr1zwXBNa+BfD1vcRSQzxaZbJJHIpVkYRKCCDyCDxigDcooooAKKKKAPC/2ltNml0bQNUVo/It7iW3dSTuLSKrKRxjGImzz3HXt3HxM+Jlj4A0sIgjudauEJtbQngDp5kmOQgPbqxGB0JXc8Y+DtL8caH/ZOreesKyrNHJA+143GRkZBB4LDkHr64I5vw98HtF0jxBB4h1HUNS1zWI0UmfUZQ6+aAAJAMZyMfKGZtvHcAgAr/BvwTN4e8Pya1rVvJ/wkWqu0txJcgmeOMnIRiSTkkb26HLAMMqK9MoooAK+f/jRYeOZtOn066tP7X0aTVYrmwvLaMCSDeHRbZ415bl1AfuSMklgq/QFFAFewsbfTNOtrCzj8u1tYkhhTcTtRQAoyeTgAdasUUUAFFFFABRRRQBx/xK8Ef8J74SbSo7r7NdRSi5tnYZQyKrKFfjO0hiMjkcHnGD5fD8CvFmtz28PivxRA1hZ2hhs0tXeXySEVECoyqqr8qlsctt9TuH0BRQB438AtWhsvhbfXGqahHBZ2upyIsl1MFjhUpEcAscKCzk49WPc0aj+0JpCeJbfTdF0a71i0keNPtMLFHdmPIjiZMuRkAAlcnI6YJ0PEXwe8AWT3via6067S0s7eW5uNPtZtsUu1WYkLwVPoqsq/KBgDOeT+Fnwvi1jXI/G9/pf9k6Wsq3Gk6WkrsTjBSRmY7iowCOm884CYDAH0BRRRQAUUUUAFFFFABRRRQAUUVh+LfFVj4M8Py6zqMN3LbxuqFbWLe2WOBnoFHuxA6DqQCAblFU9K1Wx1zS7fU9MuY7mzuE3xSp0YfzBByCDyCCDgirlABRXF+M/il4Z8DukGoXElzeM+1rOy2ySxjaG3OCwCjBXGTk54BAJHWWF9b6np1tf2cnmWt1Ek0L7SNyMAVODyMgjrQB4B8aNQT/haGixeLNOvj4VtIi0QtSpa6JGXKtxj5hGjLuyFXcMbhXT2H7QfgeP7NZRadqtlarsiU/ZYhHCgwB8qOSFA7KDwOBXsFcP8XtTs9L+F+tNeQwT/AGiIW0MMzAbpHICsuQcsnMgA5+TPGMgA6zStVsdc0u31PTLmO5s7hN8UqdGH8wQcgg8ggg4Iq5Xl/wAKW0fwV8L9G/tm9sdKuNT8y9P2u8RPO3EbWG5sf6vyuB0yMgEmtSf4z/D62uJYH8RRl43KMY7aZ1JBxwyoQw9wSD2oA7yiuDg+M/w+ubiKBPEUYeRwimS2mRQSccsyAKPckAd66zTNd0fW/N/snVbG/wDJx5n2S4SXZnOM7ScZwevoaANCiiigAooooAKKKKACiiigAooooAKKKKACiiigDL8Sa3D4c8NalrM/llLO3eUI8gjEjAfKm49CzYUcHkjg18+fC34c/wDCb+Hr7xMviLVdN8QpqEkUd7DJu6ojMzdHLMJHBO8de/Oej+PniGbUrjSPA2jXMc15eXCm6to3KtuJUQoxyFwxYthv7qNwME+weG9Eh8OeGtN0aDyylnbpEXSMRiRgPmfaOhZsseTyTyaAPJ4tU+MHgF4F1XT4/F2mlEjzZ5eVCFYAblQPngFmdHB4G4Emu08C/FPw/wCOohFby/YtTGA1hcuodjt3ExnP7xRhuQARtyQMjPcV5/4y+D3hbxf5tz9m/s3U3y32y0ULvY7jmRPuvlmyTwxwBuFAHoFFeFyar8SPhPew3HiC5k8R+Eo38uS4XDSorudrMW+cOOOGLJ8wQNkgj2Dw5r9j4p8P2etaa0htLpCyeYu1lIJVlI9QwI4yOOCRzQBqUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAVn6fqyX95f2htp7a4spQjRz7QZEIykqbWOY2+YAnHKMCAVIGhVPVNSh0mwa9uFkMCOgkZAP3as4Uu2SAEUHcx7KrHtQBcooooAKKKKACiiigAooooAKKKKACo4J4bq3iuLeWOaCVA8ckbBldSMggjggjnNSUUAFFFFABXJ/E7TYdV+GXiK3naRUSye4BQgHdEPNUcg8bkAPtnp1rrKKAPI/2eNVa9+H1xYS3Mcj2F66RwjbuiicK4yBzguZcE+hHbA9cr58+HkbfDH406n4SvZpE0/U022ckgUCU53QMXYLk7TJH8vBkOMHGR1/w28YX2rfEbx1oep6lJObe9d7GBk4jiSRom2kDAAHk8Z5JJ5JY0AeqV4n+0H42m0rS7Xwvp9xJFcX6GW8ZCVP2flQnTkOwbOCDhCCMNXtleT+JfhVrHiv4qxeI7/WIIdItPI+yxQ7xPtjKsUyu3ZljIQ4YsMjHsAdZ8NvC7eEPAem6VPHGt5sM13tRQfNc7iGIJDFQQm7JyEHbAqTxj8P/AA/44s/K1a123C7RHewBVnjAJO0OQcry3ykEc5xnBHUUUAY/hXw9b+FPC+naHatvjtIghfBHmOTl3wScbmLHGeM4HFbFFFABRRRQAUUUUAeD2rf8Jj+1DPKJYLqx0GJgqTR42+WuwhQV5ZbiQsCfTIPC17xXh/wLurPW/GXj3XILfH2m7SWB5UHmJHLJM5XIzjOFyAcZUdcCvcKAPmzxJcr8V/jrZaRbXlpd6HYuiKPtDIksS4efZzlnY7lBQchVP3VLV9J15f4i+A3g7WvMlsYp9Ium8xg1o+Yy7cgmNsgKD/ChTgkemMP4NeI9XtvF+ueAtQvpNQtNISRLOdwFMawzeWRjBJDb1wCx2hABx0APYNV1Wx0PS7jU9TuY7azt03yyv0UfzJJwABySQBkmrEE8N1bxXFvLHNBKgeOSNgyupGQQRwQRzmuL+Lnh638Q/DXVknbZJYRNfwPgna8SkngEZyu5ec43ZwSBWH+z7NfS/DIJdiQQRXsqWe6PaDFhWO04+YeY0nPPOR2wAD1SiiigAoorh/Hnw8fxjeabqNj4gvtF1Ow3rFcQMzAIwwwCh12sf7wIyODnjaAdhfX9nplnJeX93BaWseN808gjRckAZY8DJIH41X0zXdH1vzf7J1Wxv/Jx5n2S4SXZnOM7ScZwevoa+aPiJ4SvIdY0nwtH4l1Xxb4mllYiKWYlLWNlUhdjFsM2CxbeAFUErghq9n+Gvwt07wFZrdSN9p12WIx3N0rsECsVJjRem0FR8xG48ngHaAD0CiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigArD8Y6AvinwdquissZe6t2WLzGZVWUfNGxK84DhT36dD0rcooA8T+B3iO00P4fa7b67fSWh0W9ZriO6Dj7NGwACgEcEyLINg53E8ZYZp+IvjjrGs6jJpnw70ae92RSeZctZvLJ12iSONTwoyDlwclgCoxzxHxA8MW7/AB+bSHgntrHVdQtiWXILiYp5joWzn5zJ6gEEY4xXZ/EfU9M+GOveDdJ8OyXenWFtcNfajZWUsgNxE0keC5LYkJEcigMxwBjgYoAyPFngO28C/CbUNQ11pNQ8Sa5cWy+dNEjtaS/62Rd5JJJ2yBnB+b5eMZNe5+BP+SeeGv8AsFWv/opa87+OPiOHUfhDZXmjX0c+n6pexI0iAESRhXfHIypDxrkcEFSD3FeieBP+SeeGv+wVa/8AopaAMPxn8ULHwvqiaHp+nXeueIJE3rp9kMlBwfnIBIO3cwAVjgZOAQa8g8Z6p4/+JXixPAt1o9pYS21x9oa2ibeIVKja80oLAhUfquMlsbSSoHpfxA+K3hvw5odzc6HqFjf67exG3gexkjlMe3O15GGRtQuSFOckkAYLEHwX8F3nh/Q7nWdetNuv6pK0rzTsWnEJwQr5+6xbcxHU5XdyMAAseGPgj4O8PxQyXVl/a98nLT3vzISV2kCL7m3OSAwYjPU4GO803SdN0a3a30vT7SxgZy7R2sKxKWwBkhQBnAAz7CrlFAGXqXhrQdZuFuNU0TTb6dUCLJdWqSsFyTgFgTjJJx7muP1/4JeCNedpV0+TTJ2cM0mnOIgQFxt2EFAOhOFByM55Oes8VeIbfwp4X1HXLpd8dpEXCZI8xycImQDjcxUZxxnJ4qPStavtV8FW+uxaZGby5svtcFilzkOWXdGnmMqgEgqCSMAk9QMkA8vv9b8b/B57OTXtSj8TeF5rgQfaHBF3ESrMfvHJJO4jczgiMDKZFeyWF9b6np1tf2cnmWt1Ek0L7SNyMAVODyMgjrXi+reDPiF8UNcgTxbFBoHh60l3raQXCyu4O7kbSwaQDC7m2gbsqvLA+2QQQ2tvFb28UcMESBI441CqigYAAHAAHGKAJKKKKACiiigAooooAKKKKACiiigAooooA870n4QaLp3xB1DxbcTyX0s9w11a28yDbbSsSzPn+Mhj8vA2+5AYZ/jbwt8S9R8Yz3XhjxPHa6XdWRh8uWQxrb42gqAFY72JLCQAMORkYXd6Rquq2Oh6XcanqdzHbWdum+WV+ij+ZJOAAOSSAMk1ydj8YPAOoXkdrD4jgSR84M8UkKDAJ5d1Cjp3PPTrQB0HhXT9U0rwvp1jrWo/2jqUMQWe6x9854GTy2Bhdx5bGTyTWxRRQBXvrCz1Ozks7+0gu7WTG+GeMSI2CCMqeDggH8KkgghtbeK3t4o4YIkCRxxqFVFAwAAOAAOMVJRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFcP8WofEEnw/un8NXV9DfxSxOUsI2aaZNwBRSvzL1DEjshHQmu4ooA4/4WX2qal8M9Eu9ZknlvpImLSTrh3QOwjY+uUCncfvZzk5zXYUUUAFFFFAEZhVrhJyZN6IyACRguGIJyucE/KMEjI5xjJzG6XhvFZJ4BajG6MwkueHzht2BkmPHynG1hzuBWxRQAUUUUAFRwyNKhZ4ZISHZdrlSSAxAb5SRggZHfBGQDkCSigAooooAKKjhmWdC6CQAOyfPGyHKsVPDAHGRwehGCMgg1JQAUUUUAFFFFABRRRQB538UPhtN41Sw1HR7yOw17TnBguGygddwOC6gupUjcpGcEtx82Rx/wL8I61pfirxHqviOy1K31BUFuJLpTtuC7lpWDkfvDujT5gxHzE85Br3SigAooooAKKKKACiiigAooooAKKKKAPE/2bNNhi8K6zqitJ59xei3dSRtCxoGUjjOcytnnsOnf2yvD/wBmvU/N8Pa5pPk4+zXaXPm7vveam3bjHGPJznPO7tjnrPAPxGufE3irxH4c1a1tLbUNMuJBCtqzsrxK5RslhyVbb83y7t4wowaAPRK+dPhxJfJ+0h4kW0hjkge4v1vGY8xxedkMvI58wRjvwx47j2vxx4k/4RHwXqmuCPzJLWL90hXIMjEIm4ZHy7mXODnGcc14h8NbG38K+LV8U6r450pLe7iKTw3MhF3cmVlwzI43LGz7ZRIeSgVmChm2gHrfxR8L614x8HHRdEu7S3ea4Rrn7USFeJcttBCsQd4Q8Y6Hnsdzwr4et/CnhfTtDtW3x2kQQvgjzHJy74JONzFjjPGcDitSCeG6t4ri3ljmglQPHJGwZXUjIII4II5zUlABRRRQAVj+KvENv4U8L6jrl0u+O0iLhMkeY5OETIBxuYqM44zk8VsV438ed2pv4T8NyXVpZWeoXsk095cOqCARqq7ssyrgLK5wTkkKAc8EAsfBHwpNFYXXjfWBI2ta48jhmymIWcMTswAC7DdkZG3ZjGTn0TT/ABVoWq65f6LY6nBPqWn4+0wITlOxwejYPBxnaTg4PFeT69r2qfEfXJfAfgOWC18M20SQ6hqUEO1EQbgY0IODGQAoVQN21ufLyak8A6XaeGv2gfFGg6QkltpcemI62wldl3YgOTuJJIMj4J6bjjrQB6h4x19fC3g7VdaZow9rbs0XmKzK0p+WNSF5wXKjt16jrXD/AAWtda1PRG8X654g1K+n1F5litXuS1uibwN3l4wr7kcDbwFIAA7SfHXXNLtPhxqWkT38Cald+QYLXfmRwJlYttHIXCN8x4yMZzgVj6H4613RvCVlr1/p0Gi+EtP0pbeytbycS3OqTbcRbWVVKcIDnZjDM2GBDIAe0UVh+D7/AFrU/CdhfeIbGOx1SdGea3RSoQbjs4JJB27SQTkEkHHQblABRRRQAUVT1LVtN0a3W41TULSxgZwiyXUyxKWwTgFiBnAJx7GuH1/42+CNBdol1CTU51cK0enIJQAVzu3khCOgOGJycY4OAD0Sq99f2emWcl5f3cFpax43zTyCNFyQBljwMkgfjXk/9v8Axa8bcaHokHhK1i+9NqeWkkcdVAePO0hgc+X1Q/P/AA1YsfgXp1zeR3vi7xBqviS6jyi+fK0aGPBwp+Zn4Ys3Dgc9OuQCxrPx28LWOovpulQ32t3xzHCLGMGOSbJVYwxOTkgfMqsCGGM9Kyz8R/ifrVwkGg/DiSydEZ5Tqm8KwyANrN5QB5PGST7YNeoaJ4b0Xw5b+Ro2l2lihREcwxBWkCjC726uRk8sSeT61qUAeD+LPih8TPBNnpk+uaT4chk1DzdkCLK7x+WVB3ESbedwIwT74PFdh8HvG3iDxnodzLr2neV9n8sW9+kTRpeA7lYgH5SwZDkqcfNjC45j0P4NabB4gufEHinUJPE2qTuzFruBVg5AAzF8wJABA52gEYUbQa9MoAKKKKACiiigAooooAKKKKAPG/jd4R1q8vdE8W+GrKSfUtMcLL5CmSXAcNEyx4IYKxfOBn5hkEA4x/j3ftpWs+B/EMNjHDqELyT7J1UtmNoXWOQoeQrFuAxHLYPOa98rn/EvgvQvF0uny6xaefJYSiW3JY4HzIzKyn5WVtgUhgeCcYPNAHinxT1++1X4LeFpdeaNNY1O9a+WKNcL5GJNpGMgAJLCME7ueckNXu8GgWMPhWLw46yT6elkLFlkbDPEE2YJXHJXuMe2Kw/E/wAO9L8TeJtH8RSTT2+p6XLC8bocpIiSiTYyn/gYBBGN2TuwBXYUAcPo3wi8E6DrCapZaPm6ilEsBmnkkWAhQBtVjg4ILAtkhjkEYGOb+D/jvX9c1nXvDniy6jl1bT33IohVWAVikqkxjYQrbMdzuPJHT1yvlTWfCupXfx81jwxpmsyWj6rcSme4UsgMMqfaJEZVPzgKSMEgMQM4zwAfVdFfP+i3158FviHYeFb7V77WtG1G0QpFHCf9GkeVwpSLLk8hshCC2/OGKgHqPjB8SP7Es28LeH5p5PEt9sjH2Tl7ZWIx0BPmODhVHzDduyPl3AFPXLub4ofFK28M2RjPh3w5cLdam09qWWa4Rypi54IIyoB2g/vD84Va9kri/hh4Jh8E+DrW1e3jTVLhBLfyADc0hyQhIJBCA7Rg4OCQBuNdpQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAZev+HNI8U6W2m61Yx3doXD7GJUqw6FWUgqeoyCOCR0JrxP4xeDvBPgnwME0rRYIdSv7uNIXa6kaSMIGLOoZiSuDtIGBl1J5C19AV4P8AHLR/EGp+MvD80WgX2taFaxB2t7SNjufzMyoWQFk3IIxk++OQaAPVPh/ps2k/D7QLK4a7M6WUZkW7J8yNmG4ocgEBSdoHYKB2rpK+TPiRf+L9cfTk8TS3cGrXzxvaeHreBvKSErtSXG8nzWdnXYQXGGyVG1T9J+B9BvPDHgvS9Gv737bdWkWx5gSRySQq552qCFHThRwOgAOgooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACio45lleZFEgMT7G3RsoJ2hvlJGGGGHIyM5HUEAoAkooooAKKKKACiiigAooooAKKKKACiiigAooooA8D+CzWPgrUvH0GpX8aWmnXttZvdyLsUnzZolY9doLEdTgZ5OBmuo+JPw21fVfEFp4w8H3kdn4gtEw6HCeftB2kNjBcj5CH+VlwCQBz458YL64034j+KtJsZPs1hfS2s11BCoVZnWFWBbHX5nZj6scnJAI+s4IVtreKBDIUjQIpkkZ2IAxyzElj7kknvQB88eKNb1f4yaNoug2X2TT9atb1o9U0q6kELeaFIE0Yf5iigS7kGXXPKtgNWnF8H9G8G/DHXNS8SLY3WtxWl08NyJHEURaMpGihiFZtxBBKg7mAHIBrtPiB8KbHxdcf21p1xJpniSFAYLuJtqyOpBQyYGcjGA64IyPvBQK8g8b+MPF9p4fHw48VCOG5W4j8zVpGZ1uLUHKMcIWYbgG3r8x2bSpbdkA9H/Z1gmh+HN08sUiJNqcrxMykB18uNcr6jcrDI7gjtXrlc/wCB9N0vR/Bel6fo15Be2MEWxbqCTekz5PmODubq+44BOOnaugoAKKKKACvE/jbomm6x418B2d95kCahcS2k1xbxqZSpaIIuTjjc55Odu5iAeh9srx/4v/8AJQ/hj/2FT/6Nt6ANyHWfDPwsvdC8E2ulako1F0WG6SJWSWV3CFpHLAl87S2AcArgAYA4DQ54fEv7U1zqujSx3lhbozyTxsNu1bYQkjP3h5jAZGc5yMjmvoOvA/AnhWx8P/tG63pmmTXcFnYWRniiEuQwdYv3b92QGUkA85RCSSOQDc+Llt4J0fXNG13XtKgluJpd8zR+YZ7hIdpCLHjyXyWQMZSP3YIXJ27c/wAPabcfFzxRYa9faTBZeB9F3xaXYSwgfaCAF6DjaCq5HKDYEAb5zVf4w2el2Pxa8I634kO7QpIvKlSNPMbMTl/nQggxkypkDJKhwBnGfWP+E78H/wDQ16H/AODGH/4qgDoKKjgnhureK4t5Y5oJUDxyRsGV1IyCCOCCOc1JQAVXv7G31PTrmwvI/MtbqJ4Zk3EbkYEMMjkZBPSrFFAHlem/s++CLG4aW4GpaghQqIrq5CqDkfMPLVDnjHXHJ46Y7zRvCnh/w9sOkaLY2UixCHzoYFEjIMcM+NzdATknJGTzWxRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAV5XqPwx1qX412/jaw1a0gs98bzI0ZMoCxeW0argqQyjG7II3nAyoz6pRQB82fEjx3c+G/jheajHp1pc3el2UdpYNKXCxl1Ds7hWG87ZZVAyB8wPVeez+DXw3vNF8/xV4mh363ffPAtxlprdWyXZyTxI+eeNwHBOWZR65JBDM8LyxRu8L74mZQSjbSuV9DtZhkdiR3qSgAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACsvxHq02heH7zUrfTLvU54EBjs7RC0krEgAAAE4yck4OACcHGK1KKAPF/hr8PdU1DxRfeOvHNht1KeVZ7G2lk3eUSM7ihJK7RtVFY5XacgEKa9ooooAKKKKACiiigAooooAKKKKACiiigAooqOYzKgMEcbvvUEO5Ubdw3HIB5C5IHcgDIzkAElFV0S8F4zPPAbU52xiEhxwmMtuwcESZ+UZ3KONpLWKACiiigAooooAKKKKACiiigAooqOSRkeFVhkkDvtZlK4jG0nc2SDjIA4ycsOMZIAJKjMyrcJARJvdGcERsVwpAOWxgH5hgE5POM4OARsLh5TNIUZFURELtUgnLDjOTkA5JHyjAHOZKACq+y8/tHf58H2HyseT5J8zzM/e37sbccbduc87u1SGZVuEgIk3ujOCI2K4UgHLYwD8wwCcnnGcHAZGFwkQhkKMjMZQV2qQRhTznJySMAj5TkjjIBJRRRQAUUVHMZlQGCON33qCHcqNu4bjkA8hckDuQBkZyACSioyJvtCMskYgCMHQoSxbI2kNnAAG7Iwc5HIxyRxsjzM00kgd9yqwXEY2gbVwAcZBPOTljzjAABJRRRQAUUUUARwwQ2yFIIo4kLs5VFCgszFmPHcsSSe5JNSUUUAFFFFABRRRQAVGDN9odWjjEARSjhyWLZO4FcYAA24OTnJ4GOZKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA8D+Onw41rWNZTxNomnx3UQt4oLmC1QtcO4Zh5hQD5wFKLkEkADjAyPcJ47660aWJJo7DUJbcqssY85YJSv3huC7wrc8gZxyBmrlFAHh/gD4rS2Pwl1TWvEeof2lfafdi3ggaRFmkUogjUnqckSEsdzEK5+baRVf4Z+D7j4gajq3jvxlbWN3a6vE9vBbGIHgEIXXBzHtEexTnf1ORwW7TX/gr4Q8ReJW1y6iu4ZZHD3FvbSqkU7A5JYbcgt0JUrnr1JJ9AgghtbeK3t4o4YIkCRxxqFVFAwAAOAAOMUAeJ3PgDxj8Lp21P4f38+r6b85n0i8+bA2DL7VKiRsr/AFf7qgMCa7DwN8WNL8YajLo1zZz6RrsW4PY3JzuKkhlRsAllAyVKqRzgEKSOs8R6TNrvh+80231O70yedAI7y0crJEwIIIIIOMjBGRkEjIzmvF/Enw8+J/jPVLKXUn8P2d5o6IsGrW8jxPdE4bdlQWBRgT91ACzFQQaAPfKKKKACvA/2k55rW48JXFvLJDPE908ckbFWRgYSCCOQQec175XnfxT+Gc3xETSjBqkdk9i7gh4S4dZGj3HIIwVVCQO5wMr1oA7y/huLjTrmGzuvsl1JE6Q3HliTynIIV9p4bBwcHrivN/Avw313TPGl34w8XarY6jq80RhUQQDC8IokDbV2ttVkwF6HryRXqFcn48+IGkeAdLW41DzJbudHNnaopzOy7cjdghQNykk9s4BPFAFzxpY+Gr3wvdHxbHA2kQYmkaZmXYQeCpX5gxztG3k7tvOcH5osfBsXxL8Wxr4N8PT6HoCZjmu55HmQbWJLksf9YVZP3QY+ucZYdp4a8BeJvipqlt4r8c3ciaLI7TW9gHZSyHbgRp0jiYAfNncwXP8Qevf4IIbW3it7eKOGCJAkccahVRQMAADgADjFAGX4W8N2fhHw5aaHYSTyWtrv2POwLnc7OckADqx7VsUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFRmRhcJEIZCjIzGUFdqkEYU85yckjAI+U5I4zJQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQBXbyrzzIT56+TKm4jfFlhtcYYY3LyAcEqfmU5+YVYoooAKKKKACo5BMXhMUkaoHzKGQsWXaeFORtO7acnPAIxzkSUUAFFFFABRRRQAUUUUAFFFFABUcE8N1bxXFvLHNBKgeOSNgyupGQQRwQRzmpKy/D2mzaNo0GlO0bwWKLb2sik7nhVQELjGA4HBxwcbht3bVANSiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiisPxpPNa+BfENxbyyQzxaZcvHJGxVkYRMQQRyCDzmgC5da7o9jqMGnXmq2NvfT7fJtprhEkk3Hau1ScnJBAx1NaFfAk8811cS3FxLJNPK5eSSRizOxOSSTySTzmvtvwJ/yTzw1/wBgq1/9FLQB0FFFFABRRRQAUUUUAFRzwQ3VvLb3EUc0EqFJI5FDK6kYIIPBBHGKkooAKjmghuUCTxRyoHVwrqGAZWDKee4YAg9iAakooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKjjhWJ5nUyEyvvbdIzAHaF+UE4UYUcDAzk9SSZKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACq/263/tH7AJM3Qi85kVSdqZwCxHC5OcZxu2tjO1sWKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD//2Q==')], dimensions=OCRPageDimensions(dpi=200, height=1678, width=1300))], model='mistral-ocr-2503-completion', usage_info=OCRUsageInfo(pages_processed=1, doc_size_bytes=94073))"
+"processing_time":8.566082954406738
+}
+
+
+ About This Application
+
+ <#about-this-application>
+
+This app uses Mistral AI's Document OCR to extract text and images from historical
+documents.
+
+It can process:
+
+ * Image files (jpg, png, etc.)
+ * PDF documents (multi-page support)
+
+The extracted content is processed into structured data based on the
+document type, combining:
+
+ * Text extraction with |mistral-ocr-latest|
+ * Analysis with language models
+ * Layout preservation with images
+
+View results in three formats:
+
+ * Structured HTML view
+ * Raw JSON (for developers)
+ * Markdown with images (preserves document layout)
+
+*New Features:*
+
+ * Image preprocessing for better OCR quality
+ * PDF resolution and page controls
+ * Progress tracking during processing
+
diff --git a/output/recipe_test.json b/output/recipe_test.json
new file mode 100644
index 0000000000000000000000000000000000000000..e6de184505b2b91c41cfaa25d95b1091e0a991e4
--- /dev/null
+++ b/output/recipe_test.json
@@ -0,0 +1,16 @@
+{
+ "file_name": "img-0.jpeg",
+ "topics": [
+ "Cooking",
+ "Recipes",
+ "Baking"
+ ],
+ "languages": [
+ "English"
+ ],
+ "ocr_contents": {
+ "title": "Pecan Butterballs Cookies",
+ "recipe": "1 cup butter, creamy if possible\n1/4 inch honey\n2 \" ounces flour\n1/2 teaspoon salt\n2 \" ounces pecans\n2 cups finely chopped pecans\nForm into small balls, bake at 300 40-45 min roll in uncoated sugar"
+ },
+ "confidence_score": 0.85,
+ "raw_response":
\ No newline at end of file
diff --git a/output/ymca-letter.jpg b/output/ymca-letter.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..9f657aa990425c29e571e523938ba1d82b79df13
--- /dev/null
+++ b/output/ymca-letter.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:22e0102df7d37ad482169f796435fa228a0b42b2a1661380044f781589ccbac8
+size 210565
diff --git a/packages.txt b/packages.txt
index f6afc4d1a00929ae04e5658d8d584556f0b71b50..c1298f5aebdba9b2e9b819d036559cac9e687863 100644
--- a/packages.txt
+++ b/packages.txt
@@ -1,2 +1,2 @@
-poppler-utils
tesseract-ocr
+poppler-utils
\ No newline at end of file
diff --git a/prepare_for_hf.py b/prepare_for_hf.py
new file mode 100644
index 0000000000000000000000000000000000000000..2c1ce53e16789faf3ff5634e55ac67b3a3eac54a
--- /dev/null
+++ b/prepare_for_hf.py
@@ -0,0 +1,84 @@
+#!/usr/bin/env python3
+"""
+Prepare the repository for Hugging Face Spaces deployment.
+This script:
+1. Creates a requirements.txt file with only the necessary dependencies
+2. Ensures app.py is ready for HF deployment
+3. Makes sure all configuration files are properly set up
+4. Configures Hugging Face module integration if needed
+"""
+
+import os
+import shutil
+import sys
+from pathlib import Path
+
+# Configuration for HF module
+HF_MODULE_ENABLED = True # Set to False to disable the educational module
+
+def setup_hf_module():
+ """Setup the Hugging Face educational module if enabled"""
+ if not HF_MODULE_ENABLED:
+ print("Hugging Face educational module is disabled.")
+ return
+
+ print("Setting up Hugging Face educational module...")
+
+ # Ensure directories exist
+ for directory in ["modules", "ui"]:
+ if not os.path.exists(directory):
+ os.makedirs(directory)
+ print(f"Created {directory} directory")
+
+ # Check if module files exist
+ required_files = ["streamlit_app.py", "modules/modular_app.py", "ui/layout.py"]
+ missing_files = [f for f in required_files if not os.path.exists(f)]
+
+ if missing_files:
+ print("Warning: Some module files are missing:")
+ for file in missing_files:
+ print(f" - {file}")
+ print("The educational version may not work correctly.")
+ else:
+ print("All required module files are present.")
+
+def main():
+ print("Preparing repository for Hugging Face Spaces deployment...")
+
+ # Make sure output directory exists
+ if not os.path.exists("output"):
+ os.makedirs("output")
+ print("Created output directory")
+
+ # Clean up unnecessary files
+ files_to_remove = [".env", ".env.example", ".git"]
+ for file in files_to_remove:
+ if os.path.exists(file):
+ if os.path.isdir(file):
+ shutil.rmtree(file)
+ else:
+ os.remove(file)
+ print(f"Removed {file}")
+
+ # Check requirements.txt exists
+ if not os.path.exists("requirements.txt"):
+ print("ERROR: requirements.txt not found. Please create it before deploying.")
+ sys.exit(1)
+
+ # Make sure run_local.sh is executable
+ if os.path.exists("run_local.sh"):
+ os.chmod("run_local.sh", 0o755)
+ print("Made run_local.sh executable")
+
+ # Configure HF module if enabled
+ setup_hf_module()
+
+ # Remove any large unnecessary files from input directory
+ # Keep only sample files that are needed for demos
+ print("NOTE: Large files in the input directory will be uploaded to Hugging Face.")
+ print("You may want to remove unnecessary files before deployment.")
+
+ print("Repository preparation complete!")
+
+if __name__ == "__main__":
+ main()
\ No newline at end of file
diff --git a/preprocessing.py b/preprocessing.py
deleted file mode 100644
index 9ad43d18919d0ab9e3fd2e1d36cdf6c63ae55af8..0000000000000000000000000000000000000000
--- a/preprocessing.py
+++ /dev/null
@@ -1,637 +0,0 @@
-import os
-import io
-import cv2
-import numpy as np
-import tempfile
-import time
-import math
-import json
-from PIL import Image, ImageEnhance, ImageFilter
-from pdf2image import convert_from_bytes
-import streamlit as st
-import logging
-import concurrent.futures
-from pathlib import Path
-
-# Configure logging
-logger = logging.getLogger("preprocessing")
-logger.setLevel(logging.INFO)
-
-# Ensure logs directory exists
-def ensure_log_directory(config):
- """Create logs directory if it doesn't exist"""
- if config.get("logging", {}).get("enabled", False):
- log_path = config.get("logging", {}).get("output_path", "logs/preprocessing_metrics.json")
- log_dir = os.path.dirname(log_path)
- if log_dir:
- Path(log_dir).mkdir(parents=True, exist_ok=True)
-
-def log_preprocessing_metrics(metrics, config):
- """Log preprocessing metrics to JSON file"""
- if not config.get("enabled", False):
- return
-
- log_path = config.get("output_path", "logs/preprocessing_metrics.json")
- ensure_log_directory({"logging": {"enabled": True, "output_path": log_path}})
-
- # Add timestamp
- metrics["timestamp"] = time.strftime("%Y-%m-%d %H:%M:%S")
-
- # Append to log file
- try:
- existing_data = []
- if os.path.exists(log_path):
- with open(log_path, 'r') as f:
- existing_data = json.load(f)
- if not isinstance(existing_data, list):
- existing_data = [existing_data]
-
- existing_data.append(metrics)
-
- with open(log_path, 'w') as f:
- json.dump(existing_data, f, indent=2)
-
- logger.info(f"Logged preprocessing metrics to {log_path}")
- except Exception as e:
- logger.error(f"Error logging preprocessing metrics: {str(e)}")
-
-def get_document_config(document_type, global_config):
- """
- Get document-specific preprocessing configuration by merging with global settings.
-
- Args:
- document_type: The type of document (e.g., 'standard', 'newspaper', 'handwritten')
- global_config: The global preprocessing configuration
-
- Returns:
- A merged configuration dictionary with document-specific overrides
- """
- # Start with a copy of the global config
- config = {
- "deskew": global_config.get("deskew", {}),
- "thresholding": global_config.get("thresholding", {}),
- "morphology": global_config.get("morphology", {}),
- "performance": global_config.get("performance", {}),
- "logging": global_config.get("logging", {})
- }
-
- # Apply document-specific overrides if they exist
- doc_types = global_config.get("document_types", {})
- if document_type in doc_types:
- doc_config = doc_types[document_type]
-
- # Merge document-specific settings into the config
- for section in doc_config:
- if section in config:
- config[section].update(doc_config[section])
-
- return config
-
-def deskew_image(img_array, config):
- """
- Detect and correct skew in document images.
-
- Uses a combination of methods (minAreaRect and/or Hough transform)
- to estimate the skew angle more robustly.
-
- Args:
- img_array: Input image as numpy array
- config: Deskew configuration dict
-
- Returns:
- Deskewed image as numpy array, estimated angle, success flag
- """
- if not config.get("enabled", False):
- return img_array, 0.0, True
-
- # Convert to grayscale if needed
- gray = img_array if len(img_array.shape) == 2 else cv2.cvtColor(img_array, cv2.COLOR_RGB2GRAY)
-
- # Start with a threshold to get binary image for angle detection
- _, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
-
- angles = []
- angle_threshold = config.get("angle_threshold", 0.1)
- max_angle = config.get("max_angle", 45.0)
-
- # Method 1: minAreaRect approach
- try:
- # Find all contours
- contours, _ = cv2.findContours(binary, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
-
- # Filter contours by area to avoid noise
- min_area = binary.shape[0] * binary.shape[1] * 0.0001 # 0.01% of image area
- filtered_contours = [cnt for cnt in contours if cv2.contourArea(cnt) > min_area]
-
- # Get angles from rotated rectangles around contours
- for contour in filtered_contours:
- rect = cv2.minAreaRect(contour)
- width, height = rect[1]
-
- # Calculate the angle based on the longer side
- # (This is important for getting the orientation right)
- angle = rect[2]
- if width < height:
- angle += 90
-
- # Normalize angle to -45 to 45 range
- if angle > 45:
- angle -= 90
- if angle < -45:
- angle += 90
-
- # Clamp angle to max limit
- angle = max(min(angle, max_angle), -max_angle)
- angles.append(angle)
- except Exception as e:
- logger.error(f"Error in minAreaRect skew detection: {str(e)}")
-
- # Method 2: Hough Transform approach (if enabled)
- if config.get("use_hough", True):
- try:
- # Apply Canny edge detection
- edges = cv2.Canny(gray, 50, 150, apertureSize=3)
-
- # Apply Hough lines
- lines = cv2.HoughLinesP(edges, 1, np.pi/180,
- threshold=100, minLineLength=100, maxLineGap=10)
-
- if lines is not None:
- for line in lines:
- x1, y1, x2, y2 = line[0]
- if x2 - x1 != 0: # Avoid division by zero
- # Calculate line angle in degrees
- angle = math.atan2(y2 - y1, x2 - x1) * 180.0 / np.pi
-
- # Normalize angle to -45 to 45 range
- if angle > 45:
- angle -= 90
- if angle < -45:
- angle += 90
-
- # Clamp angle to max limit
- angle = max(min(angle, max_angle), -max_angle)
- angles.append(angle)
- except Exception as e:
- logger.error(f"Error in Hough transform skew detection: {str(e)}")
-
- # If no angles were detected, return original image
- if not angles:
- logger.warning("No skew angles detected, using original image")
- return img_array, 0.0, False
-
- # Combine angles using the specified consensus method
- consensus_method = config.get("consensus_method", "average")
- if consensus_method == "average":
- final_angle = sum(angles) / len(angles)
- elif consensus_method == "median":
- final_angle = sorted(angles)[len(angles) // 2]
- elif consensus_method == "min":
- final_angle = min(angles, key=abs)
- elif consensus_method == "max":
- final_angle = max(angles, key=abs)
- else:
- final_angle = sum(angles) / len(angles) # Default to average
-
- # If angle is below threshold, don't rotate
- if abs(final_angle) < angle_threshold:
- logger.info(f"Detected angle ({final_angle:.2f}°) is below threshold, skipping deskew")
- return img_array, final_angle, True
-
- # Log the detected angle
- logger.info(f"Deskewing image with angle: {final_angle:.2f}°")
-
- # Get image dimensions
- h, w = img_array.shape[:2]
- center = (w // 2, h // 2)
-
- # Get rotation matrix
- rotation_matrix = cv2.getRotationMatrix2D(center, final_angle, 1.0)
-
- # Calculate new image dimensions
- abs_cos = abs(rotation_matrix[0, 0])
- abs_sin = abs(rotation_matrix[0, 1])
- new_w = int(h * abs_sin + w * abs_cos)
- new_h = int(h * abs_cos + w * abs_sin)
-
- # Adjust the rotation matrix to account for new dimensions
- rotation_matrix[0, 2] += (new_w / 2) - center[0]
- rotation_matrix[1, 2] += (new_h / 2) - center[1]
-
- # Perform the rotation
- try:
- # Determine the number of channels to create the correct output array
- if len(img_array.shape) == 3:
- rotated = cv2.warpAffine(img_array, rotation_matrix, (new_w, new_h),
- flags=cv2.INTER_LINEAR, borderMode=cv2.BORDER_CONSTANT,
- borderValue=(255, 255, 255))
- else:
- rotated = cv2.warpAffine(img_array, rotation_matrix, (new_w, new_h),
- flags=cv2.INTER_LINEAR, borderMode=cv2.BORDER_CONSTANT,
- borderValue=255)
- return rotated, final_angle, True
- except Exception as e:
- logger.error(f"Error rotating image: {str(e)}")
- if config.get("fallback", {}).get("enabled", True):
- logger.info("Using original image as fallback after rotation failure")
- return img_array, final_angle, False
- return img_array, final_angle, False
-
-def preblur(img_array, config):
- """
- Apply pre-filtering blur to stabilize thresholding results.
-
- Args:
- img_array: Input image as numpy array
- config: Pre-blur configuration dict
-
- Returns:
- Blurred image as numpy array
- """
- if not config.get("enabled", False):
- return img_array
-
- method = config.get("method", "gaussian")
- kernel_size = config.get("kernel_size", 3)
-
- # Ensure kernel size is odd
- if kernel_size % 2 == 0:
- kernel_size += 1
-
- try:
- if method == "gaussian":
- return cv2.GaussianBlur(img_array, (kernel_size, kernel_size), 0)
- elif method == "median":
- return cv2.medianBlur(img_array, kernel_size)
- else:
- logger.warning(f"Unknown blur method: {method}, using gaussian")
- return cv2.GaussianBlur(img_array, (kernel_size, kernel_size), 0)
- except Exception as e:
- logger.error(f"Error applying {method} blur: {str(e)}")
- return img_array
-
-def apply_threshold(img_array, config):
- """
- Apply thresholding to create binary image.
-
- Supports Otsu's method and adaptive thresholding.
- Includes pre-filtering and fallback mechanisms.
-
- Args:
- img_array: Input image as numpy array
- config: Thresholding configuration dict
-
- Returns:
- Binary image as numpy array, success flag
- """
- method = config.get("method", "adaptive")
- if method == "none":
- return img_array, True
-
- # Convert to grayscale if needed
- gray = img_array if len(img_array.shape) == 2 else cv2.cvtColor(img_array, cv2.COLOR_RGB2GRAY)
-
- # Apply pre-blur if configured
- preblur_config = config.get("preblur", {})
- if preblur_config.get("enabled", False):
- gray = preblur(gray, preblur_config)
-
- binary = None
- try:
- if method == "otsu":
- # Apply Otsu's thresholding
- _, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
- elif method == "adaptive":
- # Apply adaptive thresholding
- block_size = config.get("adaptive_block_size", 11)
- constant = config.get("adaptive_constant", 2)
-
- # Ensure block size is odd
- if block_size % 2 == 0:
- block_size += 1
-
- binary = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
- cv2.THRESH_BINARY, block_size, constant)
- else:
- logger.warning(f"Unknown thresholding method: {method}, using adaptive")
- block_size = config.get("adaptive_block_size", 11)
- constant = config.get("adaptive_constant", 2)
-
- # Ensure block size is odd
- if block_size % 2 == 0:
- block_size += 1
-
- binary = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
- cv2.THRESH_BINARY, block_size, constant)
- except Exception as e:
- logger.error(f"Error applying {method} thresholding: {str(e)}")
- if config.get("fallback", {}).get("enabled", True):
- logger.info("Using original grayscale image as fallback after thresholding failure")
- return gray, False
- return gray, False
-
- # Calculate percentage of non-zero pixels for logging
- nonzero_pct = np.count_nonzero(binary) / binary.size * 100
- logger.info(f"Binary image has {nonzero_pct:.2f}% non-zero pixels")
-
- # Check if thresholding was successful (crude check)
- if nonzero_pct < 1 or nonzero_pct > 99:
- logger.warning(f"Thresholding produced extreme result ({nonzero_pct:.2f}% non-zero)")
- if config.get("fallback", {}).get("enabled", True):
- logger.info("Using original grayscale image as fallback after poor thresholding")
- return gray, False
-
- return binary, True
-
-def apply_morphology(binary_img, config):
- """
- Apply morphological operations to clean up binary image.
-
- Supports opening, closing, or both operations.
-
- Args:
- binary_img: Binary image as numpy array
- config: Morphology configuration dict
-
- Returns:
- Processed binary image as numpy array
- """
- if not config.get("enabled", False):
- return binary_img
-
- operation = config.get("operation", "close")
- kernel_size = config.get("kernel_size", 1)
- kernel_shape = config.get("kernel_shape", "rect")
-
- # Create appropriate kernel
- if kernel_shape == "rect":
- kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (kernel_size*2+1, kernel_size*2+1))
- elif kernel_shape == "ellipse":
- kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (kernel_size*2+1, kernel_size*2+1))
- elif kernel_shape == "cross":
- kernel = cv2.getStructuringElement(cv2.MORPH_CROSS, (kernel_size*2+1, kernel_size*2+1))
- else:
- logger.warning(f"Unknown kernel shape: {kernel_shape}, using rect")
- kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (kernel_size*2+1, kernel_size*2+1))
-
- result = binary_img
- try:
- if operation == "open":
- # Opening: Erosion followed by dilation - removes small noise
- result = cv2.morphologyEx(binary_img, cv2.MORPH_OPEN, kernel)
- elif operation == "close":
- # Closing: Dilation followed by erosion - fills small holes
- result = cv2.morphologyEx(binary_img, cv2.MORPH_CLOSE, kernel)
- elif operation == "both":
- # Both operations in sequence
- result = cv2.morphologyEx(binary_img, cv2.MORPH_OPEN, kernel)
- result = cv2.morphologyEx(result, cv2.MORPH_CLOSE, kernel)
- else:
- logger.warning(f"Unknown morphological operation: {operation}, using close")
- result = cv2.morphologyEx(binary_img, cv2.MORPH_CLOSE, kernel)
- except Exception as e:
- logger.error(f"Error applying morphological operation: {str(e)}")
- return binary_img
-
- return result
-
-@st.cache_data(ttl=24*3600, show_spinner=False) # Cache for 24 hours
-def convert_pdf_to_images(pdf_bytes, dpi=150, rotation=0):
- """Convert PDF bytes to a list of images with caching"""
- try:
- images = convert_from_bytes(pdf_bytes, dpi=dpi)
-
- # Apply rotation if specified
- if rotation != 0 and images:
- rotated_images = []
- for img in images:
- rotated_img = img.rotate(rotation, expand=True, resample=Image.BICUBIC)
- rotated_images.append(rotated_img)
- return rotated_images
-
- return images
- except Exception as e:
- st.error(f"Error converting PDF: {str(e)}")
- logger.error(f"PDF conversion error: {str(e)}")
- return []
-
-@st.cache_data(ttl=24*3600, show_spinner=False, hash_funcs={dict: lambda x: str(sorted(x.items()))})
-def preprocess_image(image_bytes, preprocessing_options):
- """
- Conservative preprocessing function for handwritten documents with early exit for clean scans.
- Implements light processing: grayscale → denoise (gently) → contrast (conservative)
-
- Args:
- image_bytes: Image content as bytes
- preprocessing_options: Dictionary with document_type, grayscale, denoise, contrast options
-
- Returns:
- Processed image bytes or original image bytes if no processing needed
- """
- # Setup basic console logging
- logger = logging.getLogger("image_preprocessor")
- logger.setLevel(logging.INFO)
-
- # Log which preprocessing options are being applied
- logger.info(f"Document type: {preprocessing_options.get('document_type', 'standard')}")
-
- # Check if any preprocessing is actually requested
- has_preprocessing = (
- preprocessing_options.get("grayscale", False) or
- preprocessing_options.get("denoise", False) or
- preprocessing_options.get("contrast", 0) != 0
- )
-
- # Convert bytes to PIL Image
- image = Image.open(io.BytesIO(image_bytes))
-
- # Check for minimal skew and exit early if document is already straight
- # This avoids unnecessary processing for clean scans
- try:
- from utils.image_utils import detect_skew
- skew_angle = detect_skew(image)
- if abs(skew_angle) < 0.5:
- logger.info(f"Document has minimal skew ({skew_angle:.2f}°), skipping preprocessing")
- # Return original image bytes as is for perfectly straight documents
- if not has_preprocessing:
- return image_bytes
- except Exception as e:
- logger.warning(f"Error in skew detection: {str(e)}, continuing with preprocessing")
-
- # If no preprocessing options are selected, return the original image
- if not has_preprocessing:
- logger.info("No preprocessing options selected, skipping preprocessing")
- return image_bytes
-
- # Initialize metrics for logging
- metrics = {
- "file": preprocessing_options.get("filename", "unknown"),
- "document_type": preprocessing_options.get("document_type", "standard"),
- "preprocessing_applied": []
- }
- start_time = time.time()
-
- # Handle RGBA images (transparency) by converting to RGB
- if image.mode == 'RGBA':
- # Convert RGBA to RGB by compositing onto white background
- logger.info("Converting RGBA image to RGB")
- background = Image.new('RGB', image.size, (255, 255, 255))
- background.paste(image, mask=image.split()[3]) # 3 is the alpha channel
- image = background
- metrics["preprocessing_applied"].append("alpha_conversion")
- elif image.mode not in ('RGB', 'L'):
- # Convert other modes to RGB
- logger.info(f"Converting {image.mode} image to RGB")
- image = image.convert('RGB')
- metrics["preprocessing_applied"].append("format_conversion")
-
- # Convert to NumPy array for OpenCV processing
- img_array = np.array(image)
-
- # Apply grayscale if requested (useful for handwritten text)
- if preprocessing_options.get("grayscale", False):
- if len(img_array.shape) == 3: # Only convert if it's not already grayscale
- # For handwritten documents, apply gentle CLAHE to enhance contrast locally
- if preprocessing_options.get("document_type") == "handwritten":
- img_array = cv2.cvtColor(img_array, cv2.COLOR_RGB2GRAY)
- clahe = cv2.createCLAHE(clipLimit=1.5, tileGridSize=(8,8)) # Conservative clip limit
- img_array = clahe.apply(img_array)
- else:
- # Standard grayscale for printed documents
- img_array = cv2.cvtColor(img_array, cv2.COLOR_RGB2GRAY)
-
- metrics["preprocessing_applied"].append("grayscale")
-
- # Apply light denoising if requested
- if preprocessing_options.get("denoise", False):
- try:
- # Apply very gentle denoising
- is_color = len(img_array.shape) == 3 and img_array.shape[2] == 3
- if is_color:
- # Very light color denoising with conservative parameters
- img_array = cv2.fastNlMeansDenoisingColored(img_array, None, 2, 2, 3, 7)
- else:
- # Very light grayscale denoising
- img_array = cv2.fastNlMeansDenoising(img_array, None, 2, 3, 7)
-
- metrics["preprocessing_applied"].append("light_denoise")
- except Exception as e:
- logger.error(f"Denoising error: {str(e)}")
-
- # Apply contrast adjustment if requested (conservative range)
- contrast_value = preprocessing_options.get("contrast", 0)
- if contrast_value != 0:
- # Use a gentler contrast adjustment factor
- contrast_factor = 1 + (contrast_value / 200) # Conservative scaling factor
-
- # Convert NumPy array back to PIL Image for contrast adjustment
- if len(img_array.shape) == 2: # If grayscale, convert to RGB for PIL
- image = Image.fromarray(cv2.cvtColor(img_array, cv2.COLOR_GRAY2RGB))
- else:
- image = Image.fromarray(img_array)
-
- enhancer = ImageEnhance.Contrast(image)
- image = enhancer.enhance(contrast_factor)
-
- # Convert back to NumPy array
- img_array = np.array(image)
- metrics["preprocessing_applied"].append(f"contrast_{contrast_value}")
-
- # Convert back to PIL Image
- if len(img_array.shape) == 2: # If grayscale, convert to RGB for saving
- processed_image = Image.fromarray(cv2.cvtColor(img_array, cv2.COLOR_GRAY2RGB))
- else:
- processed_image = Image.fromarray(img_array)
-
- # Record total processing time
- metrics["processing_time"] = (time.time() - start_time) * 1000 # ms
-
- # Higher quality for OCR processing
- byte_io = io.BytesIO()
- try:
- # Make sure the image is in RGB mode before saving as JPEG
- if processed_image.mode not in ('RGB', 'L'):
- processed_image = processed_image.convert('RGB')
-
- processed_image.save(byte_io, format='JPEG', quality=92, optimize=True)
- byte_io.seek(0)
-
- logger.info(f"Preprocessing complete. Original image mode: {image.mode}, processed mode: {processed_image.mode}")
- logger.info(f"Original size: {len(image_bytes)/1024:.1f}KB, processed size: {len(byte_io.getvalue())/1024:.1f}KB")
- logger.info(f"Applied preprocessing steps: {', '.join(metrics['preprocessing_applied'])}")
-
- return byte_io.getvalue()
- except Exception as e:
- logger.error(f"Error saving processed image: {str(e)}")
- # Fallback to original image
- logger.info("Using original image as fallback")
- return image_bytes
-
-def create_temp_file(content, suffix, temp_file_paths):
- """Create a temporary file and track it for cleanup"""
- with tempfile.NamedTemporaryFile(delete=False, suffix=suffix) as tmp:
- tmp.write(content)
- temp_path = tmp.name
- # Track temporary file for cleanup
- temp_file_paths.append(temp_path)
- logger.info(f"Created temporary file: {temp_path}")
- return temp_path
-
-def apply_preprocessing_to_file(file_bytes, file_ext, preprocessing_options, temp_file_paths):
- """
- Apply conservative preprocessing to file and return path to the temporary file.
- Handles format conversion and user-selected preprocessing options.
-
- Args:
- file_bytes: File content as bytes
- file_ext: File extension (e.g., '.jpg', '.pdf')
- preprocessing_options: Dictionary with document_type and preprocessing options
- temp_file_paths: List to track temporary files for cleanup
-
- Returns:
- Tuple of (temp_file_path, was_processed_flag)
- """
- document_type = preprocessing_options.get("document_type", "standard")
-
- # Check for user-selected preprocessing
- has_preprocessing = (
- preprocessing_options.get("grayscale", False) or
- preprocessing_options.get("denoise", False) or
- preprocessing_options.get("contrast", 0) != 0
- )
-
- # Check for RGBA/transparency that needs conversion
- format_needs_conversion = False
-
- # Only check formats that might have transparency
- if file_ext.lower() in ['.png', '.tif', '.tiff']:
- try:
- # Check if image has transparency
- image = Image.open(io.BytesIO(file_bytes))
- if image.mode == 'RGBA' or image.mode not in ('RGB', 'L'):
- format_needs_conversion = True
- except Exception as e:
- logger.warning(f"Error checking image format: {str(e)}")
-
- # Process if user requested preprocessing OR format needs conversion
- needs_processing = has_preprocessing or format_needs_conversion
-
- if needs_processing:
- # Apply preprocessing
- logger.info(f"Applying preprocessing with options: {preprocessing_options}")
- logger.info(f"Using document type '{document_type}' with advanced preprocessing options")
-
- # Add filename to preprocessing options for logging if available
- if hasattr(file_bytes, 'name'):
- preprocessing_options["filename"] = file_bytes.name
-
- processed_bytes = preprocess_image(file_bytes, preprocessing_options)
-
- # Save processed image to temp file
- temp_path = create_temp_file(processed_bytes, file_ext, temp_file_paths)
- return temp_path, True # Return path and flag indicating preprocessing was applied
- else:
- # No preprocessing needed, just save the original file
- logger.info("No preprocessing applied - using original image")
- temp_path = create_temp_file(file_bytes, file_ext, temp_file_paths)
- return temp_path, False # Return path and flag indicating no preprocessing was applied
diff --git a/process_file.py b/process_file.py
index 6195b15ad669892adde85ce058feac0f192016b8..e7dfb5bde566f22aa86a9e8104e88bf5989365ee 100644
--- a/process_file.py
+++ b/process_file.py
@@ -53,7 +53,7 @@ def process_file(uploaded_file, use_vision=True, processor=None, custom_prompt=N
"file_size_mb": round(file_size_mb, 2),
"use_vision": use_vision
})
-
+
return result
except Exception as e:
return {
@@ -63,4 +63,4 @@ def process_file(uploaded_file, use_vision=True, processor=None, custom_prompt=N
finally:
# Clean up the temporary file
if os.path.exists(temp_path):
- os.unlink(temp_path)
+ os.unlink(temp_path)
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
index 22ac57441a5d1d21ec79d17b116a6da46ab78ab2..cd8bfec2f8a8e2662446f6ae690ed53227bf7b47 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,24 +1,10 @@
-# Requirements for Historical OCR application
-
-# Core dependencies
-streamlit>=1.30.0
-mistralai>=0.1.0 # Updated to latest Mistral AI SDK
-pydantic>=2.5.0 # Updated for better BaseModel support
-
-# Image processing
-Pillow>=10.0.0
-opencv-python-headless>=4.8.0.74
-pdf2image>=1.16.0
-pytesseract>=0.3.10 # For local OCR fallback
-matplotlib>=3.7.0 # For visualization in preprocessing tests
-
-# Data handling and utilities
-numpy>=1.24.0
-pycountry>=22.1.10
-requests>=2.31.0
-python-dotenv>=1.0.0
+streamlit>=1.43.2
+mistralai>=0.0.7
+pydantic>=2.0.0
+pycountry>=23.12.11
+pillow>=10.0.0
python-multipart>=0.0.6
-
-# Type checking and linting
-mypy>=1.5.0
-ruff>=0.1.5
+pdf2image>=1.17.0
+pytesseract>=0.3.10
+opencv-python-headless>=4.6.0
+numpy>=1.23.5
\ No newline at end of file
diff --git a/run_large_files.sh b/run_large_files.sh
new file mode 100644
index 0000000000000000000000000000000000000000..743833fe12bf1c017e9457601a79447dc9cb51a8
--- /dev/null
+++ b/run_large_files.sh
@@ -0,0 +1,22 @@
+#!/bin/bash
+# Run the Streamlit app with increased buffer size for large files
+
+# Load environment variables from .env file if it exists
+if [ -f .env ]; then
+ echo "Loading environment variables from .env file"
+ set -o allexport
+ source .env
+ set +o allexport
+else
+ echo "No .env file found. Using API key from config.py"
+fi
+
+# Check if MISTRAL_API_KEY is set
+if [ -z "$MISTRAL_API_KEY" ]; then
+ echo "WARNING: MISTRAL_API_KEY is not set in environment. Using key from config.py if available."
+fi
+
+# Run the Streamlit app with increased buffer size
+streamlit run app.py \
+ --server.maxUploadSize=500 \
+ --server.maxMessageSize=500
\ No newline at end of file
diff --git a/run_local.sh b/run_local.sh
new file mode 100644
index 0000000000000000000000000000000000000000..5e66df0fc27162d901ac9e8a90516e231b19ec97
--- /dev/null
+++ b/run_local.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+
+# Determine which version of the app to run
+if [ "$1" == "educational" ]; then
+ APP_FILE="streamlit_app.py"
+ echo "Starting Educational Version..."
+else
+ APP_FILE="app.py"
+ echo "Starting Standard Version..."
+fi
+
+# Check if .env file exists and load it
+if [ -f .env ]; then
+ echo "Loading environment variables from .env file"
+ export $(grep -v '^#' .env | xargs)
+fi
+
+# Check if the Mistral API key is set
+if [ -z "${MISTRAL_API_KEY}" ]; then
+ echo "Warning: MISTRAL_API_KEY environment variable is not set."
+ echo "The application will run in demo mode with limited functionality."
+fi
+
+# Start the Streamlit app
+streamlit run $APP_FILE --server.maxUploadSize=50 --server.enableCORS=false --server.enableXsrfProtection=false
\ No newline at end of file
diff --git a/setup_git.sh b/setup_git.sh
new file mode 100644
index 0000000000000000000000000000000000000000..331be4e7e41b38ce4ccce2f3efaf6b0a64f4eead
--- /dev/null
+++ b/setup_git.sh
@@ -0,0 +1,35 @@
+#!/bin/bash
+# Setup git repository for Hugging Face Spaces
+
+# Check if HF_TOKEN environment variable is set
+if [ -z "$HF_TOKEN" ]; then
+ echo "Error: HF_TOKEN environment variable is not set."
+ echo "Please set it first with: export HF_TOKEN=your_hugging_face_token"
+ exit 1
+fi
+
+# Get your username
+echo "Enter your Hugging Face username:"
+read HF_USERNAME
+
+# Get the space name
+echo "Enter the name for your Hugging Face Space (e.g., historical-ocr):"
+read HF_SPACE
+
+# Prepare the files for deployment
+echo "Preparing files for deployment..."
+python3 prepare_for_hf.py
+
+# Initialize git
+git init
+git add .
+git commit -m "Initial commit"
+
+# Create the repository on Hugging Face
+echo "Creating and pushing to Hugging Face Space..."
+git remote add origin https://huggingface.co/spaces/$HF_USERNAME/$HF_SPACE
+huggingface-cli login --token $HF_TOKEN
+git push -u origin main
+
+echo "Deployment completed! Your app should be available at:"
+echo "https://huggingface.co/spaces/$HF_USERNAME/$HF_SPACE"
\ No newline at end of file
diff --git a/simple_test.py b/simple_test.py
new file mode 100644
index 0000000000000000000000000000000000000000..2ee95cede8e3bfb76f7ac767e6133990d545dde5
--- /dev/null
+++ b/simple_test.py
@@ -0,0 +1,69 @@
+#!/usr/bin/env python3
+"""
+Simple test script for structured_ocr.py
+"""
+
+import os
+import sys
+import json
+from pathlib import Path
+
+def main():
+ print("Testing OCR with a sample image file")
+
+ # Path to the sample image file
+ image_path = os.path.join("input", "recipe.jpg")
+
+ # Check if the file exists
+ if not os.path.isfile(image_path):
+ print(f"Error: Image file not found at {image_path}")
+ return
+
+ print(f"File found: {image_path}")
+
+ # Create the output directory if it doesn't exist
+ output_dir = "output"
+ os.makedirs(output_dir, exist_ok=True)
+
+ output_path = os.path.join(output_dir, "recipe_test.json")
+
+ # Import the StructuredOCR class
+ from structured_ocr import StructuredOCR
+
+ # Initialize OCR processor
+ processor = StructuredOCR()
+
+ try:
+ # Process the image file
+ print(f"Processing image file: {image_path}")
+ result = processor.process_file(image_path, file_type="image")
+
+ # Save the result to the output file
+ with open(output_path, 'w') as f:
+ json.dump(result, f, indent=2)
+
+ print(f"Image processing completed successfully. Output saved to {output_path}")
+
+ # Check if the output file exists
+ if os.path.isfile(output_path):
+ print(f"Output file exists at {output_path}")
+ # Print the file size
+ file_size = os.path.getsize(output_path)
+ print(f"Output file size: {file_size} bytes")
+
+ # Print a preview of the output file
+ print("\nPreview of output file:")
+ with open(output_path, 'r') as f:
+ data = json.load(f)
+ print(f"File name: {data.get('file_name', '')}")
+ print(f"Topics: {', '.join(data.get('topics', []))}")
+ print(f"Languages: {', '.join(data.get('languages', []))}")
+ print("OCR contents keys:", list(data.get('ocr_contents', {}).keys()))
+ else:
+ print(f"Error: Output file not found at {output_path}")
+
+ except Exception as e:
+ print(f"Error processing image: {e}")
+
+if __name__ == "__main__":
+ main()
\ No newline at end of file
diff --git a/static/favicon.ico b/static/favicon.ico
deleted file mode 100644
index 7aec5b7630b636cd1cf2635bb21ffd73b465f019..0000000000000000000000000000000000000000
Binary files a/static/favicon.ico and /dev/null differ
diff --git a/static/favicon.png b/static/favicon.png
deleted file mode 100644
index 48371841ee942974030a8bf6469febf1a081695b..0000000000000000000000000000000000000000
--- a/static/favicon.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:579585886ddea743aa3e212e698632f315c6130d5d6dd3287a015011dbb8fc3a
-size 779
diff --git a/static/scroll.svg b/static/scroll.svg
deleted file mode 100644
index 9a430b354d0ee5bbe861fcd4dcc637ce48fb3905..0000000000000000000000000000000000000000
--- a/static/scroll.svg
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
\ No newline at end of file
diff --git a/.DS_Store b/streamlit/.DS_Store
similarity index 67%
rename from .DS_Store
rename to streamlit/.DS_Store
index 8b1cf4613515d5bb678b199e9ed7d7bd425758c6..02ff0e872c9af3306af9533047a7c8e73b93eea6 100644
Binary files a/.DS_Store and b/streamlit/.DS_Store differ
diff --git a/streamlit/.gitattributes b/streamlit/.gitattributes
new file mode 100644
index 0000000000000000000000000000000000000000..9712efeb00e191c078f0a36993a34be7b7d5bada
--- /dev/null
+++ b/streamlit/.gitattributes
@@ -0,0 +1,39 @@
+*.7z filter=lfs diff=lfs merge=lfs -text
+*.arrow filter=lfs diff=lfs merge=lfs -text
+*.bin filter=lfs diff=lfs merge=lfs -text
+*.bz2 filter=lfs diff=lfs merge=lfs -text
+*.ckpt filter=lfs diff=lfs merge=lfs -text
+*.ftz filter=lfs diff=lfs merge=lfs -text
+*.gz filter=lfs diff=lfs merge=lfs -text
+*.h5 filter=lfs diff=lfs merge=lfs -text
+*.joblib filter=lfs diff=lfs merge=lfs -text
+*.lfs.* filter=lfs diff=lfs merge=lfs -text
+*.mlmodel filter=lfs diff=lfs merge=lfs -text
+*.model filter=lfs diff=lfs merge=lfs -text
+*.msgpack filter=lfs diff=lfs merge=lfs -text
+*.npy filter=lfs diff=lfs merge=lfs -text
+*.npz filter=lfs diff=lfs merge=lfs -text
+*.onnx filter=lfs diff=lfs merge=lfs -text
+*.ot filter=lfs diff=lfs merge=lfs -text
+*.parquet filter=lfs diff=lfs merge=lfs -text
+*.pb filter=lfs diff=lfs merge=lfs -text
+*.pickle filter=lfs diff=lfs merge=lfs -text
+*.pkl filter=lfs diff=lfs merge=lfs -text
+*.pt filter=lfs diff=lfs merge=lfs -text
+*.pth filter=lfs diff=lfs merge=lfs -text
+*.rar filter=lfs diff=lfs merge=lfs -text
+*.safetensors filter=lfs diff=lfs merge=lfs -text
+saved_model/**/* filter=lfs diff=lfs merge=lfs -text
+*.tar.* filter=lfs diff=lfs merge=lfs -text
+*.tar filter=lfs diff=lfs merge=lfs -text
+*.tflite filter=lfs diff=lfs merge=lfs -text
+*.tgz filter=lfs diff=lfs merge=lfs -text
+*.wasm filter=lfs diff=lfs merge=lfs -text
+*.xz filter=lfs diff=lfs merge=lfs -text
+*.zip filter=lfs diff=lfs merge=lfs -text
+*.zst filter=lfs diff=lfs merge=lfs -text
+*tfevents* filter=lfs diff=lfs merge=lfs -text
+input/baldwin-letter-1.jpg filter=lfs diff=lfs merge=lfs -text
+input/baldwin-letter-2.jpg filter=lfs diff=lfs merge=lfs -text
+input/magellan-travels.jpg filter=lfs diff=lfs merge=lfs -text
+input/okeefe-menu.pdf filter=lfs diff=lfs merge=lfs -text
diff --git a/streamlit/config.py b/streamlit/config.py
new file mode 100644
index 0000000000000000000000000000000000000000..a089ed5e6c6d0c1f08b9b4a6fc3118f31e4b48e4
--- /dev/null
+++ b/streamlit/config.py
@@ -0,0 +1,28 @@
+# config.py
+"""
+Configuration file for OCR processing.
+Contains model settings.
+"""
+import os
+
+# API key is set by the backend - not user facing
+MISTRAL_API_KEY = os.environ.get("MISTRAL_API_KEY", "")
+
+# Model settings
+OCR_MODEL = "mistral-ocr-latest"
+TEXT_MODEL = "mistral-small-latest"
+VISION_MODEL = "pixtral-12b-latest"
+
+# System message for historical document processing
+SYSTEM_MESSAGE = """You are an AI Assistant with document understanding for historical materials. You will be provided with documents, and you must answer any questions related to those materials.
+
+# HISTORICAL DOCUMENT UNDERSTANDING
+When analyzing historical documents, consider these important factors:
+1. Physical Condition: Documents may be degraded, damaged, or faded over time.
+2. Historical Context: Consider the time period, cultural context, and historical significance.
+3. Annotation: Documents may contain handwritten notes, stamps, or additions from various time periods.
+4. Layout Complexity: Historical documents often have complex layouts, multiple columns, and varied typography.
+5. Mixed Media: Documents may combine text, imagery, diagrams, and decorative elements.
+6. Language Evolution: Historical language, terminology, and abbreviations may differ from modern usage.
+
+Provide thoughtful analysis that acknowledges these complexities and maintains scholarly integrity when interpreting historical materials."""
\ No newline at end of file
diff --git a/streamlit/custom.css b/streamlit/custom.css
new file mode 100644
index 0000000000000000000000000000000000000000..e701ad662756ea4df4382bc4e124401e4581bae8
--- /dev/null
+++ b/streamlit/custom.css
@@ -0,0 +1,303 @@
+/* Base Tailwind-like styles */
+:root {
+ --color-gray-900: #111827;
+ --color-gray-800: #1f2937;
+ --color-gray-700: #374151;
+ --color-gray-600: #4B5563;
+ --color-gray-500: #6B7280;
+ --color-gray-400: #9CA3AF;
+ --color-gray-300: #D1D5DB;
+ --color-gray-200: #E5E7EB;
+ --color-gray-100: #F3F4F6;
+ --color-gray-50: #F9FAFB;
+
+ --color-blue-900: #1E3A8A;
+ --color-blue-800: #1E40AF;
+ --color-blue-700: #1D4ED8;
+ --color-blue-600: #2563EB;
+ --color-blue-500: #3B82F6;
+ --color-blue-400: #60A5FA;
+ --color-blue-300: #93C5FD;
+ --color-blue-200: #BFDBFE;
+ --color-blue-100: #DBEAFE;
+ --color-blue-50: #EFF6FF;
+
+ --color-yellow-50: #FFFBEB;
+ --color-yellow-100: #FEF3C7;
+}
+
+/* Global Styles */
+.stApp {
+ background-color: var(--color-gray-900);
+ color: white;
+}
+
+/* Main header */
+.main-header {
+ background-color: black;
+ padding: 1rem;
+ border-bottom: 1px solid var(--color-gray-700);
+}
+
+.title-text {
+ font-size: 1.5rem;
+ font-weight: bold;
+ color: white;
+}
+
+/* Content containers */
+.content-container {
+ background-color: var(--color-gray-800);
+ border-radius: 0.75rem;
+ padding: 1.5rem;
+ margin-bottom: 1.5rem;
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2);
+ color: white;
+}
+
+.blue-container {
+ background-color: var(--color-blue-100);
+ color: var(--color-gray-900);
+ border-radius: 0.75rem;
+ padding: 1.5rem;
+ margin-bottom: 1.5rem;
+}
+
+.yellow-container {
+ background-color: var(--color-yellow-50);
+ color: var(--color-gray-900);
+ border-radius: 0.75rem;
+ padding: 1.5rem;
+ margin-bottom: 1.5rem;
+}
+
+/* Card grid styles */
+.card-grid {
+ display: grid;
+ grid-template-columns: repeat(1, 1fr);
+ gap: 1.5rem;
+ margin-bottom: 1.5rem;
+}
+
+@media (min-width: 768px) {
+ .card-grid {
+ grid-template-columns: repeat(3, 1fr);
+ }
+}
+
+.card {
+ background-color: var(--color-gray-700);
+ border-radius: 0.5rem;
+ padding: 1rem;
+ color: white;
+}
+
+/* Special containers */
+.key-concept {
+ background-color: var(--color-gray-700);
+ border-radius: 0.5rem;
+ padding: 0.75rem;
+ margin: 1rem 0;
+ border-left: 3px solid var(--color-blue-500);
+ color: white;
+}
+
+.research-question {
+ background-color: var(--color-blue-900);
+ border-radius: 0.5rem;
+ padding: 0.75rem;
+ margin: 1rem 0;
+ border-left: 3px solid var(--color-blue-400);
+ color: white;
+}
+
+.quote-container {
+ font-style: italic;
+ color: var(--color-gray-300);
+ padding: 0.5rem 1rem;
+ border-left: 3px solid var(--color-gray-600);
+ margin: 1rem 0;
+}
+
+/* Navigation */
+.nav-container {
+ position: fixed;
+ bottom: 0;
+ left: 0;
+ width: 100%;
+ background-color: black;
+ border-top: 1px solid var(--color-gray-700);
+ padding: 0.75rem 1rem;
+ display: flex;
+ justify-content: space-between;
+ z-index: 1000;
+}
+
+.nav-buttons {
+ display: flex;
+ gap: 0.5rem;
+}
+
+.prev-button {
+ background-color: var(--color-gray-700);
+ color: white;
+ padding: 0.5rem 1rem;
+ border-radius: 0.25rem;
+ border: none;
+ cursor: pointer;
+}
+
+.prev-button:hover {
+ background-color: var(--color-gray-600);
+}
+
+.next-button {
+ background-color: var(--color-blue-600);
+ color: white;
+ padding: 0.5rem 1rem;
+ border-radius: 0.25rem;
+ border: none;
+ cursor: pointer;
+}
+
+.next-button:hover {
+ background-color: var(--color-blue-700);
+}
+
+.nav-dots {
+ display: none;
+}
+
+@media (min-width: 768px) {
+ .nav-dots {
+ display: flex;
+ gap: 0.25rem;
+ }
+}
+
+.nav-dot {
+ width: 2rem;
+ height: 2rem;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: var(--color-gray-300);
+ border-radius: 0.25rem;
+ text-decoration: none;
+ font-size: 0.875rem;
+}
+
+.nav-dot:hover {
+ background-color: var(--color-gray-800);
+}
+
+.nav-dot.active {
+ background-color: var(--color-blue-800);
+ color: white;
+ font-weight: 500;
+}
+
+/* Override Streamlit Styles */
+.stTextInput > div > div > input {
+ background-color: var(--color-gray-700);
+ color: white;
+}
+
+.stSelectbox > div > div > div {
+ background-color: var(--color-gray-700);
+ color: white;
+}
+
+.stCheckbox > div > label {
+ color: white;
+}
+
+/* Button styling */
+.stButton > button {
+ background-color: var(--color-blue-600);
+ color: white;
+}
+
+.stButton > button:hover {
+ background-color: var(--color-blue-700);
+}
+
+/* Sidebars */
+[data-testid="stSidebar"] {
+ background-color: var(--color-gray-900);
+}
+
+[data-testid="stSidebar"] .stMarkdown {
+ color: white;
+}
+
+/* Module card styles */
+.module-card {
+ background-color: var(--color-gray-800);
+ border-radius: 0.5rem;
+ padding: 1rem;
+ margin-bottom: 1rem;
+ border-top: 4px solid var(--color-blue-500);
+ transition: transform 0.2s;
+}
+
+.module-card:hover {
+ transform: translateY(-3px);
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
+}
+
+.module-number {
+ background-color: var(--color-blue-500);
+ color: white;
+ font-weight: bold;
+ padding: 0.25rem 0.5rem;
+ border-radius: 1rem;
+ font-size: 0.9rem;
+ display: inline-block;
+ margin-bottom: 0.5rem;
+}
+
+.module-title {
+ font-weight: 600;
+ margin-bottom: 0.5rem;
+ font-size: 1.1rem;
+}
+
+/* Add space at bottom for fixed nav */
+.main-content {
+ padding-bottom: 4rem;
+}
+
+/* Tool container styles */
+.tool-container {
+ background-color: var(--color-gray-800);
+ color: white;
+ padding: 1.5rem;
+ border-radius: 0.5rem;
+ border: 1px solid var(--color-gray-700);
+ margin-bottom: 1.5rem;
+}
+
+/* Upload container */
+.upload-container {
+ border: 2px dashed var(--color-gray-600);
+ padding: 1.5rem;
+ text-align: center;
+ border-radius: 0.5rem;
+ margin-bottom: 1rem;
+ background-color: var(--color-gray-700);
+}
+
+/* Footer spacing */
+.footer-spacer {
+ height: 4rem;
+}
+
+/* Tabs */
+.stTabs [data-baseweb="tab"] {
+ color: white;
+}
+
+.stTabs [data-baseweb="tab-highlight"] {
+ background-color: var(--color-blue-600);
+}
\ No newline at end of file
diff --git a/streamlit/layout.py b/streamlit/layout.py
new file mode 100644
index 0000000000000000000000000000000000000000..ebcdb8c19ffde05be13e8feec5b1184c13fc4f39
--- /dev/null
+++ b/streamlit/layout.py
@@ -0,0 +1,172 @@
+import streamlit as st
+from pathlib import Path
+import os
+
+# Load custom CSS
+def load_css():
+ css_file = Path(__file__).parent / "custom.css"
+ if css_file.exists():
+ with open(css_file) as f:
+ st.markdown(f"", unsafe_allow_html=True)
+ else:
+ st.warning("Custom CSS file not found. Some styles may be missing.")
+
+# Header component
+def header():
+ st.markdown("""
+
+
Historical OCR Workshop
+
+ """, unsafe_allow_html=True)
+
+# Create a page wrapper similar to the React component
+def page_wrapper(content_function, current_module=1):
+ """
+ Creates a consistent page layout with navigation
+ Args:
+ content_function: Function that renders the page content
+ current_module: Current module number (1-6)
+ """
+ # Load custom CSS
+ load_css()
+
+ # Display header
+ header()
+
+ # Ensure session state for navigation
+ if 'current_module' not in st.session_state:
+ st.session_state.current_module = current_module
+
+ # Main content area with bottom padding for the nav
+ st.markdown('
', unsafe_allow_html=True)
+
+ # Call the content function to render the module content
+ content_function()
+
+ # Add spacer for fixed nav
+ st.markdown('', unsafe_allow_html=True)
+
+ # Navigation
+ render_navigation(current_module)
+
+ st.markdown('
+ """, unsafe_allow_html=True)
+
+# Previous button HTML
+def prev_button_html(current_module, modules):
+ if current_module > 1:
+ prev_module = current_module - 1
+ return f"""
+
+ """
+ return ""
+
+# Next button HTML
+def next_button_html(current_module, modules):
+ if current_module < len(modules):
+ next_module = current_module + 1
+ return f"""
+
+ """
+ return ""
+
+# Navigation dots HTML
+def nav_dots_html(current_module, modules):
+ dots_html = ""
+ for i, name in enumerate(modules, 1):
+ active_class = "active" if i == current_module else ""
+ dots_html += f"""
+
+ {i}
+
+ """
+ return dots_html
+
+# Helper functions for container styles
+def gray_container(content, padding="1.5rem"):
+ """Renders content in a gray container with consistent styling"""
+ st.markdown(f'
{content}
', unsafe_allow_html=True)
+
+def blue_container(content, padding="1.5rem"):
+ """Renders content in a blue container with consistent styling"""
+ st.markdown(f'
{content}
', unsafe_allow_html=True)
+
+def yellow_container(content, padding="1.5rem"):
+ """Renders content in a yellow container with consistent styling"""
+ st.markdown(f'
{content}
', unsafe_allow_html=True)
+
+def card_grid(cards):
+ """
+ Renders a responsive grid of cards
+ Args:
+ cards: List of HTML strings for each card
+ """
+ grid_html = '
', unsafe_allow_html=True)
+
+def research_question(content):
+ """Renders a research question box"""
+ st.markdown(f'
{content}
', unsafe_allow_html=True)
+
+def quote(content, author=""):
+ """Renders a quote with optional author"""
+ quote_html = f'
{content}'
+ if author:
+ quote_html += f'
— {author}'
+ quote_html += '
'
+ st.markdown(quote_html, unsafe_allow_html=True)
+
+def tool_container(content):
+ """Renders content in a tool container"""
+ st.markdown(f'
{content}
', unsafe_allow_html=True)
+
+def upload_container(content):
+ """Renders content in an upload container"""
+ st.markdown(f'
{content}
', unsafe_allow_html=True)
\ No newline at end of file
diff --git a/ui/.DS_Store b/streamlit/modules/.DS_Store
similarity index 100%
rename from ui/.DS_Store
rename to streamlit/modules/.DS_Store
diff --git a/streamlit/modules/__init__.py b/streamlit/modules/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..9b5767eca0096d8d3be1514fcb79c97e0f4caf7b
--- /dev/null
+++ b/streamlit/modules/__init__.py
@@ -0,0 +1,36 @@
+"""
+Module initialization file for the workshop modules.
+"""
+from . import module1, module2, module3, module4, module5, module6
+
+# Module registry for easy access by module number
+modules = {
+ 1: module1,
+ 2: module2,
+ 3: module3,
+ 4: module4,
+ 5: module5,
+ 6: module6
+}
+
+# Module names for navigation and display
+module_names = [
+ "Introduction",
+ "Text-Image Relations",
+ "OCR Technology",
+ "Methodological Approaches",
+ "Interactive OCR",
+ "Conclusion"
+]
+
+def get_module(module_number):
+ """Get a module by its number (1-6)"""
+ if module_number in modules:
+ return modules[module_number]
+ raise ValueError(f"Unknown module number: {module_number}")
+
+def get_module_name(module_number):
+ """Get a module name by its number (1-6)"""
+ if 1 <= module_number <= len(module_names):
+ return module_names[module_number - 1]
+ return f"Module {module_number}"
\ No newline at end of file
diff --git a/streamlit/modules/module1.py b/streamlit/modules/module1.py
new file mode 100644
index 0000000000000000000000000000000000000000..822f36dd501269596ad5f37b510027a362eb375a
--- /dev/null
+++ b/streamlit/modules/module1.py
@@ -0,0 +1,85 @@
+import streamlit as st
+from layout import gray_container, blue_container, yellow_container, card_grid, key_concept
+
+def render():
+ """Module 1: Introduction and Problematization"""
+
+ st.title("Module 1: Introduction and Problematization")
+
+ # Workshop overview in gray container
+ overview_content = """
+
Workshop Overview
+
+ This interactive workshop explores the application of OCR technology to historical documents,
+ combining theoretical understanding with practical experiences. Designed for historians,
+ archivists, and digital humanities scholars, it offers both conceptual frameworks and hands-on skills.
+
+ """
+ gray_container(overview_content)
+
+ # For historians section with blue background
+ historians_content = """
+
For Historians:
+
+ How might OCR technology transform our access to and interpretation of historical
+ documents? What new research questions become possible when large archives
+ become machine-readable?
+
+ """
+ blue_container(historians_content)
+
+ # What is OCR section with yellow background
+ ocr_content = """
+
What is OCR?
+
+ Optical Character Recognition (OCR) technology enables computers to extract text from images and documents.
+ Modern OCR uses AI vision models to understand both the text and its visual context.
+
How might the capabilities of vision-language models change our approach to digitizing historical archives?
+ """
+ research_question(research_content)
+
+ # Display history if available
+ if 'processing_history' in st.session_state and st.session_state.processing_history:
+ with st.expander("Your OCR Processing History"):
+ st.markdown("You've already processed the following documents:")
+
+ for item in st.session_state.processing_history:
+ st.markdown(f"**{item['fileName']}**")
+ col1, col2 = st.columns(2)
+ with col1:
+ st.write(f"**Topics:** {', '.join(item['result'].get('topics', ['Unknown']))}")
+ with col2:
+ st.write(f"**Vision model used:** {'Yes' if item['useVision'] else 'No'}")
\ No newline at end of file
diff --git a/streamlit/modules/module4.py b/streamlit/modules/module4.py
new file mode 100644
index 0000000000000000000000000000000000000000..81dac95f5f7b62960b00dbe68f0ed1b7be357812
--- /dev/null
+++ b/streamlit/modules/module4.py
@@ -0,0 +1,124 @@
+import streamlit as st
+from pathlib import Path
+from layout import gray_container, tool_container, key_concept, quote
+
+def render():
+ """Module 4: Methodological Approaches"""
+
+ st.title("Module 4: Methodological Approaches")
+
+ col1, col2 = st.columns([1, 1])
+
+ with col1:
+ hybrid_content = """
+
Hybrid Methodologies
+
+
1. Computational + Human Reading
+
+
OCR for initial processing and discovery
+
Human review for context and interpretation
+
Iterative refinement of computational outputs
+
+
+
2. Close + Distant Reading
+
+
Distant reading through large-scale OCR processing
+
Close reading of selected passages
+
Zooming between scales of analysis
+
+ """
+ gray_container(hybrid_content)
+
+ # Check if the diagram image is available and display it
+ input_dir = Path(__file__).parent.parent / "input"
+ diagram_path = input_dir / "diagram.jpg"
+
+ if diagram_path.exists():
+ try:
+ from PIL import Image
+ with Image.open(diagram_path) as img:
+ st.image(img, caption="Historical VLM architecture", use_column_width=True)
+ except Exception:
+ # If there's an error, just show a placeholder
+ st.image("https://placekitten.com/800/400", caption="Historical VLM architecture placeholder")
+ else:
+ # If the file doesn't exist, show a placeholder
+ st.image("https://placekitten.com/800/400", caption="Historical VLM architecture placeholder")
+
+ with col2:
+ mistral_content = """
+
Mistral-OCR-Latest: State-of-the-Art
+
+
The Mistral-OCR model represents a significant advancement:
+
+
Multimodal Understanding: Processes both visual and textual information
Historical Font Adaptation: Trained on diverse historical typography
+
+ """
+ gray_container(mistral_content)
+
+ # Check if the workflow image is available and display it
+ workflow_path = input_dir / "workflow.jpg"
+
+ if workflow_path.exists():
+ try:
+ from PIL import Image
+ with Image.open(workflow_path) as img:
+ st.image(img, caption="Mistral OCR workflow", use_column_width=True)
+ except Exception:
+ # If there's an error, just show a placeholder
+ st.image("https://placekitten.com/800/400", caption="Mistral OCR workflow placeholder")
+ else:
+ # If the file doesn't exist, show a placeholder
+ st.image("https://placekitten.com/800/400", caption="Mistral OCR workflow placeholder")
+
+ # Practical workflow section
+ workflow_content = """
+
Practical Workflow
+
+
A typical historical OCR workflow with Mistral-OCR includes:
+
+
Selection: Choosing appropriate documents
+
Preprocessing: Enhancing images before OCR
+
OCR Processing: Running documents through vision-enhanced OCR
+
Post-processing: Cleaning up outputs and structured extraction
+
Verification: Cross-checking results against originals
+
Integration: Incorporating OCR outputs into research materials
+ """
+ gray_container(limitations_content)
+
+ # Quote
+ quote_content = "The most powerful digital humanities work occurs at the intersection of computational methods and traditional humanistic inquiry."
+ quote(quote_content, "Dr. Sarah E. Bond, Digital Humanities Scholar")
\ No newline at end of file
diff --git a/streamlit/modules/module5.py b/streamlit/modules/module5.py
new file mode 100644
index 0000000000000000000000000000000000000000..6bc50f5e03de8e234a6c3369da6b68ce4a0faee0
--- /dev/null
+++ b/streamlit/modules/module5.py
@@ -0,0 +1,547 @@
+import streamlit as st
+import io
+import tempfile
+from pathlib import Path
+from datetime import datetime
+from layout import tool_container, key_concept, research_question, upload_container
+import sys
+
+# Import the necessary modules for OCR processing
+sys.path.append(str(Path(__file__).parent.parent))
+try:
+ from process_file import process_file as process_file_util
+ process_file = process_file_util
+except ImportError:
+ # Fallback if process_file is not available
+ def process_file(uploaded_file, use_vision=True, custom_prompt=None):
+ """Fallback function for processing files"""
+ st.warning("Using mock processing function. Real OCR functionality is not available.")
+ return {
+ "file_name": uploaded_file.name,
+ "languages": ["English"],
+ "topics": ["History", "Document"],
+ "ocr_contents": {
+ "content": f"This is mock OCR content for {uploaded_file.name}. Vision model: {use_vision}"
+ }
+ }
+
+def render():
+ """Module 5: Interactive OCR Experiment"""
+
+ st.title("Module 5: Interactive OCR Experiment")
+
+ # Introduction to the interactive experiment
+ intro_content = """
+
Interactive OCR Experiment
+
+ This interactive experiment allows you to process historical documents with OCR and analyze the results.
+ Try different settings and compare the outcomes to understand the strengths and limitations of OCR technology.
+
+ """
+ st.markdown(intro_content, unsafe_allow_html=True)
+
+ # Create tabs for different activities
+ experiment_tab, compare_tab, analyze_tab = st.tabs(["Process Documents", "Compare Results", "Analysis Guide"])
+
+ # Try to import PDF tools if available
+ try:
+ from pdf2image import convert_from_bytes
+ pdf_support = True
+ except ImportError:
+ pdf_support = False
+ st.warning("PDF preview functionality is limited. The pdf2image module is required for PDF previews.")
+
+ with experiment_tab:
+ # Create a two-column layout
+ col1, col2 = st.columns([1, 1])
+
+ with col1:
+ # Tool container for document selection and options
+ st.subheader("Step 1: Select Document & Options")
+
+ # Processing options
+ use_vision = st.checkbox("Use Vision Model", value=True,
+ help="Use the vision model for improved analysis")
+
+ # Additional prompt
+ st.markdown("### Custom Research Prompt (Optional)")
+ st.markdown("""Provide additional instructions to guide the OCR analysis.
+ Focus on specific aspects of historical research you're interested in.""")
+ custom_prompt = st.text_area("Research Prompt",
+ placeholder="E.g., Focus on identifying dates and historical figures...",
+ help="Optional instructions to guide the analysis")
+
+ # Sample document selection
+ input_dir = Path(__file__).parent.parent / "input"
+
+ if input_dir.exists():
+ sample_files = list(input_dir.glob("*.jpg")) + list(input_dir.glob("*.png")) + list(input_dir.glob("*.pdf"))
+
+ if sample_files:
+ st.markdown("#### Sample Documents")
+ sample_options = ["Upload my own document"] + [f.name for f in sample_files]
+ sample_choice = st.selectbox("Choose a document:", sample_options)
+
+ if sample_choice != "Upload my own document":
+ # Process the selected sample file
+ selected_file = next((f for f in sample_files if f.name == sample_choice), None)
+
+ if selected_file:
+ # Store the selected sample file in session state
+ with open(selected_file, "rb") as f:
+ file_bytes = f.read()
+
+ st.session_state.sample_file = {
+ "name": selected_file.name,
+ "bytes": file_bytes
+ }
+
+ # Preview the selected sample
+ if selected_file.suffix.lower() == ".pdf" and pdf_support:
+ try:
+ with st.spinner("Generating PDF preview..."):
+ images = convert_from_bytes(file_bytes, first_page=1, last_page=1, dpi=150)
+ if images:
+ st.image(images[0], caption=f"Preview: {selected_file.name}", use_column_width=True)
+ except Exception:
+ st.info(f"PDF selected: {selected_file.name}")
+ else:
+ # For images display directly
+ try:
+ from PIL import Image
+ img = Image.open(io.BytesIO(file_bytes))
+ st.image(img, caption=f"Preview: {selected_file.name}", use_column_width=True)
+ except Exception:
+ st.info(f"Selected: {selected_file.name}")
+ else:
+ # Clear the sample file if "Upload my own" is selected
+ if 'sample_file' in st.session_state:
+ del st.session_state.sample_file
+
+ # Display file uploader
+ upload_html = """
+
Upload a document to get started
+
Supported formats: PDF, JPG, PNG
+ """
+
+ upload_container(upload_html)
+ uploaded_file = st.file_uploader("Choose a file", type=["pdf", "png", "jpg", "jpeg"], label_visibility="collapsed")
+
+ if uploaded_file is not None:
+ # Display preview of the uploaded file
+ file_ext = Path(uploaded_file.name).suffix.lower()
+
+ if file_ext == ".pdf" and pdf_support:
+ try:
+ # Convert first page of PDF to image for preview
+ pdf_bytes = uploaded_file.getvalue()
+ with st.spinner("Generating PDF preview..."):
+ images = convert_from_bytes(pdf_bytes, first_page=1, last_page=1, dpi=150)
+ if images:
+ st.image(images[0], caption=f"PDF Preview: {uploaded_file.name}", use_column_width=True)
+ else:
+ st.info(f"PDF uploaded: {uploaded_file.name}")
+ except Exception:
+ st.info(f"PDF uploaded: {uploaded_file.name}")
+ elif file_ext != ".pdf":
+ st.image(uploaded_file, use_column_width=True)
+ else:
+ st.info(f"PDF uploaded: {uploaded_file.name}")
+ else:
+ # No sample files, just show the uploader
+ upload_html = """
+
Upload a document to get started
+
Supported formats: PDF, JPG, PNG
+ """
+
+ upload_container(upload_html)
+ uploaded_file = st.file_uploader("Choose a file", type=["pdf", "png", "jpg", "jpeg"], label_visibility="collapsed")
+
+ if uploaded_file is not None:
+ # Display the file preview
+ file_ext = Path(uploaded_file.name).suffix.lower()
+ if file_ext == ".pdf" and pdf_support:
+ try:
+ pdf_bytes = uploaded_file.getvalue()
+ with st.spinner("Generating PDF preview..."):
+ images = convert_from_bytes(pdf_bytes, first_page=1, last_page=1, dpi=150)
+ if images:
+ st.image(images[0], caption=f"PDF Preview: {uploaded_file.name}", use_column_width=True)
+ except Exception:
+ st.info(f"PDF uploaded: {uploaded_file.name}")
+ elif file_ext != ".pdf":
+ st.image(uploaded_file, use_column_width=True)
+ else:
+ st.info(f"PDF uploaded: {uploaded_file.name}")
+ else:
+ # No input directory
+ upload_html = """
+
Upload a document to get started
+
Supported formats: PDF, JPG, PNG
+ """
+
+ upload_container(upload_html)
+ uploaded_file = st.file_uploader("Choose a file", type=["pdf", "png", "jpg", "jpeg"], label_visibility="collapsed")
+
+ # Process button
+ st.subheader("Step 2: Process the Document")
+
+ # Get the file to process (either uploaded or sample)
+ file_to_process = None
+ if 'sample_file' in st.session_state and sample_choice != "Upload my own document":
+ # Create a FileUploader-like object from the sample file
+ class SampleFileObject:
+ def __init__(self, name, data):
+ self.name = name
+ self._data = data
+
+ def getvalue(self):
+ return self._data
+
+ file_to_process = SampleFileObject(
+ st.session_state.sample_file["name"],
+ st.session_state.sample_file["bytes"]
+ )
+ elif 'uploaded_file' in locals() and uploaded_file is not None:
+ file_to_process = uploaded_file
+
+ # Process button
+ process_button = st.button(
+ "Process Document",
+ disabled=file_to_process is None,
+ use_container_width=True
+ )
+
+ if process_button and file_to_process is not None:
+ with st.spinner("Processing document..."):
+ try:
+ # Process the file
+ result = process_file(file_to_process, use_vision, custom_prompt=custom_prompt if custom_prompt else None)
+
+ if result:
+ st.success("Document processed successfully!")
+
+ # Store result in session state for display in the right column
+ st.session_state.current_result = result
+
+ # Add to processing history
+ history_item = {
+ "id": datetime.now().timestamp(),
+ "fileName": file_to_process.name,
+ "timestamp": datetime.now().isoformat(),
+ "result": result,
+ "useVision": use_vision
+ }
+
+ if 'processing_history' not in st.session_state:
+ st.session_state.processing_history = []
+
+ st.session_state.processing_history.append(history_item)
+
+ st.experimental_rerun()
+ else:
+ st.error("Failed to process document.")
+ except Exception as e:
+ st.error(f"Error processing document: {str(e)}")
+
+ # Experiment instructions
+ experiment_content = """
+
Experiment Instructions
+
+
Step 1: Select a document and choose your options
+
Step 2: Process the document with the selected options
+
Step 3: Analyze the results in the panel on the right
+
Step 4: Try again with different settings (e.g., toggle vision model)
+
Step 5: Compare results between different runs
+
+ """
+ key_concept(experiment_content)
+
+ with col2:
+ # Results display
+ st.subheader("Step 3: View Results")
+
+ if 'current_result' in st.session_state and st.session_state.current_result:
+ result = st.session_state.current_result
+
+ # Display results in a tool container
+ result_html = f"""
+
Vision model used: {'Yes' if latest['useVision'] else 'No'}
+ """
+ tool_container(latest_html)
+
+ # History in expander
+ with st.expander("View Complete Processing History"):
+ for i, item in enumerate(reversed(st.session_state.processing_history)):
+ st.markdown(f"""
+
You're comparing the same document processed with different models.
+ This is an excellent way to evaluate the impact of vision capabilities on OCR accuracy.
Process at least two documents to enable side-by-side comparison. Try processing
+ the same document with and without the vision model to see the differences in OCR quality.
+ """
+ research_question(need_more_content)
+
+ # Analysis guide tab
+ with analyze_tab:
+ st.subheader("Analysis Guide")
+
+ st.markdown("""
+ ### How to Analyze OCR Results
+
+ When analyzing OCR results from historical documents, consider these key factors:
+
+ 1. **Text Accuracy**
+ - Check for common OCR errors (e.g., mistaking "e" for "c", "l" for "1")
+ - Assess recognition of period-specific typography and writing styles
+ - Evaluate handling of degraded or damaged text areas
+
+ 2. **Structure Preservation**
+ - Does the OCR maintain paragraph and section breaks?
+ - Are columns and tabular data correctly preserved?
+ - How well are page transitions handled?
+
+ 3. **Special Elements**
+ - Recognition of footnotes, marginalia, and annotations
+ - Handling of illustrations, diagrams, and decorative elements
+ - Treatment of watermarks, signatures, and stamps
+
+ 4. **Metadata Extraction**
+ - Accuracy of detected languages, topics, and document type
+ - Identification of dates, names, and key entities
+ - Recognition of document purpose and context
+ """)
+
+ col1, col2 = st.columns(2)
+
+ with col1:
+ challenge_content = """
+
Common OCR Challenges
+
+
Typography Variations: Historical fonts that differ from modern text
+
Material Degradation: Fading, stains, tears affecting legibility
+
Handwritten Elements: Marginalia, signatures, and annotations
+
Complex Layouts: Multi-column formats and decorative elements
+
Language and Terminology: Archaic terms and multilingual content
Contextual Reading: Use context to interpret unclear passages
+
Error Patterns: Identify and correct systematic OCR errors
+
Hybrid Analysis: Combine OCR search with close reading
+
Comparative Processing: Try different settings on documents
+
Iterative Refinement: Use insights to improve future processing
+
+ """
+ gray_container(tips_content)
+
+ # Show example analysis if there's processing history
+ if 'processing_history' in st.session_state and st.session_state.processing_history:
+ with st.expander("Example Analysis from Your Documents"):
+ # Pick the latest document
+ latest = st.session_state.processing_history[-1]
+
+ st.markdown(f"""
+ #### Sample Analysis for: {latest['fileName']}
+
+ **Document Context:**
+ - Languages: {', '.join(latest['result'].get('languages', ['Unknown']))}
+ - Topics: {', '.join(latest['result'].get('topics', ['Unknown']))}
+ - Vision model used: {'Yes' if latest['useVision'] else 'No'}
+
+ **What to Look For:**
+ 1. Check how well the model identified key topics and languages
+ 2. Evaluate the completeness of extracted text
+ 3. Note any systematic errors in text recognition
+ 4. Assess how well document structure was preserved
+ """)
\ No newline at end of file
diff --git a/streamlit/modules/module6.py b/streamlit/modules/module6.py
new file mode 100644
index 0000000000000000000000000000000000000000..5efa8ee87b1f737172445d3f7caa20dd79c588b4
--- /dev/null
+++ b/streamlit/modules/module6.py
@@ -0,0 +1,154 @@
+import streamlit as st
+from layout import gray_container, key_concept, quote, tool_container
+from datetime import datetime
+
+def render():
+ """Module 6: Conclusion and Future Directions"""
+
+ st.title("Module 6: Conclusion and Future Directions")
+
+ col1, col2 = st.columns([3, 2])
+
+ with col1:
+ summary_content = """
+
Workshop Summary
+
Throughout this workshop, we've explored:
+
+
Text-Image Interdependence: The complex relationship between textual and visual elements
+
OCR Technology: The evolution of OCR and its application to historical materials
+
Methodological Approaches: Hybrid strategies for working with historical texts
+
Practical Application: Hands-on experience with OCR processing tools
+ """
+ gray_container(research_content)
+
+ # Inspiring quote
+ quote_content = "The digital humanities are not about building, they're about sharing. The digital humanities are not about the digital at all. They're all about innovation and disruption. The digital humanities are really an insurgent humanities."
+ quote(quote_content, "Matthew Kirschenbaum, Professor of Digital Humanities")
+
+ # Additional resources
+ resources_content = """
+
This workshop was designed as an educational resource for historians, archivists, and digital humanities scholars.
+
It demonstrates the integration of modern AI vision-language models with historical research methodologies.
+
Special thanks to the digital humanities community for continued innovation in computational approaches to historical research.
+ """
+ st.markdown(acknowledgment_content, unsafe_allow_html=True)
+
+ # Restart the workshop button
+ if st.button("Start Workshop Again", use_container_width=True):
+ # Reset the session state to start the workshop again
+ if 'current_module' in st.session_state:
+ st.session_state.current_module = 1
+
+ # Do not reset the processing history
+
+ st.experimental_rerun()
\ No newline at end of file
diff --git a/streamlit/packages.txt b/streamlit/packages.txt
new file mode 100644
index 0000000000000000000000000000000000000000..c1298f5aebdba9b2e9b819d036559cac9e687863
--- /dev/null
+++ b/streamlit/packages.txt
@@ -0,0 +1,2 @@
+tesseract-ocr
+poppler-utils
\ No newline at end of file
diff --git a/streamlit/process_file.py b/streamlit/process_file.py
new file mode 100644
index 0000000000000000000000000000000000000000..e7dfb5bde566f22aa86a9e8104e88bf5989365ee
--- /dev/null
+++ b/streamlit/process_file.py
@@ -0,0 +1,66 @@
+"""
+Utility function for processing files with OCR in the Historical OCR Workshop app.
+"""
+
+import os
+import tempfile
+from pathlib import Path
+from datetime import datetime
+
+def process_file(uploaded_file, use_vision=True, processor=None, custom_prompt=None):
+ """Process the uploaded file and return the OCR results
+
+ Args:
+ uploaded_file: The uploaded file to process
+ use_vision: Whether to use vision model
+ processor: StructuredOCR processor (if None, it will be imported)
+ custom_prompt: Optional additional instructions for the model
+
+ Returns:
+ dict: The OCR results
+ """
+ # Import the processor if not provided
+ if processor is None:
+ from structured_ocr import StructuredOCR
+ processor = StructuredOCR()
+
+ # Save the uploaded file to a temporary file
+ with tempfile.NamedTemporaryFile(delete=False, suffix=Path(uploaded_file.name).suffix) as tmp:
+ tmp.write(uploaded_file.getvalue())
+ temp_path = tmp.name
+
+ try:
+ # Determine file type from extension
+ file_ext = Path(uploaded_file.name).suffix.lower()
+ file_type = "pdf" if file_ext == ".pdf" else "image"
+
+ # Get file size in MB
+ file_size_mb = os.path.getsize(temp_path) / (1024 * 1024)
+
+ # Process the file with file size information for automatic page limiting
+ result = processor.process_file(
+ temp_path,
+ file_type=file_type,
+ use_vision=use_vision,
+ file_size_mb=file_size_mb,
+ custom_prompt=custom_prompt
+ )
+
+ # Add processing metadata
+ result.update({
+ "file_name": uploaded_file.name,
+ "processed_at": datetime.now().isoformat(),
+ "file_size_mb": round(file_size_mb, 2),
+ "use_vision": use_vision
+ })
+
+ return result
+ except Exception as e:
+ return {
+ "error": str(e),
+ "file_name": uploaded_file.name
+ }
+ finally:
+ # Clean up the temporary file
+ if os.path.exists(temp_path):
+ os.unlink(temp_path)
\ No newline at end of file
diff --git a/streamlit/requirements.txt b/streamlit/requirements.txt
new file mode 100644
index 0000000000000000000000000000000000000000..76bdca7345b8cabe8acbfcb8b3080597fc47150f
--- /dev/null
+++ b/streamlit/requirements.txt
@@ -0,0 +1,9 @@
+streamlit>=1.43.2
+mistralai
+pydantic
+pycountry
+pillow
+python-multipart
+pdf2image
+pytesseract
+streamlit-javascript
\ No newline at end of file
diff --git a/streamlit/run_local.sh b/streamlit/run_local.sh
new file mode 100644
index 0000000000000000000000000000000000000000..af6e179c7fd64fac7a11852f791128e99a6adf1a
--- /dev/null
+++ b/streamlit/run_local.sh
@@ -0,0 +1,26 @@
+#!/bin/bash
+# Run the Streamlit app locally
+
+# No longer need to add parent directory to PYTHONPATH
+# as we now have a local copy of structured_ocr.py
+
+# Load environment variables from .env file if it exists
+if [ -f .env ]; then
+ echo "Loading environment variables from .env file"
+ set -o allexport
+ source .env
+ set +o allexport
+else
+ echo "No .env file found. Make sure to set MISTRAL_API_KEY environment variable manually."
+fi
+
+# Check if MISTRAL_API_KEY is set
+if [ -z "$MISTRAL_API_KEY" ]; then
+ echo "WARNING: MISTRAL_API_KEY is not set. The app will run with sample data."
+else
+ echo "MISTRAL_API_KEY is set. The app will use the Mistral API for OCR processing."
+fi
+
+# Run the Streamlit app
+# We can run either app.py (which now imports streamlit_app.py) or streamlit_app.py directly
+streamlit run streamlit_app.py
\ No newline at end of file
diff --git a/streamlit/setup_git.sh b/streamlit/setup_git.sh
new file mode 100644
index 0000000000000000000000000000000000000000..331be4e7e41b38ce4ccce2f3efaf6b0a64f4eead
--- /dev/null
+++ b/streamlit/setup_git.sh
@@ -0,0 +1,35 @@
+#!/bin/bash
+# Setup git repository for Hugging Face Spaces
+
+# Check if HF_TOKEN environment variable is set
+if [ -z "$HF_TOKEN" ]; then
+ echo "Error: HF_TOKEN environment variable is not set."
+ echo "Please set it first with: export HF_TOKEN=your_hugging_face_token"
+ exit 1
+fi
+
+# Get your username
+echo "Enter your Hugging Face username:"
+read HF_USERNAME
+
+# Get the space name
+echo "Enter the name for your Hugging Face Space (e.g., historical-ocr):"
+read HF_SPACE
+
+# Prepare the files for deployment
+echo "Preparing files for deployment..."
+python3 prepare_for_hf.py
+
+# Initialize git
+git init
+git add .
+git commit -m "Initial commit"
+
+# Create the repository on Hugging Face
+echo "Creating and pushing to Hugging Face Space..."
+git remote add origin https://huggingface.co/spaces/$HF_USERNAME/$HF_SPACE
+huggingface-cli login --token $HF_TOKEN
+git push -u origin main
+
+echo "Deployment completed! Your app should be available at:"
+echo "https://huggingface.co/spaces/$HF_USERNAME/$HF_SPACE"
\ No newline at end of file
diff --git a/streamlit/streamlit_app.py b/streamlit/streamlit_app.py
new file mode 100644
index 0000000000000000000000000000000000000000..f941891f42dbc951d73a962a9b7f445e166ef07e
--- /dev/null
+++ b/streamlit/streamlit_app.py
@@ -0,0 +1,2028 @@
+import os
+import streamlit as st
+import json
+import sys
+from pathlib import Path
+import tempfile
+from datetime import datetime
+import io
+import base64
+from io import BytesIO
+from enum import Enum
+import inspect
+
+# Add parent directory to path so we can import the OCR modules
+parent_dir = Path(__file__).parent.absolute()
+sys.path.append(str(parent_dir))
+
+# Import the StructuredOCR class and process_file utility
+from structured_ocr import StructuredOCR
+
+# Add API endpoint support for the React app
+from streamlit.web.server.server import Server
+from streamlit.runtime.scriptrunner import get_script_run_ctx
+
+# Custom JSON encoder to handle Enum types and other non-serializable objects
+class EnhancedJSONEncoder(json.JSONEncoder):
+ def default(self, obj):
+ if isinstance(obj, Enum):
+ return obj.value
+ elif hasattr(obj, '__dict__'):
+ # For objects that have a __dict__ but aren't directly serializable
+ return {key: value for key, value in obj.__dict__.items()
+ if not key.startswith('_')}
+ elif hasattr(obj, 'model_dump'):
+ # For Pydantic models
+ return obj.model_dump()
+ elif hasattr(obj, 'to_dict'):
+ # For objects with to_dict method
+ return obj.to_dict()
+ # Let the base class handle other types or raise TypeError
+ return super().default(obj)
+
+# Helper function to convert any result to JSON-serializable
+def make_serializable(obj):
+ """Convert any object to a JSON-serializable form"""
+ if isinstance(obj, dict):
+ return {k: make_serializable(v) for k, v in obj.items()}
+ elif isinstance(obj, list):
+ return [make_serializable(item) for item in obj]
+ elif isinstance(obj, Enum):
+ return obj.value
+ elif hasattr(obj, 'pages'):
+ # Special case for OCRResponse objects which have pages attribute
+ if hasattr(obj, '__dict__'):
+ result = {k: make_serializable(v) for k, v in obj.__dict__.items()
+ if not k.startswith('_')}
+ # Explicitly handle pages attribute
+ if hasattr(obj, 'pages'):
+ result['pages'] = [make_serializable(page) for page in obj.pages]
+ return result
+ elif hasattr(obj, '__dict__'):
+ # For objects with __dict__ attribute
+ return {k: make_serializable(v) for k, v in obj.__dict__.items()
+ if not k.startswith('_')}
+ elif hasattr(obj, 'model_dump'):
+ # For Pydantic models
+ return make_serializable(obj.model_dump())
+ elif hasattr(obj, 'to_dict'):
+ # For objects with to_dict method
+ return make_serializable(obj.to_dict())
+ # Basic types will be returned as is
+ return obj
+
+# API response handler
+def process_api_request():
+ """Handle API requests from the React frontend"""
+ # Get the current Streamlit session
+ ctx = get_script_run_ctx()
+ if ctx is None:
+ return
+
+ session_id = ctx.session_id
+ session_info = Server.get_current()._get_session_info(session_id)
+
+ if session_info is None:
+ return
+
+ request = session_info.uploaded_file_mgr._uploaded_files.get('file')
+
+ if not request:
+ return
+
+ # Extract file and parameters
+ uploaded_file = request[0]
+ use_vision = session_info.query_string.get('use_vision', ['true'])[0].lower() == 'true'
+
+ try:
+ # Process file
+ result = process_file(uploaded_file, use_vision=use_vision)
+
+ # Convert result to JSON-serializable format
+ serializable_result = make_serializable(result)
+
+ # Return JSON response
+ return serializable_result
+ except Exception as e:
+ # Return error response
+ return {"error": str(e)}
+
+try:
+ from process_file import process_file as process_file_util
+ # Use the utility function instead of the local function
+ process_file = process_file_util
+except ImportError:
+ # Define the process_file function if it's not available
+ def process_file(uploaded_file, use_vision=True):
+ """Process the uploaded file and return the OCR results"""
+ # Save the uploaded file to a temporary file
+ with tempfile.NamedTemporaryFile(delete=False, suffix=Path(uploaded_file.name).suffix) as tmp:
+ tmp.write(uploaded_file.getvalue())
+ temp_path = tmp.name
+
+ try:
+ # Initialize OCR processor
+ processor = StructuredOCR()
+
+ # Determine file type from extension
+ file_ext = Path(uploaded_file.name).suffix.lower()
+ file_type = "pdf" if file_ext == ".pdf" else "image"
+
+ # Process the file
+ result = processor.process_file(temp_path, file_type=file_type, use_vision=use_vision)
+
+ # Add to processing history
+ history_item = {
+ "id": datetime.now().timestamp(),
+ "fileName": uploaded_file.name,
+ "timestamp": datetime.now().isoformat(),
+ "result": result,
+ "useVision": use_vision
+ }
+
+ if 'processing_history' not in st.session_state:
+ st.session_state.processing_history = []
+
+ st.session_state.processing_history.append(history_item)
+
+ return result
+ except Exception as e:
+ st.error(f"Error processing document: {str(e)}")
+ return None
+ finally:
+ # Clean up the temporary file
+ if os.path.exists(temp_path):
+ os.unlink(temp_path)
+
+# Set page configuration
+st.set_page_config(
+ page_title="Historical OCR Workshop",
+ page_icon="📜",
+ layout="wide",
+ initial_sidebar_state="collapsed" # Start with sidebar collapsed for cleaner landing
+)
+
+# Custom CSS to match React dark theme and improve Streamlit integration
+st.markdown("""
+
+""", unsafe_allow_html=True)
+
+# Initialize session state for workshop progress
+if 'current_module' not in st.session_state:
+ st.session_state.current_module = 1
+if 'processing_history' not in st.session_state:
+ st.session_state.processing_history = []
+if 'workshop_started' not in st.session_state:
+ st.session_state.workshop_started = False
+
+def navigate_to_module(module_number):
+ """Navigate to a specific module"""
+ st.session_state.current_module = module_number
+
+# Welcome/Start screen if workshop hasn't been started
+if not st.session_state.workshop_started:
+ # Hero section with eye-catching design
+ st.markdown("""
+
+
Historical OCR Workshop
+
Unlock the potential of historical documents with modern OCR technology
+
+ This interactive workshop explores the application of OCR technology to historical documents,
+ combining theoretical understanding with practical experiences. Designed for historians,
+ archivists, and digital humanities scholars, it offers both conceptual frameworks and hands-on skills.
+
+ Optical Character Recognition (OCR) technology enables computers to extract text from images and documents.
+ Modern OCR uses AI vision models to understand both the text and its visual context, making it powerful for
+ historical research and digital humanities.
+
+ """, unsafe_allow_html=True)
+
+ with col2:
+ # Add an engaging research question to connect with historians
+ st.markdown("""
+
+
For Historians:
+ How might OCR technology transform our access to and interpretation of historical documents?
+ What new research questions become possible when large archives become machine-readable?
+
+ """, unsafe_allow_html=True)
+
+ # Display sample historical document images in a better format
+ input_dir = Path(__file__).parent / "input"
+ sample_images = [
+ {"path": input_dir / "letter-1.jpg", "caption": "Historical Letter"},
+ {"path": input_dir / "recipe.jpg", "caption": "Historical Recipe"}
+ ]
+
+ # Try to find any of the sample images
+ for sample in sample_images:
+ if sample["path"].exists():
+ try:
+ from PIL import Image
+ with Image.open(sample["path"]) as img:
+ # Add a better styled border and shadow
+ st.markdown(f"""
+
+
+ - Processing historical documents with OCR
+ - Analyzing and structuring extracted information
+ - Integrating OCR into research workflows
+ - Building searchable archives
+
+ """, unsafe_allow_html=True)
+
+ with col2:
+ # Workshop modules with visually appealing cards
+ st.markdown('
Workshop Modules
', unsafe_allow_html=True)
+
+ # Add some styling for the module cards
+ st.markdown("""
+
+ """, unsafe_allow_html=True)
+
+ # Modules inside the col2 from earlier
+ with col2:
+ st.markdown("""
+
+
Module 1
+
Introduction and Problematization
+
Explore the challenges of historical document digitization and the potential of OCR technologies
+ to transform historical research. Examine key problems and opportunities in historical OCR.
Understand the evolution of OCR technology from pattern matching to modern AI vision-language models,
+ and how they address the unique challenges of historical documents.
Develop hybrid methodologies that combine computational processing with traditional
+ historical research practices, balancing distant and close reading.
Synthesize workshop insights and explore future directions for OCR in historical research,
+ from large-scale corpus analysis to computational paleography.
+
+ """, unsafe_allow_html=True)
+
+ # Engaging quote to inspire participation with citation - in a better styled container
+ st.markdown("""
+
+
"
+
+
The digital turn in historical research is not just about converting analog to digital;
+ it's about transforming how we access, analyze, and interpret the past.
+
+
— Dr. Jane Winters, Professor of Digital Humanities
+
+ """, unsafe_allow_html=True)
+
+ # Feature highlight before call to action with better styling
+ st.markdown("""
+
+
+
+
Workshop Highlights
+
+
+
+
Interactive Learning
+
Hands-on document processing with real-time feedback and analysis
+
+
+
+
Real Historical Documents
+
Work with authentic materials spanning different eras and formats
+
+
+
+
Vision AI Models
+
Experience state-of-the-art OCR technology powered by advanced AI
+
+
+
+
Research Applications
+
Learn to integrate OCR into historical research workflows
+
+
+
+ """, unsafe_allow_html=True)
+
+ # Enhanced start button with dynamic styling and clear call to action
+ st.markdown("""
+
+
Ready to Start Your Journey?
+
+
+
+
No installation required • Start immediately • Interactive experience
+
+
+
+ """, unsafe_allow_html=True)
+
+ # Hidden button to trigger the workshop start
+ col1, col2, col3 = st.columns([1, 1, 1])
+ with col2:
+ if st.button("Begin Workshop", key="streamlit-button", use_container_width=True, type="primary"):
+ st.session_state.workshop_started = True
+ st.rerun()
+
+# Display workshop navigation sidebar only if workshop has started
+elif st.session_state.workshop_started:
+ # Define input directory for images
+ input_dir = Path(__file__).parent / "input"
+
+ # Enhanced sidebar navigation
+ with st.sidebar:
+ st.markdown("
+ """, unsafe_allow_html=True)
+
+ # Hidden button for jump
+ if st.button("Jump to Experiment", key="jump_exp"):
+ navigate_to_module(5)
+ st.rerun()
+
+ # Workshop information in a cleaner collapsible section
+ with st.expander("About the Workshop"):
+ st.markdown("""
+ This interactive workshop explores OCR technology for historical documents.
+
+ **How to use this workshop:**
+ 1. Navigate through modules sequentially
+ 2. Expand content sections to read more
+ 3. Try the interactive OCR experiment
+ 4. Reflect on research questions
+
+ For help or more information, use the reference materials in Module 6.
+ """)
+
+ # Enhanced progress tracking
+ if st.session_state.processing_history:
+ with st.expander("Your Activity"):
+ st.markdown(f"Documents processed: {len(st.session_state.processing_history)}", unsafe_allow_html=True)
+
+ # Show the most recent document processed with better formatting
+ latest = st.session_state.processing_history[-1]
+ st.markdown(f"""
+
+ Latest document: {latest['fileName']}
+ Processed with {' vision model' if latest['useVision'] else ' basic OCR'}
+
+ """, unsafe_allow_html=True)
+
+ # Main content based on current module
+ if st.session_state.current_module == 1:
+ # MODULE 1: Introduction
+ st.title("Module 1: Introduction and Problematization")
+
+ col1, col2 = st.columns([2, 1])
+
+ with col1:
+ st.markdown("""
+ ## Historical OCR Workshop
+
+ ### The Problem
+
+ Historical documents present unique challenges for OCR technology:
+
+ - Varying typography and handwriting styles
+ - Document degradation and damage
+ - Complex layouts and formatting
+ - Multiple languages and archaic terminology
+ - Illustrations and decorative elements
+ """)
+
+ with col2:
+ st.markdown("""
+ ### Workshop Goals
+
+ By the end of this workshop, you will:
+
+ 1. Understand text-image relationships in historical archives
+ 2. Learn about advanced OCR technology
+ 3. Explore methodological approaches
+ 4. Gain hands-on experience with OCR tools
+ 5. Develop research integration strategies
+ """)
+
+ # Next button
+ st.button("Next: Text-Image Relations", key="next_to_2", on_click=navigate_to_module, args=(2,))
+
+ elif st.session_state.current_module == 2:
+ # MODULE 2: Text-Image Relations
+ st.title("Module 2: Text-Image Relations in Historical Archives")
+
+ col1, col2 = st.columns([1, 1])
+
+ with col1:
+ st.markdown("""
+ ### Textual Elements
+
+ - **Typography**: Varying fonts, sizes, and styles
+ - **Layout**: Columns, margins, and spacing
+ - **Marginalia**: Notes, comments, and additions
+ - **Decorative Text**: Illuminated letters and calligraphy
+ """)
+
+ st.markdown("""
+ ### Visual Elements
+
+ - **Illustrations**: Diagrams, maps, and artistic representations
+ - **Watermarks**: Hidden identifiers that locate documents
+ - **Damage**: Tears, stains, and fading affecting legibility
+ - **Material Features**: Paper quality and physical dimensions
+ """)
+
+ with col2:
+ st.markdown("""
+ ### Interdependence
+
+ The relationship between text and image in historical documents exists on a complex spectrum:
+
+ - Text functions as image (decorative headings)
+ - Images function as text (symbolic representations)
+ - Layout creates meaning through visual organization
+ - Material conditions affect both textual and visual elements
+ """)
+
+ st.image("https://upload.wikimedia.org/wikipedia/commons/thumb/0/0c/Book_of_Kells_folio_292r.jpg/800px-Book_of_Kells_folio_292r.jpg",
+ caption="Book of Kells - Example of text-image integration")
+
+ st.markdown("""
+ ### OCR Challenges
+
+ These complex text-image relationships create particular challenges for OCR:
+
+ 1. **Distinguishing Text from Decoration**: Where does ornamental text end and functional text begin?
+ 2. **Handling Illustrations**: Should they be processed as images or described as text?
+ 3. **Interpreting Layout**: How to capture the significance of spacing and organization?
+ 4. **Preserving Context**: Maintaining the relationship between textual and visual elements
+ """)
+
+ # Navigation buttons
+ col1, col2 = st.columns(2)
+ with col1:
+ st.button("Previous: Introduction", key="prev_to_1", on_click=navigate_to_module, args=(1,))
+
+ with col2:
+ st.button("Next: OCR Technology", key="next_to_3", on_click=navigate_to_module, args=(3,))
+
+ elif st.session_state.current_module == 3:
+ # MODULE 3: OCR Technology
+ st.title("Module 3: OCR Technology and Historical Documents")
+
+ col1, col2 = st.columns([1, 1])
+
+ with col1:
+ st.markdown("""
+ ### Traditional OCR Approaches
+
+ 1. **Pattern Matching**: Early OCR compared characters to templates
+ 2. **Feature Extraction**: Identifying key features of characters
+ 3. **Statistical Models**: Using probabilities to improve recognition
+ """)
+
+ st.markdown("""
+ ### Modern AI-Enhanced OCR
+
+ 1. **Neural Networks**: Deep learning models trained on vast datasets
+ 2. **Computer Vision**: Advanced image processing techniques
+ 3. **Language Models**: Contextual understanding to resolve ambiguities
+ 4. **Multimodal Models**: Integration of text, layout, and visual understanding
+ """)
+
+ with col2:
+ st.markdown("""
+ ### Challenges with Historical Documents
+
+ Historical materials present unique difficulties:
+
+ - **Typography Variation**: Non-standardized fonts and styles
+ - **Historical Language**: Archaic vocabulary and grammar
+ - **Layout Complexity**: Non-linear arrangements
+ - **Document Degradation**: Fading, tears, stains, and damage
+ - **Material Artifacts**: Paper texture, binding shadows, etc.
+ """)
+
+ st.image("https://cdn.dribbble.com/users/412119/screenshots/16353886/media/82e593c60a5e4d460db917236eab6ece.jpg",
+ caption="OCR processing layers")
+
+ # Display processing history if available
+ if st.session_state.processing_history:
+ with st.expander("Your OCR Processing History"):
+ st.markdown("You've already processed the following documents:")
+
+ for item in st.session_state.processing_history:
+ st.markdown(f"**{item['fileName']}** - {datetime.fromisoformat(item['timestamp']).strftime('%Y-%m-%d %H:%M')}")
+ col1, col2 = st.columns(2)
+ with col1:
+ st.write(f"**Topics:** {', '.join(item['result'].get('topics', ['Unknown']))}")
+ with col2:
+ st.write(f"**Vision model used:** {'Yes' if item['useVision'] else 'No'}")
+
+ # Quick link to experiment
+ st.button("Jump to OCR Experiment", key="jump_to_5", on_click=navigate_to_module, args=(5,))
+
+ # Navigation buttons
+ col1, col2 = st.columns(2)
+ with col1:
+ st.button("Previous: Text-Image Relations", key="prev_to_2", on_click=navigate_to_module, args=(2,))
+
+ with col2:
+ st.button("Next: Methodological Approaches", key="next_to_4", on_click=navigate_to_module, args=(4,))
+
+ elif st.session_state.current_module == 4:
+ # MODULE 4: Methodological Approaches
+ st.title("Module 4: Methodological Approaches")
+
+ col1, col2 = st.columns([1, 1])
+
+ with col1:
+ st.markdown("""
+ ### Hybrid Methodologies
+
+ 1. **Computational + Human Reading**
+ - OCR for initial processing and discovery
+ - Human review for context and interpretation
+ - Iterative refinement of computational outputs
+
+ 2. **Close + Distant Reading**
+ - Distant reading through large-scale OCR processing
+ - Close reading of selected passages
+ - Zooming between scales of analysis
+ """)
+
+ # Reference to diagram.jpg
+ input_dir = Path(__file__).parent / "input"
+ diagram_path = input_dir / "diagram.jpg"
+ if diagram_path.exists():
+ try:
+ # Load image file directly from disk
+ from PIL import Image
+ with Image.open(diagram_path) as img:
+ st.image(img, caption="Historical VLM architecture", use_column_width=True)
+ except Exception:
+ pass
+
+ with col2:
+ st.markdown("""
+ ### Mistral-OCR-Latest: State-of-the-Art
+
+ The Mistral-OCR model represents a significant advancement:
+
+ - **Multimodal Understanding**: Processes both visual and textual information
+ - **Contextual Awareness**: Considers historical context
+ - **Layout Recognition**: Preserves complex document structures
+ - **Historical Font Adaptation**: Trained on diverse historical typography
+ """)
+
+ # Reference to workflow.jpg
+ workflow_path = input_dir / "workflow.jpg"
+ if workflow_path.exists():
+ try:
+ # Load image file directly from disk
+ from PIL import Image
+ with Image.open(workflow_path) as img:
+ st.image(img, caption="Mistral OCR workflow", use_column_width=True)
+ except Exception:
+ pass
+
+ st.markdown("""
+ ### Practical Workflow
+
+ A typical historical OCR workflow with Mistral-OCR includes:
+
+ 1. **Selection**: Choosing appropriate documents
+ 2. **Preprocessing**: Enhancing images before OCR
+ 3. **OCR Processing**: Running documents through vision-enhanced OCR
+ 4. **Post-processing**: Cleaning up outputs and structured extraction
+ 5. **Verification**: Cross-checking results against originals
+ 6. **Integration**: Incorporating OCR outputs into research materials
+ """)
+
+ # Navigation buttons
+ col1, col2 = st.columns(2)
+ with col1:
+ st.button("Previous: OCR Technology", key="prev_to_3", on_click=navigate_to_module, args=(3,))
+
+ with col2:
+ st.button("Next: Interactive OCR Experiment", key="next_to_5", on_click=navigate_to_module, args=(5,))
+
+ elif st.session_state.current_module == 5:
+ # MODULE 5: Interactive OCR Experiment
+ st.title("Module 5: Interactive OCR Experiment")
+
+ # More modular design with sequenced steps
+ st.markdown("""
+
+ This interactive module allows you to process historical documents with OCR and analyze the results.
+ Follow the sequenced steps below to experiment with historical document analysis.
+
+ """, unsafe_allow_html=True)
+
+ # Tabbed interface for different activities
+ experiment_tab, compare_tab, analyze_tab = st.tabs(["Process Documents", "Compare Results", "Analysis Guide"])
+
+ with experiment_tab:
+ # Import additional libraries for enhanced functionality
+ try:
+ from pdf2image import convert_from_bytes
+ pdf_support = True
+ except ImportError:
+ pdf_support = False
+ st.warning("PDF preview functionality is limited. The pdf2image module is required for PDF previews.")
+
+ # OCR tool in a compact layout
+ col1, col2 = st.columns([1, 1])
+
+ with col1:
+ st.markdown('
', unsafe_allow_html=True)
+ st.markdown("
Step 1: Select Document & Options
", unsafe_allow_html=True)
+
+ # Processing options
+ use_vision = st.checkbox("Use Vision Model", value=True,
+ help="Use the vision model for improved analysis")
+
+ # Additional prompt for the model
+ st.markdown("### Custom Research Prompt (Optional)")
+ st.markdown("""Provide additional instructions to guide the OCR analysis.
+ Focus on specific aspects of historical research you're interested in.""")
+ custom_prompt = st.text_area("Research Prompt",
+ placeholder="E.g., Focus on identifying dates and historical figures; Analyze the writing style for period-specific terminology; Highlight any cultural or social indicators of the time period...",
+ help="Optional instructions to guide the analysis of the historical document")
+
+ # Example preset documents selection
+ input_dir = Path(__file__).parent / "input"
+ if input_dir.exists():
+ sample_files = list(input_dir.glob("*.jpg")) + list(input_dir.glob("*.png")) + list(input_dir.glob("*.pdf"))
+ if sample_files:
+ st.markdown("#### Sample Documents")
+ sample_options = ["Upload my own document"] + [f.name for f in sample_files]
+ sample_choice = st.selectbox("Choose a document:", sample_options)
+
+ if sample_choice != "Upload my own document":
+ selected_file = next((f for f in sample_files if f.name == sample_choice), None)
+ if selected_file:
+ # Store the selected sample file in session state
+ with open(selected_file, "rb") as f:
+ file_bytes = f.read()
+ st.session_state.sample_file = {
+ "name": selected_file.name,
+ "bytes": file_bytes
+ }
+
+ # Preview the selected sample
+ if selected_file.suffix.lower() == ".pdf" and pdf_support:
+ try:
+ with st.spinner("Generating PDF preview..."):
+ images = convert_from_bytes(file_bytes, first_page=1, last_page=1, dpi=150)
+ if images:
+ # Convert PIL image to bytes for Streamlit
+ first_page = images[0]
+ img_bytes = io.BytesIO()
+ first_page.save(img_bytes, format='JPEG')
+ img_bytes.seek(0)
+ st.image(img_bytes, caption=f"Preview: {selected_file.name}", use_container_width=True)
+ except Exception:
+ st.info(f"PDF selected: {selected_file.name}")
+ else:
+ # For images display directly
+ try:
+ from PIL import Image
+ img = Image.open(BytesIO(file_bytes))
+ st.image(img, caption=f"Preview: {selected_file.name}", use_container_width=True)
+ except Exception:
+ st.info(f"Selected: {selected_file.name}")
+ else:
+ # Clear the sample file if "Upload my own" is selected
+ if 'sample_file' in st.session_state:
+ del st.session_state.sample_file
+
+ # File uploader with styling matched to React theme
+ st.markdown('
', unsafe_allow_html=True)
+ uploaded_file = st.file_uploader("Choose a file", type=["pdf", "png", "jpg", "jpeg"], label_visibility="collapsed")
+
+ if uploaded_file is None:
+ st.markdown("### Upload a document to get started")
+ st.markdown("Supported formats: PDF, JPG, PNG")
+ else:
+ # Display the uploaded file
+ file_ext = Path(uploaded_file.name).suffix.lower()
+
+ if file_ext == ".pdf" and pdf_support:
+ try:
+ # Convert first page of PDF to image for preview
+ pdf_bytes = uploaded_file.getvalue()
+ with st.spinner("Generating PDF preview..."):
+ images = convert_from_bytes(pdf_bytes, first_page=1, last_page=1, dpi=150)
+
+ if images:
+ # Convert PIL image to bytes for Streamlit
+ first_page = images[0]
+ img_bytes = io.BytesIO()
+ first_page.save(img_bytes, format='JPEG')
+ img_bytes.seek(0)
+
+ # Display the PDF preview
+ st.image(img_bytes, caption=f"PDF Preview: {uploaded_file.name}", use_container_width=True)
+ else:
+ st.info(f"PDF uploaded: {uploaded_file.name}")
+ except Exception:
+ # Simply show the file name without an error message
+ st.info(f"PDF uploaded: {uploaded_file.name}")
+ elif file_ext != ".pdf":
+ st.image(uploaded_file, use_container_width=True)
+ else:
+ st.info(f"PDF uploaded: {uploaded_file.name}")
+
+ st.markdown('
', unsafe_allow_html=True)
+ else:
+ # No sample files found, just show the uploader
+ st.markdown('
', unsafe_allow_html=True)
+ uploaded_file = st.file_uploader("Choose a file", type=["pdf", "png", "jpg", "jpeg"], label_visibility="collapsed")
+
+ if uploaded_file is None:
+ st.markdown("### Upload a document to get started")
+ st.markdown("Supported formats: PDF, JPG, PNG")
+ else:
+ # Display the uploaded file preview
+ file_ext = Path(uploaded_file.name).suffix.lower()
+ if file_ext == ".pdf" and pdf_support:
+ try:
+ # PDF preview logic
+ pdf_bytes = uploaded_file.getvalue()
+ with st.spinner("Generating PDF preview..."):
+ images = convert_from_bytes(pdf_bytes, first_page=1, last_page=1, dpi=150)
+ if images:
+ first_page = images[0]
+ img_bytes = io.BytesIO()
+ first_page.save(img_bytes, format='JPEG')
+ img_bytes.seek(0)
+ st.image(img_bytes, caption=f"PDF Preview: {uploaded_file.name}", use_container_width=True)
+ else:
+ st.info(f"PDF uploaded: {uploaded_file.name}")
+ except Exception:
+ st.info(f"PDF uploaded: {uploaded_file.name}")
+ elif file_ext != ".pdf":
+ st.image(uploaded_file, use_container_width=True)
+ else:
+ st.info(f"PDF uploaded: {uploaded_file.name}")
+ st.markdown('
', unsafe_allow_html=True)
+ else:
+ # Input directory doesn't exist, just show the uploader
+ st.markdown('
', unsafe_allow_html=True)
+ uploaded_file = st.file_uploader("Choose a file", type=["pdf", "png", "jpg", "jpeg"], label_visibility="collapsed")
+
+ if uploaded_file is None:
+ st.markdown("### Upload a document to get started")
+ st.markdown("Supported formats: PDF, JPG, PNG")
+ st.markdown('
', unsafe_allow_html=True)
+
+ # Step 2: Process document
+ st.subheader("Step 2: Process the Document")
+
+ # Get the file to process (either uploaded or sample)
+ file_to_process = None
+ if 'sample_file' in st.session_state and sample_choice != "Upload my own document":
+ # Create a FileUploader-like object from the sample file
+ class SampleFileObject:
+ def __init__(self, name, data):
+ self.name = name
+ self._data = data
+
+ def getvalue(self):
+ return self._data
+
+ file_to_process = SampleFileObject(
+ st.session_state.sample_file["name"],
+ st.session_state.sample_file["bytes"]
+ )
+ elif 'uploaded_file' in locals() and uploaded_file is not None:
+ file_to_process = uploaded_file
+
+ # Process button (disabled if no file selected)
+ process_button = st.button(
+ "Process Document",
+ disabled=file_to_process is None,
+ use_container_width=True
+ )
+
+ if process_button and file_to_process is not None:
+ with st.spinner("Processing document..."):
+ try:
+ # Process the file
+ result = process_file(file_to_process, use_vision, custom_prompt=custom_prompt if custom_prompt else None)
+
+ if result:
+ st.success("Document processed successfully!")
+
+ # Store result in session state for display in the right column
+ st.session_state.current_result = result
+ st.rerun() # Refresh to show result
+ else:
+ st.error("Failed to process document.")
+ except Exception as e:
+ st.error(f"Error processing document: {str(e)}")
+
+ st.markdown('
', unsafe_allow_html=True)
+
+ # Experiment instructions in a compact format
+ st.markdown('
', unsafe_allow_html=True)
+ st.markdown("
Experiment Instructions
", unsafe_allow_html=True)
+ st.markdown("""
+ 1. **Step 1:** Select a document and choose your options
+ 2. **Step 2:** Process the document with the selected options
+ 3. **Step 3:** Analyze the results in the panel on the right
+ 4. **Step 4:** Try again with different settings (e.g., toggle vision model)
+ 5. **Step 5:** Compare results between different runs
+ """)
+ st.markdown('
', unsafe_allow_html=True)
+
+ with col2:
+ st.markdown('
', unsafe_allow_html=True)
+ st.markdown("
Step 3: View Results
", unsafe_allow_html=True)
+
+ if 'current_result' in st.session_state and st.session_state.current_result:
+ result = st.session_state.current_result
+
+ # File info in a compact format
+ st.markdown(f"**File:** {result.get('file_name', 'Unknown')}")
+
+ # Horizontal display of metadata
+ col1, col2 = st.columns(2)
+ with col1:
+ if 'languages' in result and result['languages']:
+ languages = [lang for lang in result['languages'] if lang]
+ if languages:
+ st.markdown(f"**Languages:** {', '.join(languages)}")
+
+ with col2:
+ if 'topics' in result and result['topics']:
+ st.markdown(f"**Topics:** {', '.join(result['topics'])}")
+
+ # Create tabs for different views with inline styling to ensure visibility
+ tab1, tab2 = st.tabs(["Structured View", "Raw JSON"])
+ st.markdown("""
+
+ """, unsafe_allow_html=True)
+
+ with tab1:
+ # Display in a more user-friendly format based on the content structure
+ if 'ocr_contents' in result:
+ if isinstance(result['ocr_contents'], dict):
+ for section, content in result['ocr_contents'].items():
+ if content: # Only display non-empty sections
+ st.markdown(f"
{section.replace('_', ' ').title()}
", unsafe_allow_html=True)
+
+ if isinstance(content, str):
+ st.markdown(f"
{content}
", unsafe_allow_html=True)
+ elif isinstance(content, list):
+ for item in content:
+ if isinstance(item, str):
+ st.markdown(f"
", unsafe_allow_html=True)
+ elif isinstance(content, dict):
+ for k, v in content.items():
+ st.markdown(f"
{k}: {v}
", unsafe_allow_html=True)
+
+ with tab2:
+ # Show the raw JSON for developers
+ # Convert to serializable format first
+ serializable_result = make_serializable(result)
+ st.json(serializable_result)
+
+ # Download options
+ st.markdown("### Export Results")
+
+ col1, col2 = st.columns(2)
+
+ with col1:
+ # Export as JSON (using the serializable converter)
+ serializable_result = make_serializable(result)
+ json_bytes = json.dumps(serializable_result, indent=2).encode()
+ st.download_button(
+ label="Download JSON",
+ data=json_bytes,
+ file_name="ocr_results.json",
+ mime="application/json",
+ use_container_width=True
+ )
+
+ with col2:
+ # Export as text
+ if 'ocr_contents' in result and isinstance(result['ocr_contents'], dict) and 'content' in result['ocr_contents']:
+ text_content = result['ocr_contents']['content']
+ st.download_button(
+ label="Download Text",
+ data=text_content.encode(),
+ file_name="ocr_text.txt",
+ mime="text/plain",
+ use_container_width=True
+ )
+ else:
+ st.markdown("""
+
+
Results will appear here
+
+
Upload and process a document to see the OCR results in this panel.
+
+
The OCR tool will:
+
+
Extract text from your document
+
Identify languages and topics
+
Provide structured content analysis
+
Generate downloadable results
+
+
+ """, unsafe_allow_html=True)
+ st.markdown('
', unsafe_allow_html=True)
+
+ # Processing history
+ if st.session_state.processing_history:
+ st.markdown('
', unsafe_allow_html=True)
+ st.markdown("
Step 4: Review Processing History
", unsafe_allow_html=True)
+
+ # Most recent result summary
+ latest = st.session_state.processing_history[-1]
+ st.markdown(f"**Latest Document:** {latest['fileName']}")
+ st.markdown(f"**Processed at:** {datetime.fromisoformat(latest['timestamp']).strftime('%Y-%m-%d %H:%M')}")
+ st.markdown(f"**Vision model used:** {'Yes' if latest['useVision'] else 'No'}")
+
+ # Full history in expander
+ with st.expander("View Complete Processing History"):
+ for i, item in enumerate(reversed(st.session_state.processing_history)):
+ st.markdown(f"""
+
+ """, unsafe_allow_html=True)
+
+ # Add option to view a previous result
+ if st.button(f"View This Result", key=f"view_history_{i}"):
+ st.session_state.current_result = item['result']
+ st.rerun()
+ st.markdown('
', unsafe_allow_html=True)
+
+ # Compare tab for side-by-side comparison
+ with compare_tab:
+ st.subheader("Compare OCR Results")
+
+ if len(st.session_state.processing_history) >= 2:
+ st.markdown("""
+ Select two processing results to compare side by side. This allows you to see
+ how different options (like using the vision model) affect OCR quality.
+ """)
+
+ # Create selection dropdowns for the documents
+ col1, col2 = st.columns(2)
+ with col1:
+ # First document selector
+ doc_options_1 = [f"{i+1}: {item['fileName']} ({'Vision' if item['useVision'] else 'No Vision'})"
+ for i, item in enumerate(st.session_state.processing_history)]
+ doc_choice_1 = st.selectbox("First Document:", doc_options_1, key="compare_doc_1")
+ doc_index_1 = int(doc_choice_1.split(":")[0]) - 1
+
+ with col2:
+ # Second document selector
+ doc_options_2 = [f"{i+1}: {item['fileName']} ({'Vision' if item['useVision'] else 'No Vision'})"
+ for i, item in enumerate(st.session_state.processing_history)]
+ default_index = min(1, len(st.session_state.processing_history) - 1) # Default to second item
+ doc_choice_2 = st.selectbox("Second Document:", doc_options_2, key="compare_doc_2", index=default_index)
+ doc_index_2 = int(doc_choice_2.split(":")[0]) - 1
+
+ # Retrieve the selected documents
+ doc1 = st.session_state.processing_history[doc_index_1]
+ doc2 = st.session_state.processing_history[doc_index_2]
+
+ # Show comparison
+ col1, col2 = st.columns(2)
+
+ with col1:
+ st.markdown(f"### Document 1: {doc1['fileName']}")
+ st.markdown(f"**Processed at:** {datetime.fromisoformat(doc1['timestamp']).strftime('%Y-%m-%d %H:%M')}")
+ st.markdown(f"**Vision model used:** {'Yes' if doc1['useVision'] else 'No'}")
+
+ # Display content summary
+ if 'ocr_contents' in doc1['result'] and isinstance(doc1['result']['ocr_contents'], dict):
+ if 'content' in doc1['result']['ocr_contents']:
+ content = doc1['result']['ocr_contents']['content']
+ # Display first 500 characters with word wrap
+ st.markdown(f"""
+
+ {content[:500]}{'...' if len(content) > 500 else ''}
+
+ """, unsafe_allow_html=True)
+
+ with col2:
+ st.markdown(f"### Document 2: {doc2['fileName']}")
+ st.markdown(f"**Processed at:** {datetime.fromisoformat(doc2['timestamp']).strftime('%Y-%m-%d %H:%M')}")
+ st.markdown(f"**Vision model used:** {'Yes' if doc2['useVision'] else 'No'}")
+
+ # Display content summary
+ if 'ocr_contents' in doc2['result'] and isinstance(doc2['result']['ocr_contents'], dict):
+ if 'content' in doc2['result']['ocr_contents']:
+ content = doc2['result']['ocr_contents']['content']
+ # Display first 500 characters with word wrap
+ st.markdown(f"""
+
+ {content[:500]}{'...' if len(content) > 500 else ''}
+
+ """, unsafe_allow_html=True)
+
+ # Comparison analysis
+ if doc1['fileName'] == doc2['fileName'] and doc1['useVision'] != doc2['useVision']:
+ st.markdown("""
+
+
Vision vs. Non-Vision Model Comparison
+
You're comparing the same document processed with different models.
+ This is an excellent way to evaluate the impact of vision capabilities on OCR accuracy.
Process at least two documents to enable side-by-side comparison. Try processing
+ the same document with and without the vision model to see the differences in OCR quality.
+
+ """, unsafe_allow_html=True)
+
+ # Analysis tab for guidance on working with OCR results
+ with analyze_tab:
+ st.subheader("Analysis Guide")
+
+ st.markdown("""
+
+
How to Analyze OCR Results
+
This guide helps you assess the quality and usefulness of OCR output for historical research.
+
+ """, unsafe_allow_html=True)
+
+ st.markdown("""
+ ### Evaluating OCR Quality
+
+ When analyzing OCR results from historical documents, consider these key factors:
+
+ 1. **Text Accuracy**
+ - Check for common OCR errors (e.g., mistaking "e" for "c", "l" for "1")
+ - Assess recognition of period-specific typography and writing styles
+ - Evaluate handling of degraded or damaged text areas
+
+ 2. **Structure Preservation**
+ - Does the OCR maintain paragraph and section breaks?
+ - Are columns and tabular data correctly preserved?
+ - How well are page transitions handled?
+
+ 3. **Special Elements**
+ - Recognition of footnotes, marginalia, and annotations
+ - Handling of illustrations, diagrams, and decorative elements
+ - Treatment of watermarks, signatures, and stamps
+
+ 4. **Metadata Extraction**
+ - Accuracy of detected languages, topics, and document type
+ - Identification of dates, names, and key entities
+ - Recognition of document purpose and context
+ """)
+
+ col1, col2 = st.columns(2)
+
+ with col1:
+ st.markdown("""
+ ### Common OCR Challenges in Historical Documents
+
+ - **Typography Variations**: Historical fonts and writing styles that differ from modern text
+ - **Material Degradation**: Fading, stains, tears, and other damage affecting legibility
+ - **Handwritten Elements**: Marginalia, signatures, and handwritten annotations
+ - **Complex Layouts**: Multi-column formats, non-linear reading order, and decorative elements
+ - **Language and Terminology**: Archaic terms, specialized vocabulary, and multilingual content
+ """)
+
+ with col2:
+ st.markdown("""
+ ### Making the Most of OCR Results
+
+ - **Contextual Reading**: Use historical context to interpret unclear passages
+ - **Error Patterns**: Identify and mentally correct for systematic OCR errors
+ - **Hybrid Analysis**: Combine OCR-based search with close reading of original images
+ - **Comparative Processing**: Try different OCR settings and models on the same document
+ - **Iterative Refinement**: Use insights from each document to improve future processing
+ """)
+
+ st.markdown("""
+ ### Research Integration
+
+ Once you've obtained and analyzed OCR results from historical documents, consider these approaches for integrating them into your research:
+
+ 1. **Digital Corpus Building**: Create searchable collections of processed texts
+ 2. **Computational Analysis**: Apply text mining, topic modeling, or network analysis
+ 3. **Cross-Document Linking**: Identify connections across multiple sources
+ 4. **Annotation and Enrichment**: Add context, translations, or explanatory notes
+ 5. **Collaborative Research**: Share processed texts with other researchers
+
+ Remember that OCR is a tool to assist your research, not replace careful reading and analysis. The most effective approaches combine computational methods with traditional historical research practices.
+ """)
+
+ # Example of what to look for
+ if st.session_state.processing_history:
+ with st.expander("Example Analysis from Your Documents"):
+ # Pick the latest document
+ latest = st.session_state.processing_history[-1]
+
+ st.markdown(f"""
+ #### Sample Analysis for: {latest['fileName']}
+
+ **Document Context:**
+ - Languages: {', '.join(latest['result'].get('languages', ['Unknown']))}
+ - Topics: {', '.join(latest['result'].get('topics', ['Unknown']))}
+ - Vision model used: {'Yes' if latest['useVision'] else 'No'}
+
+ **What to Look For:**
+ 1. Check how well the model identified key topics and languages
+ 2. Evaluate the completeness of extracted text
+ 3. Note any systematic errors in text recognition
+ 4. Assess how well document structure was preserved
+ """)
+
+ # Navigation buttons
+ col1, col2 = st.columns(2)
+ with col1:
+ st.button("Previous: Methodological Approaches", key="prev_to_4", on_click=navigate_to_module, args=(4,))
+
+ with col2:
+ st.button("Next: Conclusion", key="next_to_6", on_click=navigate_to_module, args=(6,))
+
+ else: # Module 6
+ # MODULE 6: Conclusion
+ st.title("Module 6: Conclusion and Future Directions")
+
+ col1, col2 = st.columns([3, 2])
+
+ with col1:
+ st.markdown("""
+ ### Workshop Summary
+
+ Throughout this workshop, we've explored:
+
+ 1. **Text-Image Interdependence**: The complex relationship between textual and visual elements
+ 2. **OCR Technology**: The evolution of OCR and its application to historical materials
+ 3. **Methodological Approaches**: Hybrid strategies for working with historical texts
+ 4. **Practical Application**: Hands-on experience with OCR processing tools
+ """)
+
+ st.markdown("""
+ ### Key Takeaways
+
+ 1. **OCR is Not Perfect**: Even advanced AI models face challenges with historical documents
+ 2. **Context Matters**: Vision-enhanced models provide better results by understanding document context
+ 3. **Hybrid Approaches**: Combining computational methods with traditional research yields best results
+ 4. **Critical Evaluation**: Always evaluate OCR outputs with awareness of limitations
+ 5. **Structured Extraction**: Modern OCR goes beyond text recognition to understand document structure
+ """)
+
+ with col2:
+ # Display statistics if there's processing history
+ if st.session_state.processing_history:
+ st.subheader("Your Workshop Statistics")
+
+ # Calculate statistics
+ total_docs = len(st.session_state.processing_history)
+ vision_docs = len([item for item in st.session_state.processing_history if item['useVision']])
+ non_vision_docs = total_docs - vision_docs
+
+ # Create metrics for statistics
+ col1, col2 = st.columns(2)
+
+ with col1:
+ st.metric("Documents Processed", total_docs)
+ st.metric("With Vision Model", vision_docs)
+
+ with col2:
+ st.metric("Without Vision Model", non_vision_docs)
+
+ # Topics word cloud
+ if total_docs > 0:
+ st.subheader("Topics Encountered")
+ all_topics = []
+ for item in st.session_state.processing_history:
+ if 'topics' in item['result']:
+ all_topics.extend(item['result']['topics'])
+
+ if all_topics:
+ # Count topic frequencies
+ topic_counts = {}
+ for topic in all_topics:
+ if topic in topic_counts:
+ topic_counts[topic] += 1
+ else:
+ topic_counts[topic] = 1
+
+ # Display as a horizontal bar chart
+ st.bar_chart(topic_counts)
+
+ st.subheader("Future Directions")
+
+ col1, col2 = st.columns(2)
+
+ with col1:
+ st.markdown("""
+ ### Technological Developments
+
+ - **Multimodal AI models**: Increasingly sophisticated understanding
+ - **Historical font training**: Models trained on historical typography
+ - **Document intelligence**: Enhanced understanding of structures
+ - **Collaborative correction**: Platforms for collective improvement
+ """)
+
+ with col2:
+ st.markdown("""
+ ### Research Applications
+
+ - **Large-scale corpus analysis**: Processing entire archives
+ - **Multilingual historical research**: Working across languages
+ - **Image-text integration**: New methodologies for visual analysis
+ - **Computational paleography**: AI-assisted handwriting analysis
+ """)
+
+ st.markdown("""
+ ### Additional Resources
+
+ - **[Mistral AI Documentation](https://docs.mistral.ai/)**: Learn more about the OCR models used in this workshop
+ - **[Transkribus](https://readcoop.eu/transkribus/)**: Platform for historical document transcription
+ - **[OCR-D](https://ocr-d.de/en/)**: Coordinated OCR research project for historical documents
+ - **[Historical OCR Research Papers](https://scholar.google.com/scholar?q=historical+OCR)**: Academic research on historical OCR
+ """)
+
+ # Reset button to start over
+ if st.button("Start Workshop Again", key="reset_workshop", use_container_width=True):
+ st.session_state.current_module = 1
+ st.rerun()
+
+# Handle API requests if the URL contains /api/process
+if 'api/process' in st.query_params.get('', ''):
+ # Process the API request
+ result = process_api_request()
+ if result:
+ # Return the result as JSON
+ # Make sure result is serializable
+ serializable_result = make_serializable(result)
+ st.json(serializable_result)
+ else:
+ st.json({"error": "Invalid request"})
\ No newline at end of file
diff --git a/streamlit_app.py b/streamlit_app.py
new file mode 100644
index 0000000000000000000000000000000000000000..f941891f42dbc951d73a962a9b7f445e166ef07e
--- /dev/null
+++ b/streamlit_app.py
@@ -0,0 +1,2028 @@
+import os
+import streamlit as st
+import json
+import sys
+from pathlib import Path
+import tempfile
+from datetime import datetime
+import io
+import base64
+from io import BytesIO
+from enum import Enum
+import inspect
+
+# Add parent directory to path so we can import the OCR modules
+parent_dir = Path(__file__).parent.absolute()
+sys.path.append(str(parent_dir))
+
+# Import the StructuredOCR class and process_file utility
+from structured_ocr import StructuredOCR
+
+# Add API endpoint support for the React app
+from streamlit.web.server.server import Server
+from streamlit.runtime.scriptrunner import get_script_run_ctx
+
+# Custom JSON encoder to handle Enum types and other non-serializable objects
+class EnhancedJSONEncoder(json.JSONEncoder):
+ def default(self, obj):
+ if isinstance(obj, Enum):
+ return obj.value
+ elif hasattr(obj, '__dict__'):
+ # For objects that have a __dict__ but aren't directly serializable
+ return {key: value for key, value in obj.__dict__.items()
+ if not key.startswith('_')}
+ elif hasattr(obj, 'model_dump'):
+ # For Pydantic models
+ return obj.model_dump()
+ elif hasattr(obj, 'to_dict'):
+ # For objects with to_dict method
+ return obj.to_dict()
+ # Let the base class handle other types or raise TypeError
+ return super().default(obj)
+
+# Helper function to convert any result to JSON-serializable
+def make_serializable(obj):
+ """Convert any object to a JSON-serializable form"""
+ if isinstance(obj, dict):
+ return {k: make_serializable(v) for k, v in obj.items()}
+ elif isinstance(obj, list):
+ return [make_serializable(item) for item in obj]
+ elif isinstance(obj, Enum):
+ return obj.value
+ elif hasattr(obj, 'pages'):
+ # Special case for OCRResponse objects which have pages attribute
+ if hasattr(obj, '__dict__'):
+ result = {k: make_serializable(v) for k, v in obj.__dict__.items()
+ if not k.startswith('_')}
+ # Explicitly handle pages attribute
+ if hasattr(obj, 'pages'):
+ result['pages'] = [make_serializable(page) for page in obj.pages]
+ return result
+ elif hasattr(obj, '__dict__'):
+ # For objects with __dict__ attribute
+ return {k: make_serializable(v) for k, v in obj.__dict__.items()
+ if not k.startswith('_')}
+ elif hasattr(obj, 'model_dump'):
+ # For Pydantic models
+ return make_serializable(obj.model_dump())
+ elif hasattr(obj, 'to_dict'):
+ # For objects with to_dict method
+ return make_serializable(obj.to_dict())
+ # Basic types will be returned as is
+ return obj
+
+# API response handler
+def process_api_request():
+ """Handle API requests from the React frontend"""
+ # Get the current Streamlit session
+ ctx = get_script_run_ctx()
+ if ctx is None:
+ return
+
+ session_id = ctx.session_id
+ session_info = Server.get_current()._get_session_info(session_id)
+
+ if session_info is None:
+ return
+
+ request = session_info.uploaded_file_mgr._uploaded_files.get('file')
+
+ if not request:
+ return
+
+ # Extract file and parameters
+ uploaded_file = request[0]
+ use_vision = session_info.query_string.get('use_vision', ['true'])[0].lower() == 'true'
+
+ try:
+ # Process file
+ result = process_file(uploaded_file, use_vision=use_vision)
+
+ # Convert result to JSON-serializable format
+ serializable_result = make_serializable(result)
+
+ # Return JSON response
+ return serializable_result
+ except Exception as e:
+ # Return error response
+ return {"error": str(e)}
+
+try:
+ from process_file import process_file as process_file_util
+ # Use the utility function instead of the local function
+ process_file = process_file_util
+except ImportError:
+ # Define the process_file function if it's not available
+ def process_file(uploaded_file, use_vision=True):
+ """Process the uploaded file and return the OCR results"""
+ # Save the uploaded file to a temporary file
+ with tempfile.NamedTemporaryFile(delete=False, suffix=Path(uploaded_file.name).suffix) as tmp:
+ tmp.write(uploaded_file.getvalue())
+ temp_path = tmp.name
+
+ try:
+ # Initialize OCR processor
+ processor = StructuredOCR()
+
+ # Determine file type from extension
+ file_ext = Path(uploaded_file.name).suffix.lower()
+ file_type = "pdf" if file_ext == ".pdf" else "image"
+
+ # Process the file
+ result = processor.process_file(temp_path, file_type=file_type, use_vision=use_vision)
+
+ # Add to processing history
+ history_item = {
+ "id": datetime.now().timestamp(),
+ "fileName": uploaded_file.name,
+ "timestamp": datetime.now().isoformat(),
+ "result": result,
+ "useVision": use_vision
+ }
+
+ if 'processing_history' not in st.session_state:
+ st.session_state.processing_history = []
+
+ st.session_state.processing_history.append(history_item)
+
+ return result
+ except Exception as e:
+ st.error(f"Error processing document: {str(e)}")
+ return None
+ finally:
+ # Clean up the temporary file
+ if os.path.exists(temp_path):
+ os.unlink(temp_path)
+
+# Set page configuration
+st.set_page_config(
+ page_title="Historical OCR Workshop",
+ page_icon="📜",
+ layout="wide",
+ initial_sidebar_state="collapsed" # Start with sidebar collapsed for cleaner landing
+)
+
+# Custom CSS to match React dark theme and improve Streamlit integration
+st.markdown("""
+
+""", unsafe_allow_html=True)
+
+# Initialize session state for workshop progress
+if 'current_module' not in st.session_state:
+ st.session_state.current_module = 1
+if 'processing_history' not in st.session_state:
+ st.session_state.processing_history = []
+if 'workshop_started' not in st.session_state:
+ st.session_state.workshop_started = False
+
+def navigate_to_module(module_number):
+ """Navigate to a specific module"""
+ st.session_state.current_module = module_number
+
+# Welcome/Start screen if workshop hasn't been started
+if not st.session_state.workshop_started:
+ # Hero section with eye-catching design
+ st.markdown("""
+
+
Historical OCR Workshop
+
Unlock the potential of historical documents with modern OCR technology
+
+ This interactive workshop explores the application of OCR technology to historical documents,
+ combining theoretical understanding with practical experiences. Designed for historians,
+ archivists, and digital humanities scholars, it offers both conceptual frameworks and hands-on skills.
+
+ Optical Character Recognition (OCR) technology enables computers to extract text from images and documents.
+ Modern OCR uses AI vision models to understand both the text and its visual context, making it powerful for
+ historical research and digital humanities.
+
+ """, unsafe_allow_html=True)
+
+ with col2:
+ # Add an engaging research question to connect with historians
+ st.markdown("""
+
+
For Historians:
+ How might OCR technology transform our access to and interpretation of historical documents?
+ What new research questions become possible when large archives become machine-readable?
+
+ """, unsafe_allow_html=True)
+
+ # Display sample historical document images in a better format
+ input_dir = Path(__file__).parent / "input"
+ sample_images = [
+ {"path": input_dir / "letter-1.jpg", "caption": "Historical Letter"},
+ {"path": input_dir / "recipe.jpg", "caption": "Historical Recipe"}
+ ]
+
+ # Try to find any of the sample images
+ for sample in sample_images:
+ if sample["path"].exists():
+ try:
+ from PIL import Image
+ with Image.open(sample["path"]) as img:
+ # Add a better styled border and shadow
+ st.markdown(f"""
+
+
+ - Processing historical documents with OCR
+ - Analyzing and structuring extracted information
+ - Integrating OCR into research workflows
+ - Building searchable archives
+
+ """, unsafe_allow_html=True)
+
+ with col2:
+ # Workshop modules with visually appealing cards
+ st.markdown('
Workshop Modules
', unsafe_allow_html=True)
+
+ # Add some styling for the module cards
+ st.markdown("""
+
+ """, unsafe_allow_html=True)
+
+ # Modules inside the col2 from earlier
+ with col2:
+ st.markdown("""
+
+
Module 1
+
Introduction and Problematization
+
Explore the challenges of historical document digitization and the potential of OCR technologies
+ to transform historical research. Examine key problems and opportunities in historical OCR.
Understand the evolution of OCR technology from pattern matching to modern AI vision-language models,
+ and how they address the unique challenges of historical documents.
Develop hybrid methodologies that combine computational processing with traditional
+ historical research practices, balancing distant and close reading.
Synthesize workshop insights and explore future directions for OCR in historical research,
+ from large-scale corpus analysis to computational paleography.
+
+ """, unsafe_allow_html=True)
+
+ # Engaging quote to inspire participation with citation - in a better styled container
+ st.markdown("""
+
+
"
+
+
The digital turn in historical research is not just about converting analog to digital;
+ it's about transforming how we access, analyze, and interpret the past.
+
+
— Dr. Jane Winters, Professor of Digital Humanities
+
+ """, unsafe_allow_html=True)
+
+ # Feature highlight before call to action with better styling
+ st.markdown("""
+
+
+
+
Workshop Highlights
+
+
+
+
Interactive Learning
+
Hands-on document processing with real-time feedback and analysis
+
+
+
+
Real Historical Documents
+
Work with authentic materials spanning different eras and formats
+
+
+
+
Vision AI Models
+
Experience state-of-the-art OCR technology powered by advanced AI
+
+
+
+
Research Applications
+
Learn to integrate OCR into historical research workflows
+
+
+
+ """, unsafe_allow_html=True)
+
+ # Enhanced start button with dynamic styling and clear call to action
+ st.markdown("""
+
+
Ready to Start Your Journey?
+
+
+
+
No installation required • Start immediately • Interactive experience
+
+
+
+ """, unsafe_allow_html=True)
+
+ # Hidden button to trigger the workshop start
+ col1, col2, col3 = st.columns([1, 1, 1])
+ with col2:
+ if st.button("Begin Workshop", key="streamlit-button", use_container_width=True, type="primary"):
+ st.session_state.workshop_started = True
+ st.rerun()
+
+# Display workshop navigation sidebar only if workshop has started
+elif st.session_state.workshop_started:
+ # Define input directory for images
+ input_dir = Path(__file__).parent / "input"
+
+ # Enhanced sidebar navigation
+ with st.sidebar:
+ st.markdown("
+ """, unsafe_allow_html=True)
+
+ # Hidden button for jump
+ if st.button("Jump to Experiment", key="jump_exp"):
+ navigate_to_module(5)
+ st.rerun()
+
+ # Workshop information in a cleaner collapsible section
+ with st.expander("About the Workshop"):
+ st.markdown("""
+ This interactive workshop explores OCR technology for historical documents.
+
+ **How to use this workshop:**
+ 1. Navigate through modules sequentially
+ 2. Expand content sections to read more
+ 3. Try the interactive OCR experiment
+ 4. Reflect on research questions
+
+ For help or more information, use the reference materials in Module 6.
+ """)
+
+ # Enhanced progress tracking
+ if st.session_state.processing_history:
+ with st.expander("Your Activity"):
+ st.markdown(f"Documents processed: {len(st.session_state.processing_history)}", unsafe_allow_html=True)
+
+ # Show the most recent document processed with better formatting
+ latest = st.session_state.processing_history[-1]
+ st.markdown(f"""
+
+ Latest document: {latest['fileName']}
+ Processed with {' vision model' if latest['useVision'] else ' basic OCR'}
+
+ """, unsafe_allow_html=True)
+
+ # Main content based on current module
+ if st.session_state.current_module == 1:
+ # MODULE 1: Introduction
+ st.title("Module 1: Introduction and Problematization")
+
+ col1, col2 = st.columns([2, 1])
+
+ with col1:
+ st.markdown("""
+ ## Historical OCR Workshop
+
+ ### The Problem
+
+ Historical documents present unique challenges for OCR technology:
+
+ - Varying typography and handwriting styles
+ - Document degradation and damage
+ - Complex layouts and formatting
+ - Multiple languages and archaic terminology
+ - Illustrations and decorative elements
+ """)
+
+ with col2:
+ st.markdown("""
+ ### Workshop Goals
+
+ By the end of this workshop, you will:
+
+ 1. Understand text-image relationships in historical archives
+ 2. Learn about advanced OCR technology
+ 3. Explore methodological approaches
+ 4. Gain hands-on experience with OCR tools
+ 5. Develop research integration strategies
+ """)
+
+ # Next button
+ st.button("Next: Text-Image Relations", key="next_to_2", on_click=navigate_to_module, args=(2,))
+
+ elif st.session_state.current_module == 2:
+ # MODULE 2: Text-Image Relations
+ st.title("Module 2: Text-Image Relations in Historical Archives")
+
+ col1, col2 = st.columns([1, 1])
+
+ with col1:
+ st.markdown("""
+ ### Textual Elements
+
+ - **Typography**: Varying fonts, sizes, and styles
+ - **Layout**: Columns, margins, and spacing
+ - **Marginalia**: Notes, comments, and additions
+ - **Decorative Text**: Illuminated letters and calligraphy
+ """)
+
+ st.markdown("""
+ ### Visual Elements
+
+ - **Illustrations**: Diagrams, maps, and artistic representations
+ - **Watermarks**: Hidden identifiers that locate documents
+ - **Damage**: Tears, stains, and fading affecting legibility
+ - **Material Features**: Paper quality and physical dimensions
+ """)
+
+ with col2:
+ st.markdown("""
+ ### Interdependence
+
+ The relationship between text and image in historical documents exists on a complex spectrum:
+
+ - Text functions as image (decorative headings)
+ - Images function as text (symbolic representations)
+ - Layout creates meaning through visual organization
+ - Material conditions affect both textual and visual elements
+ """)
+
+ st.image("https://upload.wikimedia.org/wikipedia/commons/thumb/0/0c/Book_of_Kells_folio_292r.jpg/800px-Book_of_Kells_folio_292r.jpg",
+ caption="Book of Kells - Example of text-image integration")
+
+ st.markdown("""
+ ### OCR Challenges
+
+ These complex text-image relationships create particular challenges for OCR:
+
+ 1. **Distinguishing Text from Decoration**: Where does ornamental text end and functional text begin?
+ 2. **Handling Illustrations**: Should they be processed as images or described as text?
+ 3. **Interpreting Layout**: How to capture the significance of spacing and organization?
+ 4. **Preserving Context**: Maintaining the relationship between textual and visual elements
+ """)
+
+ # Navigation buttons
+ col1, col2 = st.columns(2)
+ with col1:
+ st.button("Previous: Introduction", key="prev_to_1", on_click=navigate_to_module, args=(1,))
+
+ with col2:
+ st.button("Next: OCR Technology", key="next_to_3", on_click=navigate_to_module, args=(3,))
+
+ elif st.session_state.current_module == 3:
+ # MODULE 3: OCR Technology
+ st.title("Module 3: OCR Technology and Historical Documents")
+
+ col1, col2 = st.columns([1, 1])
+
+ with col1:
+ st.markdown("""
+ ### Traditional OCR Approaches
+
+ 1. **Pattern Matching**: Early OCR compared characters to templates
+ 2. **Feature Extraction**: Identifying key features of characters
+ 3. **Statistical Models**: Using probabilities to improve recognition
+ """)
+
+ st.markdown("""
+ ### Modern AI-Enhanced OCR
+
+ 1. **Neural Networks**: Deep learning models trained on vast datasets
+ 2. **Computer Vision**: Advanced image processing techniques
+ 3. **Language Models**: Contextual understanding to resolve ambiguities
+ 4. **Multimodal Models**: Integration of text, layout, and visual understanding
+ """)
+
+ with col2:
+ st.markdown("""
+ ### Challenges with Historical Documents
+
+ Historical materials present unique difficulties:
+
+ - **Typography Variation**: Non-standardized fonts and styles
+ - **Historical Language**: Archaic vocabulary and grammar
+ - **Layout Complexity**: Non-linear arrangements
+ - **Document Degradation**: Fading, tears, stains, and damage
+ - **Material Artifacts**: Paper texture, binding shadows, etc.
+ """)
+
+ st.image("https://cdn.dribbble.com/users/412119/screenshots/16353886/media/82e593c60a5e4d460db917236eab6ece.jpg",
+ caption="OCR processing layers")
+
+ # Display processing history if available
+ if st.session_state.processing_history:
+ with st.expander("Your OCR Processing History"):
+ st.markdown("You've already processed the following documents:")
+
+ for item in st.session_state.processing_history:
+ st.markdown(f"**{item['fileName']}** - {datetime.fromisoformat(item['timestamp']).strftime('%Y-%m-%d %H:%M')}")
+ col1, col2 = st.columns(2)
+ with col1:
+ st.write(f"**Topics:** {', '.join(item['result'].get('topics', ['Unknown']))}")
+ with col2:
+ st.write(f"**Vision model used:** {'Yes' if item['useVision'] else 'No'}")
+
+ # Quick link to experiment
+ st.button("Jump to OCR Experiment", key="jump_to_5", on_click=navigate_to_module, args=(5,))
+
+ # Navigation buttons
+ col1, col2 = st.columns(2)
+ with col1:
+ st.button("Previous: Text-Image Relations", key="prev_to_2", on_click=navigate_to_module, args=(2,))
+
+ with col2:
+ st.button("Next: Methodological Approaches", key="next_to_4", on_click=navigate_to_module, args=(4,))
+
+ elif st.session_state.current_module == 4:
+ # MODULE 4: Methodological Approaches
+ st.title("Module 4: Methodological Approaches")
+
+ col1, col2 = st.columns([1, 1])
+
+ with col1:
+ st.markdown("""
+ ### Hybrid Methodologies
+
+ 1. **Computational + Human Reading**
+ - OCR for initial processing and discovery
+ - Human review for context and interpretation
+ - Iterative refinement of computational outputs
+
+ 2. **Close + Distant Reading**
+ - Distant reading through large-scale OCR processing
+ - Close reading of selected passages
+ - Zooming between scales of analysis
+ """)
+
+ # Reference to diagram.jpg
+ input_dir = Path(__file__).parent / "input"
+ diagram_path = input_dir / "diagram.jpg"
+ if diagram_path.exists():
+ try:
+ # Load image file directly from disk
+ from PIL import Image
+ with Image.open(diagram_path) as img:
+ st.image(img, caption="Historical VLM architecture", use_column_width=True)
+ except Exception:
+ pass
+
+ with col2:
+ st.markdown("""
+ ### Mistral-OCR-Latest: State-of-the-Art
+
+ The Mistral-OCR model represents a significant advancement:
+
+ - **Multimodal Understanding**: Processes both visual and textual information
+ - **Contextual Awareness**: Considers historical context
+ - **Layout Recognition**: Preserves complex document structures
+ - **Historical Font Adaptation**: Trained on diverse historical typography
+ """)
+
+ # Reference to workflow.jpg
+ workflow_path = input_dir / "workflow.jpg"
+ if workflow_path.exists():
+ try:
+ # Load image file directly from disk
+ from PIL import Image
+ with Image.open(workflow_path) as img:
+ st.image(img, caption="Mistral OCR workflow", use_column_width=True)
+ except Exception:
+ pass
+
+ st.markdown("""
+ ### Practical Workflow
+
+ A typical historical OCR workflow with Mistral-OCR includes:
+
+ 1. **Selection**: Choosing appropriate documents
+ 2. **Preprocessing**: Enhancing images before OCR
+ 3. **OCR Processing**: Running documents through vision-enhanced OCR
+ 4. **Post-processing**: Cleaning up outputs and structured extraction
+ 5. **Verification**: Cross-checking results against originals
+ 6. **Integration**: Incorporating OCR outputs into research materials
+ """)
+
+ # Navigation buttons
+ col1, col2 = st.columns(2)
+ with col1:
+ st.button("Previous: OCR Technology", key="prev_to_3", on_click=navigate_to_module, args=(3,))
+
+ with col2:
+ st.button("Next: Interactive OCR Experiment", key="next_to_5", on_click=navigate_to_module, args=(5,))
+
+ elif st.session_state.current_module == 5:
+ # MODULE 5: Interactive OCR Experiment
+ st.title("Module 5: Interactive OCR Experiment")
+
+ # More modular design with sequenced steps
+ st.markdown("""
+
+ This interactive module allows you to process historical documents with OCR and analyze the results.
+ Follow the sequenced steps below to experiment with historical document analysis.
+
+ """, unsafe_allow_html=True)
+
+ # Tabbed interface for different activities
+ experiment_tab, compare_tab, analyze_tab = st.tabs(["Process Documents", "Compare Results", "Analysis Guide"])
+
+ with experiment_tab:
+ # Import additional libraries for enhanced functionality
+ try:
+ from pdf2image import convert_from_bytes
+ pdf_support = True
+ except ImportError:
+ pdf_support = False
+ st.warning("PDF preview functionality is limited. The pdf2image module is required for PDF previews.")
+
+ # OCR tool in a compact layout
+ col1, col2 = st.columns([1, 1])
+
+ with col1:
+ st.markdown('
', unsafe_allow_html=True)
+ st.markdown("
Step 1: Select Document & Options
", unsafe_allow_html=True)
+
+ # Processing options
+ use_vision = st.checkbox("Use Vision Model", value=True,
+ help="Use the vision model for improved analysis")
+
+ # Additional prompt for the model
+ st.markdown("### Custom Research Prompt (Optional)")
+ st.markdown("""Provide additional instructions to guide the OCR analysis.
+ Focus on specific aspects of historical research you're interested in.""")
+ custom_prompt = st.text_area("Research Prompt",
+ placeholder="E.g., Focus on identifying dates and historical figures; Analyze the writing style for period-specific terminology; Highlight any cultural or social indicators of the time period...",
+ help="Optional instructions to guide the analysis of the historical document")
+
+ # Example preset documents selection
+ input_dir = Path(__file__).parent / "input"
+ if input_dir.exists():
+ sample_files = list(input_dir.glob("*.jpg")) + list(input_dir.glob("*.png")) + list(input_dir.glob("*.pdf"))
+ if sample_files:
+ st.markdown("#### Sample Documents")
+ sample_options = ["Upload my own document"] + [f.name for f in sample_files]
+ sample_choice = st.selectbox("Choose a document:", sample_options)
+
+ if sample_choice != "Upload my own document":
+ selected_file = next((f for f in sample_files if f.name == sample_choice), None)
+ if selected_file:
+ # Store the selected sample file in session state
+ with open(selected_file, "rb") as f:
+ file_bytes = f.read()
+ st.session_state.sample_file = {
+ "name": selected_file.name,
+ "bytes": file_bytes
+ }
+
+ # Preview the selected sample
+ if selected_file.suffix.lower() == ".pdf" and pdf_support:
+ try:
+ with st.spinner("Generating PDF preview..."):
+ images = convert_from_bytes(file_bytes, first_page=1, last_page=1, dpi=150)
+ if images:
+ # Convert PIL image to bytes for Streamlit
+ first_page = images[0]
+ img_bytes = io.BytesIO()
+ first_page.save(img_bytes, format='JPEG')
+ img_bytes.seek(0)
+ st.image(img_bytes, caption=f"Preview: {selected_file.name}", use_container_width=True)
+ except Exception:
+ st.info(f"PDF selected: {selected_file.name}")
+ else:
+ # For images display directly
+ try:
+ from PIL import Image
+ img = Image.open(BytesIO(file_bytes))
+ st.image(img, caption=f"Preview: {selected_file.name}", use_container_width=True)
+ except Exception:
+ st.info(f"Selected: {selected_file.name}")
+ else:
+ # Clear the sample file if "Upload my own" is selected
+ if 'sample_file' in st.session_state:
+ del st.session_state.sample_file
+
+ # File uploader with styling matched to React theme
+ st.markdown('
', unsafe_allow_html=True)
+ uploaded_file = st.file_uploader("Choose a file", type=["pdf", "png", "jpg", "jpeg"], label_visibility="collapsed")
+
+ if uploaded_file is None:
+ st.markdown("### Upload a document to get started")
+ st.markdown("Supported formats: PDF, JPG, PNG")
+ else:
+ # Display the uploaded file
+ file_ext = Path(uploaded_file.name).suffix.lower()
+
+ if file_ext == ".pdf" and pdf_support:
+ try:
+ # Convert first page of PDF to image for preview
+ pdf_bytes = uploaded_file.getvalue()
+ with st.spinner("Generating PDF preview..."):
+ images = convert_from_bytes(pdf_bytes, first_page=1, last_page=1, dpi=150)
+
+ if images:
+ # Convert PIL image to bytes for Streamlit
+ first_page = images[0]
+ img_bytes = io.BytesIO()
+ first_page.save(img_bytes, format='JPEG')
+ img_bytes.seek(0)
+
+ # Display the PDF preview
+ st.image(img_bytes, caption=f"PDF Preview: {uploaded_file.name}", use_container_width=True)
+ else:
+ st.info(f"PDF uploaded: {uploaded_file.name}")
+ except Exception:
+ # Simply show the file name without an error message
+ st.info(f"PDF uploaded: {uploaded_file.name}")
+ elif file_ext != ".pdf":
+ st.image(uploaded_file, use_container_width=True)
+ else:
+ st.info(f"PDF uploaded: {uploaded_file.name}")
+
+ st.markdown('
', unsafe_allow_html=True)
+ else:
+ # No sample files found, just show the uploader
+ st.markdown('
', unsafe_allow_html=True)
+ uploaded_file = st.file_uploader("Choose a file", type=["pdf", "png", "jpg", "jpeg"], label_visibility="collapsed")
+
+ if uploaded_file is None:
+ st.markdown("### Upload a document to get started")
+ st.markdown("Supported formats: PDF, JPG, PNG")
+ else:
+ # Display the uploaded file preview
+ file_ext = Path(uploaded_file.name).suffix.lower()
+ if file_ext == ".pdf" and pdf_support:
+ try:
+ # PDF preview logic
+ pdf_bytes = uploaded_file.getvalue()
+ with st.spinner("Generating PDF preview..."):
+ images = convert_from_bytes(pdf_bytes, first_page=1, last_page=1, dpi=150)
+ if images:
+ first_page = images[0]
+ img_bytes = io.BytesIO()
+ first_page.save(img_bytes, format='JPEG')
+ img_bytes.seek(0)
+ st.image(img_bytes, caption=f"PDF Preview: {uploaded_file.name}", use_container_width=True)
+ else:
+ st.info(f"PDF uploaded: {uploaded_file.name}")
+ except Exception:
+ st.info(f"PDF uploaded: {uploaded_file.name}")
+ elif file_ext != ".pdf":
+ st.image(uploaded_file, use_container_width=True)
+ else:
+ st.info(f"PDF uploaded: {uploaded_file.name}")
+ st.markdown('
', unsafe_allow_html=True)
+ else:
+ # Input directory doesn't exist, just show the uploader
+ st.markdown('
', unsafe_allow_html=True)
+ uploaded_file = st.file_uploader("Choose a file", type=["pdf", "png", "jpg", "jpeg"], label_visibility="collapsed")
+
+ if uploaded_file is None:
+ st.markdown("### Upload a document to get started")
+ st.markdown("Supported formats: PDF, JPG, PNG")
+ st.markdown('
', unsafe_allow_html=True)
+
+ # Step 2: Process document
+ st.subheader("Step 2: Process the Document")
+
+ # Get the file to process (either uploaded or sample)
+ file_to_process = None
+ if 'sample_file' in st.session_state and sample_choice != "Upload my own document":
+ # Create a FileUploader-like object from the sample file
+ class SampleFileObject:
+ def __init__(self, name, data):
+ self.name = name
+ self._data = data
+
+ def getvalue(self):
+ return self._data
+
+ file_to_process = SampleFileObject(
+ st.session_state.sample_file["name"],
+ st.session_state.sample_file["bytes"]
+ )
+ elif 'uploaded_file' in locals() and uploaded_file is not None:
+ file_to_process = uploaded_file
+
+ # Process button (disabled if no file selected)
+ process_button = st.button(
+ "Process Document",
+ disabled=file_to_process is None,
+ use_container_width=True
+ )
+
+ if process_button and file_to_process is not None:
+ with st.spinner("Processing document..."):
+ try:
+ # Process the file
+ result = process_file(file_to_process, use_vision, custom_prompt=custom_prompt if custom_prompt else None)
+
+ if result:
+ st.success("Document processed successfully!")
+
+ # Store result in session state for display in the right column
+ st.session_state.current_result = result
+ st.rerun() # Refresh to show result
+ else:
+ st.error("Failed to process document.")
+ except Exception as e:
+ st.error(f"Error processing document: {str(e)}")
+
+ st.markdown('
', unsafe_allow_html=True)
+
+ # Experiment instructions in a compact format
+ st.markdown('
', unsafe_allow_html=True)
+ st.markdown("
Experiment Instructions
", unsafe_allow_html=True)
+ st.markdown("""
+ 1. **Step 1:** Select a document and choose your options
+ 2. **Step 2:** Process the document with the selected options
+ 3. **Step 3:** Analyze the results in the panel on the right
+ 4. **Step 4:** Try again with different settings (e.g., toggle vision model)
+ 5. **Step 5:** Compare results between different runs
+ """)
+ st.markdown('
', unsafe_allow_html=True)
+
+ with col2:
+ st.markdown('
', unsafe_allow_html=True)
+ st.markdown("
Step 3: View Results
", unsafe_allow_html=True)
+
+ if 'current_result' in st.session_state and st.session_state.current_result:
+ result = st.session_state.current_result
+
+ # File info in a compact format
+ st.markdown(f"**File:** {result.get('file_name', 'Unknown')}")
+
+ # Horizontal display of metadata
+ col1, col2 = st.columns(2)
+ with col1:
+ if 'languages' in result and result['languages']:
+ languages = [lang for lang in result['languages'] if lang]
+ if languages:
+ st.markdown(f"**Languages:** {', '.join(languages)}")
+
+ with col2:
+ if 'topics' in result and result['topics']:
+ st.markdown(f"**Topics:** {', '.join(result['topics'])}")
+
+ # Create tabs for different views with inline styling to ensure visibility
+ tab1, tab2 = st.tabs(["Structured View", "Raw JSON"])
+ st.markdown("""
+
+ """, unsafe_allow_html=True)
+
+ with tab1:
+ # Display in a more user-friendly format based on the content structure
+ if 'ocr_contents' in result:
+ if isinstance(result['ocr_contents'], dict):
+ for section, content in result['ocr_contents'].items():
+ if content: # Only display non-empty sections
+ st.markdown(f"
{section.replace('_', ' ').title()}
", unsafe_allow_html=True)
+
+ if isinstance(content, str):
+ st.markdown(f"
{content}
", unsafe_allow_html=True)
+ elif isinstance(content, list):
+ for item in content:
+ if isinstance(item, str):
+ st.markdown(f"
", unsafe_allow_html=True)
+ elif isinstance(content, dict):
+ for k, v in content.items():
+ st.markdown(f"
{k}: {v}
", unsafe_allow_html=True)
+
+ with tab2:
+ # Show the raw JSON for developers
+ # Convert to serializable format first
+ serializable_result = make_serializable(result)
+ st.json(serializable_result)
+
+ # Download options
+ st.markdown("### Export Results")
+
+ col1, col2 = st.columns(2)
+
+ with col1:
+ # Export as JSON (using the serializable converter)
+ serializable_result = make_serializable(result)
+ json_bytes = json.dumps(serializable_result, indent=2).encode()
+ st.download_button(
+ label="Download JSON",
+ data=json_bytes,
+ file_name="ocr_results.json",
+ mime="application/json",
+ use_container_width=True
+ )
+
+ with col2:
+ # Export as text
+ if 'ocr_contents' in result and isinstance(result['ocr_contents'], dict) and 'content' in result['ocr_contents']:
+ text_content = result['ocr_contents']['content']
+ st.download_button(
+ label="Download Text",
+ data=text_content.encode(),
+ file_name="ocr_text.txt",
+ mime="text/plain",
+ use_container_width=True
+ )
+ else:
+ st.markdown("""
+
+
Results will appear here
+
+
Upload and process a document to see the OCR results in this panel.
+
+
The OCR tool will:
+
+
Extract text from your document
+
Identify languages and topics
+
Provide structured content analysis
+
Generate downloadable results
+
+
+ """, unsafe_allow_html=True)
+ st.markdown('
', unsafe_allow_html=True)
+
+ # Processing history
+ if st.session_state.processing_history:
+ st.markdown('
', unsafe_allow_html=True)
+ st.markdown("
Step 4: Review Processing History
", unsafe_allow_html=True)
+
+ # Most recent result summary
+ latest = st.session_state.processing_history[-1]
+ st.markdown(f"**Latest Document:** {latest['fileName']}")
+ st.markdown(f"**Processed at:** {datetime.fromisoformat(latest['timestamp']).strftime('%Y-%m-%d %H:%M')}")
+ st.markdown(f"**Vision model used:** {'Yes' if latest['useVision'] else 'No'}")
+
+ # Full history in expander
+ with st.expander("View Complete Processing History"):
+ for i, item in enumerate(reversed(st.session_state.processing_history)):
+ st.markdown(f"""
+
+ """, unsafe_allow_html=True)
+
+ # Add option to view a previous result
+ if st.button(f"View This Result", key=f"view_history_{i}"):
+ st.session_state.current_result = item['result']
+ st.rerun()
+ st.markdown('
', unsafe_allow_html=True)
+
+ # Compare tab for side-by-side comparison
+ with compare_tab:
+ st.subheader("Compare OCR Results")
+
+ if len(st.session_state.processing_history) >= 2:
+ st.markdown("""
+ Select two processing results to compare side by side. This allows you to see
+ how different options (like using the vision model) affect OCR quality.
+ """)
+
+ # Create selection dropdowns for the documents
+ col1, col2 = st.columns(2)
+ with col1:
+ # First document selector
+ doc_options_1 = [f"{i+1}: {item['fileName']} ({'Vision' if item['useVision'] else 'No Vision'})"
+ for i, item in enumerate(st.session_state.processing_history)]
+ doc_choice_1 = st.selectbox("First Document:", doc_options_1, key="compare_doc_1")
+ doc_index_1 = int(doc_choice_1.split(":")[0]) - 1
+
+ with col2:
+ # Second document selector
+ doc_options_2 = [f"{i+1}: {item['fileName']} ({'Vision' if item['useVision'] else 'No Vision'})"
+ for i, item in enumerate(st.session_state.processing_history)]
+ default_index = min(1, len(st.session_state.processing_history) - 1) # Default to second item
+ doc_choice_2 = st.selectbox("Second Document:", doc_options_2, key="compare_doc_2", index=default_index)
+ doc_index_2 = int(doc_choice_2.split(":")[0]) - 1
+
+ # Retrieve the selected documents
+ doc1 = st.session_state.processing_history[doc_index_1]
+ doc2 = st.session_state.processing_history[doc_index_2]
+
+ # Show comparison
+ col1, col2 = st.columns(2)
+
+ with col1:
+ st.markdown(f"### Document 1: {doc1['fileName']}")
+ st.markdown(f"**Processed at:** {datetime.fromisoformat(doc1['timestamp']).strftime('%Y-%m-%d %H:%M')}")
+ st.markdown(f"**Vision model used:** {'Yes' if doc1['useVision'] else 'No'}")
+
+ # Display content summary
+ if 'ocr_contents' in doc1['result'] and isinstance(doc1['result']['ocr_contents'], dict):
+ if 'content' in doc1['result']['ocr_contents']:
+ content = doc1['result']['ocr_contents']['content']
+ # Display first 500 characters with word wrap
+ st.markdown(f"""
+
+ {content[:500]}{'...' if len(content) > 500 else ''}
+
+ """, unsafe_allow_html=True)
+
+ with col2:
+ st.markdown(f"### Document 2: {doc2['fileName']}")
+ st.markdown(f"**Processed at:** {datetime.fromisoformat(doc2['timestamp']).strftime('%Y-%m-%d %H:%M')}")
+ st.markdown(f"**Vision model used:** {'Yes' if doc2['useVision'] else 'No'}")
+
+ # Display content summary
+ if 'ocr_contents' in doc2['result'] and isinstance(doc2['result']['ocr_contents'], dict):
+ if 'content' in doc2['result']['ocr_contents']:
+ content = doc2['result']['ocr_contents']['content']
+ # Display first 500 characters with word wrap
+ st.markdown(f"""
+
+ {content[:500]}{'...' if len(content) > 500 else ''}
+
+ """, unsafe_allow_html=True)
+
+ # Comparison analysis
+ if doc1['fileName'] == doc2['fileName'] and doc1['useVision'] != doc2['useVision']:
+ st.markdown("""
+
+
Vision vs. Non-Vision Model Comparison
+
You're comparing the same document processed with different models.
+ This is an excellent way to evaluate the impact of vision capabilities on OCR accuracy.
Process at least two documents to enable side-by-side comparison. Try processing
+ the same document with and without the vision model to see the differences in OCR quality.
+
+ """, unsafe_allow_html=True)
+
+ # Analysis tab for guidance on working with OCR results
+ with analyze_tab:
+ st.subheader("Analysis Guide")
+
+ st.markdown("""
+
+
How to Analyze OCR Results
+
This guide helps you assess the quality and usefulness of OCR output for historical research.
+
+ """, unsafe_allow_html=True)
+
+ st.markdown("""
+ ### Evaluating OCR Quality
+
+ When analyzing OCR results from historical documents, consider these key factors:
+
+ 1. **Text Accuracy**
+ - Check for common OCR errors (e.g., mistaking "e" for "c", "l" for "1")
+ - Assess recognition of period-specific typography and writing styles
+ - Evaluate handling of degraded or damaged text areas
+
+ 2. **Structure Preservation**
+ - Does the OCR maintain paragraph and section breaks?
+ - Are columns and tabular data correctly preserved?
+ - How well are page transitions handled?
+
+ 3. **Special Elements**
+ - Recognition of footnotes, marginalia, and annotations
+ - Handling of illustrations, diagrams, and decorative elements
+ - Treatment of watermarks, signatures, and stamps
+
+ 4. **Metadata Extraction**
+ - Accuracy of detected languages, topics, and document type
+ - Identification of dates, names, and key entities
+ - Recognition of document purpose and context
+ """)
+
+ col1, col2 = st.columns(2)
+
+ with col1:
+ st.markdown("""
+ ### Common OCR Challenges in Historical Documents
+
+ - **Typography Variations**: Historical fonts and writing styles that differ from modern text
+ - **Material Degradation**: Fading, stains, tears, and other damage affecting legibility
+ - **Handwritten Elements**: Marginalia, signatures, and handwritten annotations
+ - **Complex Layouts**: Multi-column formats, non-linear reading order, and decorative elements
+ - **Language and Terminology**: Archaic terms, specialized vocabulary, and multilingual content
+ """)
+
+ with col2:
+ st.markdown("""
+ ### Making the Most of OCR Results
+
+ - **Contextual Reading**: Use historical context to interpret unclear passages
+ - **Error Patterns**: Identify and mentally correct for systematic OCR errors
+ - **Hybrid Analysis**: Combine OCR-based search with close reading of original images
+ - **Comparative Processing**: Try different OCR settings and models on the same document
+ - **Iterative Refinement**: Use insights from each document to improve future processing
+ """)
+
+ st.markdown("""
+ ### Research Integration
+
+ Once you've obtained and analyzed OCR results from historical documents, consider these approaches for integrating them into your research:
+
+ 1. **Digital Corpus Building**: Create searchable collections of processed texts
+ 2. **Computational Analysis**: Apply text mining, topic modeling, or network analysis
+ 3. **Cross-Document Linking**: Identify connections across multiple sources
+ 4. **Annotation and Enrichment**: Add context, translations, or explanatory notes
+ 5. **Collaborative Research**: Share processed texts with other researchers
+
+ Remember that OCR is a tool to assist your research, not replace careful reading and analysis. The most effective approaches combine computational methods with traditional historical research practices.
+ """)
+
+ # Example of what to look for
+ if st.session_state.processing_history:
+ with st.expander("Example Analysis from Your Documents"):
+ # Pick the latest document
+ latest = st.session_state.processing_history[-1]
+
+ st.markdown(f"""
+ #### Sample Analysis for: {latest['fileName']}
+
+ **Document Context:**
+ - Languages: {', '.join(latest['result'].get('languages', ['Unknown']))}
+ - Topics: {', '.join(latest['result'].get('topics', ['Unknown']))}
+ - Vision model used: {'Yes' if latest['useVision'] else 'No'}
+
+ **What to Look For:**
+ 1. Check how well the model identified key topics and languages
+ 2. Evaluate the completeness of extracted text
+ 3. Note any systematic errors in text recognition
+ 4. Assess how well document structure was preserved
+ """)
+
+ # Navigation buttons
+ col1, col2 = st.columns(2)
+ with col1:
+ st.button("Previous: Methodological Approaches", key="prev_to_4", on_click=navigate_to_module, args=(4,))
+
+ with col2:
+ st.button("Next: Conclusion", key="next_to_6", on_click=navigate_to_module, args=(6,))
+
+ else: # Module 6
+ # MODULE 6: Conclusion
+ st.title("Module 6: Conclusion and Future Directions")
+
+ col1, col2 = st.columns([3, 2])
+
+ with col1:
+ st.markdown("""
+ ### Workshop Summary
+
+ Throughout this workshop, we've explored:
+
+ 1. **Text-Image Interdependence**: The complex relationship between textual and visual elements
+ 2. **OCR Technology**: The evolution of OCR and its application to historical materials
+ 3. **Methodological Approaches**: Hybrid strategies for working with historical texts
+ 4. **Practical Application**: Hands-on experience with OCR processing tools
+ """)
+
+ st.markdown("""
+ ### Key Takeaways
+
+ 1. **OCR is Not Perfect**: Even advanced AI models face challenges with historical documents
+ 2. **Context Matters**: Vision-enhanced models provide better results by understanding document context
+ 3. **Hybrid Approaches**: Combining computational methods with traditional research yields best results
+ 4. **Critical Evaluation**: Always evaluate OCR outputs with awareness of limitations
+ 5. **Structured Extraction**: Modern OCR goes beyond text recognition to understand document structure
+ """)
+
+ with col2:
+ # Display statistics if there's processing history
+ if st.session_state.processing_history:
+ st.subheader("Your Workshop Statistics")
+
+ # Calculate statistics
+ total_docs = len(st.session_state.processing_history)
+ vision_docs = len([item for item in st.session_state.processing_history if item['useVision']])
+ non_vision_docs = total_docs - vision_docs
+
+ # Create metrics for statistics
+ col1, col2 = st.columns(2)
+
+ with col1:
+ st.metric("Documents Processed", total_docs)
+ st.metric("With Vision Model", vision_docs)
+
+ with col2:
+ st.metric("Without Vision Model", non_vision_docs)
+
+ # Topics word cloud
+ if total_docs > 0:
+ st.subheader("Topics Encountered")
+ all_topics = []
+ for item in st.session_state.processing_history:
+ if 'topics' in item['result']:
+ all_topics.extend(item['result']['topics'])
+
+ if all_topics:
+ # Count topic frequencies
+ topic_counts = {}
+ for topic in all_topics:
+ if topic in topic_counts:
+ topic_counts[topic] += 1
+ else:
+ topic_counts[topic] = 1
+
+ # Display as a horizontal bar chart
+ st.bar_chart(topic_counts)
+
+ st.subheader("Future Directions")
+
+ col1, col2 = st.columns(2)
+
+ with col1:
+ st.markdown("""
+ ### Technological Developments
+
+ - **Multimodal AI models**: Increasingly sophisticated understanding
+ - **Historical font training**: Models trained on historical typography
+ - **Document intelligence**: Enhanced understanding of structures
+ - **Collaborative correction**: Platforms for collective improvement
+ """)
+
+ with col2:
+ st.markdown("""
+ ### Research Applications
+
+ - **Large-scale corpus analysis**: Processing entire archives
+ - **Multilingual historical research**: Working across languages
+ - **Image-text integration**: New methodologies for visual analysis
+ - **Computational paleography**: AI-assisted handwriting analysis
+ """)
+
+ st.markdown("""
+ ### Additional Resources
+
+ - **[Mistral AI Documentation](https://docs.mistral.ai/)**: Learn more about the OCR models used in this workshop
+ - **[Transkribus](https://readcoop.eu/transkribus/)**: Platform for historical document transcription
+ - **[OCR-D](https://ocr-d.de/en/)**: Coordinated OCR research project for historical documents
+ - **[Historical OCR Research Papers](https://scholar.google.com/scholar?q=historical+OCR)**: Academic research on historical OCR
+ """)
+
+ # Reset button to start over
+ if st.button("Start Workshop Again", key="reset_workshop", use_container_width=True):
+ st.session_state.current_module = 1
+ st.rerun()
+
+# Handle API requests if the URL contains /api/process
+if 'api/process' in st.query_params.get('', ''):
+ # Process the API request
+ result = process_api_request()
+ if result:
+ # Return the result as JSON
+ # Make sure result is serializable
+ serializable_result = make_serializable(result)
+ st.json(serializable_result)
+ else:
+ st.json({"error": "Invalid request"})
\ No newline at end of file
diff --git a/structured_ocr.py b/structured_ocr.py
index e94e9acef06f53728833bf21f0ac36866cb14cb4..ff80ba6010cd863225a93486561ee5dd12742c51 100644
--- a/structured_ocr.py
+++ b/structured_ocr.py
@@ -1,276 +1,49 @@
-# Standard library imports
import os
import sys
import time
-import random
+from enum import Enum
+from pathlib import Path
import json
import base64
+import pycountry
import logging
-from enum import Enum
-from pathlib import Path
-from functools import lru_cache
-from typing import Optional, Dict, Any, List, Union, Tuple
+from pydantic import BaseModel
+from mistralai import Mistral
+from mistralai import DocumentURLChunk, ImageURLChunk, TextChunk
# Configure logging
logging.basicConfig(level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
-logger = logging.getLogger(__name__)
-
-# Third-party imports
-from pydantic import BaseModel
-
-# Try to import pycountry, provide fallback if not available
-try:
- import pycountry
- PYCOUNTRY_AVAILABLE = True
-except ImportError:
- PYCOUNTRY_AVAILABLE = False
- logger.warning("pycountry module not available - using language code fallback")
-
-# Try to import Mistral AI, provide fallback if not available
-try:
- from mistralai import Mistral
- from mistralai import DocumentURLChunk, ImageURLChunk, TextChunk
- from mistralai.models import OCRImageObject
- MISTRAL_AVAILABLE = True
-except ImportError:
- MISTRAL_AVAILABLE = False
- logger.warning("mistralai module not available - OCR functionality will be limited")
-
-# Import our language detection module
-try:
- from utils.helpers.language_detection import LanguageDetector
- LANG_DETECTOR_AVAILABLE = True
-except ImportError:
- LANG_DETECTOR_AVAILABLE = False
- logger.warning("language_detection module not available - using fallback language detection")
# Import utilities for OCR processing
try:
- from utils.image_utils import replace_images_in_markdown, get_combined_markdown
+ from ocr_utils import replace_images_in_markdown, get_combined_markdown
except ImportError:
- # Define minimal fallback functions if module not found
- logger.warning("Could not import utils.image_utils - using minimal fallback functions")
-
+ # Define fallback functions if module not found
def replace_images_in_markdown(markdown_str, images_dict):
- """Minimal fallback implementation of replace_images_in_markdown"""
- import re
- for img_id, base64_str in images_dict.items():
- # Match alt text OR link part, ignore extension
- base_id = img_id.split('.')[0]
- pattern = re.compile(rf"!\[[^\]]*{base_id}[^\]]*\]\([^\)]+\)")
- markdown_str = pattern.sub(f"", markdown_str)
+ for img_name, base64_str in images_dict.items():
+ markdown_str = markdown_str.replace(
+ f"", f""
+ )
return markdown_str
def get_combined_markdown(ocr_response):
- """Minimal fallback implementation of get_combined_markdown"""
markdowns = []
for page in ocr_response.pages:
image_data = {}
- if hasattr(page, "images"):
- for img in page.images:
- if hasattr(img, "id") and hasattr(img, "image_base64"):
- image_data[img.id] = img.image_base64
- page_markdown = page.markdown if hasattr(page, "markdown") else ""
- processed_markdown = replace_images_in_markdown(page_markdown, image_data)
- markdowns.append(processed_markdown)
+ for img in page.images:
+ image_data[img.id] = img.image_base64
+ markdowns.append(replace_images_in_markdown(page.markdown, image_data))
return "\n\n".join(markdowns)
# Import config directly (now local to historical-ocr)
-try:
- from config import MISTRAL_API_KEY, OCR_MODEL, TEXT_MODEL, VISION_MODEL, TEST_MODE, IMAGE_PREPROCESSING
-except ImportError:
- # Fallback defaults if config is not available
- import os
- MISTRAL_API_KEY = os.environ.get("MISTRAL_API_KEY", "")
- OCR_MODEL = "mistral-ocr-latest"
- TEXT_MODEL = "mistral-large-latest"
- VISION_MODEL = "mistral-large-latest"
- TEST_MODE = True
- # Default image preprocessing settings if config not available
- IMAGE_PREPROCESSING = {
- "max_size_mb": 8.0,
- # Add basic defaults for preprocessing
- "enhance_contrast": 1.2,
- "denoise": True,
- "compression_quality": 95
- }
- logging.warning("Config module not found. Using environment variables and defaults.")
-
-# Helper function to make OCR objects JSON serializable
-# Removed caching to fix unhashable type error
-def serialize_ocr_response(obj):
- """
- Convert OCR response objects to JSON serializable format
- Optimized for speed and memory usage
- """
- # Fast path: Handle primitive types directly
- if obj is None or isinstance(obj, (str, int, float, bool)):
- return obj
-
- # Handle collections with optimized recursion
- if isinstance(obj, list):
- return [serialize_ocr_response(item) for item in obj]
- elif isinstance(obj, dict):
- return {k: serialize_ocr_response(v) for k, v in obj.items()}
- elif hasattr(obj, '__dict__'):
- # For OCR objects with __dict__ attribute
- result = {}
- for key, value in obj.__dict__.items():
- if key.startswith('_'):
- continue # Skip private attributes
-
- # Fast path for OCRImageObject - most common complex object
- if isinstance(value, OCRImageObject):
- # Get image base64 data for validation
- image_base64 = value.image_base64 if hasattr(value, 'image_base64') else None
-
- # COMPLETELY REWRITTEN validation logic using proven test approach
- # Default to FALSE (treating as text) unless proven to be an image
- is_valid_image = False
-
- # Quick exit conditions
- if not image_base64 or not isinstance(image_base64, str):
- # No data or not a string - not a valid image
- is_valid_image = False
- logging.warning("Invalid image data (not a string)")
-
- # Case 1: Definite image with proper data URL prefix
- elif image_base64.startswith('data:image/'):
- is_valid_image = True
- logging.debug("Valid image with data:image/ prefix")
-
- # Case 2: Markdown image reference, not an actual image
- elif image_base64.startswith(''):
- is_valid_image = False
- logging.warning("Markdown image reference detected")
-
- # Extract the image ID for logging
- try:
- img_id = image_base64.split('![')[1].split('](')[0]
- logging.debug(f"Markdown reference for image: {img_id}")
- except:
- img_id = "unknown"
-
- # Case 3: Needs detailed text content detection
- else:
- # Use the same proven approach as in our tests
- # Take a sample for efficiency
- sample = image_base64[:min(len(image_base64), 1000)]
- sample_lower = sample.lower()
-
- # Check for obvious text features using multiple indicators
- has_spaces = ' ' in sample
- has_newlines = '\n' in sample
- has_punctuation = any(p in sample for p in ',.;:!?"\'()[]{}')
-
- # Check for sentence-like structures
- has_sentences = False
- for i in range(len(sample) - 5):
- if sample[i] in '.!?\n' and i+2 < len(sample) and sample[i+1] == ' ' and sample[i+2].isupper():
- has_sentences = True
- break
-
- # Check for common words with word boundary protection
- common_words = ['the', 'and', 'of', 'to', 'a', 'in', 'is', 'that', 'this', 'for']
- has_common_words = any(f" {word} " in f" {sample_lower} " for word in common_words)
-
- # Count the text indicators
- text_indicators = [has_spaces, has_newlines, has_punctuation, has_sentences, has_common_words]
- text_indicator_count = sum(1 for indicator in text_indicators if indicator)
-
- # Log detailed findings for debugging
- logging.debug(f"Text detection - spaces: {has_spaces}, newlines: {has_newlines}, " +
- f"punctuation: {has_punctuation}, sentences: {has_sentences}, " +
- f"common words: {has_common_words}")
- logging.debug(f"Text indicators found: {text_indicator_count}/5")
-
- # CRITICAL FIX: If we detect 2 or more text indicators, this is TEXT not an image!
- if text_indicator_count >= 2:
- is_valid_image = False
- logging.warning(f"Content identified as TEXT with {text_indicator_count}/5 indicators")
- # Only if we have no clear text indicators AND valid base64 chars, treat as image
- elif all(c in 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='
- for c in image_base64[:100]):
- is_valid_image = True
- logging.debug("Valid base64 data with no text indicators")
- else:
- # Default to TEXT for anything else - safer approach
- is_valid_image = False
- logging.warning("No clear image patterns detected - treating as text by default")
-
- # Final validation result with definitive message
- logging.warning(f"FINAL CLASSIFICATION: OCRImageObject content type = {'IMAGE' if is_valid_image else 'TEXT'}")
-
- # Process based on final validation result
- if is_valid_image:
- # Process as image if validation passes
- result[key] = {
- 'id': value.id if hasattr(value, 'id') else None,
- 'image_base64': image_base64
- }
- else:
- # Process as text if validation fails, but properly handle markdown references
- if image_base64 and isinstance(image_base64, str):
- # Special handling for markdown image references
- if image_base64.startswith(''):
- # Extract the image description (alt text) if available
- try:
- # Parse the alt text from 
- alt_text = image_base64.split('![')[1].split('](')[0]
- # Use the alt text or a placeholder if it's just the image name
- if alt_text and not alt_text.endswith('.jpeg') and not alt_text.endswith('.jpg'):
- result[key] = f"[Image: {alt_text}]"
- else:
- # Just note that there's an image without the reference
- result[key] = "[Image]"
- logging.info(f"Converted markdown reference to text placeholder: {result[key]}")
- except:
- # Fallback for parsing errors
- result[key] = "[Image]"
- else:
- # Regular text content
- result[key] = image_base64
- else:
- result[key] = str(value)
- # Handle collections
- elif isinstance(value, list):
- result[key] = [serialize_ocr_response(item) for item in value]
- # Handle nested objects
- elif hasattr(value, '__dict__'):
- result[key] = serialize_ocr_response(value)
- # Handle primitives and other types
- else:
- result[key] = value
- return result
- else:
- return obj
+from config import MISTRAL_API_KEY, OCR_MODEL, TEXT_MODEL, VISION_MODEL
-# Create language enum for structured output - cache language lookup to avoid repeated processing
-@lru_cache(maxsize=1)
-def get_language_dict():
- if PYCOUNTRY_AVAILABLE:
- return {lang.alpha_2: lang.name for lang in pycountry.languages if hasattr(lang, 'alpha_2')}
- else:
- # Fallback with basic languages when pycountry is not available
- return {
- "en": "English",
- "es": "Spanish",
- "fr": "French",
- "de": "German",
- "it": "Italian",
- "pt": "Portuguese",
- "ru": "Russian",
- "zh": "Chinese",
- "ja": "Japanese",
- "ar": "Arabic",
- "hi": "Hindi",
- "la": "Latin"
- }
+# Create language enum for structured output
+languages = {lang.alpha_2: lang.name for lang in pycountry.languages if hasattr(lang, 'alpha_2')}
class LanguageMeta(Enum.__class__):
def __new__(metacls, cls, bases, classdict):
- languages = get_language_dict()
for code, name in languages.items():
classdict[name.upper().replace(' ', '_')] = name
return super().__new__(metacls, cls, bases, classdict)
@@ -287,59 +60,10 @@ class StructuredOCRModel(BaseModel):
class StructuredOCR:
def __init__(self, api_key=None):
"""Initialize the OCR processor with API key"""
- # Set up logger for this class instance
- self.logger = logging.getLogger(__name__)
-
- # Check if we're running in test mode or if Mistral is not available
- self.test_mode = TEST_MODE or not MISTRAL_AVAILABLE
- # Initialize current filename for language detection
- self.current_filename = None
-
- if not MISTRAL_AVAILABLE:
- self.logger.warning("Mistral AI package not available - running in test mode")
- self.api_key = "placeholder_key"
- self.client = None
- return
-
- # Initialize API key - use provided key, or environment var
- if self.test_mode and not api_key:
- self.api_key = "placeholder_key"
- else:
- self.api_key = api_key or MISTRAL_API_KEY
-
- # Ensure we have a valid API key when not in test mode
- if not self.api_key and not self.test_mode:
- raise ValueError("No Mistral API key provided. Please set the MISTRAL_API_KEY environment variable or enable TEST_MODE.")
-
- # Clean the API key by removing any whitespace
- self.api_key = self.api_key.strip()
-
- # Check if API key exists but don't enforce length requirements
- if not self.test_mode and not self.api_key:
- self.logger.warning("Warning: No API key provided")
-
- # Initialize client with the API key
- try:
- self.client = Mistral(api_key=self.api_key)
- # Skip validation to avoid unnecessary API calls
- except Exception as e:
- error_msg = str(e).lower()
- if "unauthorized" in error_msg or "401" in error_msg:
- raise ValueError(f"API key authentication failed. Please check your Mistral API key: {str(e)}")
- else:
- self.logger.warning(f"Failed to initialize Mistral client: {str(e)}")
- self.test_mode = True
- self.client = None
-
- # Initialize language detector
- if LANG_DETECTOR_AVAILABLE:
- self.logger.info("Using statistical language detection module")
- self.language_detector = LanguageDetector()
- else:
- self.logger.warning("External language detection not available - using internal fallback")
- self.language_detector = None
+ self.api_key = api_key or MISTRAL_API_KEY
+ self.client = Mistral(api_key=self.api_key)
- def process_file(self, file_path, file_type=None, use_vision=True, max_pages=None, file_size_mb=None, custom_pages=None, custom_prompt=None):
+ def process_file(self, file_path, file_type=None, use_vision=True, max_pages=None, file_size_mb=None, custom_pages=None):
"""Process a file and return structured OCR results
Args:
@@ -349,7 +73,6 @@ class StructuredOCR:
max_pages: Optional limit on number of pages to process
file_size_mb: Optional file size in MB (used for automatic page limiting)
custom_pages: Optional list of specific page numbers to process
- custom_prompt: Optional instructions for the AI to handle unusual document formatting or specific extraction needs
Returns:
Dictionary with structured OCR results
@@ -357,19 +80,10 @@ class StructuredOCR:
# Convert file_path to Path object if it's a string
file_path = Path(file_path)
- # Store current filename for language detection
- self.current_filename = file_path.name
-
# Auto-detect file type if not provided
if file_type is None:
suffix = file_path.suffix.lower()
file_type = "pdf" if suffix == ".pdf" else "image"
-
- # Check for handwritten document by filename
- filename_lower = file_path.name.lower()
- if "handwritten" in filename_lower or "manuscript" in filename_lower or "letter" in filename_lower:
- logger.info(f"Detected likely handwritten document from filename: {file_path.name}")
- # This will be used during processing to apply handwritten-specific handling
# Get file size if not provided
if file_size_mb is None and file_path.exists():
@@ -406,9 +120,9 @@ class StructuredOCR:
# Read and process the file
if file_type == "pdf":
- result = self._process_pdf(file_path, use_vision, max_pages, custom_pages, custom_prompt)
+ result = self._process_pdf(file_path, use_vision, max_pages, custom_pages)
else:
- result = self._process_image(file_path, use_vision, custom_prompt)
+ result = self._process_image(file_path, use_vision)
# Add processing time information
processing_time = time.time() - start_time
@@ -418,689 +132,120 @@ class StructuredOCR:
if 'confidence_score' not in result:
result['confidence_score'] = 0.85 # Default confidence
- # Ensure the entire result is fully JSON serializable by running it through our serializer
- try:
- # First convert to a standard dict if it's not already
- if not isinstance(result, dict):
- result = serialize_ocr_response(result)
-
- # Make a final pass to check for any remaining non-serializable objects
- # Proactively check for OCRImageObject instances to avoid serialization warnings
- def has_ocr_image_objects(obj):
- """Check if object contains any OCRImageObject instances recursively"""
- if isinstance(obj, dict):
- return any(has_ocr_image_objects(v) for v in obj.values())
- elif isinstance(obj, list):
- return any(has_ocr_image_objects(item) for item in obj)
- else:
- return 'OCRImageObject' in str(type(obj))
-
- # Apply serialization preemptively if OCRImageObjects are detected
- if has_ocr_image_objects(result):
- # Quietly apply full serialization before any errors occur
- result = serialize_ocr_response(result)
- else:
- # Test JSON serialization to catch any other issues
- json.dumps(result)
- except TypeError as e:
- # If there's still a serialization error, run the whole result through our serializer
- logger = logging.getLogger("serializer")
- logger.warning(f"JSON serialization error in result: {str(e)}. Applying full serialization.")
- # Use a more robust approach to ensure complete serialization
- try:
- # First attempt with our custom serializer
- result = serialize_ocr_response(result)
- # Test if it's fully serializable now
- json.dumps(result)
- except Exception as inner_e:
- # If still not serializable, convert to a simpler format
- logger.warning(f"Secondary serialization error: {str(inner_e)}. Converting to basic format.")
- # Create a simplified result with just the essential information
- simplified_result = {
- "file_name": result.get("file_name", "unknown"),
- "topics": result.get("topics", ["Document"]),
- "languages": [str(lang) for lang in result.get("languages", ["English"]) if lang is not None],
- "ocr_contents": {
- "raw_text": result.get("ocr_contents", {}).get("raw_text", "Text extraction failed due to serialization error")
- },
- "serialization_error": f"Original result could not be fully serialized: {str(e)}"
- }
- result = simplified_result
-
return result
- def _process_pdf(self, file_path, use_vision=True, max_pages=None, custom_pages=None, custom_prompt=None):
- """
- Process a PDF file with OCR - optimized version with smart page handling and memory management
+ def _process_pdf(self, file_path, use_vision=True, max_pages=None, custom_pages=None):
+ """Process a PDF file with OCR
Args:
file_path: Path to the PDF file
- use_vision: Whether to use vision model for enhanced analysis
+ use_vision: Whether to use vision model
max_pages: Optional limit on the number of pages to process
custom_pages: Optional list of specific page numbers to process
- custom_prompt: Optional custom prompt for specialized extraction
"""
logger = logging.getLogger("pdf_processor")
logger.info(f"Processing PDF: {file_path}")
- # Track processing time
- start_time = time.time()
-
- # Fast path: Return placeholder if in test mode
- if self.test_mode:
- logger.info("Test mode active, returning placeholder response")
- # Enhanced test mode placeholder that's more realistic
- return {
- "file_name": file_path.name,
- "topics": ["Historical Document", "Literature", "American History"],
- "languages": ["English"],
- "ocr_contents": {
- "title": "Harper's New Monthly Magazine",
- "publication_date": "1855",
- "publisher": "Harper & Brothers, New York",
- "raw_text": "This is a test mode placeholder for Harper's New Monthly Magazine from 1855. The actual document contains articles on literature, politics, science, and culture from mid-19th century America.",
- "content": "The magazine includes various literary pieces, poetry, political commentary, and illustrations typical of 19th century periodicals. Known for publishing works by prominent American authors including Herman Melville and Charles Dickens.",
- "key_figures": ["Herman Melville", "Charles Dickens", "Henry Wadsworth Longfellow"],
- "noted_articles": ["Continued serialization of popular novels", "Commentary on contemporary political events", "Scientific discoveries and technological advancements"]
- },
- "pdf_processing_method": "enhanced_test_mode",
- "total_pages": 12,
- "processed_pages": 3,
- "processing_time": 0.5,
- "confidence_score": 0.9
- }
-
try:
- # PDF processing strategy decision based on file size
- file_size_mb = file_path.stat().st_size / (1024 * 1024)
- logger.info(f"PDF size: {file_size_mb:.2f} MB")
-
- # Always use pdf2image for better control and consistency across all PDF files
- use_pdf2image = True
-
- # First try local PDF processing for better performance and control
- if use_pdf2image:
- try:
- import tempfile
- from pdf2image import convert_from_path
-
- logger.info("Processing PDF using pdf2image for better multi-page handling")
-
- # Convert PDF to images with optimized parameters
- conversion_start = time.time()
-
- # Use consistent DPI for all files to ensure reliable results
- dpi = 200 # Higher quality DPI for all files to ensure better text recognition
-
- # Only convert first page initially to check document type
- pdf_first_page = convert_from_path(file_path, dpi=dpi, first_page=1, last_page=1)
- logger.info(f"First page converted in {time.time() - conversion_start:.2f}s")
-
- # Quick check if PDF has readable content
- if not pdf_first_page:
- logger.warning("PDF conversion produced no images, falling back to API")
- raise Exception("PDF conversion failed to produce images")
-
- # Determine total pages in the document
- # First, try simple estimate from first page conversion
- total_pages = 1
-
- # Try pdf2image info extraction
- try:
- # Try with pdf2image page counting - use simpler parameters
- logger.info("Determining PDF page count...")
- count_start = time.time()
-
- # Use a lightweight approach with multi-threading for faster processing
- pdf_info = convert_from_path(
- file_path,
- dpi=72, # Low DPI just for info
- first_page=1,
- last_page=1,
- size=(100, 100), # Tiny image to save memory
- fmt="jpeg",
- thread_count=4, # Increased thread count for faster processing
- output_file=None
- )
-
- # Extract page count
- if hasattr(pdf_info, 'n_pages'):
- total_pages = pdf_info.n_pages
- elif isinstance(pdf_info, dict) and "Pages" in pdf_info:
- total_pages = int(pdf_info.get("Pages", "1"))
- elif len(pdf_first_page) > 0:
- # Just estimate based on first page - at least we have one
- total_pages = 1
-
- logger.info(f"Page count determined in {time.time() - count_start:.2f}s")
- except Exception as count_error:
- logger.warning(f"Error determining page count: {str(count_error)}. Using default of 1")
- total_pages = 1
-
- logger.info(f"PDF has {total_pages} total pages")
-
- # Determine which pages to process
- pages_to_process = []
-
- # Handle custom page selection if provided
- if custom_pages and any(0 < p <= total_pages for p in custom_pages):
- # Filter valid page numbers
- pages_to_process = [p for p in custom_pages if 0 < p <= total_pages]
- logger.info(f"Processing {len(pages_to_process)} custom-selected pages: {pages_to_process}")
- # Otherwise use max_pages limit if provided
- elif max_pages and max_pages < total_pages:
- pages_to_process = list(range(1, max_pages + 1))
- logger.info(f"Processing first {max_pages} pages of {total_pages} total")
- # Or process all pages if reasonable count
- elif total_pages <= 10:
- pages_to_process = list(range(1, total_pages + 1))
- logger.info(f"Processing all {total_pages} pages")
- # For large documents without limits, process subset of pages
- else:
- # Smart sampling: first page, last page, and some pages in between
- pages_to_process = [1] # Always include first page
-
- if total_pages > 1:
- if total_pages <= 5:
- # For few pages, process all
- pages_to_process = list(range(1, total_pages + 1))
- else:
- # For many pages, sample intelligently
- # Add pages from the middle of the document
- middle = total_pages // 2
- # Add last page if more than 3 pages
- if total_pages > 3:
- pages_to_process.append(total_pages)
- # Add up to 3 pages from middle if document is large
- if total_pages > 5:
- pages_to_process.append(middle)
- if total_pages > 10:
- pages_to_process.append(middle // 2)
- pages_to_process.append(middle + (middle // 2))
-
- # Sort pages for sequential processing
- pages_to_process = sorted(list(set(pages_to_process)))
- logger.info(f"Processing {len(pages_to_process)} sampled pages out of {total_pages} total: {pages_to_process}")
-
- # Convert only the selected pages to minimize memory usage
- selected_images = []
- combined_text = []
- detected_languages = set() # Track detected languages across all pages
-
- # Process pages in larger batches for better efficiency
- batch_size = 5 # Process 5 pages at a time for better throughput
- for i in range(0, len(pages_to_process), batch_size):
- batch_pages = pages_to_process[i:i+batch_size]
- logger.info(f"Converting batch of pages {batch_pages}")
-
- # Convert batch of pages with multi-threading for better performance
- batch_start = time.time()
- batch_images = convert_from_path(
- file_path,
- dpi=dpi,
- first_page=min(batch_pages),
- last_page=max(batch_pages),
- thread_count=4, # Use multi-threading for faster PDF processing
- fmt="jpeg" # Use JPEG format for better compatibility
- )
- logger.info(f"Batch conversion completed in {time.time() - batch_start:.2f}s")
-
- # Map converted images to requested page numbers
- for idx, page_num in enumerate(range(min(batch_pages), max(batch_pages) + 1)):
- if page_num in pages_to_process and idx < len(batch_images):
- if page_num == pages_to_process[0]: # First page to process
- selected_images.append(batch_images[idx])
-
- # Process each page individually
- with tempfile.NamedTemporaryFile(suffix='.jpeg', delete=False) as tmp:
- batch_images[idx].save(tmp.name, format='JPEG')
- # Simple OCR to extract text
- try:
- page_result = self._process_image(Path(tmp.name), False, None)
- if 'ocr_contents' in page_result and 'raw_text' in page_result['ocr_contents']:
- # Add page text to combined text without obvious page markers
- page_text = page_result['ocr_contents']['raw_text']
- combined_text.append(f"{page_text}")
-
- # Collect detected languages from each page
- if 'languages' in page_result:
- for lang in page_result['languages']:
- detected_languages.add(lang)
- except Exception as page_e:
- logger.warning(f"Error processing page {page_num}: {str(page_e)}")
- # Clean up temp file
- import os
- os.unlink(tmp.name)
-
- # If we have processed pages
- if selected_images and combined_text:
- # Save first image to temp file for vision model
- with tempfile.NamedTemporaryFile(suffix='.jpeg', delete=False) as tmp:
- selected_images[0].save(tmp.name, format='JPEG', quality=95)
- first_image_path = tmp.name
-
- # Combine all extracted text
- all_text = "\n\n".join(combined_text)
-
- # For custom prompts, use specialized processing
- if custom_prompt:
- try:
- # Process image with vision model
- result = self._process_image(Path(first_image_path), use_vision, None)
-
- # Enhance with text analysis using combined text from all pages
- enhanced_result = self._extract_structured_data_text_only(all_text, file_path.name, custom_prompt)
-
- # Merge results, keeping images from original result
- for key, value in enhanced_result.items():
- if key not in ('raw_response_data', 'pages_data', 'has_images'):
- result[key] = value
-
- # Update raw text with full document text
- if 'ocr_contents' in result:
- result['ocr_contents']['raw_text'] = all_text
-
- # Add flag to indicate custom prompt was applied
- result['custom_prompt_applied'] = 'text_only'
-
- # Simplified approach - no document type detection
-
- except Exception as e:
- logger.warning(f"Custom prompt processing failed: {str(e)}. Using standard processing.")
- # Fall back to standard processing
- result = self._process_image(Path(first_image_path), use_vision, None)
- if 'ocr_contents' in result:
- result['ocr_contents']['raw_text'] = all_text
- else:
- # Standard processing with combined text
- result = self._process_image(Path(first_image_path), use_vision, None)
- if 'ocr_contents' in result:
- result['ocr_contents']['raw_text'] = all_text
-
- # Merge detected languages if available
- if detected_languages:
- result['languages'] = list(detected_languages)
-
- # Add PDF metadata
- result['file_name'] = file_path.name
- result['pdf_processing_method'] = 'pdf2image_optimized'
- result['total_pages'] = total_pages
- result['processed_pages'] = len(pages_to_process)
- result['pages_processed'] = pages_to_process
-
- # Add processing info
- result['processing_info'] = {
- 'method': 'local_pdf_processing',
- 'dpi': dpi,
- 'pages_sampled': pages_to_process,
- 'processing_time': time.time() - start_time
- }
-
- # Clean up
- os.unlink(first_image_path)
-
- return result
- else:
- logger.warning("No pages successfully processed with pdf2image, falling back to API")
- raise Exception("Failed to process PDF pages locally")
-
- except Exception as pdf2image_error:
- logger.warning(f"Local PDF processing failed, falling back to API: {str(pdf2image_error)}")
- # Fall back to API processing
-
- # API-based PDF processing
- logger.info("Processing PDF via Mistral API")
-
- # Optimize file upload for faster processing
+ # Upload the PDF file
logger.info("Uploading PDF file to Mistral API")
- upload_start = time.time()
-
- # Set appropriate timeout based on file size
- upload_timeout = max(60, min(300, int(file_size_mb * 5))) # 60s to 300s based on size
-
- try:
- # Upload the file (Mistral client doesn't support timeout parameter for upload)
- uploaded_file = self.client.files.upload(
- file={
- "file_name": file_path.stem,
- "content": file_path.read_bytes(),
- },
- purpose="ocr"
- )
-
- logger.info(f"PDF uploaded in {time.time() - upload_start:.2f}s")
-
- # Get a signed URL for the uploaded file
- signed_url = self.client.files.get_signed_url(file_id=uploaded_file.id, expiry=1)
-
- # Process the PDF with OCR - use adaptive timeout based on file size
- logger.info(f"Processing PDF with OCR using {OCR_MODEL}")
-
- # Adaptive retry strategy based on file size
- max_retries = 3 if file_size_mb < 20 else 2 # Fewer retries for large files
- base_retry_delay = 1 if file_size_mb < 10 else 2 # Longer delays for large files
-
- # Adaptive timeout based on file size
- ocr_timeout_ms = min(180000, max(60000, int(file_size_mb * 3000))) # 60s to 180s
-
- # Try processing with retries
- for retry in range(max_retries):
- try:
- ocr_start = time.time()
- pdf_response = self.client.ocr.process(
- document=DocumentURLChunk(document_url=signed_url.url),
- model=OCR_MODEL,
- include_image_base64=True,
- timeout_ms=ocr_timeout_ms
- )
- logger.info(f"PDF OCR processing completed in {time.time() - ocr_start:.2f}s")
- break # Success, exit retry loop
- except Exception as e:
- error_msg = str(e)
- logger.warning(f"API error on attempt {retry+1}/{max_retries}: {error_msg}")
-
- # Handle errors with optimized retry logic
- error_lower = error_msg.lower()
-
- # Authentication errors - no point in retrying
- if any(term in error_lower for term in ["unauthorized", "401", "403", "authentication"]):
- logger.error("API authentication failed. Check your API key.")
- raise ValueError(f"Authentication failed. Please verify your Mistral API key: {error_msg}")
-
- # Connection or server errors - worth retrying
- elif any(term in error_lower for term in ["connection", "timeout", "520", "server error", "502", "503", "504"]):
- if retry < max_retries - 1:
- # Exponential backoff with jitter for better retry behavior
- wait_time = base_retry_delay * (2 ** retry) * (0.8 + 0.4 * random.random())
- logger.info(f"Connection issue detected. Waiting {wait_time:.1f}s before retry...")
- time.sleep(wait_time)
- else:
- # Last retry failed
- logger.error("Maximum retries reached, API connection error persists.")
- raise ValueError(f"Could not connect to Mistral API after {max_retries} attempts: {error_msg}")
-
- # Rate limit errors - much longer wait
- elif any(term in error_lower for term in ["rate limit", "429", "too many requests", "requests rate limit exceeded"]):
- # Check specifically for token exhaustion vs temporary rate limit
- if "quota" in error_lower or "credit" in error_lower or "subscription" in error_lower:
- logger.error("API quota or credit limit reached. No retry will help.")
- raise ValueError(f"Mistral API quota or credit limit reached. Please check your subscription: {error_msg}")
- elif retry < max_retries - 1:
- wait_time = base_retry_delay * (2 ** retry) * 6.0 # Significantly longer wait for rate limits
- logger.info(f"Rate limit exceeded. Waiting {wait_time:.1f}s before retry...")
- time.sleep(wait_time)
- else:
- logger.error("Maximum retries reached, rate limit error persists.")
- raise ValueError(f"API rate limit exceeded. Please try again later: {error_msg}")
-
- # Misc errors - typically no retry will help
- else:
- if retry < max_retries - 1 and any(term in error_lower for term in ["transient", "temporary"]):
- # Only retry for errors explicitly marked as transient
- wait_time = base_retry_delay * (2 ** retry)
- logger.info(f"Transient error detected. Waiting {wait_time:.1f}s before retry...")
- time.sleep(wait_time)
- else:
- logger.error(f"Unrecoverable API error: {error_msg}")
- raise
-
- # Calculate the number of pages to process
- pages_to_process = pdf_response.pages
- total_pages = len(pdf_response.pages)
- limited_pages = False
-
- logger.info(f"API returned {total_pages} total PDF pages")
-
- # Smart page selection logic for better performance
- if custom_pages:
- # Convert to 0-based indexing and filter valid page numbers
- valid_indices = [i-1 for i in custom_pages if 0 < i <= total_pages]
- if valid_indices:
- pages_to_process = [pdf_response.pages[i] for i in valid_indices]
- limited_pages = True
- logger.info(f"Processing {len(valid_indices)} custom-selected pages")
- # Max pages limit with smart sampling
- elif max_pages and total_pages > max_pages:
- if max_pages == 1:
- # Just first page
- pages_to_process = pages_to_process[:1]
- elif max_pages < 5 and total_pages > 10:
- # For small max_pages on large docs, include first, last, and middle
- indices = [0] # First page
- if max_pages > 1:
- indices.append(total_pages - 1) # Last page
- if max_pages > 2:
- indices.append(total_pages // 2) # Middle page
- # Add more pages up to max_pages if needed
- if max_pages > 3:
- remaining = max_pages - len(indices)
- step = total_pages // (remaining + 1)
- for i in range(1, remaining + 1):
- idx = i * step
- if idx not in indices and 0 <= idx < total_pages:
- indices.append(idx)
- indices.sort()
- pages_to_process = [pdf_response.pages[i] for i in indices]
- else:
- # Default: first max_pages
- pages_to_process = pages_to_process[:max_pages]
-
+ uploaded_file = self.client.files.upload(
+ file={
+ "file_name": file_path.stem,
+ "content": file_path.read_bytes(),
+ },
+ purpose="ocr",
+ )
+
+ # Get a signed URL for the uploaded file
+ signed_url = self.client.files.get_signed_url(file_id=uploaded_file.id, expiry=1)
+
+ # Process the PDF with OCR
+ logger.info(f"Processing PDF with OCR using {OCR_MODEL}")
+ pdf_response = self.client.ocr.process(
+ document=DocumentURLChunk(document_url=signed_url.url),
+ model=OCR_MODEL,
+ include_image_base64=True
+ )
+
+ # Limit pages if requested
+ pages_to_process = pdf_response.pages
+ total_pages = len(pdf_response.pages)
+ limited_pages = False
+
+ logger.info(f"PDF has {total_pages} total pages")
+
+ # Handle custom page selection if provided
+ if custom_pages:
+ # Convert to 0-based indexing and filter valid page numbers
+ valid_indices = [i-1 for i in custom_pages if 0 < i <= total_pages]
+ if valid_indices:
+ pages_to_process = [pdf_response.pages[i] for i in valid_indices]
limited_pages = True
- logger.info(f"Processing {len(pages_to_process)} pages out of {total_pages} total")
-
- # Directly extract any language information from the OCR response
- detected_languages = set()
-
- # Check if the response has a 'languages' attribute in any form
- # First check direct attributes on the response object
- if hasattr(pdf_response, 'languages') and pdf_response.languages:
- for lang in pdf_response.languages:
- detected_languages.add(str(lang))
- logger.info(f"Found language in OCR response: {lang}")
-
- # Then check if it's in the response as a dictionary format
- elif hasattr(pdf_response, '__dict__'):
- response_dict = pdf_response.__dict__
- if 'languages' in response_dict and response_dict['languages']:
- for lang in response_dict['languages']:
- detected_languages.add(str(lang))
- logger.info(f"Found language in OCR response dict: {lang}")
-
- # Calculate confidence score if available
- try:
- confidence_values = [page.confidence for page in pages_to_process if hasattr(page, 'confidence')]
- confidence_score = sum(confidence_values) / len(confidence_values) if confidence_values else 0.89
- except Exception:
- confidence_score = 0.89 # Improved default
-
- # Merge page content intelligently - include page numbers for better context
- all_markdown = []
- for idx, page in enumerate(pages_to_process):
- # Try to determine actual page number
- if custom_pages and len(custom_pages) == len(pages_to_process):
- page_num = custom_pages[idx]
- else:
- # Estimate page number - may not be accurate with sampling
- page_num = idx + 1
-
- page_markdown = page.markdown if hasattr(page, 'markdown') else ""
- # Add page content without obvious page markers
- if page_markdown.strip():
- all_markdown.append(f"{page_markdown}")
-
- # Collect language information from individual pages if available
- if hasattr(page, 'languages') and page.languages:
- for lang in page.languages:
- detected_languages.add(str(lang))
- logger.info(f"Found language in page {page_num}: {lang}")
-
- # Join all pages with separation
- combined_markdown = "\n\n".join(all_markdown)
-
- # Extract structured data with the appropriate model
- if use_vision:
- # Try to get a good image for vision model
- vision_image = None
-
- # Try first page with images
- for page in pages_to_process:
- if hasattr(page, 'images') and page.images:
- vision_image = page.images[0].image_base64
- break
-
- if vision_image:
- # Use vision model with enhanced prompt
- logger.info(f"Using vision model: {VISION_MODEL}")
- result = self._extract_structured_data_with_vision(
- vision_image, combined_markdown, file_path.name, custom_prompt
- )
- else:
- # Fall back to text-only if no images available
- logger.info(f"No images in PDF, falling back to text model: {TEXT_MODEL}")
- result = self._extract_structured_data_text_only(
- combined_markdown, file_path.name, custom_prompt
- )
+ logger.info(f"Processing {len(valid_indices)} custom-selected pages")
+ # Otherwise handle max_pages limit
+ elif max_pages and total_pages > max_pages:
+ pages_to_process = pages_to_process[:max_pages]
+ limited_pages = True
+ logger.info(f"Processing only first {max_pages} pages out of {total_pages} total pages")
+
+ # Calculate average confidence score based on OCR response if available
+ confidence_score = 0.0
+ try:
+ # Some OCR APIs provide confidence scores
+ confidence_values = []
+ for page in pages_to_process:
+ if hasattr(page, 'confidence'):
+ confidence_values.append(page.confidence)
+
+ if confidence_values:
+ confidence_score = sum(confidence_values) / len(confidence_values)
else:
- # Use text-only model as requested
- logger.info(f"Using text-only model as specified: {TEXT_MODEL}")
- result = self._extract_structured_data_text_only(
- combined_markdown, file_path.name, custom_prompt
- )
-
- # If we have detected languages directly from the OCR model, use them
- if detected_languages:
- logger.info(f"Using languages detected by OCR model: {', '.join(detected_languages)}")
- result['languages'] = list(detected_languages)
- # Add flag to indicate source of language detection
- result['language_detection_source'] = 'mistral-ocr-latest'
-
- # Add metadata about pages
- if limited_pages:
- result['limited_pages'] = {
- 'processed': len(pages_to_process),
- 'total': total_pages
- }
-
- # Set confidence score from OCR
- result['confidence_score'] = confidence_score
-
- # Add processing method info
- result['pdf_processing_method'] = 'api'
- result['total_pages'] = total_pages
- result['processed_pages'] = len(pages_to_process)
-
- # Store serialized OCR response for rendering
- serialized_response = serialize_ocr_response(pdf_response)
- result['raw_response_data'] = serialized_response
-
- # Check if there are images to include
- has_images = hasattr(pdf_response, 'pages') and any(
- hasattr(page, 'images') and page.images for page in pdf_response.pages
- )
- result['has_images'] = has_images
+ confidence_score = 0.85 # Default if no confidence scores available
+ except:
+ confidence_score = 0.85 # Default fallback
- # Include image data for rendering if available
- if has_images:
- # Prepare pages data with image references
- result['pages_data'] = []
-
- # Get serialized pages - handle different formats
- serialized_pages = None
- try:
- if hasattr(serialized_response, 'pages'):
- serialized_pages = serialized_response.pages
- elif isinstance(serialized_response, dict) and 'pages' in serialized_response:
- serialized_pages = serialized_response.get('pages', [])
- else:
- # No pages found in response
- logger.warning("No pages found in OCR response")
- serialized_pages = []
- except Exception as pages_err:
- logger.warning(f"Error extracting pages from OCR response: {str(pages_err)}")
- serialized_pages = []
-
- # Process each page to extract images
- for page_idx, page in enumerate(serialized_pages):
- try:
- # Skip processing pages not in our selection
- if limited_pages and page_idx >= len(pages_to_process):
- continue
-
- # Extract page data with careful error handling
- markdown = ""
- images = []
-
- # Handle different page formats safely
- if isinstance(page, dict):
- markdown = page.get('markdown', '')
- images = page.get('images', [])
- else:
- # Try attribute access
- if hasattr(page, 'markdown'):
- markdown = page.markdown
- if hasattr(page, 'images'):
- images = page.images
-
- # Create page data record
- page_data = {
- 'page_number': page_idx + 1,
- 'markdown': markdown,
- 'images': []
- }
-
- # Process images with careful error handling
- for img_idx, img in enumerate(images):
- try:
- # Extract image ID and base64 data
- img_id = None
- img_base64 = None
-
- if isinstance(img, dict):
- img_id = img.get('id')
- img_base64 = img.get('image_base64')
- else:
- # Try attribute access
- if hasattr(img, 'id'):
- img_id = img.id
- if hasattr(img, 'image_base64'):
- img_base64 = img.image_base64
-
- # Only add if we have valid image data
- if img_base64 and isinstance(img_base64, str):
- # Ensure ID exists
- safe_id = img_id if img_id else f"img_{page_idx}_{img_idx}"
- page_data['images'].append({
- 'id': safe_id,
- 'image_base64': img_base64
- })
- except Exception as img_err:
- logger.warning(f"Error processing image {img_idx} on page {page_idx+1}: {str(img_err)}")
- continue # Skip this image
-
- # Add page data if it has content
- if page_data['markdown'] or page_data['images']:
- result['pages_data'].append(page_data)
-
- except Exception as page_err:
- logger.warning(f"Error processing page {page_idx+1}: {str(page_err)}")
- continue # Skip this page
-
- # Record final processing time
- total_time = time.time() - start_time
- result['processing_time'] = total_time
- logger.info(f"PDF API processing completed in {total_time:.2f}s")
-
- return result
+ # Combine pages' markdown into a single string
+ all_markdown = "\n\n".join([page.markdown for page in pages_to_process])
+
+ # Extract structured data using the appropriate model
+ if use_vision:
+ # Get base64 of first page for vision model
+ first_page_image = None
+ if pages_to_process and pages_to_process[0].images:
+ first_page_image = pages_to_process[0].images[0].image_base64
+
+ if first_page_image:
+ # Use vision model
+ logger.info(f"Using vision model: {VISION_MODEL}")
+ result = self._extract_structured_data_with_vision(first_page_image, all_markdown, file_path.name)
+ else:
+ # Fall back to text-only model if no image available
+ logger.info(f"No images in PDF, falling back to text model: {TEXT_MODEL}")
+ result = self._extract_structured_data_text_only(all_markdown, file_path.name)
+ else:
+ # Use text-only model
+ logger.info(f"Using text-only model: {TEXT_MODEL}")
+ result = self._extract_structured_data_text_only(all_markdown, file_path.name)
+
+ # Add page limit info to result if needed
+ if limited_pages:
+ result['limited_pages'] = {
+ 'processed': len(pages_to_process),
+ 'total': total_pages
+ }
- except Exception as api_e:
- logger.error(f"Error in API-based PDF processing: {str(api_e)}")
- # Re-raise to be caught by outer exception handler
- raise
+ # Add confidence score
+ result['confidence_score'] = confidence_score
+ # Store the raw OCR response for image rendering
+ result['raw_response'] = pdf_response
+
+ logger.info(f"PDF processing completed successfully")
+ return result
+
except Exception as e:
- # Log the error and return a helpful error result
logger.error(f"Error processing PDF: {str(e)}")
-
# Return basic result on error
return {
"file_name": file_path.name,
@@ -1111,346 +256,52 @@ class StructuredOCR:
"ocr_contents": {
"error": f"Failed to process PDF: {str(e)}",
"partial_text": "Document could not be fully processed."
- },
- "processing_time": time.time() - start_time
+ }
}
- def _process_image(self, file_path, use_vision=True, custom_prompt=None):
+ def _process_image(self, file_path, use_vision=True):
"""Process an image file with OCR"""
logger = logging.getLogger("image_processor")
logger.info(f"Processing image: {file_path}")
- # Check if we're in test mode
- if self.test_mode:
- # Return a placeholder document response
- return {
- "file_name": file_path.name,
- "topics": ["Document"],
- "languages": ["English"],
- "ocr_contents": {
- "title": "Document",
- "content": "Please set up API key to process documents."
- },
- "processing_time": 0.5,
- "confidence_score": 0.0
- }
-
- # No automatic document type detection - rely on the document type specified in the custom prompt
- # The document type is passed from the UI through the custom prompt in ocr_processing.py
-
try:
- # Check file size
- file_size_mb = file_path.stat().st_size / (1024 * 1024)
- logger.info(f"Original image size: {file_size_mb:.2f} MB")
-
- # Use enhanced preprocessing functions from ocr_utils
- try:
- from preprocessing import preprocess_image
- from utils.file_utils import get_base64_from_bytes
-
- logger.info(f"Applying image preprocessing for OCR")
-
- # Get preprocessing settings from config
- max_size_mb = IMAGE_PREPROCESSING.get("max_size_mb", 8.0)
-
- if file_size_mb > max_size_mb:
- logger.info(f"Image is large ({file_size_mb:.2f} MB), optimizing for API submission")
-
- # Use standard preprocessing - document type will be handled by preprocessing.py
- # based on the options passed from the UI
- base64_data_url = get_base64_from_bytes(
- preprocess_image(file_path.read_bytes(),
- {"document_type": "standard",
- "grayscale": True,
- "denoise": True,
- "contrast": 0})
- )
-
- logger.info(f"Image preprocessing completed successfully")
-
- except (ImportError, AttributeError) as e:
- # Fallback to basic processing if advanced functions not available
- logger.warning(f"Advanced preprocessing not available: {str(e)}. Using basic image processing.")
-
- # If image is larger than 8MB, resize it to reduce API payload size
- if file_size_mb > 8:
- logger.info("Image is large, resizing before API submission")
- try:
- from PIL import Image
- import io
-
- # Open and process the image
- with Image.open(file_path) as img:
- # Convert to RGB if not already (prevents mode errors)
- if img.mode != 'RGB':
- img = img.convert('RGB')
-
- # Calculate new dimensions (maintain aspect ratio)
- # Target around 2000-2500 pixels on longest side for better OCR quality
- width, height = img.size
- max_dimension = max(width, height)
- target_dimension = 2000 # Restored to 2000 for better image quality
-
- if max_dimension > target_dimension:
- scale_factor = target_dimension / max_dimension
- resized_width = int(width * scale_factor)
- resized_height = int(height * scale_factor)
- # Use LANCZOS instead of BILINEAR for better quality
- img = img.resize((resized_width, resized_height), Image.LANCZOS)
-
- # Enhance contrast for better text recognition
- from PIL import ImageEnhance
- enhancer = ImageEnhance.Contrast(img)
- img = enhancer.enhance(1.3)
-
- # Save to bytes with compression
- buffer = io.BytesIO()
- img.save(buffer, format="JPEG", quality=92, optimize=True) # Higher quality for better OCR
- buffer.seek(0)
-
- # Get the base64
- encoded_image = base64.b64encode(buffer.getvalue()).decode()
- base64_data_url = f"data:image/jpeg;base64,{encoded_image}"
-
- # Log the new size
- new_size_mb = len(buffer.getvalue()) / (1024 * 1024)
- logger.info(f"Resized image to {new_size_mb:.2f} MB")
- except ImportError:
- logger.warning("PIL not available for resizing. Using original image.")
- # Use enhanced encoder with proper MIME type detection
- from utils.image_utils import encode_image_for_api
- base64_data_url = encode_image_for_api(file_path)
- except Exception as e:
- logger.warning(f"Image resize failed: {str(e)}. Using original image.")
- # Use enhanced encoder with proper MIME type detection
- from utils.image_utils import encode_image_for_api
- base64_data_url = encode_image_for_api(file_path)
- else:
- # For smaller images, use as-is with proper MIME type
- from utils.image_utils import encode_image_for_api
- base64_data_url = encode_image_for_api(file_path)
- except Exception as e:
- # Fallback to original image if any preprocessing fails
- logger.warning(f"Image preprocessing failed: {str(e)}. Using original image.")
- # Use enhanced encoder with proper MIME type detection
- from utils.image_utils import encode_image_for_api
- base64_data_url = encode_image_for_api(file_path)
+ # Read and encode the image file
+ logger.info("Encoding image for API")
+ encoded_image = base64.b64encode(file_path.read_bytes()).decode()
+ base64_data_url = f"data:image/jpeg;base64,{encoded_image}"
# Process the image with OCR
logger.info(f"Processing image with OCR using {OCR_MODEL}")
-
- # Add retry logic with more retries and longer backoff periods for rate limit issues
- max_retries = 2 # Reduced to prevent rate limiting
- retry_delay = 1 # Shorter delay between retries
-
- for retry in range(max_retries):
- try:
- image_response = self.client.ocr.process(
- document=ImageURLChunk(image_url=base64_data_url),
- model=OCR_MODEL,
- include_image_base64=True,
- timeout_ms=45000 # 45 second timeout for better performance
- )
- break # Success, exit retry loop
- except Exception as e:
- error_msg = str(e)
- logger.warning(f"API error on attempt {retry+1}/{max_retries}: {error_msg}")
-
- # Check specific error types to handle them appropriately
- error_lower = error_msg.lower()
-
- # Authentication errors - no point in retrying
- if "unauthorized" in error_lower or "401" in error_lower:
- logger.error("API authentication failed. Check your API key.")
- raise ValueError(f"Authentication failed with API key. Please verify your Mistral API key is correct and active: {error_msg}")
-
- # Connection errors - worth retrying
- elif "connection" in error_lower or "timeout" in error_lower or "520" in error_msg or "server error" in error_lower:
- if retry < max_retries - 1:
- # Wait with shorter delay before retrying
- wait_time = retry_delay * (2 ** retry)
- logger.info(f"Connection issue detected. Waiting {wait_time}s before retry...")
- time.sleep(wait_time)
- else:
- # Last retry failed
- logger.error("Maximum retries reached, API connection error persists.")
- raise ValueError(f"Could not connect to Mistral API after {max_retries} attempts: {error_msg}")
-
- # Rate limit errors
- elif "rate limit" in error_lower or "429" in error_lower or "requests rate limit exceeded" in error_lower:
- # Check specifically for token exhaustion vs temporary rate limit
- if "quota" in error_lower or "credit" in error_lower or "subscription" in error_lower:
- logger.error("API quota or credit limit reached. No retry will help.")
- raise ValueError(f"Mistral API quota or credit limit reached. Please check your subscription: {error_msg}")
- elif retry < max_retries - 1:
- # More aggressive backoff for rate limits
- wait_time = retry_delay * (2 ** retry) * 5 # 5x longer wait for rate limits
- logger.info(f"Rate limit exceeded. Waiting {wait_time}s before retry...")
- time.sleep(wait_time)
- else:
- # Last retry failed, try local OCR as fallback
- logger.error("Maximum retries reached, rate limit error persists.")
- try:
- # Try to import the local OCR fallback function
- from utils.image_utils import try_local_ocr_fallback
-
- # Attempt local OCR fallback
- ocr_text = try_local_ocr_fallback(file_path, base64_data_url)
-
- if ocr_text:
- logger.info("Successfully used local OCR fallback")
- # Return a basic result with the local OCR text
- return {
- "file_name": file_path.name,
- "topics": ["Document"],
- "languages": ["English"],
- "ocr_contents": {
- "title": "Document (Local OCR)",
- "content": "This document was processed with local OCR due to API rate limiting.",
- "raw_text": ocr_text
- },
- "processing_method": "local_fallback",
- "processing_note": "Used local OCR due to API rate limit"
- }
- except (ImportError, Exception) as local_err:
- logger.warning(f"Local OCR fallback failed: {str(local_err)}")
-
- # If we get here, both API and local OCR failed
- raise ValueError(f"Mistral API rate limit exceeded. Please try again later: {error_msg}")
-
- # Other errors - no retry
- else:
- logger.error(f"Unrecoverable API error: {error_msg}")
- raise
+ image_response = self.client.ocr.process(
+ document=ImageURLChunk(image_url=base64_data_url),
+ model=OCR_MODEL,
+ include_image_base64=True
+ )
# Get the OCR markdown from the first page
image_ocr_markdown = image_response.pages[0].markdown if image_response.pages else ""
- # Check if the OCR response has images
- has_images = hasattr(image_response, 'pages') and image_response.pages and hasattr(image_response.pages[0], 'images') and image_response.pages[0].images
-
- # Check for language information directly from the OCR model
- detected_languages = set()
-
- # Check if the response has a 'languages' attribute in any form
- # First check direct attributes on the response object
- if hasattr(image_response, 'languages') and image_response.languages:
- for lang in image_response.languages:
- detected_languages.add(str(lang))
- logger.info(f"Found language in OCR response: {lang}")
-
- # Then check if it's in the response as a dictionary format
- elif hasattr(image_response, '__dict__'):
- response_dict = image_response.__dict__
- if 'languages' in response_dict and response_dict['languages']:
- for lang in response_dict['languages']:
- detected_languages.add(str(lang))
- logger.info(f"Found language in OCR response dict: {lang}")
-
- # Check for languages in individual pages
- if hasattr(image_response, 'pages') and image_response.pages:
- for page in image_response.pages:
- if hasattr(page, 'languages') and page.languages:
- for lang in page.languages:
- detected_languages.add(str(lang))
- logger.info(f"Found language in page: {lang}")
-
- # Optimize: Skip vision model step if ocr_markdown is very small or empty
- # BUT make an exception if custom_prompt is provided
- # OR if the image has visual content worth preserving
- if (not custom_prompt and not has_images) and (not image_ocr_markdown or len(image_ocr_markdown) < 50):
- logger.warning("OCR produced minimal text with no images. Returning basic result.")
- return {
- "file_name": file_path.name,
- "topics": ["Document"],
- "languages": ["English"],
- "ocr_contents": {
- "raw_text": image_ocr_markdown if image_ocr_markdown else "No text could be extracted from the image."
- },
- "processing_note": "OCR produced minimal text content",
- # Include raw response data for images
- "raw_response_data": serialize_ocr_response(image_response)
- }
-
- # For images with minimal text but visual content, enhance the prompt
- elif has_images and (not image_ocr_markdown or len(image_ocr_markdown) < 100):
- logger.info("Document with images but minimal text detected. Using enhanced prompt for mixed media.")
- if not custom_prompt:
- custom_prompt = "This is a mixed media document with both text and important visual elements. Please carefully describe the image content and extract all visible text, preserving the relationship between text and visuals."
- elif "visual" not in custom_prompt.lower() and "image" not in custom_prompt.lower():
- custom_prompt += " The document contains important visual elements that should be described along with the text content."
+ # Calculate confidence score if available
+ confidence_score = 0.85 # Default value
+ try:
+ if hasattr(image_response.pages[0], 'confidence'):
+ confidence_score = image_response.pages[0].confidence
+ except:
+ pass
- # Extract structured data using the appropriate model, with a single API call
+ # Extract structured data using the appropriate model
if use_vision:
logger.info(f"Using vision model: {VISION_MODEL}")
- result = self._extract_structured_data_with_vision(base64_data_url, image_ocr_markdown, file_path.name, custom_prompt)
+ result = self._extract_structured_data_with_vision(base64_data_url, image_ocr_markdown, file_path.name)
else:
logger.info(f"Using text-only model: {TEXT_MODEL}")
- result = self._extract_structured_data_text_only(image_ocr_markdown, file_path.name, custom_prompt)
-
- # If we have detected languages directly from the OCR model, use them
- if detected_languages:
- logger.info(f"Using languages detected by OCR model: {', '.join(detected_languages)}")
- result['languages'] = list(detected_languages)
- # Add flag to indicate source of language detection
- result['language_detection_source'] = 'mistral-ocr-latest'
-
- # Store the serialized OCR response for image rendering (for compatibility with original version)
- # Don't store raw_response directly as it's not JSON serializable
- serialized_response = serialize_ocr_response(image_response)
- result['raw_response_data'] = serialized_response
-
- # Store key parts of the OCR response for image rendering
- # With serialized format that can be stored in JSON
- result['has_images'] = has_images
-
- if has_images:
- # Serialize the entire response to ensure it's JSON serializable
- serialized_response = serialize_ocr_response(image_response)
+ result = self._extract_structured_data_text_only(image_ocr_markdown, file_path.name)
- # Create a structured representation of images that can be serialized
- result['pages_data'] = []
+ # Add confidence score
+ result['confidence_score'] = confidence_score
- if hasattr(serialized_response, 'pages'):
- serialized_pages = serialized_response.pages
- else:
- # Handle case where serialization returns a dict instead of an object
- serialized_pages = serialized_response.get('pages', [])
-
- for page_idx, page in enumerate(serialized_pages):
- # Handle both object and dict forms
- if isinstance(page, dict):
- markdown = page.get('markdown', '')
- images = page.get('images', [])
- else:
- markdown = page.markdown if hasattr(page, 'markdown') else ''
- images = page.images if hasattr(page, 'images') else []
-
- page_data = {
- 'page_number': page_idx + 1,
- 'markdown': markdown,
- 'images': []
- }
-
- # Extract images if present
- for img_idx, img in enumerate(images):
- img_id = None
- img_base64 = None
-
- if isinstance(img, dict):
- img_id = img.get('id')
- img_base64 = img.get('image_base64')
- else:
- img_id = img.id if hasattr(img, 'id') else None
- img_base64 = img.image_base64 if hasattr(img, 'image_base64') else None
-
- if img_base64:
- page_data['images'].append({
- 'id': img_id if img_id else f"img_{page_idx}_{img_idx}",
- 'image_base64': img_base64
- })
-
- result['pages_data'].append(page_data)
+ # Store the raw OCR response for image rendering
+ result['raw_response'] = image_response
logger.info("Image processing completed successfully")
return result
@@ -1462,6 +313,7 @@ class StructuredOCR:
"file_name": file_path.name,
"topics": ["Document"],
"languages": ["English"],
+ "confidence_score": 0.0,
"error": str(e),
"ocr_contents": {
"error": f"Failed to process image: {str(e)}",
@@ -1469,198 +321,29 @@ class StructuredOCR:
}
}
- def _extract_structured_data_with_vision(self, image_base64, ocr_markdown, filename, custom_prompt=None):
- """
- Extract structured data using vision model with detailed historical context prompting
- Optimized for speed, accuracy, and resilience
- """
- logger = logging.getLogger("vision_processor")
-
+ def _extract_structured_data_with_vision(self, image_base64, ocr_markdown, filename):
+ """Extract structured data using vision model"""
try:
- # Check if this is a newspaper or document with columns by filename
- is_likely_newspaper = False
- newspaper_keywords = ["newspaper", "gazette", "herald", "times", "journal",
- "chronicle", "post", "tribune", "news", "press", "gender"]
-
- # Check filename for newspaper indicators
- filename_lower = filename.lower()
- for keyword in newspaper_keywords:
- if keyword in filename_lower:
- is_likely_newspaper = True
- logger.info(f"Likely newspaper document detected in vision processing: {filename}")
- break
-
- # Fast path: Skip vision API if OCR already produced reasonable text
- # We'll define "reasonable" as having at least 300 characters
- if len(ocr_markdown.strip()) > 300:
- logger.info("Sufficient OCR text detected, analyzing language before using OCR text directly")
-
- # Perform language detection on the OCR text before returning
- if LANG_DETECTOR_AVAILABLE and self.language_detector:
- detected_languages = self.language_detector.detect_languages(
- ocr_markdown,
- filename=getattr(self, 'current_filename', None)
- )
- else:
- # If language detector is not available, use default English
- detected_languages = ["English"]
-
- return {
- "file_name": filename,
- "topics": ["Document"],
- "languages": detected_languages,
- "ocr_contents": {
- "raw_text": ocr_markdown
- }
- }
-
- # Only use vision model for minimal OCR text or when document has columns
- if is_likely_newspaper and (not ocr_markdown or len(ocr_markdown.strip()) < 300):
- logger.info("Using vision model for newspaper with minimal OCR text")
- if not custom_prompt:
- custom_prompt = "Document has columns. Extract text by reading each column top to bottom."
-
- # Fast path: Skip if in test mode or no API key
- if self.test_mode or not self.api_key:
- logger.info("Test mode or no API key, using text-only processing")
- return self._extract_structured_data_text_only(ocr_markdown, filename)
-
- # Use only the first part of OCR text to keep prompts small and processing fast
- if len(ocr_markdown) > 1000:
- truncated_ocr = ocr_markdown[:1000]
- logger.info(f"Truncated OCR text from {len(ocr_markdown)} to 1000 chars for faster processing")
- else:
- truncated_ocr = ocr_markdown
-
- # Build a comprehensive prompt with OCR text and detailed instructions for title detection and language handling
- enhanced_prompt = f"This is a document's OCR text:\n\n{truncated_ocr}\n\n\n"
-
- # Add custom prompt if provided
- if custom_prompt:
- enhanced_prompt += f"User instructions: {custom_prompt}\n\n"
-
- # Primary focus on document structure and title detection
- enhanced_prompt += "You are analyzing a historical document. Follow these extraction priorities:\n"
- enhanced_prompt += "1. FIRST PRIORITY: Identify and extract the TITLE of the document. Look for large text at the top, decorative typography, or centered text that appears to be a title. The title is often one of the first elements in historical documents.\n"
- enhanced_prompt += "2. SECOND: Extract all text content accurately from this document, including any text visible in the image that may not have been captured by OCR.\n\n"
- enhanced_prompt += "Document Title Guidelines:\n"
- enhanced_prompt += "- For printed historical works: Look for primary heading at top of the document, all-caps text, or larger font size text\n"
- enhanced_prompt += "- For newspapers/periodicals: Extract both newspaper name and article title if present\n"
- enhanced_prompt += "- For handwritten documents: Look for centered text at the top or underlined headings\n"
- enhanced_prompt += "- For engravings/illustrations: Include the title or caption, which often appears below the image\n\n"
-
- # Language detection guidance
- enhanced_prompt += "IMPORTANT: After extracting the title and text content, determine the languages present.\n"
- enhanced_prompt += "Precisely identify and list ALL languages present in the document separately. Look closely for multiple languages that might appear together.\n"
- enhanced_prompt += "For language detection, examine these specific indicators:\n"
- enhanced_prompt += "- French: accents (é, è, ê, à, ç, â, î, ô, û), words like 'le', 'la', 'les', 'et', 'en', 'de', 'du', 'des', 'dans', 'ce', 'cette', 'ces', 'par', 'pour', 'qui', 'que', 'où', 'avec'\n"
- enhanced_prompt += "- Portuguese: accents (ã, õ, á, é, ê, ó, ç), words like 'e', 'o', 'de', 'da', 'do', 'em', 'para', 'que', 'não', 'com'\n"
- enhanced_prompt += "- Spanish: ñ, inverted punctuation (¿, ¡), accents (á, é, í, ó, ú), words like 'el', 'la', 'los', 'las', 'y', 'en', 'por', 'que', 'con'\n"
- enhanced_prompt += "- German: umlauts (ä, ö, ü), sharp s (ß), words like 'und', 'der', 'die', 'das', 'in', 'mit'\n"
- enhanced_prompt += "- Italian: accents (à, è, é, ì, ò, ù), words like 'il', 'la', 'e', 'di', 'che', 'per', 'con'\n"
- enhanced_prompt += "- Chinese: hanzi characters (汉字), lack of spaces between words, markers like 的, 是, 了, 在, 和, 有\n"
- enhanced_prompt += "- Latin: words like 'et', 'in', 'ad', 'est', 'sunt', 'non', 'cum', 'sed'\n\n"
- enhanced_prompt += "If the document contains multiple columns or sections, process each section independently and then combine them logically.\n"
- enhanced_prompt += "Return ALL detected languages as separate entries in the languages array, never combine them.\n"
- enhanced_prompt += "CRITICAL: Do NOT default to English unless absolutely certain. If you see French characteristics like 'é', 'è', 'ê', 'ç' or French words, prioritize French in your language detection."
-
- # Measure API call time for optimization feedback
- start_time = time.time()
-
- try:
- # Use a fixed, shorter timeout for single-page documents
- timeout_ms = 45000 # 45 seconds is optimal for most single-page documents
-
- logger.info(f"Calling vision model with {timeout_ms}ms timeout")
- chat_response = self.client.chat.parse(
- model=VISION_MODEL,
- messages=[
- {
- "role": "user",
- "content": [
- ImageURLChunk(image_url=image_base64),
- TextChunk(text=enhanced_prompt)
- ],
- },
- ],
- response_format=StructuredOCRModel,
- temperature=0,
- timeout_ms=timeout_ms
- )
-
- api_time = time.time() - start_time
- logger.info(f"Vision model completed in {api_time:.2f}s")
-
- except Exception as e:
- # If there's an error with the enhanced prompt, try progressively simpler approaches
- logger.warning(f"Enhanced prompt failed after {time.time() - start_time:.2f}s: {str(e)}")
-
- # Try a very simplified approach with minimal context
- try:
- # Ultra-short prompt for faster processing
- simplified_prompt = (
- f"Extract text from this document image. "
- f"\n{truncated_ocr[:500]}\n\n"
- f"Return a JSON with file_name, topics, languages, and ocr_contents fields."
- )
-
- # Only add minimal custom prompt if provided
- if custom_prompt and len(custom_prompt) < 100:
- simplified_prompt += f"\n{custom_prompt}"
-
- logger.info(f"Trying simplified prompt approach")
- chat_response = self.client.chat.parse(
- model=VISION_MODEL,
- messages=[
- {
- "role": "user",
- "content": [
- ImageURLChunk(image_url=image_base64),
- TextChunk(text=simplified_prompt)
- ],
- },
+ # Parse with vision model with a timeout
+ chat_response = self.client.chat.parse(
+ model=VISION_MODEL,
+ messages=[
+ {
+ "role": "user",
+ "content": [
+ ImageURLChunk(image_url=image_base64),
+ TextChunk(text=(
+ f"This is a historical document's OCR in markdown:\n"
+ f"\n{ocr_markdown}\n.\n"
+ f"Convert this into a structured JSON response with the OCR contents in a sensible dictionary. "
+ f"Extract topics, languages, and organize the content logically."
+ ))
],
- response_format=StructuredOCRModel,
- temperature=0,
- timeout_ms=30000 # Very short timeout for simplified approach (30 seconds)
- )
-
- logger.info(f"Simplified prompt approach succeeded")
-
- except Exception as second_e:
- # If that fails, try with minimal prompt and just image analysis
- logger.warning(f"Simplified prompt failed: {str(second_e)}. Trying minimal prompt.")
-
- try:
- # Minimal prompt focusing only on OCR task
- minimal_prompt = (
- f"Extract the text from this image. "
- f"Return JSON with file_name, topics, languages, and ocr_contents.raw_text fields."
- )
-
- logger.info(f"Trying minimal prompt with image-only focus")
- chat_response = self.client.chat.parse(
- model=VISION_MODEL,
- messages=[
- {
- "role": "user",
- "content": [
- ImageURLChunk(image_url=image_base64),
- TextChunk(text=minimal_prompt)
- ],
- },
- ],
- response_format=StructuredOCRModel,
- temperature=0,
- timeout_ms=25000 # Minimal timeout for last attempt (25 seconds)
- )
-
- logger.info(f"Minimal prompt approach succeeded")
-
- except Exception as third_e:
- # If all vision attempts fail, fall back to text-only model
- logger.warning(f"All vision model attempts failed, falling back to text-only model: {str(third_e)}")
- return self._extract_structured_data_text_only(ocr_markdown, filename)
+ },
+ ],
+ response_format=StructuredOCRModel,
+ temperature=0
+ )
# Convert the response to a dictionary
result = json.loads(chat_response.choices[0].message.parsed.json())
@@ -1668,314 +351,51 @@ class StructuredOCR:
# Ensure languages is a list of strings, not Language enum objects
if 'languages' in result:
result['languages'] = [str(lang) for lang in result.get('languages', [])]
-
- # Add simplified metadata about processing
- result['processing_info'] = {
- 'method': 'vision_model',
- 'ocr_text_length': len(ocr_markdown),
- 'api_response_time': time.time() - start_time
- }
-
- # Note if custom prompt was applied
- if custom_prompt:
- result['custom_prompt_applied'] = 'vision_model'
- # Add confidence score if not present
- if 'confidence_score' not in result:
- result['confidence_score'] = 0.92 # Vision model typically has higher confidence
-
- # If OCR text has clear French patterns but language is English or missing, fix it
- if ocr_markdown and 'languages' in result:
- if LANG_DETECTOR_AVAILABLE and self.language_detector:
- result['languages'] = self.language_detector.detect_languages(
- ocr_markdown,
- filename=getattr(self, 'current_filename', None),
- current_languages=result['languages']
- )
-
except Exception as e:
# Fall back to text-only model if vision model fails
- logger.warning(f"Vision model processing failed, falling back to text-only model: {str(e)}")
+ print(f"Vision model failed: {str(e)}. Falling back to text-only model.")
result = self._extract_structured_data_text_only(ocr_markdown, filename)
return result
-
- # We've removed document type detection entirely for simplicity
-
-
- # Create a prompt with enhanced language detection instructions
- generic_section = (
- f"You are an OCR specialist processing historical documents. "
- f"Focus on accurately extracting text content and image chunks while preserving structure and formatting. "
- f"Pay attention to any historical features and document characteristics.\n\n"
- f"Create a structured JSON response with the following fields:\n"
- f"- file_name: The document's name\n"
- f"- topics: An array of topics covered in the document\n"
- f"- languages: An array of languages used in the document (be precise and specific about language detection)\n"
- f"- ocr_contents: A comprehensive dictionary with the document's contents including:\n"
- f" * title: The title or heading (if present)\n"
- f" * transcript: The full text of the document\n"
- f" * text: The main text content (if different from transcript)\n"
- f" * content: The body content (if different than transcript)\n"
- f" * images: An array of image objects with their base64 data\n"
- f" * alt_text: The alt text or description of the images\n"
- f" * caption: The caption or title of the images\n"
- f" * raw_text: The complete OCR text\n"
- )
-
- # Add custom prompt if provided
- custom_section = ""
- if custom_prompt:
- custom_section = f"\n\nUser-provided instructions: {custom_prompt}\n"
-
- # Return the enhanced prompt
- return generic_section + custom_section
-
- def _extract_structured_data_text_only(self, ocr_markdown, filename, custom_prompt=None):
- """
- Extract structured data using text-only model with detailed historical context prompting
- and improved error handling with enhanced language detection
- """
- logger = logging.getLogger("text_processor")
- start_time = time.time()
-
+
+ def _extract_structured_data_text_only(self, ocr_markdown, filename):
+ """Extract structured data using text-only model"""
try:
- # Fast path: Skip for minimal OCR text
- if not ocr_markdown or len(ocr_markdown.strip()) < 50:
- logger.info("Minimal OCR text - returning basic result")
-
- # Attempt comprehensive language detection even for minimal text
- detected_languages = []
-
- # Simple language detection based on character frequency
- if ocr_markdown and len(ocr_markdown.strip()) > 10:
- # Define indicators for all supported languages
- language_indicators = {
- "Portuguese": {
- "chars": ['ã', 'õ', 'á', 'é', 'ê', 'í', 'ó', 'ú', 'ç'],
- "words": ['e', 'o', 'de', 'da', 'do', 'em', 'para', 'que', 'não', 'com']
- },
- "Spanish": {
- "chars": ['ñ', 'á', 'é', 'í', 'ó', 'ú', '¿', '¡'],
- "words": ['el', 'la', 'los', 'las', 'y', 'en', 'por', 'que', 'con', 'del']
- },
- "French": {
- "chars": ['é', 'è', 'ê', 'à', 'ç', 'ù', 'â', 'î', 'ô', 'û'],
- "words": ['le', 'la', 'les', 'et', 'en', 'de', 'du', 'des', 'un', 'une', 'ce', 'cette', 'qui', 'que', 'pour', 'dans', 'par', 'sur']
- },
- "German": {
- "chars": ['ä', 'ö', 'ü', 'ß'],
- "words": ['der', 'die', 'das', 'und', 'ist', 'von', 'mit', 'für', 'sich']
- },
- "Italian": {
- "chars": ['à', 'è', 'é', 'ì', 'ò', 'ù'],
- "words": ['il', 'la', 'e', 'di', 'che', 'per', 'con', 'sono', 'non']
- },
- "Latin": {
- "chars": [],
- "words": ['et', 'in', 'ad', 'est', 'sunt', 'non', 'cum', 'sed', 'qui', 'quod']
- }
- }
-
- words = ocr_markdown.lower().split()
-
- # Check for indicators of each language
- for language, indicators in language_indicators.items():
- chars = indicators["chars"]
- lang_words = indicators["words"]
-
- has_chars = any(char in ocr_markdown for char in chars) if chars else False
- word_count = sum(1 for word in words if word in lang_words)
-
- # Add language if strong enough indicators are present
- if has_chars or word_count >= 2:
- detected_languages.append(language)
-
- # Check for English separately
- english_words = ['the', 'and', 'of', 'to', 'in', 'a', 'is', 'that', 'for', 'it']
- english_count = sum(1 for word in words if word in english_words)
- if english_count >= 2:
- detected_languages.append("English")
-
- # If no languages detected, default to English
- if not detected_languages:
- detected_languages = ["English"]
-
- return {
- "file_name": filename,
- "topics": ["Document"],
- "languages": detected_languages,
- "ocr_contents": {
- "raw_text": ocr_markdown if ocr_markdown else "No text could be extracted"
- },
- "processing_method": "minimal_text"
- }
-
- # Check for API key to avoid unnecessary processing
- if self.test_mode or not self.api_key:
- logger.info("Test mode or no API key - returning basic result")
- return {
- "file_name": filename,
- "topics": ["Document"],
- "languages": ["English"],
- "ocr_contents": {
- "raw_text": ocr_markdown[:10000] if ocr_markdown else "No text could be extracted",
- "note": "API key not provided - showing raw OCR text only"
+ # Parse with text-only model with a timeout
+ chat_response = self.client.chat.parse(
+ model=TEXT_MODEL,
+ messages=[
+ {
+ "role": "user",
+ "content": f"This is a historical document's OCR in markdown:\n"
+ f"\n{ocr_markdown}\n.\n"
+ f"Convert this into a structured JSON response with the OCR contents. "
+ f"Extract topics, languages, and organize the content logically."
},
- "processing_method": "test_mode"
- }
-
- # If OCR text is very large, truncate it to avoid API limits
- truncated_text = ocr_markdown
- if len(ocr_markdown) > 25000:
- # Keep first 15000 chars and last 5000 chars
- truncated_text = ocr_markdown[:15000] + "\n...[content truncated]...\n" + ocr_markdown[-5000:]
- logger.info(f"OCR text truncated from {len(ocr_markdown)} to {len(truncated_text)} chars")
-
- # Build a prompt with enhanced title detection and language detection instructions
- enhanced_prompt = f"This is a document's OCR text:\n\n{truncated_text}\n\n\n"
+ ],
+ response_format=StructuredOCRModel,
+ temperature=0
+ )
- # Add custom prompt if provided
- if custom_prompt:
- enhanced_prompt += f"User instructions: {custom_prompt}\n\n"
-
- # Add title detection focus
- enhanced_prompt += "You are analyzing a historical document. Please follow these extraction priorities:\n"
- enhanced_prompt += "1. FIRST PRIORITY: Identify and extract the TITLE of the document. Look for prominent text at the top, decorative typography, or centered text that appears to be a title.\n"
- enhanced_prompt += " - For historical documents with prominent headings at the top\n"
- enhanced_prompt += " - For newspapers or periodicals, extract both the publication name and article title\n"
- enhanced_prompt += " - For manuscripts or letters, identify any heading or subject line\n"
- enhanced_prompt += "2. SECOND PRIORITY: Extract all text content accurately and return structured data with the document's contents.\n\n"
- enhanced_prompt += "IMPORTANT: Precisely identify and list ALL languages present in the document separately. Look closely for multiple languages that might appear together.\n"
- enhanced_prompt += "For language detection, examine these specific indicators:\n"
- enhanced_prompt += "- French: accents (é, è, ê, à, ç), words like 'le', 'la', 'les', 'et', 'en', 'de', 'du'\n"
- enhanced_prompt += "- German: umlauts (ä, ö, ü), sharp s (ß), words like 'und', 'der', 'die', 'das', 'in', 'mit'\n"
- enhanced_prompt += "- Spanish: ñ, inverted punctuation (¿, ¡), accents (á, é, í, ó, ú), words like 'el', 'la', 'los', 'las', 'y', 'en'\n"
- enhanced_prompt += "- Italian: words like 'il', 'la', 'e', 'di', 'che', 'per', 'con'\n"
- enhanced_prompt += "- Chinese: hanzi characters (汉字), lack of spaces between words, markers like 的, 是, 了, 在, 和, 有\n"
- enhanced_prompt += "- Latin: words like 'et', 'in', 'ad', 'est', 'sunt', 'non', 'cum', 'sed'\n"
- enhanced_prompt += "Do NOT classify text as English unless you can positively confirm it contains specifically English words and phrases.\n\n"
- enhanced_prompt += "Return ALL detected languages as separate entries in the languages array. If multiple languages are present, list them ALL separately."
-
- # Use enhanced prompt with text-only model - with retry logic
- max_retries = 2
- retry_delay = 1
-
- for retry in range(max_retries):
- try:
- logger.info(f"Calling text model ({TEXT_MODEL})")
- api_start = time.time()
-
- # Set appropriate timeout based on text length
- timeout_ms = min(120000, max(30000, len(truncated_text) * 5)) # 30-120s based on length
-
- # Make API call with appropriate timeout
- chat_response = self.client.chat.parse(
- model=TEXT_MODEL,
- messages=[
- {
- "role": "user",
- "content": enhanced_prompt
- },
- ],
- response_format=StructuredOCRModel,
- temperature=0,
- timeout_ms=timeout_ms
- )
-
- api_time = time.time() - api_start
- logger.info(f"Text model API call completed in {api_time:.2f}s")
-
- # Convert the response to a dictionary
- result = json.loads(chat_response.choices[0].message.parsed.json())
-
- # Ensure languages is a list of strings, not Language enum objects
- if 'languages' in result:
- result['languages'] = [str(lang) for lang in result.get('languages', [])]
-
- # Add simplified processing metadata
- result['processing_method'] = 'text_model'
- result['model_used'] = TEXT_MODEL
- result['processing_time'] = time.time() - start_time
-
- # Flag when custom prompt has been successfully applied
- if custom_prompt:
- result['custom_prompt_applied'] = 'text_model'
-
- # Add raw text for reference if not already present
- if 'ocr_contents' in result and 'raw_text' not in result['ocr_contents']:
- # Add truncated raw text if very large
- if len(ocr_markdown) > 50000:
- result['ocr_contents']['raw_text'] = ocr_markdown[:50000] + "\n...[content truncated]..."
- else:
- result['ocr_contents']['raw_text'] = ocr_markdown
-
- return result
-
- except Exception as api_error:
- error_msg = str(api_error).lower()
- logger.warning(f"API error on attempt {retry+1}/{max_retries}: {str(api_error)}")
-
- # Check if retry would help
- if retry < max_retries - 1:
- # Rate limit errors - special handling with longer wait
- if any(term in error_msg for term in ["rate limit", "429", "too many requests", "requests rate limit exceeded"]):
- # Check specifically for token exhaustion vs temporary rate limit
- if any(term in error_msg for term in ["quota", "credit", "subscription"]):
- logger.error("API quota or credit limit reached. No retry will help.")
- raise ValueError(f"Mistral API quota or credit limit reached. Please check your subscription: {error_msg}")
- # Longer backoff for rate limit errors
- wait_time = retry_delay * (2 ** retry) * 6.0 # 6x longer wait for rate limits
- logger.info(f"Rate limit exceeded. Waiting {wait_time:.1f}s before retry...")
- time.sleep(wait_time)
- # Other transient errors
- elif any(term in error_msg for term in ["timeout", "connection", "500", "503", "504"]):
- # Wait before retrying
- wait_time = retry_delay * (2 ** retry)
- logger.info(f"Transient error, retrying in {wait_time}s")
- time.sleep(wait_time)
- else:
- # Non-retryable error
- raise
- else:
- # Last retry failed
- raise
+ # Convert the response to a dictionary
+ result = json.loads(chat_response.choices[0].message.parsed.json())
- # This shouldn't be reached due to raise in the loop, but just in case
- raise Exception("All retries failed for text model")
+ # Ensure languages is a list of strings, not Language enum objects
+ if 'languages' in result:
+ result['languages'] = [str(lang) for lang in result.get('languages', [])]
except Exception as e:
- logger.error(f"Text model failed: {str(e)}. Creating basic result.")
-
- # Create a basic result with available OCR text
- try:
- # Create a more informative fallback result
- result = {
- "file_name": filename,
- "topics": ["Document"],
- "languages": ["English"],
- "ocr_contents": {
- "raw_text": ocr_markdown[:50000] if ocr_markdown else "No text could be extracted",
- "error": "AI processing failed: " + str(e).replace('"', '\\"')
- },
- "processing_method": "fallback",
- "processing_error": str(e),
- "processing_time": time.time() - start_time
- }
-
- # No topic detection to avoid issue with document misclassification
-
- except Exception as inner_e:
- logger.error(f"Error creating basic result: {str(inner_e)}")
- result = {
- "file_name": str(filename) if filename else "unknown",
- "topics": ["Document"],
- "languages": ["English"],
- "ocr_contents": {
- "error": "Processing failed completely",
- "partial_text": ocr_markdown[:1000] if ocr_markdown else "Document could not be processed."
- }
+ # Create a basic result if parsing fails
+ print(f"Text model failed: {str(e)}. Creating basic result.")
+ result = {
+ "file_name": filename,
+ "topics": ["Document"],
+ "languages": ["English"],
+ "ocr_contents": {
+ "raw_text": ocr_markdown
}
+ }
return result
@@ -1991,4 +411,4 @@ if __name__ == "__main__":
processor = StructuredOCR()
result = processor.process_file(file_path)
- print(json.dumps(result, indent=2))
+ print(json.dumps(result, indent=2))
\ No newline at end of file
diff --git a/test_pdf.py b/test_pdf.py
new file mode 100644
index 0000000000000000000000000000000000000000..370e4fb962d73a0644048029a300acad36ff2670
--- /dev/null
+++ b/test_pdf.py
@@ -0,0 +1,42 @@
+#!/usr/bin/env python3
+"""
+Test script for pdf_ocr.py
+"""
+
+from pdf_ocr import PDFOCR
+import json
+import os
+
+def main():
+ # Initialize PDF processor
+ processor = PDFOCR()
+
+ # Define input and output paths
+ pdf_path = "input/rubric.pdf"
+ output_path = "output/rubric_test.json"
+
+ # Create output directory if it doesn't exist
+ os.makedirs(os.path.dirname(output_path), exist_ok=True)
+
+ # Process PDF and save output
+ print(f"Processing PDF: {pdf_path}")
+ processor.save_json_output(pdf_path, output_path)
+ print(f"Output saved to: {output_path}")
+
+ # Read and print the output
+ with open(output_path, 'r') as f:
+ result = json.load(f)
+
+ print("\nOutput preview:")
+ print(f"File name: {result.get('file_name')}")
+ print(f"Topics: {result.get('topics')}")
+ print(f"Languages: {result.get('languages')}")
+ print("OCR contents preview (first few keys):")
+ ocr_contents = result.get('ocr_contents', {})
+ for i, (key, value) in enumerate(ocr_contents.items()):
+ if i >= 3: # Only show first 3 keys
+ break
+ print(f" {key}: {value[:100]}..." if isinstance(value, str) and len(value) > 100 else f" {key}: {value}")
+
+if __name__ == "__main__":
+ main()
\ No newline at end of file
diff --git a/test_pdf_preview.py b/test_pdf_preview.py
new file mode 100644
index 0000000000000000000000000000000000000000..991f29993f32fc9aa5b9eac28bcd5abcfb23bdd6
--- /dev/null
+++ b/test_pdf_preview.py
@@ -0,0 +1,51 @@
+#!/usr/bin/env python3
+"""
+Test PDF preview functionality
+"""
+import os
+import io
+from pathlib import Path
+from pdf2image import convert_from_path
+from PIL import Image
+
+def test_pdf_preview():
+ """Test converting a PDF to an image preview"""
+ # Get the first PDF file from the input directory
+ input_dir = Path(__file__).parent / "input"
+ pdf_files = list(input_dir.glob("*.pdf"))
+
+ if not pdf_files:
+ print("No PDF files found in the input directory")
+ return
+
+ pdf_path = pdf_files[0]
+ print(f"Testing PDF preview with file: {pdf_path}")
+
+ try:
+ # Convert first page of PDF to image
+ images = convert_from_path(pdf_path, first_page=1, last_page=1)
+
+ if not images:
+ print("No images extracted from PDF")
+ return
+
+ # Save the preview image
+ first_page = images[0]
+ output_dir = Path(__file__).parent / "output"
+ output_dir.mkdir(exist_ok=True)
+ output_path = output_dir / f"{pdf_path.stem}_preview.jpg"
+
+ first_page.save(output_path, format='JPEG')
+ print(f"PDF preview saved to: {output_path}")
+
+ # Demonstrate converting to bytes for Streamlit
+ img_bytes = io.BytesIO()
+ first_page.save(img_bytes, format='JPEG')
+ img_bytes.seek(0)
+ print(f"Successfully converted PDF to image bytes (size: {len(img_bytes.getvalue())} bytes)")
+
+ except Exception as e:
+ print(f"Error converting PDF to image: {str(e)}")
+
+if __name__ == "__main__":
+ test_pdf_preview()
\ No newline at end of file
diff --git a/ui/__init__.py b/ui/__init__.py
deleted file mode 100644
index 00d0c72780f0d3a3992493a13655d54dfd526bcc..0000000000000000000000000000000000000000
--- a/ui/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-"""UI package for Historical OCR."""
diff --git a/ui/__pycache__/layout.cpython-312.pyc b/ui/__pycache__/layout.cpython-312.pyc
index 429b8fdb06e42a047a988d80b79d05a242b39ee2..8195077b0f7c910a242f5a01ddae54a86d52c37e 100644
Binary files a/ui/__pycache__/layout.cpython-312.pyc and b/ui/__pycache__/layout.cpython-312.pyc differ
diff --git a/ui/__pycache__/layout.cpython-313.pyc b/ui/__pycache__/layout.cpython-313.pyc
deleted file mode 100644
index a908c03eaf7161c981a183fe9592df619a3877cc..0000000000000000000000000000000000000000
Binary files a/ui/__pycache__/layout.cpython-313.pyc and /dev/null differ
diff --git a/ui/custom.css b/ui/custom.css
index 34d6585972360cfd77b215d1e599f8c62f05f8c7..e701ad662756ea4df4382bc4e124401e4581bae8 100644
--- a/ui/custom.css
+++ b/ui/custom.css
@@ -1,252 +1,303 @@
-/* Custom CSS for Historical OCR Application */
-
-/* Global styles */
-body {
- font-family: 'Source Sans Pro', sans-serif;
- color: #333;
+/* Base Tailwind-like styles */
+:root {
+ --color-gray-900: #111827;
+ --color-gray-800: #1f2937;
+ --color-gray-700: #374151;
+ --color-gray-600: #4B5563;
+ --color-gray-500: #6B7280;
+ --color-gray-400: #9CA3AF;
+ --color-gray-300: #D1D5DB;
+ --color-gray-200: #E5E7EB;
+ --color-gray-100: #F3F4F6;
+ --color-gray-50: #F9FAFB;
+
+ --color-blue-900: #1E3A8A;
+ --color-blue-800: #1E40AF;
+ --color-blue-700: #1D4ED8;
+ --color-blue-600: #2563EB;
+ --color-blue-500: #3B82F6;
+ --color-blue-400: #60A5FA;
+ --color-blue-300: #93C5FD;
+ --color-blue-200: #BFDBFE;
+ --color-blue-100: #DBEAFE;
+ --color-blue-50: #EFF6FF;
+
+ --color-yellow-50: #FFFBEB;
+ --color-yellow-100: #FEF3C7;
+}
+
+/* Global Styles */
+.stApp {
+ background-color: var(--color-gray-900);
+ color: white;
}
-/* Header styles */
-h1, h2, h3, h4, h5, h6 {
- font-family: 'Georgia', serif;
- font-weight: 600;
- color: #1E3A8A;
+/* Main header */
+.main-header {
+ background-color: black;
+ padding: 1rem;
+ border-bottom: 1px solid var(--color-gray-700);
}
-/* Document content styling - with lower specificity to allow layout.py to override text formatting */
-.document-content {
- margin-top: 12px;
+.title-text {
+ font-size: 1.5rem;
+ font-weight: bold;
+ color: white;
}
-.document-section {
- margin-bottom: 12px;
- padding: 10px;
- background-color: #fff;
- border-radius: 8px;
- border: 1px solid #e0e0e0;
+/* Content containers */
+.content-container {
+ background-color: var(--color-gray-800);
+ border-radius: 0.75rem;
+ padding: 1.5rem;
+ margin-bottom: 1.5rem;
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2);
+ color: white;
}
-/* Preserve headings style while allowing font to be overridden */
-.document-section h4 {
- margin-top: 0;
- margin-bottom: 10px;
- /* color moved to layout.py */
+.blue-container {
+ background-color: var(--color-blue-100);
+ color: var(--color-gray-900);
+ border-radius: 0.75rem;
+ padding: 1.5rem;
+ margin-bottom: 1.5rem;
}
-/* Subject tag styling - lower priority than layout.py versions */
-/* These styles will be overridden by the more specific selectors in layout.py */
-.subject-tag {
- /* Basic sizing only - styling comes from layout.py */
- display: inline-block;
- margin-right: 5px;
- margin-bottom: 5px;
+.yellow-container {
+ background-color: var(--color-yellow-50);
+ color: var(--color-gray-900);
+ border-radius: 0.75rem;
+ padding: 1.5rem;
+ margin-bottom: 1.5rem;
}
-/* Tag colors moved to layout.py with !important rules */
+/* Card grid styles */
+.card-grid {
+ display: grid;
+ grid-template-columns: repeat(1, 1fr);
+ gap: 1.5rem;
+ margin-bottom: 1.5rem;
+}
-/* Image and text side-by-side styling - layout only */
-.image-text-container {
- display: flex;
- gap: 20px;
- margin-bottom: 20px;
+@media (min-width: 768px) {
+ .card-grid {
+ grid-template-columns: repeat(3, 1fr);
+ }
}
-.image-container {
- flex: 1;
+.card {
+ background-color: var(--color-gray-700);
+ border-radius: 0.5rem;
+ padding: 1rem;
+ color: white;
}
-.text-container {
- flex: 1;
- /* Text styling will come from layout.py */
+/* Special containers */
+.key-concept {
+ background-color: var(--color-gray-700);
+ border-radius: 0.5rem;
+ padding: 0.75rem;
+ margin: 1rem 0;
+ border-left: 3px solid var(--color-blue-500);
+ color: white;
}
-/* Sidebar styling */
-.sidebar-section {
- margin-bottom: 20px;
+.research-question {
+ background-color: var(--color-blue-900);
+ border-radius: 0.5rem;
+ padding: 0.75rem;
+ margin: 1rem 0;
+ border-left: 3px solid var(--color-blue-400);
+ color: white;
}
-.sidebar-section h3 {
- margin-top: 0;
- margin-bottom: 10px;
- font-size: 16px;
+.quote-container {
+ font-style: italic;
+ color: var(--color-gray-300);
+ padding: 0.5rem 1rem;
+ border-left: 3px solid var(--color-gray-600);
+ margin: 1rem 0;
}
-/* Button styling */
-.primary-button {
- background-color: #1E88E5;
- color: white;
- border: none;
- border-radius: 4px;
- padding: 6px 12px;
- font-weight: 600;
- cursor: pointer;
- transition: background-color 0.2s;
+/* Navigation */
+.nav-container {
+ position: fixed;
+ bottom: 0;
+ left: 0;
+ width: 100%;
+ background-color: black;
+ border-top: 1px solid var(--color-gray-700);
+ padding: 0.75rem 1rem;
+ display: flex;
+ justify-content: space-between;
+ z-index: 1000;
}
-.primary-button:hover {
- background-color: #1565C0;
+.nav-buttons {
+ display: flex;
+ gap: 0.5rem;
}
-.secondary-button {
- background-color: #f8f9fa;
- color: #333;
- border: 1px solid #ddd;
- border-radius: 4px;
- padding: 6px 12px;
- font-weight: 600;
+.prev-button {
+ background-color: var(--color-gray-700);
+ color: white;
+ padding: 0.5rem 1rem;
+ border-radius: 0.25rem;
+ border: none;
cursor: pointer;
- transition: background-color 0.2s;
}
-.secondary-button:hover {
- background-color: #e9ecef;
+.prev-button:hover {
+ background-color: var(--color-gray-600);
}
-/* Processing status styling */
-.processing-status {
- padding: 8px 12px;
- border-left: 4px solid #1E88E5;
- background-color: #E3F2FD;
- border-radius: 0 4px 4px 0;
- margin: 8px 0;
- font-size: 14px;
+.next-button {
+ background-color: var(--color-blue-600);
+ color: white;
+ padding: 0.5rem 1rem;
+ border-radius: 0.25rem;
+ border: none;
+ cursor: pointer;
}
-/* Previous results styling */
-.previous-results-container {
- margin-top: 12px;
+.next-button:hover {
+ background-color: var(--color-blue-700);
}
-.result-card {
- background-color: transparent;
- border-radius: 8px;
- padding: 12px;
- margin-bottom: 12px;
- transition: all 0.2s ease;
- color: #333; /* Ensure text has good contrast with background */
+.nav-dots {
+ display: none;
}
-.result-card:hover {
- box-shadow: 0 4px 8px rgba(0,0,0,0.1);
- border-color: #c0c0c0;
+@media (min-width: 768px) {
+ .nav-dots {
+ display: flex;
+ gap: 0.25rem;
+ }
}
-.result-header {
+.nav-dot {
+ width: 2rem;
+ height: 2rem;
display: flex;
- justify-content: space-between;
- margin-bottom: 10px;
+ align-items: center;
+ justify-content: center;
+ color: var(--color-gray-300);
+ border-radius: 0.25rem;
+ text-decoration: none;
+ font-size: 0.875rem;
}
-.result-filename {
- font-weight: bold;
- font-size: 16px;
- color: #333; /* Explicit text color */
+.nav-dot:hover {
+ background-color: var(--color-gray-800);
}
-.result-date {
- color: #666;
- font-size: 14px;
+.nav-dot.active {
+ background-color: var(--color-blue-800);
+ color: white;
+ font-weight: 500;
}
-.result-metadata {
- margin-top: 10px;
- font-size: 14px;
- color: #333; /* Ensure metadata text has good contrast */
+/* Override Streamlit Styles */
+.stTextInput > div > div > input {
+ background-color: var(--color-gray-700);
+ color: white;
}
-.result-tag {
- margin-bottom: 5px;
- color: #555;
+.stSelectbox > div > div > div {
+ background-color: var(--color-gray-700);
+ color: white;
}
-.result-action-button {
- margin-top: 10px;
- text-align: right;
+.stCheckbox > div > label {
+ color: white;
}
-.selected-result-container {
- margin-top: 16px;
- padding: 12px;
- background-color: #f0f2f6;
- border-radius: 8px;
- border: 1px solid #d0d7de;
- color: #333; /* Ensure text has good contrast with background */
+/* Button styling */
+.stButton > button {
+ background-color: var(--color-blue-600);
+ color: white;
}
-.selected-result-title {
- font-size: 18px;
- font-weight: bold;
- color: #1E3A8A;
+.stButton > button:hover {
+ background-color: var(--color-blue-700);
}
-/* About tab styling */
-.about-section {
- margin-bottom: 16px;
+/* Sidebars */
+[data-testid="stSidebar"] {
+ background-color: var(--color-gray-900);
}
-.about-section h3 {
- color: #1E3A8A;
- margin-bottom: 10px;
+[data-testid="stSidebar"] .stMarkdown {
+ color: white;
}
-.feature-list {
- list-style-type: none;
- padding-left: 0;
+/* Module card styles */
+.module-card {
+ background-color: var(--color-gray-800);
+ border-radius: 0.5rem;
+ padding: 1rem;
+ margin-bottom: 1rem;
+ border-top: 4px solid var(--color-blue-500);
+ transition: transform 0.2s;
}
-.feature-list li {
- margin-bottom: 8px;
- padding-left: 20px;
- position: relative;
+.module-card:hover {
+ transform: translateY(-3px);
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
-.feature-list li:before {
- content: "•";
- position: absolute;
- left: 0;
- color: #1E88E5;
+.module-number {
+ background-color: var(--color-blue-500);
+ color: white;
+ font-weight: bold;
+ padding: 0.25rem 0.5rem;
+ border-radius: 1rem;
+ font-size: 0.9rem;
+ display: inline-block;
+ margin-bottom: 0.5rem;
}
-/* File uploader styling */
-.file-uploader {
- border: 2px dashed #ddd;
- border-radius: 8px;
- padding: 16px;
- text-align: center;
- transition: border-color 0.2s;
+.module-title {
+ font-weight: 600;
+ margin-bottom: 0.5rem;
+ font-size: 1.1rem;
}
-.file-uploader:hover {
- border-color: #1E88E5;
+/* Add space at bottom for fixed nav */
+.main-content {
+ padding-bottom: 4rem;
}
-/* Example documents styling */
-.example-documents {
- margin-top: 12px;
+/* Tool container styles */
+.tool-container {
+ background-color: var(--color-gray-800);
+ color: white;
+ padding: 1.5rem;
+ border-radius: 0.5rem;
+ border: 1px solid var(--color-gray-700);
+ margin-bottom: 1.5rem;
}
-.example-card {
- background-color: #f8f9fa;
- border-radius: 8px;
- padding: 12px;
- margin-bottom: 12px;
- border: 1px solid #e0e0e0;
- cursor: pointer;
- transition: all 0.2s ease;
+/* Upload container */
+.upload-container {
+ border: 2px dashed var(--color-gray-600);
+ padding: 1.5rem;
+ text-align: center;
+ border-radius: 0.5rem;
+ margin-bottom: 1rem;
+ background-color: var(--color-gray-700);
}
-.example-card:hover {
- box-shadow: 0 4px 8px rgba(0,0,0,0.1);
- border-color: #c0c0c0;
+/* Footer spacing */
+.footer-spacer {
+ height: 4rem;
}
-.example-title {
- font-weight: bold;
- font-size: 16px;
- margin-bottom: 5px;
+/* Tabs */
+.stTabs [data-baseweb="tab"] {
+ color: white;
}
-.example-description {
- font-size: 14px;
- color: #555;
-}
+.stTabs [data-baseweb="tab-highlight"] {
+ background-color: var(--color-blue-600);
+}
\ No newline at end of file
diff --git a/ui/layout.py b/ui/layout.py
index 24cb399938a8bf210cf673da41ef1f52c7d558fb..ebcdb8c19ffde05be13e8feec5b1184c13fc4f39 100644
--- a/ui/layout.py
+++ b/ui/layout.py
@@ -1,373 +1,172 @@
import streamlit as st
+from pathlib import Path
+import os
+# Load custom CSS
def load_css():
- """Load custom CSS for the application - inspired by mistral-ocr implementations"""
+ css_file = Path(__file__).parent / "custom.css"
+ if css_file.exists():
+ with open(css_file) as f:
+ st.markdown(f"", unsafe_allow_html=True)
+ else:
+ st.warning("Custom CSS file not found. Some styles may be missing.")
+
+# Header component
+def header():
st.markdown("""
-
+ st.markdown('
""", unsafe_allow_html=True)
+
+# Previous button HTML
+def prev_button_html(current_module, modules):
+ if current_module > 1:
+ prev_module = current_module - 1
+ return f"""
+
+ """
+ return ""
+
+# Next button HTML
+def next_button_html(current_module, modules):
+ if current_module < len(modules):
+ next_module = current_module + 1
+ return f"""
+
+ """
+ return ""
+
+# Navigation dots HTML
+def nav_dots_html(current_module, modules):
+ dots_html = ""
+ for i, name in enumerate(modules, 1):
+ active_class = "active" if i == current_module else ""
+ dots_html += f"""
+
+ {i}
+
+ """
+ return dots_html
+
+# Helper functions for container styles
+def gray_container(content, padding="1.5rem"):
+ """Renders content in a gray container with consistent styling"""
+ st.markdown(f'
{content}
', unsafe_allow_html=True)
+
+def blue_container(content, padding="1.5rem"):
+ """Renders content in a blue container with consistent styling"""
+ st.markdown(f'
{content}
', unsafe_allow_html=True)
+
+def yellow_container(content, padding="1.5rem"):
+ """Renders content in a yellow container with consistent styling"""
+ st.markdown(f'
{content}
', unsafe_allow_html=True)
+
+def card_grid(cards):
+ """
+ Renders a responsive grid of cards
+ Args:
+ cards: List of HTML strings for each card
+ """
+ grid_html = '
', unsafe_allow_html=True)
+
+def research_question(content):
+ """Renders a research question box"""
+ st.markdown(f'
{content}
', unsafe_allow_html=True)
+
+def quote(content, author=""):
+ """Renders a quote with optional author"""
+ quote_html = f'
{content}'
+ if author:
+ quote_html += f'
— {author}'
+ quote_html += '
'
+ st.markdown(quote_html, unsafe_allow_html=True)
+
+def tool_container(content):
+ """Renders content in a tool container"""
+ st.markdown(f'
{content}
', unsafe_allow_html=True)
+
+def upload_container(content):
+ """Renders content in an upload container"""
+ st.markdown(f'
{content}
', unsafe_allow_html=True)
\ No newline at end of file
diff --git a/ui/ui_components.py b/ui/ui_components.py
deleted file mode 100644
index 8cce5949309dd92d81bcc764a80983850bdda746..0000000000000000000000000000000000000000
--- a/ui/ui_components.py
+++ /dev/null
@@ -1,590 +0,0 @@
-import streamlit as st
-import os
-import io
-import base64
-import logging
-import re
-from datetime import datetime
-from pathlib import Path
-import json
-
-# Define exports
-__all__ = [
- 'ProgressReporter',
- 'create_sidebar_options',
- 'create_file_uploader',
- 'display_document_with_images',
- 'display_previous_results',
- 'display_about_tab',
- 'display_results' # Re-export from utils.ui_utils
-]
-from constants import (
- DOCUMENT_TYPES,
- DOCUMENT_LAYOUTS,
- CUSTOM_PROMPT_TEMPLATES,
- LAYOUT_PROMPT_ADDITIONS,
- DEFAULT_PDF_DPI,
- MIN_PDF_DPI,
- MAX_PDF_DPI,
- DEFAULT_MAX_PAGES,
- PERFORMANCE_MODES,
- PREPROCESSING_DOC_TYPES,
- ROTATION_OPTIONS
-)
-from utils.text_utils import format_ocr_text, clean_raw_text, format_markdown_text # Import from text_utils
-from utils.content_utils import (
- classify_document_content,
- extract_document_text,
- extract_image_description
-)
-from utils.ui_utils import display_results
-from preprocessing import preprocess_image
-
-class ProgressReporter:
- """Class to handle progress reporting in the UI"""
-
- def __init__(self, placeholder):
- self.placeholder = placeholder
- self.progress_bar = None
- self.status_text = None
-
- def setup(self):
- """Setup the progress components"""
- with self.placeholder.container():
- self.progress_bar = st.progress(0)
- self.status_text = st.empty()
- return self
-
- def update(self, percent, status_text):
- """Update the progress bar and status text"""
- if self.progress_bar is not None:
- self.progress_bar.progress(percent / 100)
- if self.status_text is not None:
- self.status_text.text(status_text)
-
- def complete(self, success=True):
- """Complete the progress reporting"""
- if success:
- if self.progress_bar is not None:
- self.progress_bar.progress(100)
- if self.status_text is not None:
- self.status_text.text("Processing complete!")
- else:
- if self.status_text is not None:
- self.status_text.text("Processing failed.")
-
- # Clear the progress components after a delay
- import time
- time.sleep(0.8) # Short delay to show completion
- if self.progress_bar is not None:
- self.progress_bar.empty()
- if self.status_text is not None:
- self.status_text.empty()
-
-def create_sidebar_options():
- """Create and return sidebar options"""
- with st.sidebar:
- st.markdown("## OCR Settings")
-
- # Create a container for the sidebar options
- with st.container():
- # Default to using vision model (removed selection from UI)
- use_vision = True
-
- # Document type selection
- doc_type = st.selectbox("Document Type", DOCUMENT_TYPES,
- help="Select the type of document you're processing for better results")
-
- # Document layout
- doc_layout = st.selectbox("Document Layout", DOCUMENT_LAYOUTS,
- help="Select the layout of your document")
-
- # Initialize preprocessing variables with default values
- grayscale = False
- denoise = False
- contrast = 0
- rotation = 0
- use_segmentation = False
-
- # Custom prompt
- custom_prompt = ""
- # Get the template for the selected document type if not auto-detect
- if doc_type != DOCUMENT_TYPES[0]:
- prompt_template = CUSTOM_PROMPT_TEMPLATES.get(doc_type, "")
-
- # Add layout information if not standard
- if doc_layout != DOCUMENT_LAYOUTS[0]: # Not standard layout
- layout_addition = LAYOUT_PROMPT_ADDITIONS.get(doc_layout, "")
- if layout_addition:
- prompt_template += " " + layout_addition
-
- # Set the custom prompt
- custom_prompt = prompt_template
-
- # Allow user to edit the prompt (always visible)
- custom_prompt = st.text_area("Custom Processing Instructions", value=custom_prompt,
- help="Customize the instructions for processing this document",
- height=80)
-
- # Image preprocessing options (always visible)
- st.markdown("### Image Preprocessing")
-
- # Grayscale conversion
- grayscale = st.checkbox("Convert to Grayscale",
- value=True,
- help="Convert color images to grayscale for better text recognition")
-
- # Light denoising option
- denoise = st.checkbox("Light Denoising",
- value=True,
- help="Apply gentle denoising to improve text clarity")
-
- # Contrast adjustment
- contrast = st.slider("Contrast Adjustment",
- min_value=-20,
- max_value=20,
- value=5,
- step=5,
- help="Adjust image contrast (limited range)")
-
-
- # Initialize rotation (keeping it set to 0)
- rotation = 0
- use_segmentation = False
-
- # Create preprocessing options dictionary
- # Map UI document types to preprocessing document types
- doc_type_for_preprocessing = "standard"
- if "Handwritten" in doc_type:
- doc_type_for_preprocessing = "handwritten"
- elif "Newspaper" in doc_type or "Magazine" in doc_type:
- doc_type_for_preprocessing = "newspaper"
- elif "Book" in doc_type or "Publication" in doc_type:
- doc_type_for_preprocessing = "book" # Match the actual preprocessing type
-
- preprocessing_options = {
- "document_type": doc_type_for_preprocessing,
- "grayscale": grayscale,
- "denoise": denoise,
- "contrast": contrast,
- "rotation": rotation
- }
-
- # PDF-specific options
- st.markdown("### PDF Options")
- max_pages = st.number_input("Maximum Pages to Process",
- min_value=1,
- max_value=20,
- value=DEFAULT_MAX_PAGES,
- help="Limit the number of pages to process (for multi-page PDFs)")
-
- # Set default values for removed options
- pdf_dpi = DEFAULT_PDF_DPI
- pdf_rotation = 0
-
- # Create options dictionary
- options = {
- "use_vision": use_vision,
- "perf_mode": "Quality", # Default to Quality, removed performance mode option
- "pdf_dpi": pdf_dpi,
- "max_pages": max_pages,
- "pdf_rotation": pdf_rotation,
- "custom_prompt": custom_prompt,
- "preprocessing_options": preprocessing_options,
- "use_segmentation": use_segmentation if 'use_segmentation' in locals() else False
- }
-
- return options
-
-def create_file_uploader():
- """Create and return a file uploader"""
- # Add app description
- st.markdown(f'
📜
Historical OCR
', unsafe_allow_html=True)
- st.markdown("
Made possible by Mistral AI
", unsafe_allow_html=True)
-
- # Add project framing
- st.markdown("""
- This tool assists scholars in historical research by extracting text from challenging documents. While it may not achieve 100% accuracy, it helps navigate:
- - **Historical newspapers** with complex layouts
- - **Handwritten documents** from various periods
- - **Photos of archival materials**
-
- Upload a document to begin, or explore the examples.
- """)
-
- # Create file uploader with a more concise label
- uploaded_file = st.file_uploader(
- "Select file",
- type=["pdf", "png", "jpg"],
- help="Upload a PDF or image file for OCR processing"
- )
- return uploaded_file
-
-def display_document_with_images(result):
- """Display document with images"""
- # Check for pages_data first
- if 'pages_data' in result and result['pages_data']:
- pages_data = result['pages_data']
- # If pages_data not available, try to extract from raw_response_data
- elif 'raw_response_data' in result and isinstance(result['raw_response_data'], dict) and 'pages' in result['raw_response_data']:
- # Build pages_data from raw_response_data
- pages_data = []
- raw_pages = result['raw_response_data']['pages']
-
- for page_idx, page in enumerate(raw_pages):
- if not isinstance(page, dict):
- continue
-
- page_data = {
- 'page_number': page_idx + 1,
- 'markdown': page.get('markdown', ''),
- 'images': []
- }
-
- # Extract images if present
- if 'images' in page and isinstance(page['images'], list):
- for img_idx, img in enumerate(page['images']):
- if isinstance(img, dict) and ('base64' in img or 'image_base64' in img):
- img_base64 = img.get('image_base64', img.get('base64', ''))
- if img_base64:
- page_data['images'].append({
- 'id': img.get('id', f"img_{page_idx}_{img_idx}"),
- 'image_base64': img_base64
- })
-
- if page_data['markdown'] or page_data['images']:
- pages_data.append(page_data)
- else:
- st.info("No image data available.")
- return
-
- # Display each page
- for i, page_data in enumerate(pages_data):
- st.markdown(f"### Page {i+1}")
-
- # Display only the image (removed text column)
- # Display the image - check multiple possible field names
- image_displayed = False
-
- # Try 'image_data' field first
- if 'image_data' in page_data:
- try:
- # Convert base64 to image
- image_data = base64.b64decode(page_data['image_data'])
- st.image(io.BytesIO(image_data), use_container_width=True)
- image_displayed = True
- except Exception as e:
- st.error(f"Error displaying image from image_data: {str(e)}")
-
- # Try 'images' array if image_data didn't work
- if not image_displayed and 'images' in page_data and len(page_data['images']) > 0:
- for img in page_data['images']:
- if 'image_base64' in img:
- try:
- st.image(img['image_base64'], use_container_width=True)
- image_displayed = True
- break
- except Exception as e:
- st.error(f"Error displaying image from images array: {str(e)}")
-
- # Try alternative image source if still not displayed
- if not image_displayed and 'raw_response_data' in result:
- raw_data = result['raw_response_data']
- if isinstance(raw_data, dict) and 'pages' in raw_data:
- for raw_page in raw_data['pages']:
- if isinstance(raw_page, dict) and 'images' in raw_page:
- for img in raw_page['images']:
- if isinstance(img, dict) and 'base64' in img:
- st.image(img['base64'], use_container_width=True)
- st.caption("Image from OCR response")
- image_displayed = True
- break
- if image_displayed:
- break
-
- if not image_displayed:
- st.info("No image available for this page.")
-
- # Extract and display alt text if available
- page_text = ""
- if 'text' in page_data:
- page_text = page_data['text']
- elif 'markdown' in page_data:
- page_text = page_data['markdown']
-
- if page_text and page_text.startswith("![") and page_text.endswith(")"):
- try:
- alt_text = page_text[2:page_text.index(']')]
- if alt_text and len(alt_text) > 5: # Only show if alt text is meaningful
- st.caption(f"Image description: {alt_text}")
- except:
- pass
-
-def display_previous_results():
- """Display previous results tab content in a simplified, structured view"""
-
- # Use a simple header without the button column
- st.header("Previous Results")
-
- # Display previous results if available
- if not st.session_state.previous_results:
- st.markdown("""
-
-
📄
-
No Previous Results
-
Process a document to see your results history.
-
- """, unsafe_allow_html=True)
- else:
- # Prepare zip download outside of the UI flow
- try:
- # Create download button for all results
- from utils.image_utils import create_results_zip_in_memory
- zip_data = create_results_zip_in_memory(st.session_state.previous_results)
- timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
-
- # Simplified filename
- zip_filename = f"ocr_results_{timestamp}.zip"
-
- # Encode the zip data for direct download link
- zip_b64 = base64.b64encode(zip_data).decode()
-
- # Add styled download tag in the metadata section
- download_html = '
'
- st.markdown(download_html, unsafe_allow_html=True)
- except Exception:
- # Silent fail - no error message to keep UI clean
- pass
-
- # Create a cleaner, more minimal grid for results using Streamlit columns
- # Calculate number of columns based on screen width - more responsive
- num_columns = 2 # Two columns for most screens
-
- # Create rows of result cards
- for i in range(0, len(st.session_state.previous_results), num_columns):
- # Create a row of columns
- cols = st.columns(num_columns)
-
- # Fill each column with a result card
- for j in range(num_columns):
- index = i + j
- if index < len(st.session_state.previous_results):
- result = st.session_state.previous_results[index]
-
- # Get basic info for the card
- file_name = result.get("file_name", f"Document {index+1}")
- timestamp = result.get("timestamp", "")
-
- # Determine file type icon
- if file_name.lower().endswith(".pdf"):
- icon = "📄"
- elif any(file_name.lower().endswith(ext) for ext in [".jpg", ".jpeg", ".png", ".gif"]):
- icon = "🖼️"
- else:
- icon = "📝"
-
- # Display a simplified card in each column
- with cols[j]:
- # Use a container for better styling control
- with st.container():
- # Create visually cleaner card with less vertical space
- st.markdown(f"""
-
-
-
{icon} {file_name}
-
{timestamp.split()[0] if timestamp else ""}
-
-
- """, unsafe_allow_html=True)
-
- # Add a simple button below each card
- if st.button(f"View", key=f"view_{index}", help=f"View {file_name}"):
- st.session_state.selected_previous_result = st.session_state.previous_results[index]
- st.rerun()
-
- # Display the selected result if available
- if 'selected_previous_result' in st.session_state and st.session_state.selected_previous_result:
- selected_result = st.session_state.selected_previous_result
-
- # Draw a separator between results list and selected document
- st.markdown("", unsafe_allow_html=True)
-
- # Create a cleaner header for the selected document
- file_name = selected_result.get('file_name', 'Document')
- st.subheader(f"{file_name}")
-
- # Add a simple back button at the top
- if st.button("← Back to Results", key="back_to_results"):
- if 'selected_previous_result' in st.session_state:
- del st.session_state.selected_previous_result
- st.session_state.perform_reset = True
- st.rerun()
-
- # Simplified metadata display - just one line with essential info
- meta_html = '
'
-
- # Add timestamp
- if 'timestamp' in selected_result:
- meta_html += f'
{selected_result["timestamp"]}
'
-
- # Add languages if available (simplified)
- if 'languages' in selected_result and selected_result['languages']:
- languages = [lang for lang in selected_result['languages'] if lang is not None]
- if languages:
- meta_html += f'
Language: {", ".join(languages)}
'
-
- # Add page count if available (simplified)
- if 'limited_pages' in selected_result:
- meta_html += f'
'
- st.markdown(meta_html, unsafe_allow_html=True)
-
- # Simplified tabs - using the same format as main view
- has_images = selected_result.get('has_images', False)
- if has_images:
- view_tabs = st.tabs(["Document Content", "Raw JSON", "Images"])
- view_tab1, view_tab2, view_tab3 = view_tabs
- else:
- view_tabs = st.tabs(["Document Content", "Raw JSON"])
- view_tab1, view_tab2 = view_tabs
- view_tab3 = None
-
- # First tab - Document Content (simplified structured view)
- with view_tab1:
- # Display content in a cleaner, more streamlined format
- if 'ocr_contents' in selected_result and isinstance(selected_result['ocr_contents'], dict):
- # Create a more focused list of important sections
- priority_sections = ["title", "content", "transcript", "summary"]
- displayed_sections = set()
-
- # First display priority sections
- for section in priority_sections:
- if section in selected_result['ocr_contents'] and selected_result['ocr_contents'][section]:
- content = selected_result['ocr_contents'][section]
- if isinstance(content, str) and content.strip():
- # Only add a subheader for meaningful section names, not raw_text
- if section != "raw_text":
- st.markdown(f"##### {section.replace('_', ' ').title()}")
-
- # Format and display content
- formatted_content = format_ocr_text(content, for_display=True)
- st.markdown(formatted_content)
- displayed_sections.add(section)
-
- # Then display any remaining sections not already shown
- for section, content in selected_result['ocr_contents'].items():
- if (section not in displayed_sections and
- section not in ['error', 'partial_text'] and
- content):
- st.markdown(f"##### {section.replace('_', ' ').title()}")
-
- if isinstance(content, str):
- st.markdown(format_ocr_text(content, for_display=True))
- elif isinstance(content, list):
- for item in content:
- st.markdown(f"- {item}")
- elif isinstance(content, dict):
- for k, v in content.items():
- st.markdown(f"**{k}:** {v}")
-
- # Second tab - Raw JSON (simplified)
- with view_tab2:
- # Extract the relevant JSON data
- json_data = {}
-
- # Include important metadata
- for field in ['file_name', 'timestamp', 'processing_time', 'title', 'languages', 'topics', 'subjects', 'text',' raw_text']:
- if field in selected_result:
- json_data[field] = selected_result[field]
-
- # Include OCR contents
- if 'ocr_contents' in selected_result:
- json_data['ocr_contents'] = selected_result['ocr_contents']
-
- # Format the JSON prettily
- json_str = json.dumps(json_data, indent=2)
-
- # Display in a monospace font with syntax highlighting
- st.code(json_str, language="json")
-
- # Third tab - Images (simplified)
- if has_images and view_tab3 is not None:
- with view_tab3:
- # Simplified image display
- if 'pages_data' in selected_result:
- for i, page_data in enumerate(selected_result['pages_data']):
- # Display each page
- if 'images' in page_data and len(page_data['images']) > 0:
- for img in page_data['images']:
- if 'image_base64' in img:
- st.image(img['image_base64'], use_container_width=True)
-
- # Get page text if available
- page_text = ""
- if 'markdown' in page_data:
- page_text = page_data['markdown']
-
- # Display text if available
- if page_text:
- with st.expander(f"Page {i+1} Text", expanded=False):
- st.text(page_text)
-
-def display_about_tab():
- """Display learn more tab content"""
- st.header("Learn More")
-
- # Add app description
- st.markdown("""
- **Historical OCR** is a tailored academic tool for extracting text from historical documents, manuscripts, and printed materials.
- """)
-
- # Purpose section with consistent formatting
- st.markdown("### Purpose")
- st.markdown("""
- This tool is designed to assist scholars in historical research by extracting text from challenging documents.
- While it may not achieve full accuracy for all materials, it serves as a tailored research aid for navigating
- historical documents, particularly:
- """)
-
- st.markdown("""
- - **Historical newspapers** with complex layouts and aged text
- - **Handwritten documents** from various time periods
- - **Photos of archival materials** that may be difficult to read
- """)
-
- # Features section with consistent formatting
- st.markdown("### Features")
- st.markdown("""
- - **Advanced Image Preprocessing**: Optimize historical documents for better OCR results
- - **Custom Document Type Processing**: Specialized handling for newspapers, letters, books, and more
- - **Editable Results**: Review and edit extracted text directly in the interface
- - **Structured Content Analysis**: Automatic organization of document content
- - **Multi-language Support**: Process documents in various languages
- - **PDF Processing**: Handle multi-page historical documents
- """)
-
- # How to Use section with consistent formatting
- st.markdown("### How to Use")
- st.markdown("""
- 1. Upload a document (PDF or image)
- 2. Select the document type and adjust preprocessing options if needed
- 3. Add custom processing instructions for specialized documents
- 4. Process the document
- 5. Review, edit, and download the results
- """)
-
- # Technologies section with consistent formatting
- st.markdown("### Technologies")
- st.markdown("""
- - OCR processing using Mistral AI's advanced document understanding capabilities
- - Image preprocessing with OpenCV
- - PDF handling with pdf2image
- - Web interface with Streamlit
- """)
-
- # Add version information
- st.markdown("**Version:** 2.0.0")
diff --git a/ui_components.py b/ui_components.py
deleted file mode 100644
index 8b7d7a812ba6b809854f9751f6e6d3b112faa9fe..0000000000000000000000000000000000000000
--- a/ui_components.py
+++ /dev/null
@@ -1,604 +0,0 @@
-import streamlit as st
-import os
-import io
-import base64
-import logging
-import re
-from datetime import datetime
-from pathlib import Path
-import json
-
-# Define exports
-__all__ = [
- 'ProgressReporter',
- 'create_sidebar_options',
- 'create_file_uploader',
- 'display_document_with_images',
- 'display_previous_results',
- 'display_about_tab',
- 'display_results' # Re-export from utils.ui_utils
-]
-from constants import (
- DOCUMENT_TYPES,
- DOCUMENT_LAYOUTS,
- CUSTOM_PROMPT_TEMPLATES,
- LAYOUT_PROMPT_ADDITIONS,
- DEFAULT_PDF_DPI,
- MIN_PDF_DPI,
- MAX_PDF_DPI,
- DEFAULT_MAX_PAGES,
- PERFORMANCE_MODES,
- PREPROCESSING_DOC_TYPES,
- ROTATION_OPTIONS
-)
-from utils.text_utils import format_ocr_text, clean_raw_text, format_markdown_text # Import from text_utils
-from utils.content_utils import (
- classify_document_content,
- extract_document_text,
- extract_image_description
-)
-from utils.ui_utils import display_results
-from preprocessing import preprocess_image
-
-class ProgressReporter:
- """Class to handle progress reporting in the UI"""
-
- def __init__(self, placeholder):
- self.placeholder = placeholder
- self.progress_bar = None
- self.status_text = None
-
- def setup(self):
- """Setup the progress components"""
- with self.placeholder.container():
- self.progress_bar = st.progress(0)
- self.status_text = st.empty()
- return self
-
- def update(self, percent, status_text):
- """Update the progress bar and status text"""
- if self.progress_bar is not None:
- self.progress_bar.progress(percent / 100)
- if self.status_text is not None:
- self.status_text.text(status_text)
-
- def complete(self, success=True):
- """Complete the progress reporting"""
- if success:
- if self.progress_bar is not None:
- self.progress_bar.progress(100)
- if self.status_text is not None:
- self.status_text.text("Processing complete!")
- else:
- if self.status_text is not None:
- self.status_text.text("Processing failed.")
-
- # Clear the progress components after a delay
- import time
- time.sleep(0.8) # Short delay to show completion
- if self.progress_bar is not None:
- self.progress_bar.empty()
- if self.status_text is not None:
- self.status_text.empty()
-
-def create_sidebar_options():
- """Create and return sidebar options"""
- with st.sidebar:
- st.markdown("## OCR Settings")
-
- # Create a container for the sidebar options
- with st.container():
- # Default to using vision model (removed selection from UI)
- use_vision = True
-
- # Document type selection
- doc_type = st.selectbox("Document Type", DOCUMENT_TYPES,
- help="Select the type of document you're processing for better results")
-
- # Document layout
- doc_layout = st.selectbox("Document Layout", DOCUMENT_LAYOUTS,
- help="Select the layout of your document")
-
- # Initialize preprocessing variables with default values
- grayscale = False
- denoise = False
- contrast = 0
- rotation = 0
- use_segmentation = False
-
- # Custom prompt
- custom_prompt = ""
- # Get the template for the selected document type if not auto-detect
- if doc_type != DOCUMENT_TYPES[0]:
- prompt_template = CUSTOM_PROMPT_TEMPLATES.get(doc_type, "")
-
- # Add layout information if not standard
- if doc_layout != DOCUMENT_LAYOUTS[0]: # Not standard layout
- layout_addition = LAYOUT_PROMPT_ADDITIONS.get(doc_layout, "")
- if layout_addition:
- prompt_template += " " + layout_addition
-
- # Set the custom prompt
- custom_prompt = prompt_template
-
- # Allow user to edit the prompt (always visible)
- custom_prompt = st.text_area("Custom Processing Instructions", value=custom_prompt,
- help="Customize the instructions for processing this document",
- height=80)
-
- # Image preprocessing options (always visible)
- st.markdown("### Image Preprocessing")
-
- # Grayscale conversion
- grayscale = st.checkbox("Convert to Grayscale",
- value=True,
- help="Convert color images to grayscale for better text recognition")
-
- # Light denoising option
- denoise = st.checkbox("Light Denoising",
- value=True,
- help="Apply gentle denoising to improve text clarity")
-
- # Contrast adjustment
- contrast = st.slider("Contrast Adjustment",
- min_value=-20,
- max_value=20,
- value=5,
- step=5,
- help="Adjust image contrast (limited range)")
-
-
- # Initialize rotation (keeping it set to 0)
- rotation = 0
- use_segmentation = False
-
- # Create preprocessing options dictionary
- # Map UI document types to preprocessing document types
- doc_type_for_preprocessing = "standard"
- if "Handwritten" in doc_type:
- doc_type_for_preprocessing = "handwritten"
- elif "Newspaper" in doc_type or "Magazine" in doc_type:
- doc_type_for_preprocessing = "newspaper"
- elif "Book" in doc_type or "Publication" in doc_type:
- doc_type_for_preprocessing = "book" # Match the actual preprocessing type
-
- preprocessing_options = {
- "document_type": doc_type_for_preprocessing,
- "grayscale": grayscale,
- "denoise": denoise,
- "contrast": contrast,
- "rotation": rotation
- }
-
- # PDF-specific options
- st.markdown("### PDF Options")
- max_pages = st.number_input("Maximum Pages to Process",
- min_value=1,
- max_value=20,
- value=DEFAULT_MAX_PAGES,
- help="Limit the number of pages to process (for multi-page PDFs)")
-
- # Set default values for removed options
- pdf_dpi = DEFAULT_PDF_DPI
- pdf_rotation = 0
-
- # Create options dictionary
- options = {
- "use_vision": use_vision,
- "perf_mode": "Quality", # Default to Quality, removed performance mode option
- "pdf_dpi": pdf_dpi,
- "max_pages": max_pages,
- "pdf_rotation": pdf_rotation,
- "custom_prompt": custom_prompt,
- "preprocessing_options": preprocessing_options,
- "use_segmentation": use_segmentation if 'use_segmentation' in locals() else False
- }
-
- return options
-
-def create_file_uploader():
- """Create and return a file uploader"""
- # Add app description
- st.markdown(f'
📜
Historical OCR
', unsafe_allow_html=True)
- st.markdown("
Made possible by Mistral AI
", unsafe_allow_html=True)
-
- # Add project framing
- st.markdown("""
- This tool assists scholars in historical research by extracting text from challenging documents. While it may not achieve 100% accuracy, it helps navigate:
- - **Historical newspapers** with complex layouts
- - **Handwritten documents** from various periods
- - **Photos of archival materials**
-
- Upload a document to begin, or explore the examples.
- """)
-
- # Create file uploader with a more concise label
- uploaded_file = st.file_uploader(
- "Select file",
- type=["pdf", "png", "jpg"],
- help="Upload a PDF or image file for OCR processing"
- )
- return uploaded_file
-
-def display_document_with_images(result):
- """Display document with images"""
- # Check for pages_data first
- if 'pages_data' in result and result['pages_data']:
- pages_data = result['pages_data']
- # If pages_data not available, try to extract from raw_response_data
- elif 'raw_response_data' in result and isinstance(result['raw_response_data'], dict) and 'pages' in result['raw_response_data']:
- # Build pages_data from raw_response_data
- pages_data = []
- raw_pages = result['raw_response_data']['pages']
-
- for page_idx, page in enumerate(raw_pages):
- if not isinstance(page, dict):
- continue
-
- page_data = {
- 'page_number': page_idx + 1,
- 'markdown': page.get('markdown', ''),
- 'images': []
- }
-
- # Extract images if present
- if 'images' in page and isinstance(page['images'], list):
- for img_idx, img in enumerate(page['images']):
- if isinstance(img, dict) and ('base64' in img or 'image_base64' in img):
- img_base64 = img.get('image_base64', img.get('base64', ''))
- if img_base64:
- page_data['images'].append({
- 'id': img.get('id', f"img_{page_idx}_{img_idx}"),
- 'image_base64': img_base64
- })
-
- if page_data['markdown'] or page_data['images']:
- pages_data.append(page_data)
- else:
- st.info("No image data available.")
- return
-
- # Display each page
- for i, page_data in enumerate(pages_data):
- st.markdown(f"### Page {i+1}")
-
- # Display only the image (removed text column)
- # Display the image - check multiple possible field names
- image_displayed = False
-
- # Try 'image_data' field first
- if 'image_data' in page_data:
- try:
- # Convert base64 to image
- image_data = base64.b64decode(page_data['image_data'])
- st.image(io.BytesIO(image_data), use_container_width=True)
- image_displayed = True
- except Exception as e:
- st.error(f"Error displaying image from image_data: {str(e)}")
-
- # Try 'images' array if image_data didn't work
- if not image_displayed and 'images' in page_data and len(page_data['images']) > 0:
- for img in page_data['images']:
- if 'image_base64' in img:
- try:
- st.image(img['image_base64'], use_container_width=True)
- image_displayed = True
- break
- except Exception as e:
- st.error(f"Error displaying image from images array: {str(e)}")
-
- # Try alternative image source if still not displayed
- if not image_displayed and 'raw_response_data' in result:
- raw_data = result['raw_response_data']
- if isinstance(raw_data, dict) and 'pages' in raw_data:
- for raw_page in raw_data['pages']:
- if isinstance(raw_page, dict) and 'images' in raw_page:
- for img in raw_page['images']:
- if isinstance(img, dict) and 'base64' in img:
- st.image(img['base64'], use_container_width=True)
- st.caption("Image from OCR response")
- image_displayed = True
- break
- if image_displayed:
- break
-
- if not image_displayed:
- st.info("No image available for this page.")
-
- # Extract and display alt text if available
- page_text = ""
- if 'text' in page_data:
- page_text = page_data['text']
- elif 'markdown' in page_data:
- page_text = page_data['markdown']
-
- if page_text and page_text.startswith("![") and page_text.endswith(")"):
- try:
- alt_text = page_text[2:page_text.index(']')]
- if alt_text and len(alt_text) > 5: # Only show if alt text is meaningful
- st.caption(f"Image description: {alt_text}")
- except:
- pass
-
-def display_previous_results():
- """Display previous results tab content in a simplified, structured view"""
-
- # Use a simple header without the button column
- st.header("Previous Results")
-
- # Display previous results if available
- if not st.session_state.previous_results:
- st.markdown("""
-
-
📄
-
No Previous Results
-
Process a document to see your results history.
-
- """, unsafe_allow_html=True)
- else:
- # Prepare zip download outside of the UI flow
- try:
- # Create download button for all results
- from utils.image_utils import create_results_zip_in_memory
- zip_data = create_results_zip_in_memory(st.session_state.previous_results)
- timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
-
- # Simplified filename
- zip_filename = f"ocr_results_{timestamp}.zip"
-
- # Encode the zip data for direct download link
- zip_b64 = base64.b64encode(zip_data).decode()
-
- # Add styled download tag in the metadata section
- download_html = '
'
- st.markdown(download_html, unsafe_allow_html=True)
- except Exception:
- # Silent fail - no error message to keep UI clean
- pass
-
- # Create a cleaner, more minimal grid for results using Streamlit columns
- # Calculate number of columns based on screen width - more responsive
- num_columns = 2 # Two columns for most screens
-
- # Create rows of result cards
- for i in range(0, len(st.session_state.previous_results), num_columns):
- # Create a row of columns
- cols = st.columns(num_columns)
-
- # Fill each column with a result card
- for j in range(num_columns):
- index = i + j
- if index < len(st.session_state.previous_results):
- result = st.session_state.previous_results[index]
-
- # Get basic info for the card
- file_name = result.get("file_name", f"Document {index+1}")
- timestamp = result.get("timestamp", "")
-
- # Determine file type icon
- if file_name.lower().endswith(".pdf"):
- icon = "📄"
- elif any(file_name.lower().endswith(ext) for ext in [".jpg", ".jpeg", ".png", ".gif"]):
- icon = "🖼️"
- else:
- icon = "📝"
-
- # Display a simplified card in each column
- with cols[j]:
- # Use a container for better styling control
- with st.container():
- # Create visually cleaner card with less vertical space
- st.markdown(f"""
-
-
-
{icon} {file_name}
-
{timestamp.split()[0] if timestamp else ""}
-
-
- """, unsafe_allow_html=True)
-
- # Add a simple button below each card
- if st.button(f"View", key=f"view_{index}", help=f"View {file_name}"):
- st.session_state.selected_previous_result = st.session_state.previous_results[index]
- st.rerun()
-
- # Display the selected result if available
- if 'selected_previous_result' in st.session_state and st.session_state.selected_previous_result:
- selected_result = st.session_state.selected_previous_result
-
- # Draw a separator between results list and selected document
- st.markdown("", unsafe_allow_html=True)
-
- # Create a cleaner header for the selected document
- file_name = selected_result.get('file_name', 'Document')
- st.subheader(f"{file_name}")
-
- # Add a simple back button at the top
- if st.button("← Back to Results", key="back_to_results"):
- if 'selected_previous_result' in st.session_state:
- del st.session_state.selected_previous_result
- st.session_state.perform_reset = True
- st.rerun()
-
- # Simplified metadata display - just one line with essential info
- meta_html = '
'
-
- # Add timestamp
- if 'timestamp' in selected_result:
- meta_html += f'
{selected_result["timestamp"]}
'
-
- # Add languages if available (simplified)
- if 'languages' in selected_result and selected_result['languages']:
- languages = [lang for lang in selected_result['languages'] if lang is not None]
- if languages:
- meta_html += f'
Language: {", ".join(languages)}
'
-
- # Add page count if available (simplified)
- if 'limited_pages' in selected_result:
- meta_html += f'
'
- st.markdown(meta_html, unsafe_allow_html=True)
-
- # Simplified tabs - using the same format as main view
- has_images = selected_result.get('has_images', False)
- if has_images:
- view_tabs = st.tabs(["Document Content", "Raw JSON", "Images"])
- view_tab1, view_tab2, view_tab3 = view_tabs
- else:
- view_tabs = st.tabs(["Document Content", "Raw JSON"])
- view_tab1, view_tab2 = view_tabs
- view_tab3 = None
-
- # First tab - Document Content (simplified structured view)
- with view_tab1:
- # Display content in a cleaner, more streamlined format
- if 'ocr_contents' in selected_result and isinstance(selected_result['ocr_contents'], dict):
- # Create a more focused list of important sections
- priority_sections = ["title", "content", "transcript", "summary"]
- displayed_sections = set()
-
- # First display priority sections
- for section in priority_sections:
- if section in selected_result['ocr_contents'] and selected_result['ocr_contents'][section]:
- content = selected_result['ocr_contents'][section]
- if isinstance(content, str) and content.strip():
- # Only add a subheader for meaningful section names, not raw_text
- if section != "raw_text":
- st.markdown(f"##### {section.replace('_', ' ').title()}")
-
- # Format and display content
- formatted_content = format_ocr_text(content, for_display=True)
- st.markdown(formatted_content)
- displayed_sections.add(section)
-
- # Then display any remaining sections not already shown
- for section, content in selected_result['ocr_contents'].items():
- if (section not in displayed_sections and
- section not in ['error', 'partial_text'] and
- content):
- st.markdown(f"##### {section.replace('_', ' ').title()}")
-
- if isinstance(content, str):
- st.markdown(format_ocr_text(content, for_display=True))
- elif isinstance(content, list):
- for item in content:
- st.markdown(f"- {item}")
- elif isinstance(content, dict):
- for k, v in content.items():
- st.markdown(f"**{k}:** {v}")
-
- # Second tab - Raw JSON (simplified)
- with view_tab2:
- # Extract the relevant JSON data
- json_data = {}
-
- # Include important metadata
- for field in ['file_name', 'timestamp', 'processing_time', 'languages', 'topics', 'subjects', 'detected_document_type', 'text']:
- if field in selected_result:
- json_data[field] = selected_result[field]
-
- # Include OCR contents
- if 'ocr_contents' in selected_result:
- json_data['ocr_contents'] = selected_result['ocr_contents']
-
- # Exclude large binary data like base64 images to keep JSON clean
- if 'pages_data' in selected_result:
- # Create simplified pages_data without large binary content
- simplified_pages = []
- for page in selected_result['pages_data']:
- simplified_page = {
- 'page_number': page.get('page_number', 0),
- 'has_text': bool(page.get('markdown', '')),
- 'has_images': bool(page.get('images', [])),
- 'image_count': len(page.get('images', []))
- }
- simplified_pages.append(simplified_page)
- json_data['pages_summary'] = simplified_pages
-
- # Format the JSON prettily
- json_str = json.dumps(json_data, indent=2)
-
- # Display in a monospace font with syntax highlighting
- st.code(json_str, language="json")
-
- # Third tab - Images (simplified)
- if has_images and view_tab3 is not None:
- with view_tab3:
- # Simplified image display
- if 'pages_data' in selected_result:
- for i, page_data in enumerate(selected_result['pages_data']):
- # Display each page
- if 'images' in page_data and len(page_data['images']) > 0:
- for img in page_data['images']:
- if 'image_base64' in img:
- st.image(img['image_base64'], use_container_width=True)
-
- # Get page text if available
- page_text = ""
- if 'markdown' in page_data:
- page_text = page_data['markdown']
-
- # Display text if available
- if page_text:
- with st.expander(f"Page {i+1} Text", expanded=False):
- st.text(page_text)
-
-def display_about_tab():
- """Display learn more tab content"""
- st.header("Learn More")
-
- # Add app description
- st.markdown("""
- **Historical OCR** is a tailored academic tool for extracting text from historical documents, manuscripts, and printed materials.
- """)
-
- # Purpose section with consistent formatting
- st.markdown("### Purpose")
- st.markdown("""
- This tool is designed to assist scholars in historical research by extracting text from challenging documents.
- While it may not achieve full accuracy for all materials, it serves as a tailored research aid for navigating
- historical documents, particularly:
- """)
-
- st.markdown("""
- - **Historical newspapers** with complex layouts and aged text
- - **Handwritten documents** from various time periods
- - **Photos of archival materials** that may be difficult to read
- """)
-
- # Features section with consistent formatting
- st.markdown("### Features")
- st.markdown("""
- - **Advanced Image Preprocessing**: Optimize historical documents for better OCR results
- - **Custom Document Type Processing**: Specialized handling for newspapers, letters, books, and more
- - **Editable Results**: Review and edit extracted text directly in the interface
- - **Structured Content Analysis**: Automatic organization of document content
- - **Multi-language Support**: Process documents in various languages
- - **PDF Processing**: Handle multi-page historical documents
- """)
-
- # How to Use section with consistent formatting
- st.markdown("### How to Use")
- st.markdown("""
- 1. Upload a document (PDF or image)
- 2. Select the document type and adjust preprocessing options if needed
- 3. Add custom processing instructions for specialized documents
- 4. Process the document
- 5. Review, edit, and download the results
- """)
-
- # Technologies section with consistent formatting
- st.markdown("### Technologies")
- st.markdown("""
- - OCR processing using Mistral AI's advanced document understanding capabilities
- - Image preprocessing with OpenCV
- - PDF handling with pdf2image
- - Web interface with Streamlit
- """)
-
- # Add version information
- st.markdown("**Version:** 1.0.0")
diff --git a/utils.py b/utils.py
deleted file mode 100644
index 88a47ecc79968faa526b7b597dff14d19e91ce79..0000000000000000000000000000000000000000
--- a/utils.py
+++ /dev/null
@@ -1,414 +0,0 @@
-import os
-import base64
-import hashlib
-import time
-import logging
-from datetime import datetime
-from pathlib import Path
-from functools import wraps
-from constants import CONTENT_THEMES, PERIOD_TAGS, DEFAULT_TAGS, GENERIC_TAGS
-
-# Configure logging
-logger = logging.getLogger("utils")
-logger.setLevel(logging.INFO)
-
-def get_base64_from_image(image_path):
- """
- Get base64 data URL from image file with proper MIME type.
-
- Args:
- image_path: Path to the image file
-
- Returns:
- Base64 data URL with appropriate MIME type prefix
- """
- try:
- # Convert to Path object for better handling
- path_obj = Path(image_path)
-
- # Determine mime type based on file extension
- mime_type = 'image/jpeg' # Default mime type
- suffix = path_obj.suffix.lower()
- if suffix == '.png':
- mime_type = 'image/png'
- elif suffix == '.gif':
- mime_type = 'image/gif'
- elif suffix in ['.jpg', '.jpeg']:
- mime_type = 'image/jpeg'
- elif suffix == '.pdf':
- mime_type = 'application/pdf'
-
- # Read and encode file
- with open(path_obj, "rb") as file:
- encoded = base64.b64encode(file.read()).decode('utf-8')
- return f"data:{mime_type};base64,{encoded}"
- except Exception as e:
- logger.error(f"Error encoding file to base64: {str(e)}")
- return ""
-
-def get_base64_from_bytes(file_bytes, mime_type=None, file_name=None):
- """
- Get base64 data URL from file bytes with proper MIME type.
-
- Args:
- file_bytes: Binary file data
- mime_type: MIME type of the file (optional)
- file_name: Original file name for MIME type detection (optional)
-
- Returns:
- Base64 data URL with appropriate MIME type prefix
- """
- try:
- # Determine mime type if not provided
- if mime_type is None and file_name is not None:
- # Get file extension
- suffix = Path(file_name).suffix.lower()
- if suffix == '.png':
- mime_type = 'image/png'
- elif suffix == '.gif':
- mime_type = 'image/gif'
- elif suffix in ['.jpg', '.jpeg']:
- mime_type = 'image/jpeg'
- elif suffix == '.pdf':
- mime_type = 'application/pdf'
- else:
- # Default to octet-stream for unknown types
- mime_type = 'application/octet-stream'
- elif mime_type is None:
- # Default MIME type if we can't determine it
- mime_type = 'application/octet-stream'
-
- # Encode and create data URL
- encoded = base64.b64encode(file_bytes).decode('utf-8')
- return f"data:{mime_type};base64,{encoded}"
- except Exception as e:
- logger.error(f"Error encoding bytes to base64: {str(e)}")
- return ""
-
-def timing(description):
- """Context manager for timing code execution"""
- class TimingContext:
- def __init__(self, description):
- self.description = description
-
- def __enter__(self):
- self.start_time = time.time()
- return self
-
- def __exit__(self, exc_type, exc_val, exc_tb):
- end_time = time.time()
- execution_time = end_time - self.start_time
- logger.info(f"{self.description} took {execution_time:.2f} seconds")
- return False
-
- return TimingContext(description)
-
-def format_timestamp(timestamp=None, for_filename=False):
- """
- Format timestamp for display or filenames
-
- Args:
- timestamp: Datetime object or string to format (defaults to current time)
- for_filename: Whether to format for use in a filename (defaults to False)
-
- Returns:
- str: Formatted timestamp
- """
- if timestamp is None:
- timestamp = datetime.now()
- elif isinstance(timestamp, str):
- try:
- timestamp = datetime.strptime(timestamp, "%Y-%m-%d %H:%M:%S")
- except ValueError:
- timestamp = datetime.now()
-
- if for_filename:
- # Format suitable for filenames: "Apr 30, 2025"
- return timestamp.strftime("%b %d, %Y")
- else:
- # Standard format for display
- return timestamp.strftime("%Y-%m-%d %H:%M")
-
-def generate_cache_key(file_bytes, file_type, use_vision, preprocessing_options=None, pdf_rotation=0, custom_prompt=None):
- """
- Generate a cache key for OCR processing
-
- Args:
- file_bytes: File content as bytes
- file_type: Type of file (pdf or image)
- use_vision: Whether to use vision model
- preprocessing_options: Dictionary of preprocessing options
- pdf_rotation: PDF rotation value
- custom_prompt: Custom prompt for OCR
-
- Returns:
- str: Cache key
- """
- # Generate file hash
- file_hash = hashlib.md5(file_bytes).hexdigest()
-
- # Include preprocessing options in cache key
- preprocessing_options_hash = ""
- if preprocessing_options:
- # Add pdf_rotation to preprocessing options to ensure it's part of the cache key
- if pdf_rotation != 0:
- preprocessing_options_with_rotation = preprocessing_options.copy()
- preprocessing_options_with_rotation['pdf_rotation'] = pdf_rotation
- preprocessing_str = str(sorted(preprocessing_options_with_rotation.items()))
- else:
- preprocessing_str = str(sorted(preprocessing_options.items()))
- preprocessing_options_hash = hashlib.md5(preprocessing_str.encode()).hexdigest()
- elif pdf_rotation != 0:
- # If no preprocessing options but we have rotation, include that in the hash
- preprocessing_options_hash = hashlib.md5(f"pdf_rotation_{pdf_rotation}".encode()).hexdigest()
-
- # Create base cache key
- cache_key = f"{file_hash}_{file_type}_{use_vision}_{preprocessing_options_hash}"
-
- # Include custom prompt in cache key if provided
- if custom_prompt:
- custom_prompt_hash = hashlib.md5(str(custom_prompt).encode()).hexdigest()
- cache_key = f"{cache_key}_{custom_prompt_hash}"
-
- return cache_key
-
-def handle_temp_files(temp_file_paths):
- """
- Clean up temporary files
-
- Args:
- temp_file_paths: List of temporary file paths to clean up
- """
- for temp_path in temp_file_paths:
- try:
- if os.path.exists(temp_path):
- os.unlink(temp_path)
- logger.info(f"Removed temporary file: {temp_path}")
- except Exception as e:
- logger.warning(f"Failed to remove temporary file {temp_path}: {str(e)}")
-
-def create_descriptive_filename(original_filename, result, file_ext, preprocessing_options=None):
- """
- Create a user-friendly descriptive filename for the result
-
- Args:
- original_filename: Original filename
- result: OCR result dictionary
- file_ext: File extension
- preprocessing_options: Dictionary of preprocessing options
-
- Returns:
- str: Human-readable descriptive filename
- """
- from datetime import datetime
-
- # Get base name without extension and capitalize words
- original_name = Path(original_filename).stem
-
- # Make the original name more readable by replacing dashes and underscores with spaces
- # Then capitalize each word
- readable_name = original_name.replace('-', ' ').replace('_', ' ')
- # Split by spaces and capitalize each word, then rejoin
- name_parts = readable_name.split()
- readable_name = ' '.join(word.capitalize() for word in name_parts)
-
- # Determine document type
- doc_type = None
- if 'detected_document_type' in result and result['detected_document_type']:
- doc_type = result['detected_document_type'].capitalize()
- elif 'topics' in result and result['topics']:
- # Use first topic as document type if not explicitly detected
- doc_type = result['topics'][0]
-
- # Find period/era information
- period_info = None
- if 'topics' in result and result['topics']:
- for tag in result['topics']:
- if "century" in tag.lower() or "pre-" in tag.lower() or "era" in tag.lower():
- period_info = tag
- break
-
- # Format metadata within parentheses if available
- metadata = []
- if doc_type:
- metadata.append(doc_type)
- if period_info:
- metadata.append(period_info)
-
- metadata_str = ""
- if metadata:
- metadata_str = f" ({', '.join(metadata)})"
-
- # Add current date for uniqueness and sorting
- current_date = format_timestamp(for_filename=True)
- date_str = f" - {current_date}"
-
- # Generate final user-friendly filename
- descriptive_name = f"{readable_name}{metadata_str}{date_str}{file_ext}"
- return descriptive_name
-
-def extract_subject_tags(result, raw_text, preprocessing_options=None):
- """
- Extract subject tags from OCR result
-
- Args:
- result: OCR result dictionary
- raw_text: Raw text from OCR
- preprocessing_options: Dictionary of preprocessing options
-
- Returns:
- list: Subject tags
- """
- subject_tags = []
-
- try:
- # Use existing topics as starting point if available
- if 'topics' in result and result['topics']:
- subject_tags = list(result['topics'])
-
- # Add document type if detected
- if 'detected_document_type' in result:
- doc_type = result['detected_document_type'].capitalize()
- if doc_type not in subject_tags:
- subject_tags.append(doc_type)
-
- # Analyze content for common themes based on keywords
- if raw_text:
- raw_text_lower = raw_text.lower()
-
- # Track keyword matches for each theme and their frequency
- theme_matches = {}
-
- # First pass - find all matching keywords for each theme
- for theme, keywords in CONTENT_THEMES.items():
- matches = []
- for keyword in keywords:
- # For multi-word keywords, we want exact phrase matching
- if " " in keyword:
- if keyword in raw_text_lower:
- matches.append(keyword)
- # For single-word keywords, we want word boundary matching to avoid partial matches
- else:
- import re
- pattern = r'\b' + re.escape(keyword) + r'\b'
- if re.search(pattern, raw_text_lower):
- matches.append(keyword)
-
- if matches:
- # Store both the matches and their count
- theme_matches[theme] = {
- "matches": matches,
- "count": len(matches)
- }
-
- # Sort themes by match count in descending order
- sorted_themes = sorted(theme_matches.keys(),
- key=lambda t: theme_matches[t]["count"],
- reverse=True)
-
- # Add the most relevant themes (more matches = more relevant)
- # Limit to top 5 themes to avoid too many irrelevant tags
- top_themes = sorted_themes[:5] if len(sorted_themes) > 5 else sorted_themes
-
- # Add historical period tags first (they're often most important for historical research)
- period_themes = [t for t in top_themes if t in [
- "Prehistoric", "Ancient World", "Medieval", "Renaissance",
- "Early Modern", "18th Century", "19th Century", "20th Century", "Contemporary"
- ]]
-
- for theme in period_themes:
- if theme not in subject_tags:
- subject_tags.append(theme)
-
- # Then add the remaining top themes
- for theme in top_themes:
- if theme not in period_themes and theme not in subject_tags:
- subject_tags.append(theme)
-
- # Add debug information to log
- if theme_matches:
- logger.info(f"Extracted themes: {', '.join(top_themes)}")
- logger.info(f"Theme match details: {theme_matches}")
-
- # Add document period tag if date patterns are detected
- if raw_text:
- # Look for years in content
- import re
- year_matches = re.findall(r'\b1[0-9]{3}\b|\b20[0-1][0-9]\b', raw_text)
- if year_matches:
- # Convert to integers
- years = [int(y) for y in year_matches]
- # Get earliest year
- earliest = min(years)
-
- # Find the period tag for this year
- for year_range, period_tag in PERIOD_TAGS.items():
- if year_range[0] <= earliest <= year_range[1]:
- if period_tag not in subject_tags:
- subject_tags.append(period_tag)
- break
-
- # Add languages as topics if available
- if 'languages' in result and result['languages']:
- for lang in result['languages']:
- if lang and lang not in subject_tags:
- lang_tag = f"{lang} Language"
- subject_tags.append(lang_tag)
-
- # Add preprocessing information as tags if preprocessing was applied
- if preprocessing_options:
- preprocessing_methods = []
- if preprocessing_options.get("document_type", "standard") != "standard":
- doc_type = preprocessing_options["document_type"].capitalize()
- preprocessing_tag = f"Enhanced ({doc_type})"
- if preprocessing_tag not in subject_tags:
- subject_tags.append(preprocessing_tag)
-
- if preprocessing_options.get("grayscale", False):
- preprocessing_methods.append("Grayscale")
- if preprocessing_options.get("denoise", False):
- preprocessing_methods.append("Denoised")
- if preprocessing_options.get("contrast", 0) != 0:
- contrast_val = preprocessing_options.get("contrast", 0)
- if contrast_val > 0:
- preprocessing_methods.append("Contrast Enhanced")
- else:
- preprocessing_methods.append("Contrast Reduced")
- if preprocessing_options.get("rotation", 0) != 0:
- preprocessing_methods.append("Rotated")
-
- # Add a combined preprocessing tag if methods were applied
- if preprocessing_methods:
- prep_tag = "Preprocessed"
- if prep_tag not in subject_tags:
- subject_tags.append(prep_tag)
-
- # Add the specific method as a tag if only one was used
- if len(preprocessing_methods) == 1:
- method_tag = preprocessing_methods[0]
- if method_tag not in subject_tags:
- subject_tags.append(method_tag)
-
- except Exception as e:
- logger.warning(f"Error generating subject tags: {str(e)}")
- # Fallback tags if extraction fails
- if not subject_tags:
- subject_tags = DEFAULT_TAGS.copy()
-
- # Ensure we have at least 3 tags
- while len(subject_tags) < 3:
- for tag in DEFAULT_TAGS:
- if tag not in subject_tags:
- subject_tags.append(tag)
- break
- else:
- # If all default tags are already used, add generic ones
- for tag in GENERIC_TAGS:
- if tag not in subject_tags:
- subject_tags.append(tag)
- break
- else:
- # If we still can't add any more tags, break the loop
- break
-
- return subject_tags
diff --git a/utils/README.md b/utils/README.md
deleted file mode 100644
index 18ced9fb79c5c151ce633a478a1a9a9879455c2a..0000000000000000000000000000000000000000
--- a/utils/README.md
+++ /dev/null
@@ -1,75 +0,0 @@
-# OCR Utilities
-
-This directory contains utility modules for the Historical OCR project.
-
-## PDF OCR Processing
-
-The `pdf_ocr.py` module provides specialized functionality for processing PDF documents with OCR.
-
-### Features
-
-- **Robust PDF-to-Image Conversion**: Converts PDF documents to images using optimized settings before OCR processing
-- **Multi-Page Support**: Intelligently handles multi-page documents, allowing processing of specific pages or page ranges
-- **Memory-Efficient Processing**: Processes PDFs in batches to prevent memory issues with large documents
-- **Fallback Mechanism**: Falls back to structured_ocr's internal processing if direct conversion fails
-- **Cleanup Management**: Automatically cleans up temporary files after processing
-
-### Key Components
-
-- **PDFOCR**: Main class for processing PDF files with OCR
-- **PDFConversionResult**: Helper class that holds PDF conversion results and manages cleanup
-
-### Basic Usage
-
-```python
-from utils.pdf_ocr import PDFOCR
-
-# Initialize the processor
-processor = PDFOCR()
-
-# Process a PDF file (all pages, with vision model)
-result = processor.process_pdf('document.pdf')
-
-# Process a PDF file (specific pages, with vision model)
-result = processor.process_pdf('document.pdf', custom_pages=[1, 3, 5])
-
-# Process a PDF file (first N pages, without vision model)
-result = processor.process_pdf('document.pdf', max_pages=3, use_vision=False)
-
-# Process a PDF file with custom prompt
-result = processor.process_pdf(
- 'document.pdf',
- custom_prompt="This is a historical newspaper with multiple columns."
-)
-
-# Save results to JSON
-output_path = processor.save_json_output('document.pdf', 'results.json')
-```
-
-### Command Line Usage
-
-The module can also be used directly from the command line:
-
-```bash
-python utils/pdf_ocr.py document.pdf --output results.json
-python utils/pdf_ocr.py document.pdf --max-pages 3
-python utils/pdf_ocr.py document.pdf --pages 1,3,5
-python utils/pdf_ocr.py document.pdf --prompt "This is a historical newspaper with multiple columns."
-python utils/pdf_ocr.py document.pdf --no-vision
-```
-
-### How It Works
-
-1. The module first attempts to convert the PDF to images using `pdf2image`
-2. It processes the first page with the vision model (if requested) for detailed analysis
-3. Additional pages are processed with the text model for efficiency
-4. All text is combined into a single result with appropriate metadata
-5. If direct conversion fails, it falls back to using `structured_ocr.py` for PDF processing
-
-### Parameters
-
-- **pdf_path**: Path to the PDF file to process
-- **use_vision**: Whether to use vision model for improved analysis (default: True)
-- **max_pages**: Maximum number of pages to process (default: all pages)
-- **custom_pages**: Specific page numbers to process, 1-based indexing (e.g., [1, 3, 5])
-- **custom_prompt**: Custom instructions for OCR processing
diff --git a/utils/__init__.py b/utils/__init__.py
deleted file mode 100644
index cc0b6de0413010eeae9b9eb35209025452086798..0000000000000000000000000000000000000000
--- a/utils/__init__.py
+++ /dev/null
@@ -1,47 +0,0 @@
-"""
-Utility functions for historical OCR processing.
-"""
-# Re-export image utilities
-from utils.image_utils import replace_images_in_markdown, get_combined_markdown, detect_skew, clean_ocr_result
-
-# Import general utilities from the new module
-from utils.general_utils import (
- generate_cache_key,
- timing,
- format_timestamp,
- create_descriptive_filename,
- extract_subject_tags
-)
-
-# Import file utilities
-from utils.file_utils import (
- get_base64_from_image,
- get_base64_from_bytes,
- handle_temp_files
-)
-
-# Import UI utilities
-from utils.ui_utils import display_results
-
-__all__ = [
- # Image utilities
- 'replace_images_in_markdown',
- 'get_combined_markdown',
- 'detect_skew',
- 'clean_ocr_result',
-
- # General utilities
- 'generate_cache_key',
- 'timing',
- 'format_timestamp',
- 'create_descriptive_filename',
- 'extract_subject_tags',
-
- # File utilities
- 'get_base64_from_image',
- 'get_base64_from_bytes',
- 'handle_temp_files',
-
- # UI utilities
- 'display_results'
-]
diff --git a/utils/content_utils.py b/utils/content_utils.py
deleted file mode 100644
index 3615adc17dc39a0365338833288a4d8186cc2615..0000000000000000000000000000000000000000
--- a/utils/content_utils.py
+++ /dev/null
@@ -1,103 +0,0 @@
-import re
-import ast
-from .text_utils import clean_raw_text, format_markdown_text
-
-def classify_document_content(result):
- """Classify document content based on structure and content"""
- classification = {
- 'has_title': False,
- 'has_content': False,
- 'has_sections': False,
- 'is_structured': False
- }
-
- if 'ocr_contents' not in result or not isinstance(result['ocr_contents'], dict):
- return classification
-
- # Check for title
- if 'title' in result['ocr_contents'] and result['ocr_contents']['title']:
- classification['has_title'] = True
-
- # Check for content
- content_fields = ['content', 'transcript', 'text']
- for field in content_fields:
- if field in result['ocr_contents'] and result['ocr_contents'][field]:
- classification['has_content'] = True
- break
-
- # Check for sections
- section_count = 0
- for key in result['ocr_contents'].keys():
- if key not in ['raw_text', 'error'] and result['ocr_contents'][key]:
- section_count += 1
-
- classification['has_sections'] = section_count > 2
-
- # Check if structured
- classification['is_structured'] = (
- classification['has_title'] and
- classification['has_content'] and
- classification['has_sections']
- )
-
- return classification
-
-def extract_document_text(result):
- """Extract main document text content"""
- if 'ocr_contents' not in result or not isinstance(result['ocr_contents'], dict):
- return ""
-
- # Try to get the text from content fields in preferred order - prioritize main_text
- for field in ['main_text', 'content', 'transcript', 'text', 'raw_text']:
- if field in result['ocr_contents'] and result['ocr_contents'][field]:
- content = result['ocr_contents'][field]
- if isinstance(content, str):
- return content
-
- return ""
-
-def extract_image_description(image_data):
- """Extract image description from data"""
- if not image_data or not isinstance(image_data, dict):
- return ""
-
- # Try different fields that might contain descriptions
- for field in ['alt_text', 'caption', 'description']:
- if field in image_data and image_data[field]:
- return image_data[field]
-
- return ""
-
-def format_structured_data(content):
- """Format structured data like lists and dictionaries into readable markdown
-
- Args:
- content: The content to format (str, list, dict)
-
- Returns:
- Formatted markdown text
- """
- if not content:
- return ""
-
- # For string content, return as-is to maintain content purity
- # This prevents JSON-like text from being transformed inappropriately
- if isinstance(content, str):
- return content
-
- # Handle native Python lists
- if isinstance(content, list):
- if not content:
- return ""
- # Convert to markdown bullet points
- return "\n".join([f"- {item}" for item in content])
-
- # Handle native Python dictionaries
- elif isinstance(content, dict):
- if not content:
- return ""
- # Convert to markdown key-value pairs
- return "\n".join([f"**{k}**: {v}" for k, v in content.items()])
-
- # Return as string for other types
- return str(content)
diff --git a/utils/file_utils.py b/utils/file_utils.py
deleted file mode 100644
index d4ee5b806a74abf3d64061081a0c15c8b4752c5d..0000000000000000000000000000000000000000
--- a/utils/file_utils.py
+++ /dev/null
@@ -1,100 +0,0 @@
-"""
-File utility functions for historical OCR processing.
-"""
-import base64
-import logging
-from pathlib import Path
-
-# Configure logging
-logger = logging.getLogger("utils")
-logger.setLevel(logging.INFO)
-
-def get_base64_from_image(image_path):
- """
- Get base64 data URL from image file with proper MIME type.
-
- Args:
- image_path: Path to the image file
-
- Returns:
- Base64 data URL with appropriate MIME type prefix
- """
- try:
- # Convert to Path object for better handling
- path_obj = Path(image_path)
-
- # Determine mime type based on file extension
- mime_type = 'image/jpeg' # Default mime type
- suffix = path_obj.suffix.lower()
- if suffix == '.png':
- mime_type = 'image/png'
- elif suffix == '.gif':
- mime_type = 'image/gif'
- elif suffix in ['.jpg', '.jpeg']:
- mime_type = 'image/jpeg'
- elif suffix == '.pdf':
- mime_type = 'application/pdf'
-
- # Read and encode file
- with open(path_obj, "rb") as file:
- encoded = base64.b64encode(file.read()).decode('utf-8')
- return f"data:{mime_type};base64,{encoded}"
- except Exception as e:
- logger.error(f"Error encoding file to base64: {str(e)}")
- return ""
-
-def get_base64_from_bytes(file_bytes, mime_type=None, file_name=None):
- """
- Get base64 data URL from file bytes with proper MIME type.
-
- Args:
- file_bytes: Binary file data
- mime_type: MIME type of the file (optional)
- file_name: Original file name for MIME type detection (optional)
-
- Returns:
- Base64 data URL with appropriate MIME type prefix
- """
- try:
- # Determine mime type if not provided
- if mime_type is None and file_name is not None:
- # Get file extension
- suffix = Path(file_name).suffix.lower()
- if suffix == '.png':
- mime_type = 'image/png'
- elif suffix == '.gif':
- mime_type = 'image/gif'
- elif suffix in ['.jpg', '.jpeg']:
- mime_type = 'image/jpeg'
- elif suffix == '.pdf':
- mime_type = 'application/pdf'
- else:
- # Default to image/jpeg for unknown types when processing images
- mime_type = 'image/jpeg'
- elif mime_type is None:
- # Default MIME type if we can't determine it - use image/jpeg instead of application/octet-stream
- # to ensure compatibility with Mistral AI OCR API
- mime_type = 'image/jpeg'
-
- # Encode and create data URL
- encoded = base64.b64encode(file_bytes).decode('utf-8')
- return f"data:{mime_type};base64,{encoded}"
- except Exception as e:
- logger.error(f"Error encoding bytes to base64: {str(e)}")
- return ""
-
-def handle_temp_files(temp_file_paths):
- """
- Clean up temporary files
-
- Args:
- temp_file_paths: List of temporary file paths to clean up
- """
- import os
- for temp_path in temp_file_paths:
- try:
- if os.path.exists(temp_path):
- os.unlink(temp_path)
- logger.info(f"Removed temporary file: {temp_path}")
- except Exception as e:
- logger.warning(f"Failed to remove temporary file {temp_path}: {str(e)}")
diff --git a/utils/general_utils.py b/utils/general_utils.py
deleted file mode 100644
index 76aebe89b9cf4a7697d71f6369533579d34f434f..0000000000000000000000000000000000000000
--- a/utils/general_utils.py
+++ /dev/null
@@ -1,198 +0,0 @@
-"""
-General utility functions for historical OCR processing.
-"""
-import os
-import base64
-import hashlib
-import time
-import logging
-from datetime import datetime
-from pathlib import Path
-from functools import wraps
-
-# Configure logging
-logger = logging.getLogger("utils")
-logger.setLevel(logging.INFO)
-
-def generate_cache_key(file_bytes, file_type, use_vision, preprocessing_options=None, pdf_rotation=0, custom_prompt=None):
- """
- Generate a cache key for OCR processing
-
- Args:
- file_bytes: File content as bytes
- file_type: Type of file (pdf or image)
- use_vision: Whether to use vision model
- preprocessing_options: Dictionary of preprocessing options
- pdf_rotation: PDF rotation value
- custom_prompt: Custom prompt for OCR
-
- Returns:
- str: Cache key
- """
- # Generate file hash
- file_hash = hashlib.md5(file_bytes).hexdigest()
-
- # Include preprocessing options in cache key
- preprocessing_options_hash = ""
- if preprocessing_options:
- # Add pdf_rotation to preprocessing options to ensure it's part of the cache key
- if pdf_rotation != 0:
- preprocessing_options_with_rotation = preprocessing_options.copy()
- preprocessing_options_with_rotation['pdf_rotation'] = pdf_rotation
- preprocessing_str = str(sorted(preprocessing_options_with_rotation.items()))
- else:
- preprocessing_str = str(sorted(preprocessing_options.items()))
- preprocessing_options_hash = hashlib.md5(preprocessing_str.encode()).hexdigest()
- elif pdf_rotation != 0:
- # If no preprocessing options but we have rotation, include that in the hash
- preprocessing_options_hash = hashlib.md5(f"pdf_rotation_{pdf_rotation}".encode()).hexdigest()
-
- # Create base cache key
- cache_key = f"{file_hash}_{file_type}_{use_vision}_{preprocessing_options_hash}"
-
- # Include custom prompt in cache key if provided
- if custom_prompt:
- custom_prompt_hash = hashlib.md5(str(custom_prompt).encode()).hexdigest()
- cache_key = f"{cache_key}_{custom_prompt_hash}"
-
- return cache_key
-
-def timing(description):
- """Context manager for timing code execution"""
- class TimingContext:
- def __init__(self, description):
- self.description = description
-
- def __enter__(self):
- self.start_time = time.time()
- return self
-
- def __exit__(self, exc_type, exc_val, exc_tb):
- end_time = time.time()
- execution_time = end_time - self.start_time
- logger.info(f"{self.description} took {execution_time:.2f} seconds")
- return False
-
- return TimingContext(description)
-
-def format_timestamp(timestamp=None, for_filename=False):
- """
- Format timestamp for display or filenames
-
- Args:
- timestamp: Datetime object or string to format (defaults to current time)
- for_filename: Whether to format for use in a filename (defaults to False)
-
- Returns:
- str: Formatted timestamp
- """
- if timestamp is None:
- timestamp = datetime.now()
- elif isinstance(timestamp, str):
- try:
- timestamp = datetime.strptime(timestamp, "%Y-%m-%d %H:%M:%S")
- except ValueError:
- timestamp = datetime.now()
-
- if for_filename:
- # Format suitable for filenames: "Apr 30, 2025"
- return timestamp.strftime("%b %d, %Y")
- else:
- # Standard format for display
- return timestamp.strftime("%Y-%m-%d %H:%M")
-
-def create_descriptive_filename(original_filename, result, file_ext, preprocessing_options=None):
- """
- Create a user-friendly descriptive filename for the result
-
- Args:
- original_filename: Original filename
- result: OCR result dictionary
- file_ext: File extension
- preprocessing_options: Dictionary of preprocessing options
-
- Returns:
- str: Human-readable descriptive filename
- """
- # Get base name without extension and capitalize words
- original_name = Path(original_filename).stem
-
- # Make the original name more readable by replacing dashes and underscores with spaces
- # Then capitalize each word
- readable_name = original_name.replace('-', ' ').replace('_', ' ')
- # Split by spaces and capitalize each word, then rejoin
- name_parts = readable_name.split()
- readable_name = ' '.join(word.capitalize() for word in name_parts)
-
- # Determine document type
- doc_type = None
- if 'detected_document_type' in result and result['detected_document_type']:
- doc_type = result['detected_document_type'].capitalize()
- elif 'topics' in result and result['topics']:
- # Use first topic as document type if not explicitly detected
- doc_type = result['topics'][0]
-
- # Find period/era information
- period_info = None
- if 'topics' in result and result['topics']:
- for tag in result['topics']:
- if "century" in tag.lower() or "pre-" in tag.lower() or "era" in tag.lower():
- period_info = tag
- break
-
- # Format metadata within parentheses if available
- metadata = []
- if doc_type:
- metadata.append(doc_type)
- if period_info:
- metadata.append(period_info)
-
- metadata_str = ""
- if metadata:
- metadata_str = f" ({', '.join(metadata)})"
-
- # Add current date for uniqueness and sorting
- current_date = format_timestamp(for_filename=True)
- date_str = f" - {current_date}"
-
- # Generate final user-friendly filename
- descriptive_name = f"{readable_name}{metadata_str}{date_str}{file_ext}"
- return descriptive_name
-
-def extract_subject_tags(result, raw_text, preprocessing_options=None):
- """
- Extract subject tags from OCR result
-
- Args:
- result: OCR result dictionary
- raw_text: Raw text from OCR
- preprocessing_options: Dictionary of preprocessing options
-
- Returns:
- list: Subject tags
- """
- subject_tags = []
-
- # Use existing topics as starting point if available
- if 'topics' in result and result['topics']:
- subject_tags = list(result['topics'])
-
- # Add document type if detected
- if 'detected_document_type' in result:
- doc_type = result['detected_document_type'].capitalize()
- if doc_type not in subject_tags:
- subject_tags.append(doc_type)
-
- # If no tags were found, add some defaults
- if not subject_tags:
- subject_tags = ["Document", "Historical Document"]
-
- # Try to infer content type
- if "letter" in raw_text.lower()[:1000] or "dear" in raw_text.lower()[:200]:
- subject_tags.append("Letter")
-
- # Check if it might be a newspaper
- if "newspaper" in raw_text.lower()[:1000] or "editor" in raw_text.lower()[:500]:
- subject_tags.append("Newspaper")
-
- return subject_tags
diff --git a/utils/helpers/__init__.py b/utils/helpers/__init__.py
deleted file mode 100644
index 5c3cf3ae0a0a5cda32fe7ec60311defd5e0afd70..0000000000000000000000000000000000000000
--- a/utils/helpers/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-"""Helper modules for OCR processing utilities."""
\ No newline at end of file
diff --git a/utils/helpers/language_detection.py b/utils/helpers/language_detection.py
deleted file mode 100644
index 7667bb22513eed1d5ad556580edbddc79b1ef53b..0000000000000000000000000000000000000000
--- a/utils/helpers/language_detection.py
+++ /dev/null
@@ -1,373 +0,0 @@
-# Standard library imports
-import logging
-import re
-from typing import List, Dict, Set, Tuple, Optional, Union, Any
-from functools import lru_cache
-
-# Configure logging
-logging.basicConfig(level=logging.INFO,
- format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
-logger = logging.getLogger(__name__)
-
-class LanguageDetector:
- """
- A language detection system that provides balanced detection across multiple languages
- using an enhanced statistical approach.
- """
-
- def __init__(self):
- """Initialize the language detector with statistical language models"""
- logger.info("Initializing language detector with statistical models")
-
- # Initialize language indicators dictionary for statistical detection
- self._init_language_indicators()
- # Set thresholds for language detection confidence
- self.single_lang_confidence = 65 # Minimum score to consider a language detected
- self.secondary_lang_threshold = 0.75 # Secondary language must be at least this fraction of primary score
-
- def _init_language_indicators(self):
- """Initialize language indicators for statistical detection with historical markers"""
- # Define indicators for all supported languages with equal detail level
- # Each language has:
- # - Distinctive characters
- # - Common words (including historical forms)
- # - N-grams (character sequences)
- # - Historical markers specific to older forms of the language
- self.language_indicators = {
- "English": {
- "chars": [], # English uses basic Latin alphabet without special chars
- "words": ['the', 'and', 'of', 'to', 'in', 'a', 'is', 'that', 'for', 'it',
- 'with', 'as', 'be', 'on', 'by', 'at', 'this', 'have', 'from', 'or',
- 'an', 'but', 'not', 'what', 'all', 'were', 'when', 'we', 'there', 'can',
- 'would', 'who', 'you', 'been', 'one', 'their', 'has', 'more', 'if', 'no'],
- "ngrams": ['th', 'he', 'in', 'er', 'an', 're', 'on', 'at', 'en', 'nd', 'ti', 'es', 'or',
- 'ing', 'tion', 'the', 'and', 'tha', 'ent', 'ion'],
- "historical": {
- "chars": ['þ', 'ȝ', 'æ', 'ſ'], # Thorn, yogh, ash, long s
- "words": ['thou', 'thee', 'thy', 'thine', 'hath', 'doth', 'ere', 'whilom', 'betwixt',
- 'ye', 'art', 'wast', 'dost', 'hast', 'shalt', 'mayst', 'verily'],
- "patterns": ['eth$', '^y[^a-z]', 'ck$', 'aught', 'ought'] # -eth endings, y- prefixes
- }
- },
- "French": {
- "chars": ['é', 'è', 'ê', 'à', 'ç', 'ù', 'â', 'î', 'ô', 'û', 'ë', 'ï', 'ü'],
- "words": ['le', 'la', 'les', 'et', 'en', 'de', 'du', 'des', 'un', 'une', 'ce', 'cette',
- 'ces', 'dans', 'par', 'pour', 'sur', 'qui', 'que', 'quoi', 'où', 'quand', 'comment',
- 'est', 'sont', 'ont', 'nous', 'vous', 'ils', 'elles', 'avec', 'sans', 'mais', 'ou'],
- "ngrams": ['es', 'le', 'de', 'en', 'on', 'nt', 'qu', 'ai', 'an', 'ou', 'ur', 're', 'me',
- 'les', 'ent', 'que', 'des', 'ons', 'ant', 'ion'],
- "historical": {
- "chars": ['ſ', 'æ', 'œ'], # Long s and ligatures
- "words": ['aultre', 'avecq', 'icelluy', 'oncques', 'moult', 'estre', 'mesme', 'ceste',
- 'ledict', 'celuy', 'ceulx', 'aulcun', 'ainſi', 'touſiours', 'eſtre',
- 'eſt', 'meſme', 'felon', 'auec', 'iufques', 'chofe', 'fcience'],
- "patterns": ['oi[ts]$', 'oi[re]$', 'f[^aeiou]', 'ff', 'ſ', 'auoit', 'eſtoit',
- 'ſi', 'ſur', 'ſa', 'cy', 'ayant', 'oy', 'uſ', 'auſ']
- },
- },
- "German": {
- "chars": ['ä', 'ö', 'ü', 'ß'],
- "words": ['der', 'die', 'das', 'und', 'in', 'zu', 'den', 'ein', 'eine', 'mit', 'ist', 'von',
- 'des', 'sich', 'auf', 'für', 'als', 'auch', 'werden', 'bei', 'durch', 'aus', 'sind',
- 'nicht', 'nur', 'wurde', 'wie', 'wenn', 'aber', 'noch', 'nach', 'so', 'sein', 'über'],
- "ngrams": ['en', 'er', 'ch', 'de', 'ei', 'in', 'te', 'nd', 'ie', 'ge', 'un', 'sch', 'ich',
- 'den', 'die', 'und', 'der', 'ein', 'ung', 'cht'],
- "historical": {
- "chars": ['ſ', 'ů', 'ė', 'ÿ'],
- "words": ['vnnd', 'vnnd', 'vnter', 'vnd', 'seyn', 'thun', 'auff', 'auß', 'deß', 'diß'],
- "patterns": ['^v[nd]', 'th', 'vnter', 'ſch']
- }
- },
- "Spanish": {
- "chars": ['á', 'é', 'í', 'ó', 'ú', 'ñ', 'ü', '¿', '¡'],
- "words": ['el', 'la', 'los', 'las', 'de', 'en', 'y', 'a', 'que', 'por', 'un', 'una', 'no',
- 'es', 'con', 'para', 'su', 'al', 'se', 'del', 'como', 'más', 'pero', 'lo', 'mi',
- 'si', 'ya', 'todo', 'esta', 'cuando', 'hay', 'muy', 'bien', 'sin', 'así'],
- "ngrams": ['de', 'en', 'os', 'es', 'la', 'ar', 'el', 'er', 'ra', 'as', 'an', 'do', 'or',
- 'que', 'nte', 'los', 'ado', 'con', 'ent', 'ien'],
- "historical": {
- "chars": ['ſ', 'ç', 'ñ'],
- "words": ['facer', 'fijo', 'fermoso', 'agora', 'asaz', 'aver', 'caſa', 'deſde', 'eſte',
- 'eſta', 'eſto', 'deſto', 'deſta', 'eſſo', 'muger', 'dixo', 'fazer'],
- "patterns": ['^f[aei]', 'ſſ', 'ſc', '^deſ', 'xo$', 'xe$']
- },
- },
- "Italian": {
- "chars": ['à', 'è', 'é', 'ì', 'í', 'ò', 'ó', 'ù', 'ú'],
- "words": ['il', 'la', 'i', 'le', 'e', 'di', 'a', 'in', 'che', 'non', 'per', 'con', 'un',
- 'una', 'del', 'della', 'è', 'sono', 'da', 'si', 'come', 'anche', 'più', 'ma', 'ci',
- 'se', 'ha', 'mi', 'lo', 'ti', 'al', 'tu', 'questo', 'questi'],
- "ngrams": ['di', 'la', 'er', 'to', 're', 'co', 'de', 'in', 'ra', 'on', 'li', 'no', 'ri',
- 'che', 'ent', 'con', 'per', 'ion', 'ato', 'lla']
- },
- "Portuguese": {
- "chars": ['á', 'â', 'ã', 'à', 'é', 'ê', 'í', 'ó', 'ô', 'õ', 'ú', 'ç'],
- "words": ['o', 'a', 'os', 'as', 'de', 'em', 'e', 'do', 'da', 'dos', 'das', 'no', 'na',
- 'para', 'que', 'um', 'uma', 'por', 'com', 'se', 'não', 'mais', 'como', 'mas',
- 'você', 'eu', 'este', 'isso', 'ele', 'seu', 'sua', 'ou', 'já', 'me'],
- "ngrams": ['de', 'os', 'em', 'ar', 'es', 'ra', 'do', 'da', 'en', 'co', 'nt', 'ad', 'to',
- 'que', 'nto', 'ent', 'com', 'ção', 'ado', 'ment']
- },
- "Dutch": {
- "chars": ['ë', 'ï', 'ö', 'ü', 'é', 'è', 'ê', 'ç', 'á', 'à', 'ä', 'ó', 'ô', 'ú', 'ù', 'û', 'ij'],
- "words": ['de', 'het', 'een', 'en', 'van', 'in', 'is', 'dat', 'op', 'te', 'zijn', 'met',
- 'voor', 'niet', 'aan', 'er', 'die', 'maar', 'dan', 'ik', 'je', 'hij', 'zij', 'we',
- 'kunnen', 'wordt', 'nog', 'door', 'over', 'als', 'uit', 'bij', 'om', 'ook'],
- "ngrams": ['en', 'de', 'er', 'ee', 'ge', 'an', 'aa', 'in', 'te', 'et', 'ng', 'ee', 'or',
- 'van', 'het', 'een', 'ing', 'ver', 'den', 'sch']
- },
- "Russian": {
- # Russian (Cyrillic alphabet) characters
- "chars": ['а', 'б', 'в', 'г', 'д', 'е', 'ё', 'ж', 'з', 'и', 'й', 'к', 'л', 'м', 'н', 'о', 'п',
- 'р', 'с', 'т', 'у', 'ф', 'х', 'ц', 'ч', 'ш', 'щ', 'ъ', 'ы', 'ь', 'э', 'ю', 'я'],
- "words": ['и', 'в', 'не', 'на', 'что', 'я', 'с', 'а', 'то', 'он', 'как', 'этот', 'по',
- 'но', 'из', 'к', 'у', 'за', 'вы', 'все', 'так', 'же', 'от', 'для', 'о', 'его',
- 'мы', 'было', 'она', 'бы', 'мне', 'еще', 'есть', 'быть', 'был'],
- "ngrams": ['о', 'е', 'а', 'н', 'и', 'т', 'р', 'с', 'в', 'л', 'к', 'м', 'д',
- 'ст', 'но', 'то', 'ни', 'на', 'по', 'ет']
- },
- "Chinese": {
- "chars": ['的', '是', '不', '了', '在', '和', '有', '我', '们', '人', '这', '上', '中',
- '个', '大', '来', '到', '国', '时', '要', '地', '出', '会', '可', '也', '就',
- '年', '生', '对', '能', '自', '那', '都', '得', '说', '过', '子', '家', '后', '多'],
- # Chinese doesn't have "words" in the same way as alphabetic languages
- "words": ['的', '是', '不', '了', '在', '和', '有', '我', '们', '人', '这', '上', '中',
- '个', '大', '来', '到', '国', '时', '要', '地', '出', '会', '可', '也', '就'],
- "ngrams": ['的', '是', '不', '了', '在', '我', '有', '和', '人', '这', '中', '大', '来', '上',
- '国', '个', '到', '说', '们', '为']
- },
- "Japanese": {
- # A mix of hiragana, katakana, and common kanji
- "chars": ['あ', 'い', 'う', 'え', 'お', 'か', 'き', 'く', 'け', 'こ', 'さ', 'し', 'す', 'せ', 'そ',
- 'ア', 'イ', 'ウ', 'エ', 'オ', 'カ', 'キ', 'ク', 'ケ', 'コ', 'サ', 'シ', 'ス', 'セ', 'ソ',
- '日', '本', '人', '大', '小', '中', '山', '川', '田', '子', '女', '男', '月', '火', '水'],
- "words": ['は', 'を', 'に', 'の', 'が', 'で', 'へ', 'から', 'より', 'まで', 'だ', 'です', 'した',
- 'ます', 'ません', 'です', 'これ', 'それ', 'あれ', 'この', 'その', 'あの', 'わたし'],
- "ngrams": ['の', 'は', 'た', 'が', 'を', 'に', 'て', 'で', 'と', 'し', 'か', 'ま', 'こ', 'い',
- 'する', 'いる', 'れる', 'なる', 'れて', 'した']
- },
- "Korean": {
- "chars": ['가', '나', '다', '라', '마', '바', '사', '아', '자', '차', '카', '타', '파', '하',
- '그', '는', '을', '이', '에', '에서', '로', '으로', '와', '과', '또는', '하지만'],
- "words": ['이', '그', '저', '나', '너', '우리', '그들', '이것', '그것', '저것', '은', '는',
- '이', '가', '을', '를', '에', '에서', '으로', '로', '와', '과', '의', '하다', '되다'],
- "ngrams": ['이', '다', '는', '에', '하', '고', '지', '서', '의', '가', '을', '로', '을', '으',
- '니다', '습니', '하는', '이다', '에서', '하고']
- },
- "Arabic": {
- "chars": ['ا', 'ب', 'ت', 'ث', 'ج', 'ح', 'خ', 'د', 'ذ', 'ر', 'ز', 'س', 'ش', 'ص', 'ض',
- 'ط', 'ظ', 'ع', 'غ', 'ف', 'ق', 'ك', 'ل', 'م', 'ن', 'ه', 'و', 'ي', 'ء', 'ة', 'ى'],
- "words": ['في', 'من', 'على', 'إلى', 'هذا', 'هذه', 'ذلك', 'تلك', 'هو', 'هي', 'هم', 'أنا',
- 'أنت', 'نحن', 'كان', 'كانت', 'يكون', 'لا', 'لم', 'ما', 'أن', 'و', 'أو', 'ثم', 'بعد'],
- "ngrams": ['ال', 'ان', 'في', 'من', 'ون', 'ين', 'ات', 'ار', 'ور', 'ما', 'لا', 'ها', 'ان',
- 'الم', 'لان', 'علا', 'الح', 'الس', 'الع', 'الت']
- },
- "Hindi": {
- "chars": ['अ', 'आ', 'इ', 'ई', 'उ', 'ऊ', 'ए', 'ऐ', 'ओ', 'औ', 'क', 'ख', 'ग', 'घ', 'ङ',
- 'च', 'छ', 'ज', 'झ', 'ञ', 'ट', 'ठ', 'ड', 'ढ', 'ण', 'त', 'थ', 'द', 'ध', 'न',
- 'प', 'फ', 'ब', 'भ', 'म', 'य', 'र', 'ल', 'व', 'श', 'ष', 'स', 'ह', 'ा', 'ि', 'ी',
- 'ु', 'ू', 'े', 'ै', 'ो', 'ौ', '्', 'ं', 'ः'],
- "words": ['और', 'का', 'के', 'की', 'एक', 'में', 'है', 'यह', 'हैं', 'से', 'को', 'पर', 'इस',
- 'हो', 'गया', 'कर', 'मैं', 'या', 'हुआ', 'था', 'वह', 'अपने', 'सकता', 'ने', 'बहुत'],
- "ngrams": ['का', 'के', 'की', 'है', 'ने', 'से', 'मे', 'को', 'पर', 'हा', 'रा', 'ता', 'या',
- 'ार', 'ान', 'कार', 'राज', 'ारा', 'जाए', 'ेजा']
- },
- "Latin": {
- "chars": [], # Latin uses basic Latin alphabet
- "words": ['et', 'in', 'ad', 'est', 'sunt', 'non', 'cum', 'sed', 'qui', 'quod', 'ut', 'si',
- 'nec', 'ex', 'per', 'quam', 'pro', 'iam', 'hoc', 'aut', 'esse', 'enim', 'de',
- 'atque', 'ac', 'ante', 'post', 'sub', 'ab'],
- "ngrams": ['us', 'is', 'um', 'er', 'it', 'nt', 'am', 'em', 're', 'at', 'ti', 'es', 'ur',
- 'tur', 'que', 'ere', 'ent', 'ius', 'rum', 'tus']
- },
- "Greek": {
- "chars": ['α', 'β', 'γ', 'δ', 'ε', 'ζ', 'η', 'θ', 'ι', 'κ', 'λ', 'μ', 'ν', 'ξ', 'ο', 'π',
- 'ρ', 'σ', 'ς', 'τ', 'υ', 'φ', 'χ', 'ψ', 'ω', 'ά', 'έ', 'ή', 'ί', 'ό', 'ύ', 'ώ'],
- "words": ['και', 'του', 'της', 'των', 'στο', 'στη', 'με', 'από', 'για', 'είναι', 'να',
- 'ότι', 'δεν', 'στον', 'μια', 'που', 'ένα', 'έχει', 'θα', 'το', 'ο', 'η', 'τον'],
- "ngrams": ['αι', 'τα', 'ου', 'τη', 'οι', 'το', 'ης', 'αν', 'ος', 'ον', 'ις', 'ει', 'ερ',
- 'και', 'την', 'τον', 'ους', 'νου', 'εντ', 'μεν']
- }
- }
-
- def detect_languages(self, text: str, filename: str = None, current_languages: List[str] = None) -> List[str]:
- """
- Detect languages in text using an enhanced statistical approach
-
- Args:
- text: Text to analyze
- filename: Optional filename to provide additional context
- current_languages: Optional list of languages already detected
-
- Returns:
- List of detected languages
- """
- logger = logging.getLogger("language_detector")
-
- # If no text provided, return current languages or default
- if not text or len(text.strip()) < 10:
- return current_languages if current_languages else ["English"]
-
- # If we already have detected languages, use them
- if current_languages and len(current_languages) > 0:
- logger.info(f"Using already detected languages: {current_languages}")
- return current_languages
-
- # Use enhanced statistical detection
- detected_languages = self._detect_statistically(text, filename)
- logger.info(f"Statistical language detection results: {detected_languages}")
- return detected_languages
-
- def _detect_statistically(self, text: str, filename: str = None) -> List[str]:
- """
- Detect languages using enhanced statistical analysis with historical language indicators
-
- Args:
- text: Text to analyze
- filename: Optional filename for additional context
-
- Returns:
- List of detected languages
- """
- logger = logging.getLogger("language_detector")
-
- # Normalize text to lowercase for consistent analysis
- text_lower = text.lower()
- words = re.findall(r'\b\w+\b', text_lower) # Extract words
-
- # Score each language based on characters, words, n-grams, and historical markers
- language_scores = {}
- historical_bonus = {}
-
- # PHASE 1: Special character analysis
- # Count special characters for each language
- special_char_counts = {}
- total_special_chars = 0
-
- for language, indicators in self.language_indicators.items():
- chars = indicators["chars"]
- count = 0
- for char in chars:
- if char in text_lower:
- count += text_lower.count(char)
- special_char_counts[language] = count
- total_special_chars += count
-
- # Normalize character scores (0-30 points)
- for language, count in special_char_counts.items():
- if total_special_chars > 0:
- # Scale score to 0-30 range (reduced from 35 to make room for historical)
- normalized_score = (count / total_special_chars) * 30
- language_scores[language] = normalized_score
- else:
- language_scores[language] = 0
-
- # PHASE 2: Word analysis (0-30 points)
- # Count common words for each language
- for language, indicators in self.language_indicators.items():
- word_list = indicators["words"]
- word_matches = sum(1 for word in words if word in word_list)
-
- # Normalize word score based on text length and word list size
- word_score_factor = min(1.0, word_matches / (len(words) * 0.1)) # Max 1.0 if 10% match
- language_scores[language] = language_scores.get(language, 0) + (word_score_factor * 30)
-
- # PHASE 3: N-gram analysis (0-20 points)
- for language, indicators in self.language_indicators.items():
- ngram_list = indicators["ngrams"]
- ngram_matches = 0
-
- # Count ngram occurrences
- for ngram in ngram_list:
- ngram_matches += text_lower.count(ngram)
-
- # Normalize ngram score based on text length
- if len(text_lower) > 0:
- ngram_score_factor = min(1.0, ngram_matches / (len(text_lower) * 0.05)) # Max 1.0 if 5% match
- language_scores[language] = language_scores.get(language, 0) + (ngram_score_factor * 20)
-
- # PHASE 4: Historical language markers (0-20 points)
- for language, indicators in self.language_indicators.items():
- if "historical" in indicators:
- historical_indicators = indicators["historical"]
- historical_score = 0
-
- # Check for historical chars
- if "chars" in historical_indicators:
- for char in historical_indicators["chars"]:
- if char in text_lower:
- historical_score += text_lower.count(char) * 0.5
-
- # Check for historical words
- if "words" in historical_indicators:
- hist_words = historical_indicators["words"]
- hist_word_matches = sum(1 for word in words if word in hist_words)
- if hist_word_matches > 0:
- # Historical words are strong indicators
- historical_score += min(10, hist_word_matches * 2)
-
- # Check for historical patterns
- if "patterns" in historical_indicators:
- for pattern in historical_indicators["patterns"]:
- matches = len(re.findall(pattern, text_lower))
- if matches > 0:
- historical_score += min(5, matches * 0.5)
-
- # Cap historical score at 20 points
- historical_score = min(20, historical_score)
- historical_bonus[language] = historical_score
-
- # Apply historical bonus
- language_scores[language] += historical_score
-
- # Apply language-specific exclusivity multiplier if present
- if "exclusivity" in indicators:
- exclusivity = indicators["exclusivity"]
- language_scores[language] *= exclusivity
- logger.info(f"Applied exclusivity multiplier {exclusivity} to {language}")
-
- # Print historical bonus for debugging
- for language, bonus in historical_bonus.items():
- if bonus > 0:
- logger.info(f"Historical language bonus for {language}: {bonus} points")
-
- # Final language selection with more stringent criteria
- # Get languages with scores above threshold
- threshold = self.single_lang_confidence # Higher minimum score
- candidates = [(lang, score) for lang, score in language_scores.items() if score >= threshold]
- candidates.sort(key=lambda x: x[1], reverse=True)
-
- logger.info(f"Language candidates: {candidates}")
-
- # If we have candidate languages, return top 1-2 with higher threshold for secondary
- if candidates:
- # Always take top language
- result = [candidates[0][0]]
-
- # Add second language only if it's significantly strong compared to primary
- # and doesn't have a historical/exclusivity conflict
- if len(candidates) > 1:
- primary_lang = candidates[0][0]
- secondary_lang = candidates[1][0]
- primary_score = candidates[0][1]
- secondary_score = candidates[1][1]
-
- # Only add secondary if it meets threshold and doesn't conflict
- ratio = secondary_score / primary_score
-
- # Check for French and Spanish conflict (historical French often gets misidentified)
- historical_conflict = False
- if (primary_lang == "French" and secondary_lang == "Spanish" and
- historical_bonus.get("French", 0) > 5):
- historical_conflict = True
- logger.info("Historical French markers detected, suppressing Spanish detection")
-
- if ratio >= self.secondary_lang_threshold and not historical_conflict:
- result.append(secondary_lang)
- logger.info(f"Added secondary language {secondary_lang} (score ratio: {ratio:.2f})")
- else:
- logger.info(f"Rejected secondary language {secondary_lang} (score ratio: {ratio:.2f})")
-
- return result
-
- # Default to English if no clear signals
diff --git a/utils/helpers/letterhead_handler.py b/utils/helpers/letterhead_handler.py
deleted file mode 100644
index 8d3f539298a5a51b2d3d169bb46dbd1f38ef4c04..0000000000000000000000000000000000000000
--- a/utils/helpers/letterhead_handler.py
+++ /dev/null
@@ -1,82 +0,0 @@
-# Standard library imports
-import os
-import logging
-from pathlib import Path
-
-# Configure logging
-logging.basicConfig(level=logging.INFO,
- format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
-logger = logging.getLogger(__name__)
-
-def is_likely_letterhead(file_path, features=None):
- """
- Determine if a document is likely to contain letterhead or marginalia
-
- Args:
- file_path: Path to the document image
- features: Optional dictionary of pre-extracted features like text density
-
- Returns:
- bool: True if the document likely contains letterhead, False otherwise
- """
- # Simple logic based on filename for initial version
- file_name = Path(file_path).name.lower()
- letterhead_indicators = ['letter', 'letterhead', 'correspondence', 'memo']
-
- # Check filename for indicators
- for indicator in letterhead_indicators:
- if indicator in file_name:
- logger.info(f"Letterhead detected based on filename: {file_name}")
- return True
-
- # Check features if provided
- if features:
- # High text density at the top of the document may indicate letterhead
- if 'top_density' in features and features['top_density'] > 0.5:
- logger.info(f"Letterhead detected based on top text density: {features['top_density']}")
- return True
-
- # Uneven text distribution may indicate marginalia
- if 'density_variance' in features and features['density_variance'] > 0.3:
- logger.info(f"Possible marginalia detected based on text density variance")
- return True
-
- # Default to standard document
- return False
-
-def get_letterhead_prompt(file_path, features=None):
- """
- Generate a specialized prompt for letterhead document OCR
-
- Args:
- file_path: Path to the document image
- features: Optional dictionary of pre-extracted features
-
- Returns:
- str: Specialized prompt for letterhead document OCR
- """
- # Base prompt for all letterhead documents
- base_prompt = ("This document appears to be a letter or includes letterhead elements. "
- "Please extract the following components separately if present:\n"
- "1. Letterhead (header with logo, organization name, address, etc.)\n"
- "2. Date\n"
- "3. Recipient information (address, name, title)\n"
- "4. Salutation (e.g., 'Dear Sir/Madam')\n"
- "5. Main body text\n"
- "6. Closing (e.g., 'Sincerely')\n"
- "7. Signature\n"
- "8. Any footnotes, marginalia, or annotations\n\n"
- "Preserve the original formatting and structure as much as possible.")
-
- # Enhanced prompts based on features
- if features:
- # Extract additional context from features if available
- if 'is_historical' in features and features['is_historical']:
- base_prompt += ("\n\nThis appears to be a historical document. Pay special attention to older "
- "letterhead styles, formal language patterns, and period-specific formatting.")
-
- if 'has_marginalia' in features and features['has_marginalia']:
- base_prompt += ("\n\nThe document contains marginalia or handwritten notes in the margins. "
- "Please extract these separately from the main text and indicate their position.")
-
- return base_prompt
diff --git a/utils/helpers/ocr_text_repair.py b/utils/helpers/ocr_text_repair.py
deleted file mode 100644
index 488e7ee577ef20a531ae6a83425e357bba2b646f..0000000000000000000000000000000000000000
--- a/utils/helpers/ocr_text_repair.py
+++ /dev/null
@@ -1,270 +0,0 @@
-# Standard library imports
-import re
-import logging
-from difflib import SequenceMatcher
-from typing import Tuple, Dict, Any, List, Optional
-
-# Configure logging
-logging.basicConfig(level=logging.INFO,
- format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
-logger = logging.getLogger(__name__)
-
-def detect_duplicate_text_issues(text: str) -> Tuple[bool, Dict[str, Any]]:
- """
- Detect if OCR text has duplication issues often found in handwritten document OCR
-
- Args:
- text: OCR text to analyze
-
- Returns:
- Tuple of (has_duplication_issues, details_dict)
- """
- # Early exit for empty text
- if not text or len(text) < 100:
- return False, {"duplication_rate": 0.0, "details": "Text too short for analysis"}
-
- # Look for repeated line patterns
- lines = text.split('\n')
- line_count = len(lines)
-
- # Basic metrics
- repeated_lines = 0
- duplicate_sections = []
- line_repetition_indices = []
-
- # Check for exact line repetitions
- seen_lines = {}
- for i, line in enumerate(lines):
- # Skip very short lines or empty lines
- stripped = line.strip()
- if len(stripped) < 5:
- continue
-
- if stripped in seen_lines:
- repeated_lines += 1
- line_repetition_indices.append((seen_lines[stripped], i))
- else:
- seen_lines[stripped] = i
-
- # Calculate line repetition rate
- line_repetition_rate = repeated_lines / max(1, line_count)
-
- # Look for longer repeated sections using sequence matcher
- text_blocks = [text[i:i+100] for i in range(0, len(text), 100) if i+100 <= len(text)]
- block_count = len(text_blocks)
-
- repeated_blocks = 0
- for i in range(block_count):
- for j in range(i+1, min(i+10, block_count)): # Only check nearby blocks for efficiency
- matcher = SequenceMatcher(None, text_blocks[i], text_blocks[j])
- similarity = matcher.ratio()
- if similarity > 0.8: # High similarity threshold
- repeated_blocks += 1
- duplicate_sections.append((i, j, similarity))
- break
-
- # Calculate block repetition rate
- block_repetition_rate = repeated_blocks / max(1, block_count)
-
- # Combine metrics for overall duplication rate
- duplication_rate = max(line_repetition_rate, block_repetition_rate)
-
- # Detect patterns of repeated words in sequence (common OCR mistake)
- word_pattern = r'\b(\w+)\s+\1\b'
- repeated_words = len(re.findall(word_pattern, text))
- repeated_words_rate = repeated_words / max(1, len(text.split()))
-
- # Update duplication rate with word repetition
- duplication_rate = max(duplication_rate, repeated_words_rate)
-
- # Log detailed analysis
- logger.info(f"OCR duplication analysis: line_repetition={line_repetition_rate:.2f}, "
- f"block_repetition={block_repetition_rate:.2f}, "
- f"word_repetition={repeated_words_rate:.2f}, "
- f"final_rate={duplication_rate:.2f}")
-
- # Determine if this is a serious issue
- has_duplication = duplication_rate > 0.1
-
- # Return detailed results
- return has_duplication, {
- "duplication_rate": duplication_rate,
- "line_repetition_rate": line_repetition_rate,
- "block_repetition_rate": block_repetition_rate,
- "word_repetition_rate": repeated_words_rate,
- "repeated_lines": repeated_lines,
- "repeated_blocks": repeated_blocks,
- "repeated_words": repeated_words,
- "duplicate_sections": duplicate_sections[:10], # Only include the first 10 for brevity
- "repetition_indices": line_repetition_indices[:10]
- }
-
-def get_enhanced_preprocessing_options(current_options: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
- """
- Generate enhanced preprocessing options for improved OCR on handwritten documents
-
- Args:
- current_options: Current preprocessing options (if available)
-
- Returns:
- Dict of enhanced options
- """
- # Start with current options or empty dict
- options = current_options.copy() if current_options else {}
-
- # Set document type to handwritten
- options["document_type"] = "handwritten"
-
- # Enhanced contrast - higher than normal for better handwriting extraction
- options["contrast"] = 1.4 # Higher than default
-
- # Apply grayscale
- options["grayscale"] = True
-
- # Apply adaptive thresholding optimized for handwriting
- options["adaptive_threshold"] = True
- options["threshold_block_size"] = 25 # Larger block size for handwriting
- options["threshold_c"] = 10 # Adjusted C value for better handwriting detection
-
- # Disable standard binarization which often loses handwriting detail
- options["binarize"] = False
-
- # Despeckle to reduce noise
- options["denoise"] = True
-
- # Enable handwriting-specific preprocessing
- options["handwriting_mode"] = True
-
- # Disable anything that might harm handwriting recognition
- if "sharpen" in options:
- options["sharpen"] = False
-
- logger.info(f"Enhanced handwriting preprocessing options generated: {options}")
- return options
-
-def get_handwritten_specific_prompt(current_prompt: Optional[str] = None) -> str:
- """
- Generate a specialized prompt for handwritten document OCR
-
- Args:
- current_prompt: Current prompt (if available)
-
- Returns:
- str: Enhanced prompt for handwritten documents
- """
- # Base prompt for all handwritten documents
- base_prompt = ("This is a handwritten document that requires careful transcription. "
- "Please transcribe all visible handwritten text, preserving the original "
- "line breaks, paragraph structure, and any special formatting or indentation. "
- "Pay special attention to:\n"
- "1. Words that may be difficult to read due to handwriting style\n"
- "2. Any crossed-out text (indicate with [crossed out: possible text])\n"
- "3. Insertions or annotations between lines or in margins\n"
- "4. Maintain the spatial layout of the text as much as possible\n"
- "5. If there are multiple columns or non-linear text, preserve the reading order\n\n"
- "If you cannot read a word with confidence, indicate with [?] or provide your best guess as [word?].")
-
- # If there's an existing prompt, combine them, otherwise just use the base
- if current_prompt:
- # Remove any redundant instructions about handwriting
- lower_prompt = current_prompt.lower()
- if "handwritten" in lower_prompt or "handwriting" in lower_prompt:
- # Extract any unique instructions from the current prompt
- # This logic is simplified and might need improvement
- current_sentences = [s.strip() for s in current_prompt.split('.') if s.strip()]
- handwriting_sentences = [s for s in current_sentences
- if "handwritten" not in s.lower()
- and "handwriting" not in s.lower()]
-
- # Add unique instructions to our base prompt
- if handwriting_sentences:
- combined_prompt = base_prompt + "\n\nAdditional instructions:\n"
- combined_prompt += ". ".join(handwriting_sentences) + "."
- return combined_prompt
- else:
- # If no handwriting instructions in the current prompt, just append it
- return f"{base_prompt}\n\nAdditional context from user:\n{current_prompt}"
-
- return base_prompt
-
-def clean_duplicated_text(text: str) -> str:
- """
- Clean up duplicated text often found in OCR output for handwritten documents
-
- Args:
- text: OCR text to clean
-
- Returns:
- str: Cleaned text with duplications removed
- """
- if not text:
- return text
-
- # Split into lines for line-based deduplication
- lines = text.split('\n')
-
- # Remove consecutive duplicate lines
- deduped_lines = []
- prev_line = None
-
- for line in lines:
- stripped = line.strip()
- # Skip empty lines
- if not stripped:
- if not deduped_lines or deduped_lines[-1].strip():
- deduped_lines.append(line) # Keep the first empty line
- continue
-
- # Skip if this line is a duplicate of the previous line
- if stripped == prev_line:
- continue
-
- deduped_lines.append(line)
- prev_line = stripped
-
- # Re-join the deduplicated lines
- deduped_text = '\n'.join(deduped_lines)
-
- # Remove repeated words
- word_pattern = r'\b(\w+)\s+\1\b'
- deduped_text = re.sub(word_pattern, r'\1', deduped_text)
-
- # Remove repeated phrases (3+ words)
- # This is a simplified approach and might need improvement
- words = deduped_text.split()
- cleaned_words = []
- i = 0
-
- while i < len(words):
- # Check for phrase repetition (phrases of 3 to 6 words)
- found_repeat = False
-
- for phrase_len in range(3, min(7, len(words) - i)):
- phrase = ' '.join(words[i:i+phrase_len])
- next_pos = i + phrase_len
-
- if next_pos + phrase_len <= len(words):
- next_phrase = ' '.join(words[next_pos:next_pos+phrase_len])
-
- if phrase.lower() == next_phrase.lower():
- # Found a repeated phrase, skip the second occurrence
- cleaned_words.extend(words[i:i+phrase_len])
- i = next_pos + phrase_len
- found_repeat = True
- break
-
- if not found_repeat:
- cleaned_words.append(words[i])
- i += 1
-
- # Rejoin the cleaned words
- final_text = ' '.join(cleaned_words)
-
- # Log the cleaning results
- original_len = len(text)
- cleaned_len = len(final_text)
- reduction = 100 * (original_len - cleaned_len) / max(1, original_len)
-
- logger.info(f"Text cleaning: removed {original_len - cleaned_len} chars ({reduction:.1f}% reduction)")
-
- return final_text
diff --git a/utils/image_utils.py b/utils/image_utils.py
deleted file mode 100644
index 9147961442195e4cd1b940560231c2ff7126947e..0000000000000000000000000000000000000000
--- a/utils/image_utils.py
+++ /dev/null
@@ -1,1266 +0,0 @@
-"""
-Utility functions for OCR image processing with Mistral AI.
-Contains helper functions for working with OCR responses and image handling.
-"""
-
-# Standard library imports
-import json
-import base64
-import io
-import zipfile
-import logging
-import re
-import time
-import math
-from datetime import datetime
-from pathlib import Path
-from typing import Dict, List, Optional, Union, Any, Tuple
-from functools import lru_cache
-
-# Configure logging
-logging.basicConfig(level=logging.INFO,
- format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
-logger = logging.getLogger(__name__)
-
-# Third-party imports
-import numpy as np
-
-# Mistral AI imports
-from mistralai import DocumentURLChunk, ImageURLChunk, TextChunk
-from mistralai.models import OCRImageObject
-
-# Check for image processing libraries
-try:
- from PIL import Image, ImageEnhance, ImageFilter, ImageOps
- PILLOW_AVAILABLE = True
-except ImportError:
- logger.warning("PIL not available - image preprocessing will be limited")
- PILLOW_AVAILABLE = False
-
-try:
- import cv2
- CV2_AVAILABLE = True
-except ImportError:
- logger.warning("OpenCV (cv2) not available - advanced image processing will be limited")
- CV2_AVAILABLE = False
-
-# Import configuration
-try:
- from config import IMAGE_PREPROCESSING
-except ImportError:
- # Fallback defaults if config not available
- IMAGE_PREPROCESSING = {
- "enhance_contrast": 1.5,
- "sharpen": True,
- "denoise": True,
- "max_size_mb": 8.0,
- "target_dpi": 300,
- "compression_quality": 92
- }
-
-def detect_skew(image: Union[Image.Image, np.ndarray]) -> float:
- """
- Quick skew detection that returns angle in degrees.
- Uses a computationally efficient approach by analyzing at 1% resolution.
-
- Args:
- image: PIL Image or numpy array
-
- Returns:
- Estimated skew angle in degrees (positive or negative)
- """
- # Convert PIL Image to numpy array if needed
- if isinstance(image, Image.Image):
- # Convert to grayscale for processing
- if image.mode != 'L':
- img_np = np.array(image.convert('L'))
- else:
- img_np = np.array(image)
- else:
- # If already numpy array, ensure it's grayscale
- if len(image.shape) == 3:
- if CV2_AVAILABLE:
- img_np = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
- else:
- # Fallback grayscale conversion
- img_np = np.mean(image, axis=2).astype(np.uint8)
- else:
- img_np = image
-
- # Downsample to 1% resolution for faster processing
- height, width = img_np.shape
- target_size = int(min(width, height) * 0.01)
-
- # Use a sane minimum size and ensure we have enough pixels to detect lines
- target_size = max(target_size, 100)
-
- if CV2_AVAILABLE:
- # OpenCV-based implementation (faster)
- # Resize the image to the target size
- scale_factor = target_size / max(width, height)
- small_img = cv2.resize(img_np, None, fx=scale_factor, fy=scale_factor, interpolation=cv2.INTER_AREA)
-
- # Apply binary thresholding to get cleaner edges
- _, binary = cv2.threshold(small_img, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
-
- # Use Hough Line Transform to detect lines
- lines = cv2.HoughLinesP(binary, 1, np.pi/180, threshold=target_size//10,
- minLineLength=target_size//5, maxLineGap=target_size//10)
-
- if lines is None or len(lines) < 3:
- # Not enough lines detected, assume no significant skew
- return 0.0
-
- # Calculate angles of lines
- angles = []
- for line in lines:
- x1, y1, x2, y2 = line[0]
- if x2 - x1 == 0: # Avoid division by zero
- continue
- angle = math.atan2(y2 - y1, x2 - x1) * 180.0 / np.pi
-
- # Normalize angle to -45 to 45 range
- angle = angle % 180
- if angle > 90:
- angle -= 180
- if angle > 45:
- angle -= 90
- if angle < -45:
- angle += 90
-
- angles.append(angle)
-
- if not angles:
- return 0.0
-
- # Use median to reduce impact of outliers
- angles.sort()
- median_angle = angles[len(angles) // 2]
-
- return median_angle
- else:
- # PIL-only fallback implementation
- # Resize using PIL
- small_img = Image.fromarray(img_np).resize(
- (int(width * target_size / max(width, height)),
- int(height * target_size / max(width, height))),
- Image.NEAREST
- )
-
- # Find edges
- edges = small_img.filter(ImageFilter.FIND_EDGES)
- edges_data = np.array(edges)
-
- # Simple edge orientation analysis (less precise than OpenCV)
- # Count horizontal vs vertical edges
- h_edges = np.sum(np.abs(np.diff(edges_data, axis=1)))
- v_edges = np.sum(np.abs(np.diff(edges_data, axis=0)))
-
- # If horizontal edges dominate, no significant skew
- if h_edges > v_edges * 1.2:
- return 0.0
-
- # Simple angle estimation based on edge distribution
- # This is a simplified approach that works for slight skews
- rows, cols = edges_data.shape
- xs, ys = [], []
-
- # Sample strong edge points
- for r in range(0, rows, 2):
- for c in range(0, cols, 2):
- if edges_data[r, c] > 128:
- xs.append(c)
- ys.append(r)
-
- if len(xs) < 10: # Not enough edge points
- return 0.0
-
- # Use simple linear regression to estimate the slope
- n = len(xs)
- mean_x = sum(xs) / n
- mean_y = sum(ys) / n
-
- # Calculate slope
- numerator = sum((xs[i] - mean_x) * (ys[i] - mean_y) for i in range(n))
- denominator = sum((xs[i] - mean_x) ** 2 for i in range(n))
-
- if abs(denominator) < 1e-6: # Avoid division by zero
- return 0.0
-
- slope = numerator / denominator
- angle = math.atan(slope) * 180.0 / math.pi
-
- # Normalize to -45 to 45 degrees
- if angle > 45:
- angle -= 90
- elif angle < -45:
- angle += 90
-
- return angle
-
-def replace_images_in_markdown(md: str, images: dict[str, str]) -> str:
- """
- Replace image placeholders in markdown with base64-encoded images.
- Uses regex-based matching to handle variations in image IDs and formats.
-
- Args:
- md: Markdown text containing image placeholders
- images: Dictionary mapping image IDs to base64 strings
-
- Returns:
- Markdown text with images replaced by base64 data
- """
- # Process each image ID in the dictionary
- for img_id, base64_str in images.items():
- # Extract the base ID without extension for more flexible matching
- base_id = img_id.split('.')[0]
-
- # Match markdown image pattern where URL contains the base ID
- # Using a single regex with groups to capture the full pattern
- pattern = re.compile(rf'!\[([^\]]*)\]\(([^\)]*{base_id}[^\)]*)\)')
-
- # Process all matches
- matches = list(pattern.finditer(md))
- for match in reversed(matches): # Process in reverse to avoid offset issues
- # Replace the entire match with a properly formatted base64 image
- md = md[:match.start()] + f"" + md[match.end():]
-
- return md
-
-def get_combined_markdown(ocr_response) -> str:
- """
- Combine OCR text and images into a single markdown document.
-
- Args:
- ocr_response: OCR response object from Mistral AI
-
- Returns:
- Combined markdown string with embedded images
- """
- markdowns = []
-
- # Process each page of the OCR response
- for page in ocr_response.pages:
- # Extract image data if available
- image_data = {}
- if hasattr(page, "images"):
- for img in page.images:
- if hasattr(img, "id") and hasattr(img, "image_base64"):
- image_data[img.id] = img.image_base64
-
- # Replace image placeholders with base64 data
- page_markdown = page.markdown if hasattr(page, "markdown") else ""
- processed_markdown = replace_images_in_markdown(page_markdown, image_data)
- markdowns.append(processed_markdown)
-
- # Join all pages' markdown with double newlines
- return "\n\n".join(markdowns)
-
-def encode_image_for_api(image_path: Union[str, Path]) -> str:
- """
- Encode an image as base64 data URL for API submission.
-
- Args:
- image_path: Path to the image file
-
- Returns:
- Base64 data URL for the image
- """
- # Convert to Path object if string
- image_file = Path(image_path) if isinstance(image_path, str) else image_path
-
- # Verify image exists
- if not image_file.is_file():
- raise FileNotFoundError(f"Image file not found: {image_file}")
-
- # Determine mime type based on file extension
- mime_type = 'image/jpeg' # Default mime type
- suffix = image_file.suffix.lower()
- if suffix == '.png':
- mime_type = 'image/png'
- elif suffix == '.gif':
- mime_type = 'image/gif'
- elif suffix in ['.jpg', '.jpeg']:
- mime_type = 'image/jpeg'
- elif suffix == '.pdf':
- mime_type = 'application/pdf'
-
- # Encode image as base64
- encoded = base64.b64encode(image_file.read_bytes()).decode()
- return f"data:{mime_type};base64,{encoded}"
-
-def encode_bytes_for_api(file_bytes: bytes, mime_type: str) -> str:
- """
- Encode binary data as base64 data URL for API submission.
-
- Args:
- file_bytes: Binary file data
- mime_type: MIME type of the file (e.g., 'image/jpeg', 'application/pdf')
-
- Returns:
- Base64 data URL for the data
- """
- # Encode data as base64
- encoded = base64.b64encode(file_bytes).decode()
- return f"data:{mime_type};base64,{encoded}"
-
-def calculate_image_entropy(pil_img: Image.Image) -> float:
- """
- Calculate the entropy of a PIL image.
- Entropy is a measure of randomness; low entropy indicates a blank or simple image,
- high entropy indicates more complex content (e.g., text or detailed images).
-
- Args:
- pil_img: PIL Image object
-
- Returns:
- float: Entropy value
- """
- # Convert to grayscale for entropy calculation
- gray_img = pil_img.convert("L")
- arr = np.array(gray_img)
- # Compute histogram
- hist, _ = np.histogram(arr, bins=256, range=(0, 255), density=True)
- # Remove zero entries to avoid log(0)
- hist = hist[hist > 0]
- # Calculate entropy
- entropy = -np.sum(hist * np.log2(hist))
- return float(entropy)
-
-def estimate_text_density(image_np):
- """
- Estimate text density patterns in an image.
- Returns metrics on text distribution and special cases.
-
- Args:
- image_np: Numpy array of the image
-
- Returns:
- dict: Text density metrics
- """
- # Convert to grayscale
- if len(image_np.shape) > 2 and image_np.shape[2] == 3:
- gray = cv2.cvtColor(image_np, cv2.COLOR_RGB2GRAY)
- else:
- gray = image_np
-
- # Binarize image
- _, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
-
- # Analyze vertical text density profile (important for headers/footers)
- height, width = gray.shape
- vertical_profile = np.sum(binary, axis=1) / width
-
- # Analyze horizontal text density profile
- horizontal_profile = np.sum(binary, axis=0) / height
-
- # Calculate statistics
- v_mean = np.mean(vertical_profile)
- v_std = np.std(vertical_profile)
- v_max = np.max(vertical_profile)
-
- # Detect uppercase text regions (common in headers of Baldwin document)
- # Uppercase text tends to have more consistent height and uniform vertical density
- section_height = height // 10 # Divide into 10 vertical sections
- uppercase_sections = 0
-
- for i in range(0, height, section_height):
- section = binary[i:min(i+section_height, height), :]
- section_profile = np.sum(section, axis=1) / width
-
- # Uppercase characteristics: high density with low variation
- if np.mean(section_profile) > v_mean * 1.5 and np.std(section_profile) < v_std * 0.7:
- uppercase_sections += 1
-
- # Determine overall pattern
- if v_std / v_mean > 0.8:
- pattern = 'varied' # High variance indicates sections with different text densities
- else:
- pattern = 'uniform' # Low variance indicates uniform text distribution
-
- return {
- 'mean_density': float(v_mean),
- 'density_variation': float(v_std),
- 'pattern': pattern,
- 'uppercase_sections': uppercase_sections,
- 'max_density': float(v_max)
- }
-
-def serialize_ocr_object(obj):
- """
- Serialize OCR response objects to JSON serializable format.
- Handles OCRImageObject specifically to prevent serialization errors.
-
- Args:
- obj: The object to serialize
-
- Returns:
- JSON serializable representation of the object
- """
- # Fast path: Handle primitive types directly
- if obj is None or isinstance(obj, (str, int, float, bool)):
- return obj
-
- # Handle collections
- if isinstance(obj, list):
- return [serialize_ocr_object(item) for item in obj]
- elif isinstance(obj, dict):
- return {k: serialize_ocr_object(v) for k, v in obj.items()}
- elif isinstance(obj, OCRImageObject):
- # Special handling for OCRImageObject
- return {
- 'id': obj.id if hasattr(obj, 'id') else None,
- 'image_base64': obj.image_base64 if hasattr(obj, 'image_base64') else None
- }
- elif hasattr(obj, '__dict__'):
- # For objects with __dict__ attribute
- return {k: serialize_ocr_object(v) for k, v in obj.__dict__.items()
- if not k.startswith('_')} # Skip private attributes
- else:
- # Try to convert to string as last resort
- try:
- return str(obj)
- except:
- return None
-
-# Clean OCR result with focus on Mistral compatibility
-def clean_ocr_result(result, use_segmentation=False, vision_enabled=True, preprocessing_options=None):
- """
- Clean text content in OCR results, preserving original structure from Mistral API.
- Only removes markdown/HTML conflicts without duplicating content across fields.
-
- Args:
- result: OCR result object or dictionary
- use_segmentation: Whether image segmentation was used
- vision_enabled: Whether vision model was used
- preprocessing_options: Dictionary of preprocessing options
-
- Returns:
- Cleaned result object
- """
- if not result:
- return result
-
- # Import text utilities for cleaning
- try:
- from utils.text_utils import clean_raw_text
- text_cleaner_available = True
- except ImportError:
- text_cleaner_available = False
-
- def clean_text(text):
- """Clean text content, removing markdown image references and base64 data"""
- if not text or not isinstance(text, str):
- return ""
-
- if text_cleaner_available:
- text = clean_raw_text(text)
- else:
- # Remove image references like 
- text = re.sub(r'!\[.*?\]\(data:image/[^)]+\)', '', text)
-
- # Remove basic markdown image references like 
- text = re.sub(r'!\[[^\]]*\]\([^)]+\)', '', text)
-
- # Remove base64 encoded image data
- text = re.sub(r'data:image/[^;]+;base64,[a-zA-Z0-9+/=]+', '', text)
-
- # Clean up any JSON-like image object references
- text = re.sub(r'{"image(_data)?":("[^"]*"|null|true|false|\{[^}]*\}|\[[^\]]*\])}', '', text)
-
- # Clean up excessive whitespace and line breaks created by removals
- text = re.sub(r'\n{3,}', '\n\n', text)
- text = re.sub(r'\s{3,}', ' ', text)
-
- return text.strip()
-
- # Process dictionary
- if isinstance(result, dict):
- # For PDF documents, preserve original structure from Mistral API
- is_pdf = result.get('file_type', '') == 'pdf' or (
- result.get('file_name', '').lower().endswith('.pdf')
- )
-
- # Ensure ocr_contents exists
- if 'ocr_contents' not in result:
- result['ocr_contents'] = {}
-
- # Clean raw_text if it exists but don't duplicate it
- if 'raw_text' in result:
- result['raw_text'] = clean_text(result['raw_text'])
-
- # Handle ocr_contents fields - clean them but don't duplicate
- if 'ocr_contents' in result:
- for key, value in list(result['ocr_contents'].items()):
- # Skip binary fields and image data
- if key in ['image_base64', 'images', 'binary_data'] and value:
- continue
-
- # Clean string values to remove markdown/HTML conflicts
- if isinstance(value, str):
- result['ocr_contents'][key] = clean_text(value)
-
- # Handle segmentation data
- if use_segmentation and preprocessing_options and 'segmentation_data' in preprocessing_options:
- # Store segmentation metadata
- result['segmentation_applied'] = True
-
- # Extract combined text if available
- if 'combined_text' in preprocessing_options['segmentation_data']:
- segmentation_text = clean_text(preprocessing_options['segmentation_data']['combined_text'])
- # Add as dedicated field
- result['ocr_contents']['segmentation_text'] = segmentation_text
-
- # IMPORTANT: For documents with overlapping regions like baldwin-15th-north,
- # the intelligently merged segmentation text is more accurate than the raw OCR
- # Always use segmentation text as the primary source when available
- # This ensures clean, non-duplicated content from overlapping regions
- result['ocr_contents']['raw_text'] = segmentation_text
-
- # Also update the 'text' field which is used in some contexts
- if 'text' in result['ocr_contents']:
- result['ocr_contents']['text'] = segmentation_text
-
- # Clean pages_data if available (Mistral OCR format)
- if 'pages_data' in result:
- for page in result['pages_data']:
- if isinstance(page, dict):
- # Clean text field
- if 'text' in page:
- page['text'] = clean_text(page['text'])
-
- # Clean markdown field
- if 'markdown' in page:
- page['markdown'] = clean_text(page['markdown'])
-
- # Handle list content recursively
- elif isinstance(result, list):
- return [clean_ocr_result(item, use_segmentation, vision_enabled, preprocessing_options)
- for item in result]
-
- return result
-
-def create_results_zip(results, output_dir=None, zip_name=None):
- """
- Create a zip file containing OCR results.
-
- Args:
- results: Dictionary or list of OCR results
- output_dir: Optional output directory
- zip_name: Optional zip file name
-
- Returns:
- Path to the created zip file
- """
- # Create temporary output directory if not provided
- if output_dir is None:
- output_dir = Path.cwd() / "output"
- output_dir.mkdir(exist_ok=True)
- else:
- output_dir = Path(output_dir)
- output_dir.mkdir(exist_ok=True)
-
- # Generate zip name if not provided
- if zip_name is None:
- timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
-
- if isinstance(results, list):
- # For a list of results, create a descriptive name
- file_count = len(results)
- zip_name = f"ocr_results_{file_count}_{timestamp}.zip"
- else:
- # For single result, create descriptive filename
- base_name = results.get('file_name', 'document').split('.')[0]
- zip_name = f"{base_name}_{timestamp}.zip"
-
- try:
- # Get zip data in memory first
- zip_data = create_results_zip_in_memory(results)
-
- # Save to file
- zip_path = output_dir / zip_name
- with open(zip_path, 'wb') as f:
- f.write(zip_data)
-
- return zip_path
- except Exception as e:
- # Create an empty zip file as fallback
- logger.error(f"Error creating zip file: {str(e)}")
- zip_path = output_dir / zip_name
- with zipfile.ZipFile(zip_path, 'w') as zipf:
- zipf.writestr("info.txt", "Could not create complete archive")
-
- return zip_path
-
-def create_results_zip_in_memory(results):
- """
- Create a zip file containing OCR results in memory.
- Packages markdown with embedded image tags, raw text, and JSON file
- in a contextually relevant structure.
-
- Args:
- results: Dictionary or list of OCR results
-
- Returns:
- Binary zip file data
- """
- # Create a BytesIO object
- zip_buffer = io.BytesIO()
-
- # Create a ZipFile instance
- with zipfile.ZipFile(zip_buffer, 'w', compression=zipfile.ZIP_DEFLATED) as zipf:
- # Check if results is a list or a dictionary
- is_list = isinstance(results, list)
-
- if is_list:
- # Handle multiple results by creating subdirectories
- for idx, result in enumerate(results):
- if result and isinstance(result, dict):
- # Create a folder name based on the file name or index
- folder_name = result.get('file_name', f'document_{idx+1}')
- folder_name = Path(folder_name).stem # Remove file extension
-
- # Add files to this folder
- add_result_files_to_zip(zipf, result, f"{folder_name}/")
- else:
- # Single result - add files directly to root of zip
- add_result_files_to_zip(zipf, results)
-
- # Seek to the beginning of the BytesIO object
- zip_buffer.seek(0)
-
- # Return the zip file bytes
- return zip_buffer.getvalue()
-
-def truncate_base64_in_result(result, prefix_length=32, suffix_length=32):
- """
- Create a copy of the result dictionary with base64 image data truncated.
- This keeps the structure intact while making the JSON more readable.
-
- Args:
- result: OCR result dictionary
- prefix_length: Number of characters to keep at the beginning
- suffix_length: Number of characters to keep at the end
-
- Returns:
- Dictionary with truncated base64 data
- """
- if not result or not isinstance(result, dict):
- return {}
-
- # Create a deep copy to avoid modifying the original
- import copy
- truncated_result = copy.deepcopy(result)
-
- # Helper function to truncate base64 strings
- def truncate_base64(data):
- if not isinstance(data, str) or len(data) <= prefix_length + suffix_length + 10:
- return data
-
- # Extract prefix and suffix based on whether this is a data URI or raw base64
- if data.startswith('data:'):
- # Handle data URIs like 'data:image/jpeg;base64,/9j/4AAQ...'
- parts = data.split(',', 1)
- if len(parts) != 2:
- return data # Unexpected format, return as is
-
- header = parts[0] + ','
- base64_content = parts[1]
-
- if len(base64_content) <= prefix_length + suffix_length + 10:
- return data # Not long enough to truncate
-
- truncated = (f"{header}{base64_content[:prefix_length]}..."
- f"[truncated {len(base64_content) - prefix_length - suffix_length} chars]..."
- f"{base64_content[-suffix_length:]}")
- else:
- # Handle raw base64 strings
- truncated = (f"{data[:prefix_length]}..."
- f"[truncated {len(data) - prefix_length - suffix_length} chars]..."
- f"{data[-suffix_length:]}")
-
- return truncated
-
- # Helper function to recursively truncate base64 in nested structures
- def truncate_base64_recursive(obj):
- if isinstance(obj, dict):
- # Check for keys that typically contain base64 data
- for key in list(obj.keys()):
- if key in ['image_base64', 'base64'] and isinstance(obj[key], str):
- obj[key] = truncate_base64(obj[key])
- elif isinstance(obj[key], (dict, list)):
- truncate_base64_recursive(obj[key])
- elif isinstance(obj, list):
- for item in obj:
- if isinstance(item, (dict, list)):
- truncate_base64_recursive(item)
-
- # Truncate base64 data throughout the result
- truncate_base64_recursive(truncated_result)
-
- # Specifically handle the pages_data structure
- if 'pages_data' in truncated_result:
- for page in truncated_result['pages_data']:
- if isinstance(page, dict) and 'images' in page:
- for img in page['images']:
- if isinstance(img, dict) and 'image_base64' in img and isinstance(img['image_base64'], str):
- img['image_base64'] = truncate_base64(img['image_base64'])
-
- # Handle raw_response_data if present
- if 'raw_response_data' in truncated_result and isinstance(truncated_result['raw_response_data'], dict):
- if 'pages' in truncated_result['raw_response_data']:
- for page in truncated_result['raw_response_data']['pages']:
- if isinstance(page, dict) and 'images' in page:
- for img in page['images']:
- if isinstance(img, dict) and 'base64' in img and isinstance(img['base64'], str):
- img['base64'] = truncate_base64(img['base64'])
-
- return truncated_result
-
-def clean_base64_from_result(result):
- """
- Create a clean copy of the result dictionary with base64 image data removed.
- This ensures JSON files don't contain large base64 strings.
-
- Args:
- result: OCR result dictionary
-
- Returns:
- Cleaned dictionary without base64 data
- """
- if not result or not isinstance(result, dict):
- return {}
-
- # Create a deep copy to avoid modifying the original
- import copy
- clean_result = copy.deepcopy(result)
-
- # Helper function to recursively clean base64 from nested structures
- def clean_base64_recursive(obj):
- if isinstance(obj, dict):
- # Check for keys that typically contain base64 data
- for key in list(obj.keys()):
- if key in ['image_base64', 'base64']:
- obj[key] = "[BASE64_DATA_REMOVED]"
- elif isinstance(obj[key], (dict, list)):
- clean_base64_recursive(obj[key])
- elif isinstance(obj, list):
- for item in obj:
- if isinstance(item, (dict, list)):
- clean_base64_recursive(item)
-
- # Clean the entire result
- clean_base64_recursive(clean_result)
-
- # Specifically handle the pages_data structure
- if 'pages_data' in clean_result:
- for page in clean_result['pages_data']:
- if isinstance(page, dict) and 'images' in page:
- for img in page['images']:
- if isinstance(img, dict) and 'image_base64' in img:
- img['image_base64'] = "[BASE64_DATA_REMOVED]"
-
- # Handle raw_response_data if present
- if 'raw_response_data' in clean_result and isinstance(clean_result['raw_response_data'], dict):
- if 'pages' in clean_result['raw_response_data']:
- for page in clean_result['raw_response_data']['pages']:
- if isinstance(page, dict) and 'images' in page:
- for img in page['images']:
- if isinstance(img, dict) and 'base64' in img:
- img['base64'] = "[BASE64_DATA_REMOVED]"
-
- return clean_result
-
-def create_markdown_with_file_references(result, image_path_prefix="images/"):
- """
- Create a markdown document with file references to images instead of base64 embedding.
- Ideal for use in zip archives where images are stored as separate files.
-
- Args:
- result: OCR result dictionary
- image_path_prefix: Path prefix for image references (e.g., "images/")
-
- Returns:
- Markdown content as string with file references
- """
- # Similar to create_markdown_with_images but uses file references
- # Import content utils to use classification functions
- try:
- from utils.content_utils import classify_document_content, extract_document_text, extract_image_description
- content_utils_available = True
- except ImportError:
- content_utils_available = False
-
- # Get content classification
- has_text = True
- has_images = False
-
- if content_utils_available:
- classification = classify_document_content(result)
- has_text = classification['has_content']
- has_images = result.get('has_images', False)
- else:
- # Minimal fallback detection
- if 'has_images' in result:
- has_images = result['has_images']
-
- # Check for image data more thoroughly
- if 'pages_data' in result and isinstance(result['pages_data'], list):
- for page in result['pages_data']:
- if isinstance(page, dict) and 'images' in page and page['images']:
- has_images = True
- break
-
- # Start building the markdown document
- md = []
-
- # Add document title/header
- md.append(f"# {result.get('file_name', 'Document')}\n")
-
- # Add metadata section
- md.append("## Document Metadata\n")
-
- # Add timestamp
- if 'timestamp' in result:
- md.append(f"**Processed:** {result['timestamp']}\n")
-
- # Add languages if available
- if 'languages' in result and result['languages']:
- languages = [lang for lang in result['languages'] if lang]
- if languages:
- md.append(f"**Languages:** {', '.join(languages)}\n")
-
- # Add document type and topics
- if 'detected_document_type' in result:
- md.append(f"**Document Type:** {result['detected_document_type']}\n")
-
- if 'topics' in result and result['topics']:
- md.append(f"**Topics:** {', '.join(result['topics'])}\n")
-
- md.append("\n---\n")
-
- # Document title - extract from result if available
- if 'ocr_contents' in result and 'title' in result['ocr_contents'] and result['ocr_contents']['title']:
- title_content = result['ocr_contents']['title']
- md.append(f"## {title_content}\n")
-
- # Add images if present
- if has_images and 'pages_data' in result:
- md.append("## Images\n")
-
- # Extract and display all images with file references
- for page_idx, page in enumerate(result['pages_data']):
- if 'images' in page and isinstance(page['images'], list):
- for img_idx, img in enumerate(page['images']):
- if 'image_base64' in img:
- # Create image reference to file in the zip
- image_filename = f"image_{page_idx+1}_{img_idx+1}.jpg"
- image_path = f"{image_path_prefix}{image_filename}"
- image_caption = f"Image {page_idx+1}-{img_idx+1}"
- md.append(f"\n")
-
- # Add image description if available through utils
- if content_utils_available:
- description = extract_image_description(result)
- if description:
- md.append(f"*{description}*\n")
-
- md.append("\n---\n")
-
- # Add document text section
- md.append("## Text Content\n")
-
- # Extract text content systematically
- text_content = ""
- structured_sections = {}
-
- # Helper function to extract clean text from dictionary objects
- def extract_clean_text(content):
- if isinstance(content, str):
- # Check if content is a stringified JSON
- if content.strip().startswith("{") and content.strip().endswith("}"):
- try:
- # Try to parse as JSON
- content_dict = json.loads(content.replace("'", '"'))
- if 'text' in content_dict:
- return content_dict['text']
- return content
- except:
- return content
- return content
- elif isinstance(content, dict):
- # If it's a dictionary with a 'text' key, return just that value
- if 'text' in content and isinstance(content['text'], str):
- return content['text']
- return content
- return content
-
- if content_utils_available:
- # Use the systematic utility function for main text
- text_content = extract_document_text(result)
- text_content = extract_clean_text(text_content)
-
- # Collect all available structured sections
- if 'ocr_contents' in result:
- for field, content in result['ocr_contents'].items():
- # Skip certain fields that are handled separately
- if field in ["raw_text", "error", "partial_text", "main_text"]:
- continue
-
- if content:
- # Extract clean text from content if possible
- clean_content = extract_clean_text(content)
- # Add this as a structured section
- structured_sections[field] = clean_content
- else:
- # Fallback extraction logic
- if 'ocr_contents' in result:
- # First find main text
- for field in ["main_text", "content", "text", "transcript", "raw_text"]:
- if field in result['ocr_contents'] and result['ocr_contents'][field]:
- content = result['ocr_contents'][field]
- if isinstance(content, str) and content.strip():
- text_content = content
- break
- elif isinstance(content, dict):
- # Try to convert complex objects to string
- try:
- text_content = json.dumps(content, indent=2)
- break
- except:
- pass
-
- # Then collect all structured sections
- for field, content in result['ocr_contents'].items():
- # Skip certain fields that are handled separately
- if field in ["raw_text", "error", "partial_text", "main_text", "content", "text", "transcript"]:
- continue
-
- if content:
- # Add this as a structured section
- structured_sections[field] = content
-
- # Add the main text content - display raw text without a field label
- if text_content:
- # Check if this is from raw_text (based on content match)
- is_raw_text = False
- if 'ocr_contents' in result and 'raw_text' in result['ocr_contents']:
- if result['ocr_contents']['raw_text'] == text_content:
- is_raw_text = True
-
- # Display content without adding a "raw_text:" label
- md.append(text_content + "\n\n")
-
- # Add structured sections if available
- if structured_sections:
- for section_name, section_content in structured_sections.items():
- # Use proper markdown header for sections - consistently capitalize all section names
- display_name = section_name.replace("_", " ").capitalize()
- # Handle different content types
- if isinstance(section_content, str):
- md.append(section_content + "\n\n")
- elif isinstance(section_content, dict):
- # Dictionary content - format as key-value pairs
- for key, value in section_content.items():
- # Treat all values as plain text to maintain content purity
- # This prevents JSON-like structures from being formatted as code blocks
- md.append(f"**{key}:** {value}\n\n")
- elif isinstance(section_content, list):
- # List content - create a markdown list
- for item in section_content:
- # Treat all items as plain text
- md.append(f"- {item}\n")
- md.append("\n")
-
- # Join all markdown parts into a single string
- return "\n".join(md)
-
-def add_result_files_to_zip(zipf, result, prefix=""):
- """
- Add files for a single result to a zip file.
-
- Args:
- zipf: ZipFile instance to add files to
- result: OCR result dictionary
- prefix: Optional prefix for file paths in the zip
- """
- if not result or not isinstance(result, dict):
- return
-
- # Create a timestamp for filename if not in result
- timestamp = result.get('timestamp', datetime.now().strftime("%Y-%m-%d_%H-%M-%S"))
-
- # Get base name for files
- file_name = result.get('file_name', 'document')
- base_name = Path(file_name).stem
-
- try:
- # 1. Add JSON file - with base64 data cleaned out
- clean_result = clean_base64_from_result(result)
- json_str = json.dumps(clean_result, indent=2)
- zipf.writestr(f"{prefix}{base_name}.json", json_str)
-
- # 2. Add markdown file that exactly matches Tab 1 display
- # Use the create_markdown_with_images function to ensure it matches the UI exactly
- try:
- markdown_content = create_markdown_with_images(result)
- zipf.writestr(f"{prefix}{base_name}.md", markdown_content)
- except Exception as e:
- logger.error(f"Error creating markdown: {str(e)}")
- # Fallback to simpler markdown if error occurs
- zipf.writestr(f"{prefix}{base_name}.md", f"# {file_name}\n\nError generating complete markdown output.")
-
- # Extract and save images first to ensure they exist before creating markdown
- img_paths = {}
- has_images = result.get('has_images', False)
-
- # 3. Add individual images if available
- if has_images and 'pages_data' in result:
- img_folder = f"{prefix}images/"
- for page_idx, page in enumerate(result['pages_data']):
- if 'images' in page and isinstance(page['images'], list):
- for img_idx, img in enumerate(page['images']):
- if 'image_base64' in img and img['image_base64']:
- # Extract the base64 data
- try:
- # Get the base64 data
- img_data = img['image_base64']
-
- # Handle the base64 data carefully
- if isinstance(img_data, str):
- # If it has a data URI prefix, remove it
- if ',' in img_data and ';base64,' in img_data:
- # Keep the complete data after the comma
- img_data = img_data.split(',', 1)[1]
-
- # Make sure we have the complete data (not truncated)
- try:
- # Decode the base64 data with padding correction
- # Add padding if needed to prevent truncation errors
- missing_padding = len(img_data) % 4
- if missing_padding:
- img_data += '=' * (4 - missing_padding)
- img_bytes = base64.b64decode(img_data)
- except Exception as e:
- logger.error(f"Base64 decoding error: {str(e)} for image {page_idx}-{img_idx}")
- # Skip this image if we can't decode it
- continue
- else:
- # If it's not a string (e.g., already bytes), use it directly
- img_bytes = img_data
-
- # Create image filename
- image_filename = f"image_{page_idx+1}_{img_idx+1}.jpg"
- img_paths[(page_idx, img_idx)] = image_filename
-
- # Write the image to the zip file
- zipf.writestr(f"{img_folder}{image_filename}", img_bytes)
- except Exception as e:
- logger.warning(f"Could not add image to zip: {str(e)}")
-
- # 4. Add markdown with file references to images for offline viewing
- try:
- if has_images:
- # Create markdown with file references
- file_ref_markdown = create_markdown_with_file_references(result, "images/")
- zipf.writestr(f"{prefix}{base_name}_with_files.md", file_ref_markdown)
- except Exception as e:
- logger.warning(f"Error creating markdown with file references: {str(e)}")
-
- # 5. Add README.txt with explanation of file contents
- readme_content = f"""
-OCR RESULTS FOR: {file_name}
-Processed: {timestamp}
-
-This archive contains the following files:
-
-- {base_name}.json: Complete JSON data with all extracted information
-- {base_name}.md: Markdown document with embedded base64 images (exactly as shown in the app)
-- {base_name}_with_files.md: Alternative markdown with file references instead of base64 (for offline viewing)
-- images/ folder: Contains extracted images from the document (if present)
-
-Generated by Historical OCR using Mistral AI
- """
- zipf.writestr(f"{prefix}README.txt", readme_content.strip())
-
- except Exception as e:
- logger.error(f"Error adding files to zip: {str(e)}")
-
-def create_markdown_with_images(result):
- """
- Create a clean Markdown document from OCR results that properly preserves
- image references and text structure, following the principle of content purity.
-
- Args:
- result: OCR result dictionary
-
- Returns:
- Markdown content as string
- """
- # Similar to create_markdown_with_file_references but embeds base64 images
- # Import content utils to use classification functions
- try:
- from utils.content_utils import classify_document_content, extract_document_text, extract_image_description
- content_utils_available = True
- except ImportError:
- content_utils_available = False
-
- # Get content classification
- has_text = True
- has_images = False
-
- if content_utils_available:
- classification = classify_document_content(result)
- has_text = classification['has_content']
- has_images = result.get('has_images', False)
- else:
- # Minimal fallback detection
- if 'has_images' in result:
- has_images = result['has_images']
-
- # Check for image data more thoroughly
- if 'pages_data' in result and isinstance(result['pages_data'], list):
- for page in result['pages_data']:
- if isinstance(page, dict) and 'images' in page and page['images']:
- has_images = True
- break
-
- # Start building the markdown document
- md = []
-
- # Add document title/header
- md.append(f"# {result.get('file_name', 'Document')}\n")
-
- # Add metadata section
- md.append("## Document Metadata\n")
-
- # Add timestamp
- if 'timestamp' in result:
- md.append(f"**Processed:** {result['timestamp']}\n")
-
- # Add languages if available
- if 'languages' in result and result['languages']:
- languages = [lang for lang in result['languages'] if lang]
- if languages:
- md.append(f"**Languages:** {', '.join(languages)}\n")
-
- # Add document type and topics
- if 'detected_document_type' in result:
- md.append(f"**Document Type:** {result['detected_document_type']}\n")
-
- if 'topics' in result and result['topics']:
- md.append(f"**Topics:** {', '.join(result['topics'])}\n")
-
- md.append("\n---\n")
-
- # Document title - extract from result if available
- if 'ocr_contents' in result and 'title' in result['ocr_contents'] and result['ocr_contents']['title']:
- title_content = result['ocr_contents']['title']
- md.append(f"## {title_content}\n")
-
- # Add images if present - with base64 embedding
- if has_images and 'pages_data' in result:
- md.append("## Images\n")
-
- # Extract and display all images with embedded base64
- for page_idx, page in enumerate(result['pages_data']):
- if 'images' in page and isinstance(page['images'], list):
- for img_idx, img in enumerate(page['images']):
- if 'image_base64' in img:
- # Use the base64 data directly
- image_caption = f"Image {page_idx+1}-{img_idx+1}"
- img_data = img['image_base64']
-
- # Make sure it has proper data URI format
- if isinstance(img_data, str) and not img_data.startswith('data:'):
- img_data = f"data:image/jpeg;base64,{img_data}"
-
- md.append(f"\n")
-
- # Add image description if available through utils
- if content_utils_available:
- description = extract_image_description(result)
- if description:
- md.append(f"*{description}*\n")
-
- md.append("\n---\n")
-
- # Add document text section
- md.append("## Text Content\n")
-
- # Extract text content systematically
- text_content = ""
- structured_sections = {}
-
- if content_utils_available:
- # Use the systematic utility function for main text
- text_content = extract_document_text(result)
-
- # Collect all available structured sections
- if 'ocr_contents' in result:
- for field, content in result['ocr_contents'].items():
- # Skip certain fields that are handled separately
- if field in ["raw_text", "error", "partial_text", "main_text"]:
- continue
-
- if content:
- # Add this as a structured section
- structured_sections[field] = content
- else:
- # Fallback extraction logic
- if 'ocr_contents' in result:
- # First find main text
- for field in ["main_text", "content", "text", "transcript", "raw_text"]:
- if field in result['ocr_contents'] and result['ocr_contents'][field]:
- content = result['ocr_contents'][field]
- if isinstance(content, str) and content.strip():
- text_content = content
- break
- elif isinstance(content, dict):
- # Try to convert complex objects to string
- try:
- text_content = json.dumps(content, indent=2)
- break
- except:
- pass
-
- # Then collect all structured sections
- for field, content in result['ocr_contents'].items():
- # Skip certain fields that are handled separately
- if field in ["raw_text", "error", "partial_text", "main_text", "content", "text", "transcript"]:
- continue
-
- if content:
- # Add this as a structured section
- structured_sections[field] = content
-
- # Add the main text content
- if text_content:
- md.append(text_content + "\n\n")
-
- # Add structured sections if available
- if structured_sections:
- for section_name, section_content in structured_sections.items():
- # Use proper markdown header for sections - consistently capitalize all section names
- display_name = section_name.replace("_", " ").capitalize()
- md.append(f"### {display_name}\n")
- # Add a separator for clarity
- md.append("\n---\n\n")
-
- # Handle different content types
- if isinstance(section_content, str):
- md.append(section_content + "\n\n")
- elif isinstance(section_content, dict):
- # Dictionary content - format as key-value pairs
- for key, value in section_content.items():
- # Treat all values as plain text to maintain content purity
- md.append(f"**{key}:** {value}\n\n")
- elif isinstance(section_content, list):
- # List content - create a markdown list
- for item in section_content:
- # Keep list items as plain text
- md.append(f"- {item}\n")
- md.append("\n")
-
- # Join all markdown parts into a single string
- return "\n".join(md)
diff --git a/utils/pdf_ocr.py b/utils/pdf_ocr.py
deleted file mode 100644
index 0a9107f821b17e6d6d880575fb98f94a05d7d181..0000000000000000000000000000000000000000
--- a/utils/pdf_ocr.py
+++ /dev/null
@@ -1,457 +0,0 @@
-#!/usr/bin/env python3
-"""
-PDFOCR - Module for processing PDF files with OCR and extracting structured data.
-Provides robust PDF to image conversion before OCR processing.
-"""
-
-import json
-import os
-import tempfile
-import logging
-from pathlib import Path
-from typing import Optional, Dict, List, Union, Tuple, Any
-
-# Configure logging
-logging.basicConfig(level=logging.INFO,
- format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
-logger = logging.getLogger("pdf_ocr")
-
-# Import StructuredOCR for OCR processing
-from structured_ocr import StructuredOCR
-
-class PDFConversionResult:
- """Class to hold results of PDF to image conversion."""
-
- def __init__(self,
- success: bool,
- images: List[Path] = None,
- error: str = None,
- page_count: int = 0,
- temp_files: List[str] = None):
- """Initialize the conversion result.
-
- Args:
- success: Whether the conversion was successful
- images: List of paths to the converted images
- error: Error message if conversion failed
- page_count: Total number of pages in the PDF
- temp_files: List of temporary files that should be cleaned up
- """
- self.success = success
- self.images = images or []
- self.error = error
- self.page_count = page_count
- self.temp_files = temp_files or []
-
- def __bool__(self):
- """Enable boolean evaluation of the result."""
- return self.success
-
- def cleanup(self):
- """Clean up any temporary files created during conversion."""
- for temp_file in self.temp_files:
- try:
- if os.path.exists(temp_file):
- os.unlink(temp_file)
- logger.debug(f"Removed temporary file: {temp_file}")
- except Exception as e:
- logger.warning(f"Failed to remove temporary file {temp_file}: {e}")
- self.temp_files = []
-
-
-class PDFOCR:
- """Class for processing PDF files with OCR and extracting structured data."""
-
- def __init__(self, api_key=None):
- """Initialize the PDF OCR processor."""
- self.processor = StructuredOCR(api_key=api_key)
- self.temp_files = []
-
- def __del__(self):
- """Clean up resources when object is destroyed."""
- self.cleanup()
-
- def cleanup(self):
- """Clean up any temporary files."""
- for temp_file in self.temp_files:
- try:
- if os.path.exists(temp_file):
- os.unlink(temp_file)
- logger.debug(f"Removed temporary file: {temp_file}")
- except Exception as e:
- logger.warning(f"Failed to remove temporary file {temp_file}: {e}")
- self.temp_files = []
-
- def convert_pdf_to_images(self,
- pdf_path: Union[str, Path],
- dpi: int = 200,
- max_pages: Optional[int] = None,
- page_numbers: Optional[List[int]] = None) -> PDFConversionResult:
- """
- Convert a PDF file to images.
-
- Args:
- pdf_path: Path to the PDF file
- dpi: DPI for the output images
- max_pages: Maximum number of pages to convert (None for all)
- page_numbers: Specific page numbers to convert (1-based indexing)
-
- Returns:
- PDFConversionResult object with conversion results
- """
- pdf_path = Path(pdf_path)
- if not pdf_path.exists():
- return PDFConversionResult(
- success=False,
- error=f"PDF file not found: {pdf_path}"
- )
-
- # Check file size
- file_size_mb = pdf_path.stat().st_size / (1024 * 1024)
- logger.info(f"PDF size: {file_size_mb:.2f} MB")
-
- try:
- # Import pdf2image for conversion
- import pdf2image
-
- # Initialize list for temporary files
- temp_files = []
-
- # Optimize conversion parameters based on file size
- thread_count = min(4, os.cpu_count() or 2)
-
- # First, determine total pages in the document
- logger.info("Determining PDF page count...")
- try:
- # Use a lightweight approach with multi-threading for faster processing
- pdf_info = pdf2image.convert_from_path(
- pdf_path,
- dpi=72, # Low DPI just for info
- first_page=1,
- last_page=1,
- size=(100, 100), # Tiny image to save memory
- fmt="jpeg",
- thread_count=thread_count,
- output_file=None
- )
-
- # Get page count from poppler info if available
- if hasattr(pdf_info, 'n_pages'):
- total_pages = pdf_info.n_pages
- else:
- # Try a different approach to get page count
- try:
- from pypdf import PdfReader
- reader = PdfReader(pdf_path)
- total_pages = len(reader.pages)
- except:
- total_pages = 1
- logger.warning("Could not determine total page count, assuming 1 page")
- except Exception as e:
- logger.warning(f"Failed to determine page count: {e}")
- total_pages = 1
-
- logger.info(f"PDF has {total_pages} total pages")
-
- # Determine which pages to process
- pages_to_process = []
-
- # If specific pages are requested, use those
- if page_numbers and any(1 <= p <= total_pages for p in page_numbers):
- pages_to_process = [p for p in page_numbers if 1 <= p <= total_pages]
- logger.info(f"Converting {len(pages_to_process)} specified pages: {pages_to_process}")
- # If max_pages is set, limit to that number
- elif max_pages and max_pages < total_pages:
- pages_to_process = list(range(1, max_pages + 1))
- logger.info(f"Converting first {max_pages} pages of {total_pages} total")
- # Otherwise convert all pages if reasonable count
- else:
- pages_to_process = list(range(1, total_pages + 1))
- logger.info(f"Converting all {total_pages} pages")
-
- # Convert PDF to images
- converted_images = []
-
- # Process in batches for better memory management
- batch_size = min(5, len(pages_to_process)) # Process up to 5 pages at a time
- for i in range(0, len(pages_to_process), batch_size):
- batch_pages = pages_to_process[i:i+batch_size]
- logger.info(f"Converting batch of pages {batch_pages}")
-
- # Convert this batch of pages
- try:
- batch_images = pdf2image.convert_from_path(
- pdf_path,
- dpi=dpi,
- first_page=min(batch_pages),
- last_page=max(batch_pages),
- thread_count=thread_count,
- fmt="jpeg"
- )
-
- # Map converted images to requested page numbers
- for idx, page_num in enumerate(range(min(batch_pages), max(batch_pages) + 1)):
- if page_num in pages_to_process and idx < len(batch_images):
- # Save the image to a temporary file
- img_temp_path = tempfile.NamedTemporaryFile(suffix=f'_page{page_num}.jpg', delete=False).name
- batch_images[idx].save(img_temp_path, format='JPEG', quality=95)
-
- # Add to results and track the temp file
- converted_images.append((page_num, Path(img_temp_path)))
- temp_files.append(img_temp_path)
- except Exception as e:
- logger.error(f"Failed to convert batch {batch_pages}: {e}")
- # Continue with other batches
-
- # Sort by page number to ensure correct order
- converted_images.sort(key=lambda x: x[0])
-
- # Extract just the image paths in correct page order
- image_paths = [img_path for _, img_path in converted_images]
-
- if not image_paths:
- # No images were successfully converted
- return PDFConversionResult(
- success=False,
- error="Failed to convert PDF to images",
- page_count=total_pages,
- temp_files=temp_files
- )
-
- # Store temp files for later cleanup
- self.temp_files.extend(temp_files)
-
- # Return successful result
- return PDFConversionResult(
- success=True,
- images=image_paths,
- page_count=total_pages,
- temp_files=temp_files
- )
-
- except ImportError:
- return PDFConversionResult(
- success=False,
- error="pdf2image module not available. Please install with: pip install pdf2image"
- )
- except Exception as e:
- logger.error(f"PDF conversion error: {str(e)}")
- return PDFConversionResult(
- success=False,
- error=f"Failed to convert PDF to images: {str(e)}"
- )
-
- def process_pdf(self, pdf_path, use_vision=True, max_pages=None, custom_pages=None, custom_prompt=None):
- """
- Process a PDF file with OCR and extract structured data.
-
- Args:
- pdf_path: Path to the PDF file
- use_vision: Whether to use vision model for improved analysis
- max_pages: Maximum number of pages to process
- custom_pages: Specific page numbers to process (1-based indexing)
- custom_prompt: Custom instructions for processing
-
- Returns:
- Dictionary with structured OCR results
- """
- pdf_path = Path(pdf_path)
- if not pdf_path.exists():
- raise FileNotFoundError(f"PDF file not found: {pdf_path}")
-
- # Convert page numbers to list if provided
- page_numbers = None
- if custom_pages:
- if isinstance(custom_pages, (list, tuple)):
- page_numbers = custom_pages
- else:
- try:
- # Try to parse as comma-separated string
- page_numbers = [int(p.strip()) for p in str(custom_pages).split(',')]
- except:
- logger.warning(f"Invalid custom_pages format: {custom_pages}. Should be list or comma-separated string.")
-
- # First try our optimized PDF to image conversion
- conversion_result = self.convert_pdf_to_images(
- pdf_path=pdf_path,
- max_pages=max_pages,
- page_numbers=page_numbers
- )
-
- if conversion_result.success and conversion_result.images:
- logger.info(f"Successfully converted PDF to {len(conversion_result.images)} images")
-
- # Determine if we need to add PDF-specific context to the prompt
- modified_prompt = custom_prompt
- if not modified_prompt:
- modified_prompt = f"This is a multi-page PDF document with {conversion_result.page_count} total pages, of which {len(conversion_result.images)} were processed."
- elif "pdf" not in modified_prompt.lower() and "multi-page" not in modified_prompt.lower():
- modified_prompt += f" This is a multi-page PDF document with {conversion_result.page_count} total pages, of which {len(conversion_result.images)} were processed."
-
- try:
- # First process the first page with vision if requested
- first_page_result = self.processor.process_file(
- file_path=conversion_result.images[0],
- file_type="image",
- use_vision=use_vision,
- custom_prompt=modified_prompt
- )
-
- # Process additional pages if available
- all_pages_text = []
- all_languages = set()
-
- # Extract text from first page
- if 'ocr_contents' in first_page_result and 'raw_text' in first_page_result['ocr_contents']:
- all_pages_text.append(first_page_result['ocr_contents']['raw_text'])
-
- # Track languages from first page
- if 'languages' in first_page_result:
- for lang in first_page_result['languages']:
- all_languages.add(str(lang))
-
- # Process additional pages if any
- for i, img_path in enumerate(conversion_result.images[1:], 1):
- try:
- # Simple text extraction for additional pages
- page_result = self.processor.process_file(
- file_path=img_path,
- file_type="image",
- use_vision=False, # Use simpler processing for additional pages
- custom_prompt=f"This is page {i+1} of a {conversion_result.page_count}-page document."
- )
-
- # Extract text
- if 'ocr_contents' in page_result and 'raw_text' in page_result['ocr_contents']:
- all_pages_text.append(page_result['ocr_contents']['raw_text'])
-
- # Track languages
- if 'languages' in page_result:
- for lang in page_result['languages']:
- all_languages.add(str(lang))
- except Exception as e:
- logger.warning(f"Error processing page {i+1}: {e}")
-
- # Combine all text into a single document
- combined_text = "\n\n".join(all_pages_text)
-
- # Update the first page result with combined data
- if 'ocr_contents' in first_page_result:
- first_page_result['ocr_contents']['raw_text'] = combined_text
-
- # Update languages with all detected languages
- if all_languages:
- first_page_result['languages'] = list(all_languages)
-
- # Add PDF metadata
- first_page_result['file_name'] = pdf_path.name
- first_page_result['file_type'] = "pdf"
- first_page_result['total_pages'] = conversion_result.page_count
- first_page_result['processed_pages'] = len(conversion_result.images)
-
- # Add conversion info
- first_page_result['pdf_conversion'] = {
- "method": "pdf2image",
- "pages_converted": len(conversion_result.images),
- "pages_requested": len(page_numbers) if page_numbers else (max_pages or conversion_result.page_count)
- }
-
- return first_page_result
- except Exception as e:
- logger.error(f"Error processing converted images: {e}")
- # Fall back to direct processing via StructuredOCR
-
- finally:
- # Clean up temporary files
- conversion_result.cleanup()
-
- # If conversion failed or processing the images failed, fall back to direct processing
- logger.info(f"Using direct StructuredOCR processing for PDF")
- return self.processor.process_file(
- file_path=pdf_path,
- file_type="pdf",
- use_vision=use_vision,
- max_pages=max_pages,
- custom_pages=custom_pages,
- custom_prompt=custom_prompt
- )
-
- def save_json_output(self, pdf_path, output_path, use_vision=True, max_pages=None, custom_pages=None, custom_prompt=None):
- """
- Process a PDF file and save the structured output as JSON.
-
- Args:
- pdf_path: Path to the PDF file
- output_path: Path where to save the JSON output
- use_vision: Whether to use vision model for improved analysis
- max_pages: Maximum number of pages to process
- custom_pages: Specific page numbers to process (1-based indexing)
- custom_prompt: Custom instructions for processing
-
- Returns:
- Path to the saved JSON file
- """
- # Process the PDF
- result = self.process_pdf(
- pdf_path,
- use_vision=use_vision,
- max_pages=max_pages,
- custom_pages=custom_pages,
- custom_prompt=custom_prompt
- )
-
- # Save the result to JSON
- output_path = Path(output_path)
- output_path.parent.mkdir(parents=True, exist_ok=True)
-
- with open(output_path, 'w') as f:
- json.dump(result, f, indent=2)
-
- return output_path
-
-# For testing directly
-if __name__ == "__main__":
- import sys
- import argparse
-
- parser = argparse.ArgumentParser(description="Process PDF files with OCR.")
- parser.add_argument("pdf_path", help="Path to the PDF file to process")
- parser.add_argument("--output", "-o", help="Path to save the output JSON")
- parser.add_argument("--no-vision", dest="use_vision", action="store_false",
- help="Disable vision model for processing")
- parser.add_argument("--max-pages", type=int, help="Maximum number of pages to process")
- parser.add_argument("--pages", help="Specific pages to process (comma-separated)")
- parser.add_argument("--prompt", help="Custom prompt for processing")
-
- args = parser.parse_args()
-
- processor = PDFOCR()
-
- # Parse custom pages if provided
- custom_pages = None
- if args.pages:
- try:
- custom_pages = [int(p.strip()) for p in args.pages.split(',')]
- except:
- print(f"Error parsing pages: {args.pages}. Should be comma-separated list of numbers.")
- sys.exit(1)
-
- if args.output:
- result_path = processor.save_json_output(
- args.pdf_path,
- args.output,
- use_vision=args.use_vision,
- max_pages=args.max_pages,
- custom_pages=custom_pages,
- custom_prompt=args.prompt
- )
- print(f"Results saved to: {result_path}")
- else:
- result = processor.process_pdf(
- args.pdf_path,
- use_vision=args.use_vision,
- max_pages=args.max_pages,
- custom_pages=custom_pages,
- custom_prompt=args.prompt
- )
- print(json.dumps(result, indent=2))
diff --git a/utils/text_utils.py b/utils/text_utils.py
deleted file mode 100644
index 7eafbda672a12751344d1082d392720267775317..0000000000000000000000000000000000000000
--- a/utils/text_utils.py
+++ /dev/null
@@ -1,279 +0,0 @@
-"""
-Utility functions for text processing.
-Contains helper functions for working with text data from OCR.
-"""
-
-import re
-import logging
-import difflib
-from typing import List, Dict, Any, Optional
-
-# Configure logging
-logging.basicConfig(level=logging.INFO,
- format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
-logger = logging.getLogger(__name__)
-
-def format_ocr_text(text: str, for_display: bool = False) -> str:
- """
- Format OCR text for display or processing.
- This function maintains clean separation between data and presentation.
-
- Args:
- text: OCR text to format
- for_display: Whether to format for display (HTML) or plain text
-
- Returns:
- Formatted text
- """
- if not text:
- return ""
-
- # Clean the text first
- text = clean_raw_text(text)
-
- # Basic text formatting (line breaks, etc.)
- formatted_text = text.replace("\n", " " if for_display else "\n")
-
- if for_display:
- # For display, wrap in paragraph tags but avoid unnecessary divs
- # to maintain content purity
- return f"
{formatted_text}
"
- else:
- # For processing, return clean text only - no markup
- return formatted_text
-
-def format_markdown_text(text: str, preserve_format: bool = True) -> str:
- """
- Format text as Markdown, preserving or enhancing its structure.
- Ensures that text has clean markdown formatting without introducing
- unnecessary presentation elements.
-
- Args:
- text: Raw text to format as Markdown
- preserve_format: Whether to preserve original formatting
-
- Returns:
- Markdown-formatted text
- """
- if not text:
- return ""
-
- # Clean the text first
- text = clean_raw_text(text)
-
- # Normalize line endings
- text = text.replace('\r\n', '\n').replace('\r', '\n')
-
- # Preserve paragraphs if requested
- if preserve_format:
- # Ensure paragraphs are separated by double line breaks
- text = re.sub(r'\n{3,}', '\n\n', text)
- else:
- # Convert single line breaks within paragraphs to spaces
- text = re.sub(r'(? str:
- """
- Clean raw text by removing unnecessary whitespace and artifacts.
-
- Args:
- text: Raw text to clean
-
- Returns:
- Cleaned text
- """
- if not text:
- return ""
-
- # Remove image references like 
- text = re.sub(r'!\[.*?\]\(data:image/[^)]+\)', '', text)
-
- # Remove basic markdown image references like 
- text = re.sub(r'!\[[^\]]*\]\([^)]+\)', '', text)
-
- # Remove base64 encoded image data
- text = re.sub(r'data:image/[^;]+;base64,[a-zA-Z0-9+/=]+', '', text)
-
- # Clean up any JSON-like image object references
- text = re.sub(r'{"image(_data)?":("[^"]*"|null|true|false|\{[^}]*\}|\[[^\]]*\])}', '', text)
-
- # Clean up excessive whitespace and line breaks created by removals
- text = re.sub(r'\n{3,}', '\n\n', text)
- text = re.sub(r'\s{3,}', ' ', text)
-
- return text.strip()
-
-def detect_content_regions(image_np):
- """
- Detect content regions based on text density analysis.
- Returns regions with adaptive overlapping.
-
- Args:
- image_np: Numpy array image
-
- Returns:
- list: List of region tuples (x, y, width, height)
- """
- # Import necessary modules
- import numpy as np
- import cv2
-
- # Convert to grayscale for text detection
- if len(image_np.shape) > 2 and image_np.shape[2] == 3:
- gray = cv2.cvtColor(image_np, cv2.COLOR_RGB2GRAY)
- else:
- gray = image_np
-
- # Create text density profile
- # Sum pixel values horizontally to get vertical text density
- v_profile = np.sum(255 - gray, axis=1)
-
- # Normalize the profile
- v_profile = v_profile / np.max(v_profile) if np.max(v_profile) > 0 else v_profile
-
- # Find significant density changes
- changes = []
- threshold = 0.2
- for i in range(1, len(v_profile)):
- if abs(v_profile[i] - v_profile[i-1]) > threshold:
- changes.append(i)
-
- # Create adaptive regions based on density changes
- img_height, img_width = gray.shape
-
- # Default to at least 3 regions with overlap
- if len(changes) < 2:
- # If no significant changes, use default division with overlapping regions
- header_height = int(img_height * 0.3)
- middle_start = int(img_height * 0.2)
- middle_height = int(img_height * 0.4)
- body_start = int(img_height * 0.5)
- body_height = img_height - body_start
- else:
- # Use detected density changes for more precise regions
- changes = sorted(changes)
- header_height = changes[0] + int(img_height * 0.05) # Add overlap
- middle_start = max(0, changes[0] - int(img_height * 0.05))
-
- if len(changes) > 1:
- middle_height = (changes[1] - middle_start) + int(img_height * 0.05)
- body_start = max(0, changes[1] - int(img_height * 0.05))
- else:
- middle_height = int(img_height * 0.4)
- body_start = int(img_height * 0.5)
-
- body_height = img_height - body_start
-
- # Define regions with adaptive overlap
- regions = [
- (0, 0, img_width, header_height), # Header region
- (0, middle_start, img_width, middle_height), # Middle region with overlap
- (0, body_start, img_width, body_height) # Body region with overlap
- ]
-
- return regions
-
-def merge_region_texts(regions: List[Dict[str, Any]], min_similarity_threshold: float = 0.7) -> str:
- """
- Intelligently merge text from multiple document regions, handling overlapping content.
- Uses text similarity detection to avoid duplicating content from overlapping regions.
-
- Args:
- regions: List of region dictionaries, each containing 'text' and 'order' keys
- min_similarity_threshold: Minimum similarity ratio to consider text as duplicate
-
- Returns:
- Merged text with duplications removed
- """
- # If no regions, return empty string
- if not regions:
- return ""
-
- # If only one region, return its text directly
- if len(regions) == 1:
- return regions[0]['text']
-
- # Sort regions by their defined order
- sorted_regions = sorted(regions, key=lambda x: x.get('order', 0))
-
- # Extract text segments from each region
- texts = [region.get('text', '').strip() for region in sorted_regions]
-
- # Remove empty texts
- texts = [t for t in texts if t]
-
- if not texts:
- return ""
-
- # Start with the first region's text
- merged_text = texts[0]
-
- # Process each subsequent region
- for i in range(1, len(texts)):
- current_text = texts[i]
-
- # Skip if current text is empty
- if not current_text:
- continue
-
- # Find potential overlap with existing merged text
- # Split both texts into lines for line-by-line comparison
- merged_lines = merged_text.splitlines()
- current_lines = current_text.splitlines()
-
- # Initialize variables to track where to start appending
- append_from_line = 0 # Default: append all lines from current text
- max_similarity = 0.0
- max_similarity_pos = -1
-
- # Check for potential line duplications
- # Look at the last N lines of merged text (N = min(20, len(merged_lines)))
- # to see if they match the first N lines of current text
- check_lines = min(20, len(merged_lines))
- for j in range(1, check_lines + 1):
- # Get the last j lines from merged text
- merged_end = "\n".join(merged_lines[-j:])
-
- # Get the first j lines from current text
- current_start = "\n".join(current_lines[:j])
-
- # Skip comparison if either section is too short
- if len(merged_end) < 10 or len(current_start) < 10:
- continue
-
- # Calculate similarity ratio
- similarity = difflib.SequenceMatcher(None, merged_end, current_start).ratio()
-
- # If we found a better match, update
- if similarity > max_similarity and similarity >= min_similarity_threshold:
- max_similarity = similarity
- max_similarity_pos = j
-
- # If we found a good match, skip those lines from current text
- if max_similarity_pos > 0:
- logger.info(f"Found overlapping text with similarity {max_similarity:.2f}, skipping {max_similarity_pos} lines")
- append_from_line = max_similarity_pos
-
- # Append non-duplicated content with a separator
- if append_from_line < len(current_lines):
- remaining_text = "\n".join(current_lines[append_from_line:])
- if remaining_text.strip():
- merged_text += "\n\n" + remaining_text
-
- return merged_text
diff --git a/utils/ui_utils.py b/utils/ui_utils.py
deleted file mode 100644
index 2738909aaa3fb27d51beb3c9fc6661f3f1ec5df8..0000000000000000000000000000000000000000
--- a/utils/ui_utils.py
+++ /dev/null
@@ -1,351 +0,0 @@
-"""
-UI utilities for OCR results display.
-"""
-import os
-import streamlit as st
-import json
-import base64
-import io
-from datetime import datetime
-
-from utils.text_utils import format_ocr_text
-from utils.content_utils import classify_document_content, format_structured_data
-
-def display_results(result, container, custom_prompt=""):
- """Display OCR results in the provided container"""
- with container:
- # Add heading for document metadata
- st.markdown("### Document Metadata")
-
- # Filter out large data structures from metadata display
- meta = {k: v for k, v in result.items()
- if k not in ['pages_data', 'illustrations', 'ocr_contents', 'raw_response_data']}
-
- # Create a compact metadata section for primary metadata
- meta_html = '
'
-
- # Document type
- if 'detected_document_type' in meta:
- meta_html += f'
Type: {meta["detected_document_type"]}
'
-
- # Page information
- if 'limited_pages' in meta:
- meta_html += f'
'
- st.markdown(meta_html, unsafe_allow_html=True)
-
- # Processing time - separate section for proper ordering of all metadata fields
- if 'processing_time' in meta:
- time_html = '
'
- time_html += '
Time:
'
- time_html += f'
{meta["processing_time"]:.1f}s
'
- time_html += '
'
- st.markdown(time_html, unsafe_allow_html=True)
-
- # Language metadata on a separate line, Subject Tags below
-
- # First show languages if available
- if 'languages' in result and result['languages']:
- languages = [lang for lang in result['languages'] if lang is not None]
- if languages:
- # Create a dedicated line for Languages
- lang_html = '
'
- lang_html += '
Language:
'
-
- # Add language tags
- for lang in languages:
- # Clean language name if needed
- clean_lang = str(lang).strip()
- if clean_lang: # Only add if not empty
- lang_html += f'{clean_lang}'
-
- lang_html += '
'
- st.markdown(lang_html, unsafe_allow_html=True)
-
- # Prepare download files
- try:
- # Get base filename
- from utils.general_utils import create_descriptive_filename
- original_file = result.get('file_name', 'document')
- base_name = create_descriptive_filename(original_file, result, "")
- base_name = os.path.splitext(base_name)[0]
-
- # 1. JSON download - with base64 data truncated for readability
- from utils.image_utils import truncate_base64_in_result
- truncated_result = truncate_base64_in_result(result)
- json_str = json.dumps(truncated_result, indent=2)
- json_filename = f"{base_name}.json"
- json_b64 = base64.b64encode(json_str.encode()).decode()
-
- # 2. Create ZIP with all files
- from utils.image_utils import create_results_zip_in_memory
- zip_data = create_results_zip_in_memory(result)
- timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
- zip_filename = f"{base_name}_{timestamp}.zip"
- zip_b64 = base64.b64encode(zip_data).decode()
-
- # Add download line with metadata styling
- download_html = '
'
- download_html += '
Download:
'
-
- # Download links in order of importance, matching the zip file contents
- download_html += f'JSON'
-
- # Zip download link (packages everything together)
- download_html += f'Zip Archive'
-
- download_html += '
'
- st.markdown(download_html, unsafe_allow_html=True)
- except Exception as e:
- # Silent fail for downloads - don't disrupt the UI
- pass
-
- # Create a separate line for Time if we have time-related tags
- if 'topics' in result and result['topics']:
- time_tags = [topic for topic in result['topics']
- if any(term in topic.lower() for term in ["century", "pre-", "era"])]
- if time_tags:
- time_html = '
'
- time_html += '
Time:
'
- for tag in time_tags:
- time_html += f'{tag}'
- time_html += '
'
- st.markdown(time_html, unsafe_allow_html=True)
-
- # Then display remaining subject tags if available
- if 'topics' in result and result['topics']:
- # Filter out time-related tags which are already displayed
- subject_tags = [topic for topic in result['topics']
- if not any(term in topic.lower() for term in ["century", "pre-", "era"])]
-
- if subject_tags:
- # Create a separate line for Subject Tags
- tags_html = '
'
- tags_html += '
Subject Tags:
'
- tags_html += '
'
-
- # Generate a badge for each remaining tag
- for topic in subject_tags:
- # Determine tag category class
- tag_class = "subject-tag" # Default class
-
- # Add specialized class based on category
- if any(term in topic.lower() for term in ["language", "english", "french", "german", "latin"]):
- tag_class += " tag-language" # Languages
- elif any(term in topic.lower() for term in ["letter", "newspaper", "book", "form", "document", "recipe"]):
- tag_class += " tag-document-type" # Document types
- elif any(term in topic.lower() for term in ["travel", "military", "science", "medicine", "education", "art", "literature"]):
- tag_class += " tag-subject" # Subject domains
- elif "historical" in topic.lower() and "document" in topic.lower():
- tag_class += " tag-document-type" # "Historical Document Analysis" should be a document type
-
- # Add each tag as an inline span
- tags_html += f'{topic}'
-
- # Close the containers
- tags_html += '
'
-
- # Render the subject tags section
- st.markdown(tags_html, unsafe_allow_html=True)
-
- # Check if we have OCR content
- if 'ocr_contents' in result:
- # Create a single view instead of tabs
- content_tab1 = st.container()
-
- # Check for images in the result to use later
- has_images = result.get('has_images', False)
- has_image_data = ('pages_data' in result and any(page.get('images', []) for page in result.get('pages_data', [])))
- has_raw_images = ('raw_response_data' in result and 'pages' in result['raw_response_data'] and
- any('images' in page for page in result['raw_response_data']['pages']
- if isinstance(page, dict)))
-
- # Display structured content
- with content_tab1:
- # Display structured content with markdown formatting
- if isinstance(result['ocr_contents'], dict):
- # CSS is now handled in the main layout.py file
-
- # Collect all available images from the result
- available_images = []
- if has_images and 'pages_data' in result:
- for page_idx, page in enumerate(result['pages_data']):
- if 'images' in page and len(page['images']) > 0:
- for img_idx, img in enumerate(page['images']):
- if 'image_base64' in img:
- available_images.append({
- 'source': 'pages_data',
- 'page': page_idx,
- 'index': img_idx,
- 'data': img['image_base64']
- })
-
- # Get images from raw response as well
- if 'raw_response_data' in result:
- raw_data = result['raw_response_data']
- if isinstance(raw_data, dict) and 'pages' in raw_data:
- for page_idx, page in enumerate(raw_data['pages']):
- if isinstance(page, dict) and 'images' in page:
- for img_idx, img in enumerate(page['images']):
- if isinstance(img, dict) and 'base64' in img:
- available_images.append({
- 'source': 'raw_response',
- 'page': page_idx,
- 'index': img_idx,
- 'data': img['base64']
- })
-
- # Extract images for display at the top
- images_to_display = []
-
- # First, collect all available images
- for img_idx, img in enumerate(available_images):
- if 'data' in img:
- images_to_display.append({
- 'data': img['data'],
- 'id': img.get('id', f"img_{img_idx}"),
- 'index': img_idx
- })
-
- # Image display now only happens in the Images tab
-
- # Organize sections in a logical order - prioritize main_text
- section_order = ["title", "author", "date", "summary", "main_text", "content", "transcript", "metadata"]
- ordered_sections = []
-
- # Add known sections first in preferred order
- for section_name in section_order:
- if section_name in result['ocr_contents'] and result['ocr_contents'][section_name]:
- ordered_sections.append(section_name)
-
- # Add any remaining sections
- for section in result['ocr_contents'].keys():
- if (section not in ordered_sections and
- section not in ['error', 'partial_text'] and
- result['ocr_contents'][section]):
- ordered_sections.append(section)
-
- # If only raw_text is available and no other content, add it last
- if ('raw_text' in result['ocr_contents'] and
- result['ocr_contents']['raw_text'] and
- len(ordered_sections) == 0):
- ordered_sections.append('raw_text')
-
- # Add minimal spacing before OCR results
- st.markdown("", unsafe_allow_html=True)
-
- # Create tabs for different views
- if has_images:
- tabs = st.tabs(["Document Content", "Raw JSON", "Images"])
- doc_tab, json_tab, img_tab = tabs
- else:
- tabs = st.tabs(["Document Content", "Raw JSON"])
- doc_tab, json_tab = tabs
- img_tab = None
-
- # Document Content tab with simple, clean formatting that matches markdown export files
- with doc_tab:
- # Create a single unified content section
- st.markdown("## Text Content")
-
- # Present content directly in the format used in markdown export files
- if 'ocr_contents' in result and isinstance(result['ocr_contents'], dict):
- # Get all content fields that should be displayed
- content_fields = {}
-
- # Add all available content fields (left_page, right_page, etc)
- for field, content in result['ocr_contents'].items():
- # Skip certain fields that shouldn't be displayed
- if field in ['error', 'partial_text'] or not content:
- continue
-
- # Clean the content if it's a string
- if isinstance(content, str) and content.strip():
- content_fields[field] = content.strip()
- # Handle dictionary or list content
- elif isinstance(content, (dict, list)):
- formatted_content = format_structured_data(content)
- if formatted_content:
- content_fields[field] = formatted_content
-
- # Process nested dictionary structures
- def flatten_content_fields(fields, parent_key=""):
- flat_fields = {}
- for field, content in fields.items():
- # Skip certain fields
- if field in ['error', 'partial_text'] or not content:
- continue
-
- # Handle string content
- if isinstance(content, str) and content.strip():
- key = f"{parent_key}_{field}".strip("_")
- flat_fields[key] = content.strip()
- # Handle dictionary content
- elif isinstance(content, dict):
- # If the dictionary has a 'text' key, extract just that value
- if 'text' in content and isinstance(content['text'], str):
- key = f"{parent_key}_{field}".strip("_")
- flat_fields[key] = content['text'].strip()
- # Otherwise, recursively process nested dictionaries
- else:
- nested_fields = flatten_content_fields(content, f"{parent_key}_{field}")
- flat_fields.update(nested_fields)
- # Handle list content
- elif isinstance(content, list):
- formatted_content = format_structured_data(content)
- if formatted_content:
- key = f"{parent_key}_{field}".strip("_")
- flat_fields[key] = formatted_content
-
- return flat_fields
-
- # Flatten the content structure
- flat_content_fields = flatten_content_fields(result['ocr_contents'])
-
- # Display the flattened content fields with proper formatting
- for field, content in flat_content_fields.items():
- # Skip any empty content
- if not content or not content.strip():
- continue
-
- # Format field name as in the markdown export
- field_display = field.replace('_', ' ')
-
- # Maintain content purity - don't parse text content as JSON
- # Historical text may contain curly braces that aren't JSON
-
- # For raw_text field, display only the content without the field name
- if field == 'raw_text':
- st.markdown(f"{content}")
- else:
- # For other fields, display the field name in bold followed by the content
- st.markdown(f"**{field}:** {content}")
-
- # Add spacing between fields
- st.markdown("\n\n")
-
- # Raw JSON tab - displays the exact same JSON that's downloaded via the JSON button
- with json_tab:
- # Use the same truncated JSON that's used in the download button
- from utils.image_utils import truncate_base64_in_result
- truncated_result = truncate_base64_in_result(result)
-
- # Format the JSON prettily
- json_str = json.dumps(truncated_result, indent=2)
-
- # Display JSON with a copy button using Streamlit's built-in functionality
- st.json(truncated_result)
-
-
- # Images tab - for viewing document images
- if has_images and img_tab:
- with img_tab:
- # Display each available image
- for i, img in enumerate(images_to_display):
- st.image(img['data'], caption=f"Image {i+1}", use_container_width=True)
-
- # Display custom prompt if provided
- if custom_prompt:
- with st.expander("Custom Processing Instructions"):
- st.write(custom_prompt)