Source code for voxcity.utils.orientation

"""Grid orientation helpers.

Contract:
- Core Phase 3 processing uses uv_m/SOUTH_UP: axis 0 = u/north (row 0 = southern origin edge),
  increasing row index moves north. Columns increase eastward: column 0 is
  west/left and indices increase toward the east/right. All processing functions
  accept and return 2D grids in this orientation unless explicitly documented
  otherwise.
- Visualization utilities may flip vertically for display purposes only.
- 3D indexing follows (row, col, z) = (north→south, west→east, ground→up).

Utilities here are intentionally minimal to avoid introducing hidden behavior.
They can be used at I/O boundaries (e.g., when reading rasters with south_up
conventions) to normalize to the internal orientation.

Coordinate-frame vocabulary (see also voxcity.utils.projector):
  uv cell — cell index (i, j) in uv_m/SOUTH_UP (Phase 3); row 0 is south/origin.
  legacy ij_south — same layout as uv_m/SOUTH_UP; kept for backward compatibility.
  ensure_orientation() is the only legitimate place to convert between the two.
"""

from __future__ import annotations

from typing import Literal
import numpy as np

# Public constants to reference orientation in docs and code
ORIENTATION_NORTH_UP: Literal["north_up"] = "north_up"
ORIENTATION_SOUTH_UP: Literal["south_up"] = "south_up"


[docs] def ensure_orientation( grid: np.ndarray, orientation_in: Literal["north_up", "south_up"], orientation_out: Literal["north_up", "south_up"] = ORIENTATION_NORTH_UP, ) -> np.ndarray: """Return ``grid`` converted from ``orientation_in`` to ``orientation_out``. Both orientations are defined for 2D arrays as: - north_up: row 0 = north/top, last row = south/bottom - south_up: row 0 = south/bottom, last row = north/top If orientations match, the input array is returned unchanged. When converting between north_up and south_up, a vertical flip is applied using ``np.flipud``. Notes ----- - This function does not copy when no conversion is needed. - Use at data boundaries (read/write, interop) rather than deep in processing code. """ if orientation_in == orientation_out: return grid # Only two orientations supported; converting between them is a vertical flip return np.flipud(grid)