feat: Add aspect ratio presets and experimental panoramic loader node

- Replaces manual width/height inputs in the Street View Loader with a user-friendly aspect_ratio dropdown.
- Adds a new experimental StreetViewPanoLoader node for stitching multiple images into a wide panorama.
- Updates the README to explain the new features, API size limits, and the recommended upscaling workflow.
This commit is contained in:
ru4ls
2025-10-18 01:49:43 +07:00
parent 3d08f95b17
commit ea08a03b5b
6 changed files with 277 additions and 83 deletions
+62 -76
View File
@@ -8,7 +8,7 @@ A custom node for ComfyUI that allows you to load images directly from Google St
Instead of manually taking screenshots, this node programmatically fetches a clean, high-resolution image from any location on Earth with Street View coverage, giving you precise control over the camera angle, direction, and field of view.
![Street View Loader Node combine with NanoBanana in ComfyUI](media/preview.png)
![Street View Loader Node](media/preview.png)
---
@@ -22,27 +22,21 @@ This gives you the best of both worlds: the authenticity of a real photograph co
### Key Advantages & Use Cases
* **Guaranteed Location Accuracy:** When you need the background to be recognizably *your* street, a specific landmark, or a favorite travel spot, this node is the only way to guarantee a 100% accurate representation. The composition, architecture, and lighting are real, not AI-generated guesswork.
* **The Ultimate Base for `img2img` and ControlNet:** This is the most powerful use case. You can feed the real Street View image into your workflow as a base for:
* **Stylization:** Keep the exact composition of a place while transforming it into an oil painting, an anime scene, or a charcoal sketch.
* **Restoration/Re-imagining:** Take a view of an old neighborhood and use AI to add futuristic elements, or render it as it might have looked in a different historical period.
* **Precise Inpainting:** Add characters, objects, or fantastical elements into a real-world scene with a background that is perfectly stable and coherent.
* **Virtual Location Scouting:** For concept artists, filmmakers, and game developers. You can instantly scout real-world locations from your desktop, pull them into ComfyUI, and experiment with different styles and moods for your project without ever leaving your chair.
* **Personalized and Sentimental Art:** Create a unique piece of art based on a place with personal meaning—a childhood home, a proposal spot, or a favorite vacation view. This creates a connection that a generic prompt could never achieve.
* **Consistent Backgrounds for Testing:** When testing LoRAs, IPAdapters, or character models, using a consistent, real-world background from this node ensures that you are only seeing the changes from your model, not the random variations of a `txt2img` background.
* **Guaranteed Location Accuracy:** When you need the background to be recognizably *your* street, a specific landmark, or a favorite travel spot, this node is the only way to guarantee a 100% accurate representation.
* **The Ultimate Base for ControlNet:** Feed the real Street View image into your workflow as a base for stylization (anime, oil painting), re-imagining (futuristic, historical), or precise inpainting.
* **Virtual Location Scouting:** Instantly scout real-world locations from your desktop and experiment with different styles and moods for your project.
* **Personalized & Sentimental Art:** Create unique art based on a place with personal meaning—a childhood home, a proposal spot, or a favorite vacation view.
* **Consistent Backgrounds for Testing:** Use a consistent, real-world background to reliably test LoRAs, IPAdapters, or character models.
![Street View Loader Node combine with NanoBanana in ComfyUI](media/preview_2.png)
## Features
- **Direct API Integration:** Pulls images directly from the Google Street View Static API.
- **Full Camera Control:** Adjust Location, Heading (pan), Pitch (tilt), and Field of View (zoom).
- **Easy Workflow with URL Parser:** Just find a spot on Google Maps and paste the URL.
- **Secure API Key Storage:** Uses a `.env` file to keep your API key safe and out of your workflow files.
- **Simple Aspect Ratio Presets:** Choose from common ratios like 16:9 or 1:1 without manual calculations.
- **Clean Output:** No UI overlays, just the pure image.
- **Easy Workflow Integration:** Outputs a standard `IMAGE` tensor and a metadata string (the fetch URL) for debugging.
- **(Experimental) Panorama Mode:** Stitch multiple images together to create ultra-wide cinematic landscapes.
---
@@ -62,7 +56,7 @@ This gives you the best of both worlds: the authenticity of a real photograph co
```bash
pip install -r requirements.txt
```
* this will install the `requests` and `python-dotenv` libraries, which are required for this node.*
*(This will install the `requests` and `python-dotenv` libraries required for the node to function.)*
4. **Restart ComfyUI.**
@@ -76,87 +70,79 @@ This node requires a Google Cloud API key to function. Google provides a generou
**Part A: Create Project & Enable API**
1. Go to the [Google Cloud Console](https://console.cloud.google.com/).
2. If you don't have a project, create one. If you do, click the project selector at the top and click **"New Project"**. Give it a name like `ComfyUI-API`.
3. Once the project is created, search for "API Library" in the top search bar.
4. In the library, search for and select **"Street View Static API"**.
5. Click the **"Enable"** button.
2. Create a **New Project**. Give it a name like `ComfyUI-API`.
3. In the new project, search for "API Library".
4. In the library, search for and **Enable** the **"Street View Static API"**.
**Part B: Set Up Billing**
6. You will be prompted to link a billing account. This is required, but you will **not be charged** unless you exceed the $200 free monthly credit (equivalent to tens of thousands of image requests).
5. You will be prompted to link a billing account. This is required, but you will **not be charged** unless you exceed the $200 free monthly credit.
**Part C: Create and Secure Your API Key**
7. In the Cloud Console search bar, navigate to **"Credentials"**.
8. Click **"+ Create Credentials"** and select **"API key"**.
9. Your new API key will be displayed. **Copy this key immediately.**
10. **(IMPORTANT!)** Click the **"Edit API key"** button. Under "API restrictions," select **"Restrict key"**.
11. In the dropdown, find and check **"Street View Static API"**, then click "OK". This ensures your key can *only* be used for this purpose, protecting your account. Click **"Save"**.
6. In the Cloud Console search bar, navigate to **"Credentials"**.
7. Click **"+ Create Credentials"** and select **"API key"**.
8. **Copy this key immediately.**
9. **(IMPORTANT!)** Click **"Edit API key"**. Under "API restrictions," select **"Restrict key"** and add **"Street View Static API"** to the list. This protects your account. Click **"Save"**.
**Part D: Configure the Node**
12. In your `ComfyUI/custom_nodes/ComfyUI_StreetView-Loader/` folder, create a new file named `.env`. or rename an existing `.env.example` file to `.env`.
13. Open this `.env` file and add your copied API key in the following format:
10. In your `ComfyUI/custom_nodes/ComfyUI_StreetView-Loader/` folder, create a new file named `.env` (or rename the existing `.env.example` file).
11. Open this `.env` file and add your copied API key in the following format:
```
GOOGLE_STREET_VIEW_API_KEY="your_actual_api_key_goes_here"
```
14. Save the file.
Your setup is now complete!
12. Save the file. Your setup is now complete!
---
## 3. How to Use
## 3. How to Use & Upscaling
1. After restarting ComfyUI, double-click the canvas and search for **"Street View Loader"**.
2. The node will appear with several input fields.
The recommended workflow is to use the **URL Parser** node to feed information into the **Loader** node.
### Finding Your Parameters
1. **Find your view** in [Google Maps](https://maps.google.com) and enter Street View.
2. Frame the perfect shot, then **copy the entire URL** from your browser's address bar.
3. In ComfyUI, add the **`Street View URL Parser`** node and paste the URL into it.
4. Add the **`Street View Loader`** node.
5. Connect the outputs of the Parser to the inputs of the Loader (`location` to `location`, etc.).
The best way to get the exact `location`, `heading`, `pitch`, and `fov` is to use the Google Maps URL.
### Understanding the Nodes and Image Size Limit
1. Open [Google Maps](https://maps.google.com) and drop into Street View at your desired location.
2. Pan, tilt, and zoom the camera until you frame the perfect shot.
3. Look at the URL in your browser's address bar. It will look like this:
`https://www.google.com/maps/@**40.74844,-73.98566**,3a,**75**y,**273.99**h,**85.73**t/data=...`
#### `Street View URL Parser`
This node takes a full Google Maps URL as input and outputs the camera parameters (`location`, `heading`, `pitch`, `fov`).
- `@**40.74844,-73.98566**`: This is the **`location`**.
- `**75**y`: This is the **`fov`** (Field of View / Zoom).
- `**273.99**h`: This is the **`heading`**.
- `**85.73**t`: This is related to the **`pitch`**. The API uses a range of -90 (straight down) to +90 (straight up) with 0 being the horizon. A `t` value of `90` in the URL is roughly a `pitch` of `0` for the API. Adjust as needed.
4. Copy these values into the corresponding fields on the node and generate!
### **The Easy Way: Using the URL Parser Node**
As of version 2.0, the recommended workflow is to use the **Street View URL Parser** node. This node extracts all the camera parameters automatically from a single Google Maps URL.
1. Add two nodes to your workflow:
- `Street View URL Parser` (found in `Ru4ls/StreetView/Utils`)
- `Street View Loader`
2. Find the exact view you want in Google Street View on your browser.
3. Copy the **entire URL** from your browser's address bar.
4. Paste the URL into the `url` field of the **Parser** node.
5. Connect the outputs of the Parser node to the inputs of the Loader node (`location` to `location`, `heading` to `heading`, etc.).
This method is faster, easier, and less error-prone than manually entering each parameter.
### Node Inputs
- `location`: The latitude and longitude (e.g., `40.74844,-73.98566`).
- `heading`: The compass direction [0 to 360]. 0 is North, 90 is East, 180 is South, 270 is West.
- `pitch`: The up/down tilt of the camera [-90 to 90]. 0 is the horizon.
- `fov`: The field of view, or zoom [10 to 120]. A lower number is more zoomed in.
- `width` / `height`: The dimensions of the output image (max 640x640 for the free API).
#### `Street View Loader`
This is the main node that fetches the image.
- **`aspect_ratio`**: Choose your desired output aspect ratio from the dropdown. This replaces manual width/height inputs.
- **API Limit & Upscaling:** The Google Street View API has a maximum output size of **640x640 pixels**. For high-resolution images (like 1080p or 4K), you **must** use an upscaling workflow.
- **Recommended HD Workflow:**
1. Select `"16:9 Widescreen (640x360)"` in the `Street View Loader`.
2. Connect its `IMAGE` output to an **`Upscale Image (using model)`** node.
3. Use a `Load Upscale Model` node (e.g., `4x-UltraSharp`) to get a final, high-quality **2560x1440** image.
---
## 4. Troubleshooting
## 4. (Experimental) Panoramic Loader Node
- **Node throws a `ValueError: API key not found`:** Your `.env` file is missing, in the wrong location, or the variable name is not `GOOGLE_STREET_VIEW_API_KEY`. It must be in the `ComfyUI_StreetView-Loader` folder.
- **The node outputs a black image:** This almost always means Google has no Street View imagery for that exact coordinate, or the API key is invalid/restricted. Check your key's restrictions in the Google Cloud Console and try a slightly different coordinate. The `metadata` output will contain an error message.
- **The node doesn't appear in ComfyUI:** Ensure you have fully restarted the ComfyUI server after installation.
For users who need to create wide, cinematic landscapes, the project includes an experimental **Street View Pano Loader** node.
![Street View Pano Loader Node](media/preview_3.png)
### What It Does
This node overcomes the API's FOV limitations by fetching multiple image "tiles" and stitching them side-by-side. For example, requesting **3 images** will result in three `640x640` images being stitched into a single `1920x640` image.
### **⚠️ Important Experimental Notes:**
- **API Usage:** This node makes multiple API calls. A panorama with **3 images** will count as **3 requests** against your free monthly Google Cloud credit.
- **Simple Stitching:** This feature uses a basic side-by-side stitch and does not perform advanced perspective correction. It works best for distant landscapes where distortion is minimal.
- **Resolution:** The output image will be very wide but only 640px tall. It is highly recommended to chain the output of this node into an **Upscale Image** node.
---
## 5. Troubleshooting
- **`ValueError: API key not found`:** Your `.env` file is missing, in the wrong location, or the variable name is not `GOOGLE_STREET_VIEW_API_KEY`.
- **Black Image Output:** This usually means Google has no Street View imagery for that coordinate, or your API key is invalid/restricted. Check your key's restrictions on the Google Cloud Console.
- **Node not appearing in ComfyUI:** Ensure you have fully restarted the ComfyUI server after installation.
---
## License
+3
View File
@@ -2,15 +2,18 @@
from .nodes.streetview_loader import StreetViewLoader
from .nodes.streetview_url_parser import StreetViewURLParser
from .nodes.streetview_pano_loader import StreetViewPanoLoader
NODE_CLASS_MAPPINGS = {
"StreetViewLoader": StreetViewLoader,
"StreetViewURLParser": StreetViewURLParser,
"StreetViewPanoLoader": StreetViewPanoLoader,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"StreetViewLoader": "Street View Loader",
"StreetViewURLParser": "Street View URL Parser",
"StreetViewPanoLoader": "Street View Pano Loader",
}
__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS']
Binary file not shown.

After

Width:  |  Height:  |  Size: 551 KiB

+77
View File
@@ -0,0 +1,77 @@
# file: ComfyUI_StreetView-Loader\nodes\streetview_loader.py
import torch
import numpy as np
import os
from dotenv import load_dotenv
from PIL import Image
from ..utils.connect_api_utils import fetch_streetview_image
# --- Load API Key from .env file ---
current_dir = os.path.dirname(os.path.abspath(__file__))
parent_dir = os.path.dirname(current_dir)
dotenv_path = os.path.join(parent_dir, '.env')
# Load the environment variables from the found .env file
load_dotenv(dotenv_path=dotenv_path)
# Get the API key from the loaded environment variables
API_KEY_FROM_ENV = os.getenv("GOOGLE_STREET_VIEW_API_KEY")
class StreetViewLoader:
"""
A ComfyUI node to load images directly from the Google Street View Static API,
using a secret API key stored in a .env file.
"""
@classmethod
def INPUT_TYPES(s):
"""
Defines the input fields for the node. The API key is handled in the
background and is not exposed as an input in the UI.
"""
return {
"required": {
"location": ("STRING", {
"multiline": False,
"default": "40.720032,-73.988354" # Example: Near Katz's Deli, NYC
}),
"heading": ("FLOAT", {"default": 151.78, "min": 0, "max": 360, "step": 0.1, "display": "slider"}),
"pitch": ("FLOAT", {"default": -0.76, "min": -90, "max": 90, "step": 0.1, "display": "slider"}),
"fov": ("INT", {"default": 90, "min": 10, "max": 120, "step": 1, "display": "slider"}),
"width": ("INT", {"default": 512, "min": 64, "max": 640, "step": 8}),
"height": ("INT", {"default": 512, "min": 64, "max": 640, "step": 8}),
}
}
RETURN_TYPES = ("IMAGE", "STRING")
RETURN_NAMES = ("image", "metadata")
FUNCTION = "load_image"
CATEGORY = "Ru4ls/StreetView"
def load_image(self, location, heading, pitch, fov, width, height):
if not API_KEY_FROM_ENV:
raise ValueError("Google Street View API key not found in .env file. Please ensure GOOGLE_STREET_VIEW_API_KEY is set in ComfyUI_StreetView-Loader/.env")
image_pil, metadata = fetch_streetview_image(
api_key=API_KEY_FROM_ENV,
location=location,
heading=heading,
pitch=pitch,
fov=fov,
width=width,
height=height
)
# Convert the returned PIL image to the tensor format ComfyUI expects.
image_tensor = self.pil_to_tensor(image_pil)
return (image_tensor, metadata)
def pil_to_tensor(self, image: Image.Image):
""" Helper function to convert a PIL Image to a PyTorch Tensor for ComfyUI. """
image_np = np.array(image).astype(np.float32) / 255.0
return torch.from_numpy(image_np)[None,]
+33 -7
View File
@@ -6,9 +6,12 @@ import os
from dotenv import load_dotenv
from PIL import Image
# Import the refactored API call function from our utility file
# No changes are needed in connect_api_utils.py
from ..utils.connect_api_utils import fetch_streetview_image
# --- Load API Key from .env file ---
# This logic finds the .env file in the parent directory (ComfyUI_StreetView-Loader)
current_dir = os.path.dirname(os.path.abspath(__file__))
parent_dir = os.path.dirname(current_dir)
dotenv_path = os.path.join(parent_dir, '.env')
@@ -23,14 +26,14 @@ API_KEY_FROM_ENV = os.getenv("GOOGLE_STREET_VIEW_API_KEY")
class StreetViewLoader:
"""
A ComfyUI node to load images directly from the Google Street View Static API,
using a secret API key stored in a .env file.
using a secret API key stored in a .env file and aspect ratio presets.
"""
@classmethod
def INPUT_TYPES(s):
"""
Defines the input fields for the node. The API key is handled in the
background and is not exposed as an input in the UI.
Defines the input fields for the node. Width and height are now controlled
by a user-friendly aspect ratio dropdown menu.
"""
return {
"required": {
@@ -41,8 +44,14 @@ class StreetViewLoader:
"heading": ("FLOAT", {"default": 151.78, "min": 0, "max": 360, "step": 0.1, "display": "slider"}),
"pitch": ("FLOAT", {"default": -0.76, "min": -90, "max": 90, "step": 0.1, "display": "slider"}),
"fov": ("INT", {"default": 90, "min": 10, "max": 120, "step": 1, "display": "slider"}),
"width": ("INT", {"default": 512, "min": 64, "max": 640, "step": 8}),
"height": ("INT", {"default": 512, "min": 64, "max": 640, "step": 8}),
# NEW: Aspect ratio dropdown replaces width/height inputs
"aspect_ratio": ([
"1:1 Square (640x640)",
"16:9 Widescreen (640x360)",
"9:16 Vertical (360x640)",
"4:3 Classic (640x480)",
"3:2 Photography (640x427)"
],),
}
}
@@ -51,11 +60,27 @@ class StreetViewLoader:
FUNCTION = "load_image"
CATEGORY = "Ru4ls/StreetView"
def load_image(self, location, heading, pitch, fov, width, height):
def load_image(self, location, heading, pitch, fov, aspect_ratio):
# The API key is now taken directly from the globally loaded variable.
if not API_KEY_FROM_ENV:
raise ValueError("Google Street View API key not found in .env file. Please ensure GOOGLE_STREET_VIEW_API_KEY is set in ComfyUI_StreetView-Loader/.env")
# NEW: Logic to determine width and height based on the selected aspect ratio
if aspect_ratio == "1:1 Square (640x640)":
width, height = 640, 640
elif aspect_ratio == "16:9 Widescreen (640x360)":
width, height = 640, 360
elif aspect_ratio == "9:16 Vertical (360x640)":
width, height = 360, 640
elif aspect_ratio == "4:3 Classic (640x480)":
width, height = 640, 480
elif aspect_ratio == "3:2 Photography (640x427)":
width, height = 640, 427
else:
# Fallback to a default just in case
width, height = 640, 640
# Call the refactored utility function with the calculated width and height.
image_pil, metadata = fetch_streetview_image(
api_key=API_KEY_FROM_ENV,
location=location,
@@ -74,4 +99,5 @@ class StreetViewLoader:
def pil_to_tensor(self, image: Image.Image):
""" Helper function to convert a PIL Image to a PyTorch Tensor for ComfyUI. """
image_np = np.array(image).astype(np.float32) / 255.0
# Add the batch dimension [None,] which is equivalent to unsqueeze(0)
return torch.from_numpy(image_np)[None,]
+102
View File
@@ -0,0 +1,102 @@
# file: ComfyUI_StreetView-Loader/nodes/streetview_pano_loader.py
import torch
import numpy as np
import os
from dotenv import load_dotenv
from PIL import Image
# We can reuse the exact same utility function! This is the power of good refactoring.
from ..utils.connect_api_utils import fetch_streetview_image
# --- Load API Key from .env file ---
current_dir = os.path.dirname(os.path.abspath(__file__))
parent_dir = os.path.dirname(current_dir)
dotenv_path = os.path.join(parent_dir, '.env')
load_dotenv(dotenv_path=dotenv_path)
API_KEY_FROM_ENV = os.getenv("GOOGLE_STREET_VIEW_API_KEY")
class StreetViewPanoLoader:
"""
A ComfyUI node to load a panoramic image by fetching multiple Google Street
View images with different headings and stitching them side-by-side.
"""
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"location": ("STRING", {"multiline": False, "default": "46.6237597,8.0305018"}), # Switzerland example
"center_heading": ("FLOAT", {"default": 133.44, "min": 0, "max": 360, "step": 0.1, "display": "slider"}),
"pitch": ("FLOAT", {"default": -5.0, "min": -90, "max": 90, "step": 0.1, "display": "slider"}),
"fov_per_image": ("INT", {"default": 90, "min": 30, "max": 120, "step": 1, "display": "slider"}),
"num_images": ("INT", {"default": 3, "min": 2, "max": 5, "step": 1, "display": "slider"}),
}
}
RETURN_TYPES = ("IMAGE", "STRING")
RETURN_NAMES = ("image", "metadata")
FUNCTION = "load_panorama"
CATEGORY = "Ru4ls/StreetView"
def load_panorama(self, location, center_heading, pitch, fov_per_image, num_images):
if not API_KEY_FROM_ENV:
raise ValueError("Google Street View API key not found in .env file.")
images = []
# We will fetch 640x640 images as they give the most vertical data for stitching.
width, height = 640, 640
# --- Calculate Headings for Each Image ---
# This determines how far apart each camera shot is.
# For a simple side-by-side stitch, the step angle is equal to the field of view.
step_angle = fov_per_image
# Calculate the heading for the very first (leftmost) image
start_heading = center_heading - (step_angle * (num_images - 1) / 2.0)
print(f"StreetView Pano: Fetching {num_images} images with {fov_per_image}° FOV each.")
for i in range(num_images):
# Calculate the heading for the current shot in the sequence
current_heading = (start_heading + i * step_angle) % 360
print(f" - Fetching image {i+1}/{num_images} at heading {current_heading:.2f}°...")
# Fetch a single image using our existing utility function
image_pil, _ = fetch_streetview_image(
api_key=API_KEY_FROM_ENV,
location=location,
heading=current_heading,
pitch=pitch,
fov=fov_per_image,
width=width,
height=height
)
if image_pil:
images.append(image_pil)
if not images:
print("StreetView Pano: Failed to fetch any images.")
blank_image = torch.zeros((1, height, width, 3), dtype=torch.float32)
return (blank_image, "Failed to fetch any images.")
# --- Stitch the Images Together ---
total_width = width * len(images)
stitched_image = Image.new('RGB', (total_width, height))
for i, img in enumerate(images):
stitched_image.paste(img, (i * width, 0))
print(f"StreetView Pano: Stitching complete. Final size: {total_width}x{height}")
# Convert the final stitched PIL image to the tensor format ComfyUI expects
final_tensor = self.pil_to_tensor(stitched_image)
metadata = f"Stitched {len(images)} images. Center Heading: {center_heading}, Total Width: {total_width}px"
return (final_tensor, metadata)
def pil_to_tensor(self, image: Image.Image):
image_np = np.array(image).astype(np.float32) / 255.0
return torch.from_numpy(image_np)[None,]