Image#

Classes for equirectangular panoramas and perspective projections.

PanoramaImage#

PanoramaImage #

PanoramaImage(panorama_id: str, image: Union[str, Image, ndarray])

An equirectangular panorama image.

Attributes:

Name Type Description
panorama_id

Identifier for this panorama.

loaded_image

The panorama as PIL Image.

loaded_image_array

The panorama as numpy array.

Initialize a panorama image.

Parameters:

Name Type Description Default
panorama_id str

Identifier for this panorama.

required
image Union[str, Image, ndarray]

Path to image file, PIL Image, or numpy array.

required

Raises:

Type Description
ValueError

If image type is not supported.

Source code in src/panoocr/image/models.py
def __init__(
    self,
    panorama_id: str,
    image: Union[str, Image.Image, np.ndarray],
):
    """Initialize a panorama image.

    Args:
        panorama_id: Identifier for this panorama.
        image: Path to image file, PIL Image, or numpy array.

    Raises:
        ValueError: If image type is not supported.
    """
    self.panorama_id = panorama_id

    if isinstance(image, str):
        self.loaded_image = Image.open(image)
        self.loaded_image_array = np.array(self.loaded_image)
    elif isinstance(image, Image.Image):
        self.loaded_image = image
        self.loaded_image_array = np.array(self.loaded_image)
    elif isinstance(image, np.ndarray):
        self.loaded_image_array = image
        self.loaded_image = Image.fromarray(self.loaded_image_array)
    else:
        raise ValueError(
            "Input image must be a path string, PIL Image, or numpy array"
        )

generate_perspective_image #

generate_perspective_image(perspective: PerspectiveMetadata) -> PerspectiveImage

Generate a perspective projection from this panorama.

Parameters:

Name Type Description Default
perspective PerspectiveMetadata

Configuration for the perspective projection.

required

Returns:

Type Description
PerspectiveImage

PerspectiveImage with the generated projection.

Raises:

Type Description
ValueError

If the panorama image has not been loaded.

Source code in src/panoocr/image/models.py
def generate_perspective_image(
    self, perspective: PerspectiveMetadata
) -> PerspectiveImage:
    """Generate a perspective projection from this panorama.

    Args:
        perspective: Configuration for the perspective projection.

    Returns:
        PerspectiveImage with the generated projection.

    Raises:
        ValueError: If the panorama image has not been loaded.
    """
    if self.loaded_image is None:
        raise ValueError("Image has not been loaded")

    perspective_image = PerspectiveImage(
        source_panorama_image_array=self.loaded_image_array,
        panorama_id=self.panorama_id,
        perspective_metadata=perspective,
    )
    return perspective_image

get_image #

get_image() -> Image.Image

Get the panorama as PIL Image.

Source code in src/panoocr/image/models.py
def get_image(self) -> Image.Image:
    """Get the panorama as PIL Image."""
    return self.loaded_image

get_image_array #

get_image_array() -> np.ndarray

Get the panorama as numpy array.

Source code in src/panoocr/image/models.py
def get_image_array(self) -> np.ndarray:
    """Get the panorama as numpy array."""
    return self.loaded_image_array

PerspectiveImage#

PerspectiveImage #

PerspectiveImage(panorama_id: str, source_panorama_image_array: ndarray, perspective_metadata: PerspectiveMetadata)

A perspective projection from an equirectangular panorama.

Attributes:

Name Type Description
panorama_id

Identifier for the source panorama.

perspective_metadata

Configuration for the perspective projection.

perspective_image

The generated perspective image as PIL Image.

perspective_image_array

The generated perspective image as numpy array.

Initialize a perspective image from a panorama.

Parameters:

Name Type Description Default
panorama_id str

Identifier for the source panorama.

required
source_panorama_image_array ndarray

The equirectangular panorama as numpy array.

required
perspective_metadata PerspectiveMetadata

Configuration for the perspective projection.

required
Source code in src/panoocr/image/models.py
def __init__(
    self,
    panorama_id: str,
    source_panorama_image_array: np.ndarray,
    perspective_metadata: PerspectiveMetadata,
):
    """Initialize a perspective image from a panorama.

    Args:
        panorama_id: Identifier for the source panorama.
        source_panorama_image_array: The equirectangular panorama as numpy array.
        perspective_metadata: Configuration for the perspective projection.
    """
    self.source_panorama_image_array = source_panorama_image_array
    self.panorama_id = panorama_id
    self.perspective_metadata = perspective_metadata

    # Generate perspective projection using py360convert
    self.perspective_image_array = py360convert.e2p(
        e_img=self.source_panorama_image_array,
        fov_deg=(
            self.perspective_metadata.horizontal_fov,
            self.perspective_metadata.vertical_fov,
        ),
        u_deg=self.perspective_metadata.yaw_offset,
        v_deg=self.perspective_metadata.pitch_offset,
        out_hw=(
            self.perspective_metadata.pixel_height,
            self.perspective_metadata.pixel_width,
        ),
        in_rot_deg=0,
        mode="bilinear",
    )

    self.perspective_image = Image.fromarray(self.perspective_image_array)

get_perspective_metadata #

get_perspective_metadata() -> PerspectiveMetadata

Get the perspective metadata.

Source code in src/panoocr/image/models.py
def get_perspective_metadata(self) -> PerspectiveMetadata:
    """Get the perspective metadata."""
    return self.perspective_metadata

get_perspective_image_array #

get_perspective_image_array() -> np.ndarray

Get the perspective image as numpy array.

Source code in src/panoocr/image/models.py
def get_perspective_image_array(self) -> np.ndarray:
    """Get the perspective image as numpy array."""
    return self.perspective_image_array

get_perspective_image #

get_perspective_image() -> Image.Image

Get the perspective image as PIL Image.

Source code in src/panoocr/image/models.py
def get_perspective_image(self) -> Image.Image:
    """Get the perspective image as PIL Image."""
    return self.perspective_image

PerspectiveMetadata#

PerspectiveMetadata dataclass #

PerspectiveMetadata(pixel_width: int, pixel_height: int, horizontal_fov: float, vertical_fov: float, yaw_offset: float, pitch_offset: float)

Metadata for a perspective projection from an equirectangular panorama.

Attributes:

Name Type Description
pixel_width int

Width of the perspective image in pixels.

pixel_height int

Height of the perspective image in pixels.

horizontal_fov float

Horizontal field of view in degrees.

vertical_fov float

Vertical field of view in degrees.

yaw_offset float

Horizontal rotation offset in degrees (-180 to 180).

pitch_offset float

Vertical rotation offset in degrees (-90 to 90).

to_file_suffix #

to_file_suffix() -> str

Generate a unique file suffix for this perspective configuration.

Source code in src/panoocr/image/models.py
def to_file_suffix(self) -> str:
    """Generate a unique file suffix for this perspective configuration."""
    return (
        f"{self.pixel_width}_{self.pixel_height}_"
        f"{self.horizontal_fov}_{self.vertical_fov}_"
        f"{self.yaw_offset}_{self.pitch_offset}"
    )

to_dict #

to_dict() -> dict

Convert to dictionary.

Source code in src/panoocr/image/models.py
def to_dict(self) -> dict:
    """Convert to dictionary."""
    return {
        "pixel_width": self.pixel_width,
        "pixel_height": self.pixel_height,
        "horizontal_fov": self.horizontal_fov,
        "vertical_fov": self.vertical_fov,
        "yaw_offset": self.yaw_offset,
        "pitch_offset": self.pitch_offset,
    }

from_dict classmethod #

from_dict(data: dict) -> 'PerspectiveMetadata'

Create from dictionary.

Source code in src/panoocr/image/models.py
@classmethod
def from_dict(cls, data: dict) -> "PerspectiveMetadata":
    """Create from dictionary."""
    return cls(
        pixel_width=data["pixel_width"],
        pixel_height=data["pixel_height"],
        horizontal_fov=data["horizontal_fov"],
        vertical_fov=data["vertical_fov"],
        yaw_offset=data["yaw_offset"],
        pitch_offset=data["pitch_offset"],
    )

Perspective Generation#

generate_perspectives #

generate_perspectives(fov: float = 45, resolution: int = 2048, overlap: float = 0.5, pitch_angles: Optional[List[float]] = None, vertical_fov: Optional[float] = None) -> List[PerspectiveMetadata]

Generate a set of perspective views covering 360° horizontally.

This is the main API for creating custom perspective configurations.

Parameters:

Name Type Description Default
fov float

Horizontal field of view in degrees (default: 45°).

45
resolution int

Pixel width and height of each perspective (default: 2048).

2048
overlap float

Overlap ratio between adjacent perspectives, 0-1 (default: 0.5). - 0.0 = no overlap (perspectives touch at edges) - 0.5 = 50% overlap (recommended for good coverage) - 1.0 = 100% overlap (each point covered by 2 perspectives)

0.5
pitch_angles Optional[List[float]]

List of pitch angles in degrees (default: [0]). Use multiple values to cover up/down, e.g., [-30, 0, 30].

None
vertical_fov Optional[float]

Vertical field of view in degrees (default: same as fov).

None

Returns:

Type Description
List[PerspectiveMetadata]

List of PerspectiveMetadata objects covering the panorama.

Examples:

>>> # Standard 45° FOV with 50% overlap (16 perspectives)
>>> perspectives = generate_perspectives(fov=45)
>>> # Wide angle for large text (8 perspectives)
>>> perspectives = generate_perspectives(fov=90, resolution=2500)
>>> # Zoomed in for small text (32 perspectives)
>>> perspectives = generate_perspectives(fov=22.5, resolution=1024)
>>> # Cover ceiling and floor too
>>> perspectives = generate_perspectives(fov=60, pitch_angles=[-45, 0, 45])
>>> # Dense coverage with 75% overlap
>>> perspectives = generate_perspectives(fov=45, overlap=0.75)
Source code in src/panoocr/image/perspectives.py
def generate_perspectives(
    fov: float = 45,
    resolution: int = 2048,
    overlap: float = 0.5,
    pitch_angles: Optional[List[float]] = None,
    vertical_fov: Optional[float] = None,
) -> List[PerspectiveMetadata]:
    """Generate a set of perspective views covering 360° horizontally.

    This is the main API for creating custom perspective configurations.

    Args:
        fov: Horizontal field of view in degrees (default: 45°).
        resolution: Pixel width and height of each perspective (default: 2048).
        overlap: Overlap ratio between adjacent perspectives, 0-1 (default: 0.5).
            - 0.0 = no overlap (perspectives touch at edges)
            - 0.5 = 50% overlap (recommended for good coverage)
            - 1.0 = 100% overlap (each point covered by 2 perspectives)
        pitch_angles: List of pitch angles in degrees (default: [0]).
            Use multiple values to cover up/down, e.g., [-30, 0, 30].
        vertical_fov: Vertical field of view in degrees (default: same as fov).

    Returns:
        List of PerspectiveMetadata objects covering the panorama.

    Examples:
        >>> # Standard 45° FOV with 50% overlap (16 perspectives)
        >>> perspectives = generate_perspectives(fov=45)

        >>> # Wide angle for large text (8 perspectives)
        >>> perspectives = generate_perspectives(fov=90, resolution=2500)

        >>> # Zoomed in for small text (32 perspectives)
        >>> perspectives = generate_perspectives(fov=22.5, resolution=1024)

        >>> # Cover ceiling and floor too
        >>> perspectives = generate_perspectives(fov=60, pitch_angles=[-45, 0, 45])

        >>> # Dense coverage with 75% overlap
        >>> perspectives = generate_perspectives(fov=45, overlap=0.75)
    """
    if pitch_angles is None:
        pitch_angles = [0]
    if vertical_fov is None:
        vertical_fov = fov

    # Calculate yaw interval based on FOV and overlap
    # With 50% overlap, interval = FOV / 2
    # With 0% overlap, interval = FOV
    yaw_interval = fov * (1 - overlap)
    if yaw_interval <= 0:
        yaw_interval = fov * 0.1  # Minimum 10% step to avoid infinite loop

    # Generate yaw angles centered at 0
    num_yaw = int(round(360 / yaw_interval))
    yaw_angles = [i * (360 / num_yaw) - 180 for i in range(num_yaw)]

    perspectives = []
    for yaw in yaw_angles:
        for pitch in pitch_angles:
            perspectives.append(
                PerspectiveMetadata(
                    pixel_width=resolution,
                    pixel_height=resolution,
                    horizontal_fov=fov,
                    vertical_fov=vertical_fov,
                    yaw_offset=yaw,
                    pitch_offset=pitch,
                )
            )

    return perspectives

combine_perspectives #

combine_perspectives(*perspective_lists: List[PerspectiveMetadata]) -> List[PerspectiveMetadata]

Combine multiple perspective lists into a single list.

Useful for multi-scale detection at different FOV settings.

Parameters:

Name Type Description Default
*perspective_lists List[PerspectiveMetadata]

Variable number of perspective lists to combine.

()

Returns:

Type Description
List[PerspectiveMetadata]

Combined list of all perspectives.

Source code in src/panoocr/image/perspectives.py
def combine_perspectives(
    *perspective_lists: List[PerspectiveMetadata],
) -> List[PerspectiveMetadata]:
    """Combine multiple perspective lists into a single list.

    Useful for multi-scale detection at different FOV settings.

    Args:
        *perspective_lists: Variable number of perspective lists to combine.

    Returns:
        Combined list of all perspectives.
    """
    combined = []
    for perspective_list in perspective_lists:
        combined.extend(perspective_list)
    return combined

Presets#

Pre-configured perspective sets:

from panoocr import (
    DEFAULT_IMAGE_PERSPECTIVES,
    ZOOMED_IN_IMAGE_PERSPECTIVES,
    ZOOMED_OUT_IMAGE_PERSPECTIVES,
    WIDEANGLE_IMAGE_PERSPECTIVES,
)

DEFAULT_IMAGE_PERSPECTIVES      # 16 perspectives, 45° FOV, 2048x2048
ZOOMED_IN_IMAGE_PERSPECTIVES    # 32 perspectives, 22.5° FOV, 1024x1024
ZOOMED_OUT_IMAGE_PERSPECTIVES   # 12 perspectives, 60° FOV, 2500x2500
WIDEANGLE_IMAGE_PERSPECTIVES    # 8 perspectives, 90° FOV, 2500x2500