| import pandas as pd |
| import json |
| from pathlib import Path |
| import re |
| from collections import defaultdict |
| import logging |
| import sys |
|
|
| |
| project_root = Path(__file__).parent.parent |
| sys.path.insert(0, str(project_root)) |
|
|
| logger = logging.getLogger(__name__) |
|
|
|
|
| def parse_year(year_str): |
| """Extract year/decade from various formats, limited to 1960s-2000s""" |
| if pd.isna(year_str) or year_str == 'n.d.': |
| return None |
|
|
| year_str = str(year_str).strip() |
|
|
| |
| if '-' in year_str: |
| years = year_str.split('-') |
| year_str = years[0] |
|
|
| |
| match = re.search(r'(19|20)\d{2}', year_str) |
| if match: |
| year = int(match.group(0)) |
|
|
| |
| if year <= 1960: |
| return "1960s" |
| elif year >= 2010: |
| return "2000s" |
| else: |
| |
| decade = (year // 10) * 10 |
| return f"{decade}s" |
|
|
| |
| match = re.search(r'(19|20)\d0s?', year_str) |
| if match: |
| decade_str = match.group(0).rstrip('s') |
| decade_num = int(decade_str) |
|
|
| |
| if decade_num < 1960: |
| return "1960s" |
| elif decade_num > 2000: |
| return "2000s" |
| else: |
| return f"{decade_str}s" |
|
|
| return None |
|
|
| def process_product_data(xlsx_path, output_dir): |
| """Process product design data from XLSX""" |
|
|
| |
| df = pd.read_excel(xlsx_path) |
| print(f"Loaded {len(df)} products from {xlsx_path}") |
|
|
| |
| processed_data = [] |
| issues = [] |
| decade_stats = defaultdict(int) |
|
|
| for idx, row in df.iterrows(): |
| try: |
| |
| decade = parse_year(row['year']) |
| if not decade: |
| issues.append(f"Row {idx}: Invalid year '{row['year']}' for product '{row['name']}'") |
| continue |
|
|
| |
| image_urls = str(row['image_urls']).split('|||') |
| image_urls = [url.strip() for url in image_urls if url.strip()] |
|
|
| if not image_urls: |
| issues.append(f"Row {idx}: No valid image URLs for '{row['name']}'") |
| continue |
|
|
| |
| for img_idx, url in enumerate(image_urls): |
| entry = { |
| 'id': f"product_{idx:05d}_img_{img_idx}", |
| 'product_id': f"product_{idx:05d}", |
| 'url': url, |
| 'decade': decade, |
| 'year_raw': row['year'], |
| 'name': row['name'], |
| 'classification': row.get('classification', 'unknown'), |
| 'makers': row.get('makers', 'unknown'), |
| 'country': row.get('country', 'unknown'), |
| 'image_index': img_idx, |
| 'total_images': len(image_urls), |
| 'dimension': row.get('dimension', ''), |
| 'source': row.get('source', '') |
| } |
|
|
| processed_data.append(entry) |
| decade_stats[decade] += 1 |
|
|
| except Exception as e: |
| issues.append(f"Row {idx}: Error processing - {str(e)}") |
|
|
| |
| output_path = Path(output_dir) |
| output_path.mkdir(parents=True, exist_ok=True) |
|
|
| with open(output_path / 'processed_metadata.json', 'w') as f: |
| json.dump(processed_data, f, indent=2) |
|
|
| |
| if issues: |
| with open(output_path / 'processing_issues.txt', 'w') as f: |
| f.write('\n'.join(issues)) |
|
|
| |
| stats = { |
| 'total_products': len(df), |
| 'total_images': len(processed_data), |
| 'products_with_valid_years': len(set(e['product_id'] for e in processed_data)), |
| 'issues_count': len(issues), |
| 'decades': dict(decade_stats), |
| 'classifications': {}, |
| 'countries': {}, |
| 'makers': {} |
| } |
|
|
| |
| for entry in processed_data: |
| |
| classification = entry['classification'] |
| stats['classifications'][classification] = stats['classifications'].get(classification, 0) + 1 |
|
|
| |
| country = entry['country'] |
| stats['countries'][country] = stats['countries'].get(country, 0) + 1 |
|
|
| |
| makers = entry['makers'] |
| stats['makers'][makers] = stats['makers'].get(makers, 0) + 1 |
|
|
| with open(output_path / 'dataset_stats.json', 'w') as f: |
| json.dump(stats, f, indent=2) |
|
|
| |
| print(f"\n=== Processing Summary ===") |
| print(f"Total products: {stats['total_products']}") |
| print(f"Total images: {stats['total_images']}") |
| print(f"Products with valid years: {stats['products_with_valid_years']}") |
| print(f"Processing issues: {stats['issues_count']}") |
| print(f"\nImages per decade:") |
| for decade in sorted(stats['decades'].keys()): |
| print(f" {decade}: {stats['decades'][decade]} images") |
| print("\n\n") |
|
|
| return processed_data, stats |
|
|
|
|
| if __name__ == "__main__": |
| from src.data.url_dataset import download_dataset_images |
| from scripts.feature_cluster import add_clustering_to_pipeline |
|
|
| data_dir = Path('data') |
| processed_json_path = data_dir / 'metadata' / 'processed_metadata.json' |
| image_cache_dir = data_dir / 'cache' / 'images' |
|
|
| |
| process_product_data(data_dir / 'metadata' / 'fetch_ALL.xlsx', data_dir / 'metadata') |
|
|
| |
| print("Starting download...") |
| download_results = download_dataset_images( |
| split_files=[str(processed_json_path)], |
| cache_dir=str(image_cache_dir), |
| num_workers=8, |
| skip_existing=True |
| ) |
|
|
| print("\n=== Download Summary ===") |
| print(json.dumps(download_results, indent=2)) |
|
|
| |
| print("\n=== Starting Clustering Process (Fast Mode) ===") |
| clustered_json_path = add_clustering_to_pipeline( |
| data_dir, |
| batch_size=64, |
| use_fixed_k=True, |
| fixed_k=5 |
| ) |
| |
| |
| print(f"\nFinal clustered metadata saved to: {clustered_json_path}") |
|
|
|
|
|
|