BilalSardar commited on
Commit
a4ef2ef
·
verified ·
1 Parent(s): b9a8bc1

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +237 -0
app.py ADDED
@@ -0,0 +1,237 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from PIL import Image
3
+ import tempfile
4
+ import os
5
+ from pathlib import Path
6
+ import shutil
7
+ import base64
8
+ import requests
9
+ import re
10
+ import time
11
+ from PIL import ImageEnhance
12
+ import concurrent.futures
13
+ import asyncio
14
+ import aiohttp
15
+ import io
16
+
17
+ class StreetViewDownloader:
18
+ def __init__(self):
19
+ self.headers = {
20
+ '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'
21
+ }
22
+ # Using a smaller session for better connection reuse
23
+ self.session = requests.Session()
24
+
25
+ def extract_panoid(self, url):
26
+ """Extract panorama ID from Google Maps URL."""
27
+ pattern = r'!1s([A-Za-z0-9-_]+)!'
28
+ match = re.search(pattern, url)
29
+ if match:
30
+ return match.group(1)
31
+ raise ValueError("Could not find panorama ID in URL")
32
+
33
+ async def download_tile_async(self, session, panoid, x, y, adjusted_y, zoom, output_dir):
34
+ """Download a single tile asynchronously."""
35
+ tile_url = f"https://streetviewpixels-pa.googleapis.com/v1/tile?cb_client=maps_sv.tactile&panoid={panoid}&x={x}&y={adjusted_y}&zoom={zoom}"
36
+ output_file = Path(output_dir) / f"tile_{x}_{y}.jpg"
37
+
38
+ try:
39
+ async with session.get(tile_url, headers=self.headers) as response:
40
+ if response.status == 200:
41
+ content = await response.read()
42
+ if len(content) > 1000:
43
+ output_file.write_bytes(content)
44
+ return (x, y)
45
+ except Exception as e:
46
+ print(f"Error downloading tile {x},{y}: {str(e)}")
47
+ return None
48
+
49
+ async def download_tiles_async(self, panoid, output_dir):
50
+ """Download tiles asynchronously with reduced resolution."""
51
+ Path(output_dir).mkdir(parents=True, exist_ok=True)
52
+
53
+ # Reduced parameters for faster download
54
+ zoom = 2 # Reduced zoom level (less detail but faster)
55
+ cols = 16 # Reduced number of horizontal tiles
56
+ rows = 8 # Reduced number of vertical tiles
57
+ vertical_offset = 2 # Adjusted for the new grid
58
+
59
+ print(f"Downloading {cols * rows} tiles for panorama...")
60
+
61
+ async with aiohttp.ClientSession() as session:
62
+ tasks = []
63
+ for x in range(cols):
64
+ for y in range(rows):
65
+ adjusted_y = y - (rows // 2) + vertical_offset
66
+ task = self.download_tile_async(session, panoid, x, y, adjusted_y, zoom, output_dir)
67
+ tasks.append(task)
68
+
69
+ downloaded_tiles = []
70
+ for result in await asyncio.gather(*tasks):
71
+ if result:
72
+ downloaded_tiles.append(result)
73
+
74
+ return cols, rows, downloaded_tiles
75
+
76
+ def download_tiles(self, panoid, output_dir):
77
+ """Synchronous wrapper for async download."""
78
+ return asyncio.run(self.download_tiles_async(panoid, output_dir))
79
+
80
+ def create_360_panorama(self, directory, cols, rows, downloaded_tiles, output_file):
81
+ """Create an equirectangular 360° panorama from tiles with optimized processing."""
82
+ directory = Path(directory)
83
+
84
+ # Find a valid tile to get dimensions
85
+ valid_tile = None
86
+ for x, y in downloaded_tiles:
87
+ tile_path = directory / f"tile_{x}_{y}.jpg"
88
+ if tile_path.exists():
89
+ valid_tile = Image.open(tile_path)
90
+ break
91
+
92
+ if not valid_tile:
93
+ raise Exception("No valid tiles found in directory")
94
+
95
+ tile_width, tile_height = valid_tile.size
96
+ valid_tile.close()
97
+
98
+ # Create the panorama at optimized resolution
99
+ panorama_width = tile_width * cols
100
+ panorama_height = tile_height * rows
101
+
102
+ # Use RGB mode directly for better performance
103
+ panorama = Image.new('RGB', (panorama_width, panorama_height))
104
+
105
+ # Process tiles in parallel
106
+ def process_tile(tile_info):
107
+ x, y = tile_info
108
+ tile_path = directory / f"tile_{x}_{y}.jpg"
109
+ if tile_path.exists():
110
+ with Image.open(tile_path) as tile:
111
+ if tile.getbbox():
112
+ return (x * tile_width, y * tile_height, tile.copy())
113
+ return None
114
+
115
+ # Use ThreadPoolExecutor for parallel processing
116
+ with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:
117
+ tile_results = list(executor.map(process_tile, downloaded_tiles))
118
+
119
+ # Paste all valid tiles
120
+ for result in tile_results:
121
+ if result:
122
+ x, y, tile = result
123
+ panorama.paste(tile, (x, y))
124
+ tile.close()
125
+
126
+ # Crop out any remaining black regions
127
+ bbox = panorama.getbbox()
128
+ if bbox:
129
+ panorama = panorama.crop(bbox)
130
+
131
+ # Quick enhancement
132
+ panorama = self.enhance_panorama(panorama)
133
+
134
+ # Save with optimized settings
135
+ panorama.save(output_file, 'JPEG', quality=95, optimize=True)
136
+ return output_file
137
+
138
+ def enhance_panorama(self, panorama):
139
+ """Quick enhancement with minimal processing."""
140
+ enhancer = ImageEnhance.Contrast(panorama)
141
+ panorama = enhancer.enhance(1.1)
142
+ return panorama
143
+
144
+ def process_street_view(url, progress=gr.Progress()):
145
+ """Process the Street View URL with progress tracking."""
146
+ try:
147
+ temp_dir = tempfile.mkdtemp()
148
+ progress(0.1, desc="Initializing...")
149
+
150
+ downloader = StreetViewDownloader()
151
+ panoid = downloader.extract_panoid(url)
152
+ progress(0.2, desc="Extracted panorama ID...")
153
+
154
+ tiles_dir = os.path.join(temp_dir, f"{panoid}_tiles")
155
+ output_file = os.path.join(temp_dir, f"{panoid}_360panorama.jpg")
156
+
157
+ progress(0.3, desc="Downloading tiles...")
158
+ cols, rows, downloaded_tiles = downloader.download_tiles(panoid, tiles_dir)
159
+
160
+ progress(0.7, desc="Creating panorama...")
161
+ final_path = downloader.create_360_panorama(tiles_dir, cols, rows, downloaded_tiles, output_file)
162
+
163
+ progress(0.8, desc="Cleaning up...")
164
+ shutil.rmtree(tiles_dir)
165
+
166
+ progress(0.9, desc="Preparing viewer...")
167
+ with open(final_path, 'rb') as img_file:
168
+ img_data = base64.b64encode(img_file.read()).decode()
169
+
170
+ viewer_html = f"""
171
+ <html>
172
+ <head>
173
+ <script src="https://aframe.io/releases/1.4.0/aframe.min.js"></script>
174
+ <style>
175
+ .viewer-container {{ height: 500px; width: 100%; }}
176
+ </style>
177
+ </head>
178
+ <body>
179
+ <div class="viewer-container">
180
+ <a-scene embedded>
181
+ <a-sky src="data:image/jpeg;base64,{img_data}"></a-sky>
182
+ <a-camera position="0 0 0" wasd-controls-enabled="false"></a-camera>
183
+ </a-scene>
184
+ </div>
185
+ </body>
186
+ </html>
187
+ """
188
+
189
+ progress(1.0, desc="Done!")
190
+ return final_path, viewer_html
191
+
192
+ except Exception as e:
193
+ return None, f"Error: {str(e)}"
194
+
195
+ def create_gradio_interface():
196
+ with gr.Blocks() as app:
197
+ gr.Markdown("# Fast Street View 360° Panorama Downloader")
198
+ gr.Markdown("Enter a Google Street View URL to download and view the panorama")
199
+
200
+ with gr.Row():
201
+ url_input = gr.Textbox(
202
+ label="Street View URL",
203
+ placeholder="Paste Google Street View URL here..."
204
+ )
205
+
206
+ with gr.Row():
207
+ submit_btn = gr.Button("Download and Process")
208
+
209
+ with gr.Row():
210
+ with gr.Column():
211
+ output_file = gr.File(label="Download Panorama")
212
+ with gr.Column():
213
+ viewer = gr.HTML(label="360° Viewer")
214
+
215
+ submit_btn.click(
216
+ fn=process_street_view,
217
+ inputs=[url_input],
218
+ outputs=[output_file, viewer]
219
+ )
220
+
221
+ gr.Markdown("""
222
+ ### Instructions:
223
+ 1. Open Google Street View at your desired location
224
+ 2. Copy the URL from your browser
225
+ 3. Paste the URL in the input box above
226
+ 4. Click "Download and Process"
227
+ 5. Wait for processing (usually takes less than a minute)
228
+ 6. Download the panorama or explore it in the 360° viewer
229
+
230
+ Note: This is an optimized version that provides faster processing with slightly reduced image quality.
231
+ """)
232
+
233
+ return app
234
+
235
+ if __name__ == "__main__":
236
+ app = create_gradio_interface()
237
+ app.launch(share=True)