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
Context
get_overview()returns a rawgdal.Band. ATODOin the code wants it to return a pyramidsDatasetinstead, so anoverview 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 aDataset. TODO location:src/pyramids/dataset/engines/io.py:3003-3004(insideget_overview).Related to #783 (also overview-area, in
recreate_overviews) but independent — that one is a perf change, this one is anAPI/ergonomics change.
Problem / Current Behaviour
get_overview()hands back a baregdal.Band:A
gdal.Bandcarries no geotransform / CRS / nodata and none of the pyramidsDatasetbehaviour. To do anythingpyramids-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, whereoperations return
Datasetobjects.The gap: a GDAL overview is a band inside the parent dataset, not a standalone
gdal.Dataset— so returning aDatasetrequires reconstructing one (the "find a way" the TODO is stuck on).Affected locations
src/pyramids/dataset/engines/io.pyget_overview(~L2995), TODO L3003-3004gdal.Bandsrc/pyramids/dataset/dataset.pyDataset.get_overviewfacade (L1139)src/pyramids/dataset/abstract_dataset.pyget_overview(L1237)-> gdal.Bandannotationsrc/pyramids/base/protocols.pyget_overview(L252)tests/dataset/spatial/test_overviews.pytest_get_overview(L29)isinstance(ovr, gdal.Band)Motivation Example
Proposed Solution
Add a new accessor rather than change
get_overview's return type (that is a breaking change — see Out of Scope):Two viable ways to build the Dataset (pick per source):
gdal.OpenEx(path, open_options=["OVERVIEW_LEVEL=<i>"])— GDAL's native way to open an overview levelas a full dataset; wrap the result in
Dataset.Dataset.create_from_array(...)with the origin fromthe 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_indexout of range) and update the staleTODO.
Out of Scope
get_overview's existinggdal.Bandcontract — keep it; add a separate Dataset accessor. Flipping thereturn type would break the pinned test (
test_get_overview),read_overview_array,recreate_overviews, and thedocs notebook.
recreate_overviewsperformance (tracked in perf(dataset): batch overview regeneration in recreate_overviews via gdal.RegenerateOverviews #783).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
Datasetwrapping the requested overview, with correct geotransform (cell size scaled bythe decimation factor), CRS, and nodata.
get_overview'sgdal.Bandcontract is unchanged (test_get_overviewstill passes).Datasethas the expected shape, cell size, CRS, and values;no overviewsandoverview_indexout-of-range still raiseValueError.io.py:3003-3004is removed/updated.