Skip to content

feat(dataset): expose overviews as Dataset objects (get_overview returns a raw gdal.Band) #784

Description

@MAfarrag

Context

get_overview() returns a raw gdal.Band. A TODO in the code wants it to return a pyramids Dataset instead, so an
overview level is a first-class pyramids object (with CRS / geotransform / nodata / plot / to_file / crop / reproject)
rather than a low-level GDAL band.

Verified not implemented: the method still returns gdal.Band, and nothing elsewhere wraps an overview as a
Dataset. TODO location: src/pyramids/dataset/engines/io.py:3003-3004 (inside get_overview).

Related to #783 (also overview-area, in recreate_overviews) but independent — that one is a perf change, this one is an
API/ergonomics change.

Problem / Current Behaviour

get_overview() hands back a bare gdal.Band:

# src/pyramids/dataset/engines/io.py  (get_overview, ~L2995-3005)
band_obj = self._ds._iloc(band)
...
# TODO: create a Dataset from the overview band and return the Dataset instead of the gdal band
return band_obj.GetOverview(overview_index)          # -> gdal.Band

A gdal.Band carries no geotransform / CRS / nodata and none of the pyramids Dataset behaviour. To do anything
pyramids-y with an overview (plot it, save it, crop / reproject it), the caller must drop to raw GDAL or go through
read_overview_array() (which returns a bare numpy array). This is inconsistent with the rest of the API, where
operations return Dataset objects.

The gap: a GDAL overview is a band inside the parent dataset, not a standalone gdal.Dataset — so returning a
Dataset requires reconstructing one (the "find a way" the TODO is stuck on).

Affected locations

File Symbol Notes
src/pyramids/dataset/engines/io.py get_overview (~L2995), TODO L3003-3004 returns gdal.Band
src/pyramids/dataset/dataset.py Dataset.get_overview facade (L1139) delegates to the engine
src/pyramids/dataset/abstract_dataset.py get_overview (L1237) -> gdal.Band annotation
src/pyramids/base/protocols.py get_overview (L252) protocol stub
tests/dataset/spatial/test_overviews.py test_get_overview (L29) asserts isinstance(ovr, gdal.Band)

Motivation Example

from pyramids.dataset import Dataset
ds = Dataset.read_file("dem.tif", read_only=False)
ds.create_overviews()

# desired: an overview level as a full pyramids Dataset
ov = ds.get_overview_dataset(band=0, overview_index=1)   # <- proposed
ov.plot()                     # works, like any Dataset
ov.to_file("dem_ov1.tif")     # correct scaled cell size + CRS + nodata preserved
print(ov.cell_size, ov.epsg)  # today: impossible — get_overview returns a raw gdal.Band

Proposed Solution

Add a new accessor rather than change get_overview's return type (that is a breaking change — see Out of Scope):

def get_overview_dataset(self, band: int = 0, overview_index: int = 0) -> "Dataset":
    """Return an overview level as a standalone pyramids Dataset."""

Two viable ways to build the Dataset (pick per source):

  • File-backed: gdal.OpenEx(path, open_options=["OVERVIEW_LEVEL=<i>"]) — GDAL's native way to open an overview level
    as a full dataset; wrap the result in Dataset.
  • In-memory / general: read the overview array and rebuild via Dataset.create_from_array(...) with the origin from
    the parent's top-left corner and the cell size scaled by the decimation factor
    (cell_size × (parent_xsize / overview_xsize)), carrying the parent CRS and nodata.

Either way, preserve the existing ValueErrors (no overviews built; overview_index out of range) and update the stale
TODO.

Out of Scope

Effort Estimate

Size: S (half-day)
Rationale: new method + geotransform-scaling reconstruction + tests for shape/cell-size/CRS/nodata and error paths;
no changes to existing call sites.

Definition of Done

  • New accessor returns a Dataset wrapping the requested overview, with correct geotransform (cell size scaled by
    the decimation factor), CRS, and nodata.
  • get_overview's gdal.Band contract is unchanged (test_get_overview still passes).
  • Tests: the overview Dataset has the expected shape, cell size, CRS, and values; no overviews and
    overview_index out-of-range still raise ValueError.
  • The stale TODO at io.py:3003-3004 is removed/updated.
  • Docstrings / relevant docs updated; existing overview tests continue to pass.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions