Metadata-Version: 2.4
Name: gsv-ocr-check
Version: 0.1.0
Summary: A Python library for verifying Google Street View panorama existence and calibrating OCR bounding boxes through perspective re-projection.
Project-URL: Homepage, https://github.com/yz3440/gsv-ocr-check
Project-URL: Repository, https://github.com/yz3440/gsv-ocr-check
Project-URL: Issues, https://github.com/yz3440/gsv-ocr-check/issues
Author-email: Yufeng Zhao <yufeng-zhao@outlook.com>
License: MIT
Keywords: bounding-box,calibration,equirectangular,google-street-view,ocr,panorama
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering :: Image Processing
Classifier: Topic :: Scientific/Engineering :: Image Recognition
Requires-Python: >=3.12
Requires-Dist: aiohttp>=3.9.0
Requires-Dist: panoocr>=0.4.0
Requires-Dist: pillow>=10.0.0
Requires-Dist: streetlevel>=0.12.0
Provides-Extra: docs
Requires-Dist: mkdocs-terminal>=4.6.0; extra == 'docs'
Requires-Dist: mkdocs>=1.5.0; extra == 'docs'
Requires-Dist: mkdocstrings[python]>=0.24.0; extra == 'docs'
Requires-Dist: pymdown-extensions>=10.0.0; extra == 'docs'
Provides-Extra: macocr
Requires-Dist: panoocr[macocr]; extra == 'macocr'
Description-Content-Type: text/markdown

# gsv-ocr-check

A Python library for verifying Google Street View panorama existence and calibrating OCR bounding boxes through perspective re-projection.

## Features

- **Panorama Existence Check**: Verify whether a Google Street View panorama ID is still accessible
- **Panorama Download**: Download panoramas to disk or into memory as PIL Images
- **OCR Bounding Box Calibration**: Refine OCR coordinates by re-running OCR on perspective crops at multiple fields of view
- **Engine Agnostic**: Works with any [panoocr](https://github.com/yz3440/panoocr)-compatible OCR engine
- **Async Support**: Both sync and async APIs for panorama operations

## Installation

Install the base package:

```bash
pip install gsv-ocr-check
```

Install with macOS OCR support:

```bash
pip install "gsv-ocr-check[macocr]"
```

Using uv (recommended):

```bash
uv add gsv-ocr-check
uv add "gsv-ocr-check[macocr]"
```

## Quick Start

```python
from gsv_ocr_check import check_panorama_exists, calibrate_ocr, download_panorama
from panoocr.engines.macocr import MacOCREngine

# 1. Check if a panorama still exists on Google Street View
exists = check_panorama_exists("abc123")

# 2. Download the panorama
image = download_panorama("abc123", output_path="pano.jpg")

# 3. Calibrate an OCR bounding box
engine = MacOCREngine()
result = calibrate_ocr(
    panorama="pano.jpg",
    target_text="LOVE",
    yaw=38.5,
    pitch=-2.4,
    engine=engine,
)

if result:
    print(f"Text: {result.text}")
    print(f"Position: yaw={result.yaw:.2f}°, pitch={result.pitch:.2f}°")
    print(f"Size: {result.width:.2f}° x {result.height:.2f}°")
    print(f"Confidence: {result.confidence}")
    print(f"FOV used: {result.fov_used}°")
```

## API

### Panorama Operations

#### `check_panorama_exists(panorama_id) -> bool`

Check if a Google Street View panorama still exists. Returns `True` if accessible, `False` otherwise.

```python
from gsv_ocr_check import check_panorama_exists

if check_panorama_exists("UVfBLpzNhpZdImXJqozDmg"):
    print("Panorama is still available")
```

#### `download_panorama(panorama_id, output_path=None, zoom=5, quality=95) -> Image | None`

Download a panorama image. Returns a PIL Image, or `None` if the panorama is not found.

```python
from gsv_ocr_check import download_panorama

# Download to memory only
image = download_panorama("abc123")

# Download and save to disk
image = download_panorama("abc123", output_path="panoramas/abc123.jpg")

# Download at lower resolution (zoom 0-5)
image = download_panorama("abc123", zoom=2)
```

#### Async Variants

Both functions have async counterparts for use in async contexts:

```python
from gsv_ocr_check import check_panorama_exists_async, download_panorama_async

exists = await check_panorama_exists_async("abc123")
image = await download_panorama_async("abc123", output_path="pano.jpg")
```

### OCR Calibration

#### `calibrate_ocr(panorama, target_text, yaw, pitch, engine, **kwargs) -> CalibrationResult | None`

Refine an OCR bounding box by generating perspective views centered on the original detection, re-running OCR, and returning the best match in spherical coordinates.

```python
from gsv_ocr_check import calibrate_ocr
from panoocr.engines.macocr import MacOCREngine

engine = MacOCREngine()
result = calibrate_ocr(
    panorama="pano.jpg",       # path or PIL Image
    target_text="LOVE",        # exact match, case-insensitive
    yaw=38.5,                  # original detection yaw
    pitch=-2.4,                # original detection pitch
    engine=engine,             # any panoocr-compatible engine
)
```

**Parameters:**

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `panorama` | `str \| Path \| Image` | required | Path to equirectangular panorama or PIL Image |
| `target_text` | `str` | required | Text to match (exact, case-insensitive) |
| `yaw` | `float` | required | Original detection yaw in degrees |
| `pitch` | `float` | required | Original detection pitch in degrees |
| `engine` | `object` | required | panoocr-compatible OCR engine |
| `panorama_id` | `str` | `None` | ID for naming saved perspective files |
| `fovs` | `list[float]` | `[45, 90, 120]` | FOVs to try in order |
| `resolution` | `int` | `2048` | Perspective image resolution in pixels |
| `save_perspectives` | `str \| Path` | `None` | Directory to save perspective crops |

### CalibrationResult

Dataclass returned by `calibrate_ocr` with the refined detection:

```python
@dataclass
class CalibrationResult:
    text: str              # Matched text
    confidence: float      # OCR confidence score
    yaw: float             # Refined yaw in degrees
    pitch: float           # Refined pitch in degrees
    width: float           # Angular width in degrees
    height: float          # Angular height in degrees
    engine: str            # OCR engine class name
    match_type: str        # "exact"
    original_yaw: float    # Input yaw (preserved)
    original_pitch: float  # Input pitch (preserved)
    fov_used: float        # FOV that produced the match
    resolution_used: int   # Resolution used
```

## How Calibration Works

1. Load the equirectangular panorama image
2. Generate a perspective view centered on the original yaw/pitch coordinates
3. Try multiple FOVs in sequence (default: 45° → 90° → 120°) — narrow FOV gives better resolution, wider FOV captures more context
4. Run OCR on each perspective image
5. Filter for exact text matches (case-insensitive)
6. Select the match closest to the image center
7. Transform pixel coordinates back to spherical using `panoocr.geometry.perspective_to_sphere`
8. Return the first successful match, or `None` if no match at any FOV

## Advanced Usage

### Custom OCR Engine

Any object with a `recognize(pil_image)` method returning results with `.text`, `.confidence`, and `.bounding_box` attributes works:

```python
from panoocr.engines.easyocr import EasyOCREngine

engine = EasyOCREngine(config={"language_preference": ["en"]})
result = calibrate_ocr("pano.jpg", "HELLO", yaw=0, pitch=0, engine=engine)
```

### Saving Perspective Crops

Save the perspective images generated during calibration for debugging:

```python
result = calibrate_ocr(
    panorama="pano.jpg",
    target_text="LOVE",
    yaw=38.5,
    pitch=-2.4,
    engine=engine,
    save_perspectives="debug/perspectives/",
)
# Saves: debug/perspectives/<pano_id>_fov45.jpg, etc.
```

### Custom FOVs and Resolution

```python
result = calibrate_ocr(
    panorama="pano.jpg",
    target_text="LOVE",
    yaw=38.5,
    pitch=-2.4,
    engine=engine,
    fovs=[30.0, 60.0, 90.0],  # custom FOV sequence
    resolution=4096,            # higher resolution perspective
)
```

### Using PIL Images Directly

```python
from PIL import Image

image = Image.open("pano.jpg")
result = calibrate_ocr(
    panorama=image,
    target_text="LOVE",
    yaw=38.5,
    pitch=-2.4,
    engine=engine,
    panorama_id="my_pano",  # required when passing PIL Image
)
```

## Coordinate System

All coordinates use spherical (yaw, pitch) format:

- **Yaw**: Horizontal angle in degrees, normalized to [-180, 180]
- **Pitch**: Vertical angle in degrees (-90 to 90)
- **Width/Height**: Angular dimensions in degrees

## Dependencies

- **[streetlevel](https://github.com/sk-zk/streetlevel)** — Google Street View panorama access
- **[panoocr](https://github.com/yz3440/panoocr)** — Perspective projection, coordinate transforms, OCR engines
- **[Pillow](https://pillow.readthedocs.io/)** — Image handling

## License

MIT License — see [LICENSE](LICENSE) for details.
