Update app.py
Browse files
app.py
CHANGED
@@ -13,7 +13,149 @@ import asyncio
|
|
13 |
import aiohttp
|
14 |
import io
|
15 |
|
16 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
17 |
|
18 |
def process_street_view(url, zoom, cols, rows, progress=gr.Progress()):
|
19 |
"""Process the Street View URL with custom parameters."""
|
|
|
13 |
import aiohttp
|
14 |
import io
|
15 |
|
16 |
+
import gradio as gr
|
17 |
+
from PIL import Image
|
18 |
+
import tempfile
|
19 |
+
import os
|
20 |
+
from pathlib import Path
|
21 |
+
import shutil
|
22 |
+
import base64
|
23 |
+
import requests
|
24 |
+
import re
|
25 |
+
import time
|
26 |
+
from PIL import ImageEnhance
|
27 |
+
import concurrent.futures
|
28 |
+
import asyncio
|
29 |
+
import aiohttp
|
30 |
+
import io
|
31 |
+
|
32 |
+
class StreetViewDownloader:
|
33 |
+
def __init__(self):
|
34 |
+
self.headers = {
|
35 |
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
|
36 |
+
}
|
37 |
+
# Using a smaller session for better connection reuse
|
38 |
+
self.session = requests.Session()
|
39 |
+
|
40 |
+
def extract_panoid(self, url):
|
41 |
+
"""Extract panorama ID from Google Maps URL."""
|
42 |
+
pattern = r'!1s([A-Za-z0-9-_]+)!'
|
43 |
+
match = re.search(pattern, url)
|
44 |
+
if match:
|
45 |
+
return match.group(1)
|
46 |
+
raise ValueError("Could not find panorama ID in URL")
|
47 |
+
|
48 |
+
async def download_tile_async(self, session, panoid, x, y, adjusted_y, zoom, output_dir):
|
49 |
+
"""Download a single tile asynchronously."""
|
50 |
+
tile_url = f"https://streetviewpixels-pa.googleapis.com/v1/tile?cb_client=maps_sv.tactile&panoid={panoid}&x={x}&y={adjusted_y}&zoom={zoom}"
|
51 |
+
output_file = Path(output_dir) / f"tile_{x}_{y}.jpg"
|
52 |
+
|
53 |
+
try:
|
54 |
+
async with session.get(tile_url, headers=self.headers) as response:
|
55 |
+
if response.status == 200:
|
56 |
+
content = await response.read()
|
57 |
+
if len(content) > 1000:
|
58 |
+
output_file.write_bytes(content)
|
59 |
+
return (x, y)
|
60 |
+
except Exception as e:
|
61 |
+
print(f"Error downloading tile {x},{y}: {str(e)}")
|
62 |
+
return None
|
63 |
+
|
64 |
+
async def download_tiles_async(self, panoid, output_dir):
|
65 |
+
"""Download tiles asynchronously with reduced resolution."""
|
66 |
+
Path(output_dir).mkdir(parents=True, exist_ok=True)
|
67 |
+
|
68 |
+
# Reduced parameters for faster download
|
69 |
+
zoom = 2 # Reduced zoom level (less detail but faster)
|
70 |
+
cols = 16 # Reduced number of horizontal tiles
|
71 |
+
rows = 8 # Reduced number of vertical tiles
|
72 |
+
vertical_offset = 2 # Adjusted for the new grid
|
73 |
+
|
74 |
+
print(f"Downloading {cols * rows} tiles for panorama...")
|
75 |
+
|
76 |
+
async with aiohttp.ClientSession() as session:
|
77 |
+
tasks = []
|
78 |
+
for x in range(cols):
|
79 |
+
for y in range(rows):
|
80 |
+
adjusted_y = y - (rows // 2) + vertical_offset
|
81 |
+
task = self.download_tile_async(session, panoid, x, y, adjusted_y, zoom, output_dir)
|
82 |
+
tasks.append(task)
|
83 |
+
|
84 |
+
downloaded_tiles = []
|
85 |
+
for result in await asyncio.gather(*tasks):
|
86 |
+
if result:
|
87 |
+
downloaded_tiles.append(result)
|
88 |
+
|
89 |
+
return cols, rows, downloaded_tiles
|
90 |
+
|
91 |
+
def download_tiles(self, panoid, output_dir):
|
92 |
+
"""Synchronous wrapper for async download."""
|
93 |
+
return asyncio.run(self.download_tiles_async(panoid, output_dir))
|
94 |
+
|
95 |
+
def create_360_panorama(self, directory, cols, rows, downloaded_tiles, output_file):
|
96 |
+
"""Create an equirectangular 360° panorama from tiles with optimized processing."""
|
97 |
+
directory = Path(directory)
|
98 |
+
|
99 |
+
# Find a valid tile to get dimensions
|
100 |
+
valid_tile = None
|
101 |
+
for x, y in downloaded_tiles:
|
102 |
+
tile_path = directory / f"tile_{x}_{y}.jpg"
|
103 |
+
if tile_path.exists():
|
104 |
+
valid_tile = Image.open(tile_path)
|
105 |
+
break
|
106 |
+
|
107 |
+
if not valid_tile:
|
108 |
+
raise Exception("No valid tiles found in directory")
|
109 |
+
|
110 |
+
tile_width, tile_height = valid_tile.size
|
111 |
+
valid_tile.close()
|
112 |
+
|
113 |
+
# Create the panorama at optimized resolution
|
114 |
+
panorama_width = tile_width * cols
|
115 |
+
panorama_height = tile_height * rows
|
116 |
+
|
117 |
+
# Use RGB mode directly for better performance
|
118 |
+
panorama = Image.new('RGB', (panorama_width, panorama_height))
|
119 |
+
|
120 |
+
# Process tiles in parallel
|
121 |
+
def process_tile(tile_info):
|
122 |
+
x, y = tile_info
|
123 |
+
tile_path = directory / f"tile_{x}_{y}.jpg"
|
124 |
+
if tile_path.exists():
|
125 |
+
with Image.open(tile_path) as tile:
|
126 |
+
if tile.getbbox():
|
127 |
+
return (x * tile_width, y * tile_height, tile.copy())
|
128 |
+
return None
|
129 |
+
|
130 |
+
# Use ThreadPoolExecutor for parallel processing
|
131 |
+
with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:
|
132 |
+
tile_results = list(executor.map(process_tile, downloaded_tiles))
|
133 |
+
|
134 |
+
# Paste all valid tiles
|
135 |
+
for result in tile_results:
|
136 |
+
if result:
|
137 |
+
x, y, tile = result
|
138 |
+
panorama.paste(tile, (x, y))
|
139 |
+
tile.close()
|
140 |
+
|
141 |
+
# Crop out any remaining black regions
|
142 |
+
bbox = panorama.getbbox()
|
143 |
+
if bbox:
|
144 |
+
panorama = panorama.crop(bbox)
|
145 |
+
|
146 |
+
# Quick enhancement
|
147 |
+
panorama = self.enhance_panorama(panorama)
|
148 |
+
|
149 |
+
# Save with optimized settings
|
150 |
+
panorama.save(output_file, 'JPEG', quality=95, optimize=True)
|
151 |
+
return output_file
|
152 |
+
|
153 |
+
def enhance_panorama(self, panorama):
|
154 |
+
"""Quick enhancement with minimal processing."""
|
155 |
+
enhancer = ImageEnhance.Contrast(panorama)
|
156 |
+
panorama = enhancer.enhance(1.1)
|
157 |
+
return panorama
|
158 |
+
|
159 |
|
160 |
def process_street_view(url, zoom, cols, rows, progress=gr.Progress()):
|
161 |
"""Process the Street View URL with custom parameters."""
|