import logging
import os
from typing import Any, Dict, List, TYPE_CHECKING
import numpy as np
from osgeo import gdal
from .dataset import RasterDataSet
from .dataset_impl import (
RasterDataImpl,
ENVI_GDALRasterDataImpl,
GTiff_GDALRasterDataImpl,
NumPyRasterDataImpl,
NetCDF_GDALRasterDataImpl,
ASC_GDALRasterDataImpl,
JP2_GDALRasterDataImpl,
PDS3_GDALRasterDataImpl,
PDS4_GDALRasterDataImpl,
GDALRasterDataImpl,
JP2_GDAL_PDR_RasterDataImpl,
)
from wiser.gui.fits_loading_dialog import FitsDatasetLoadingDialog
from PySide2.QtWidgets import QDialog
if TYPE_CHECKING:
from wiser.raster.dataset import DataCache
logger = logging.getLogger(__name__)
[docs]
class RasterDataLoader:
"""
A loader for loading 2D raster data-sets from the local filesystem, using
GDAL (Geospatial Data Abstraction Library) for reading the data.
"""
def __init__(self):
# File formats that we recognize.
self._formats = {
"ENVI": ENVI_GDALRasterDataImpl,
"GTiff": GTiff_GDALRasterDataImpl,
"NetCDF": NetCDF_GDALRasterDataImpl,
"ASCII": ASC_GDALRasterDataImpl,
"JP2": JP2_GDALRasterDataImpl,
"PDS3": PDS3_GDALRasterDataImpl,
"PDS4": PDS4_GDALRasterDataImpl,
}
# What to do when loading in each file format
self._format_loaders = {
ENVI_GDALRasterDataImpl: self.load_normal_dataset,
GTiff_GDALRasterDataImpl: self.load_normal_dataset,
NetCDF_GDALRasterDataImpl: self.load_normal_dataset,
ASC_GDALRasterDataImpl: self.load_normal_dataset,
JP2_GDALRasterDataImpl: self.load_normal_dataset,
PDS3_GDALRasterDataImpl: self.load_normal_dataset,
PDS4_GDALRasterDataImpl: self.load_normal_dataset,
GDALRasterDataImpl: self.load_normal_dataset,
}
# This is a counter so we can generate names for unnamed datasets.
self._unnamed_datasets: int = 0
[docs]
def load_normal_dataset(self, impl: RasterDataImpl, data_cache: "DataCache") -> List[RasterDataSet]:
"""
The normal way to load in a dataset
"""
# This returns a list because load_FITS_dataset could possibly return a list
return [RasterDataSet(impl, data_cache)]
def load_FITS_dataset(self, impl: RasterDataImpl, data_cache: "DataCache") -> List[RasterDataSet]:
# We should show the Fits dialog which should return to us
self._fits_dialog = FitsDatasetLoadingDialog(impl, data_cache)
result = self._fits_dialog.exec()
if result == QDialog.Accepted:
return self._fits_dialog.return_datasets
return []
[docs]
def load_from_file(
self, path, data_cache=None, interactive=True, subdataset_name=""
) -> List[RasterDataSet]:
"""
Load a raster data-set from the specified path. Returns a
list of :class:`RasterDataSet` object.
"""
if not os.path.exists(path):
raise FileNotFoundError(f"File path {path} does not exist!")
# Iterate through all supported formats, and try to use each one to
# load the raster data.
impl_list = None
for driver_name, impl_type in self._formats.items():
try:
if subdataset_name:
impl_list = impl_type.try_load_file(
path, subdataset_name=subdataset_name, interactive=interactive
)
else:
impl_list = impl_type.try_load_file(path, interactive=interactive)
except Exception as e:
logger.debug(
"Couldn't load file %s with driver %s and implementation %s. Error: %s",
path,
driver_name,
impl_type,
e,
)
# Try luck with gdal
try:
if impl_list is None:
impl_list = GDALRasterDataImpl.try_load_file(path, interactive=interactive)
except Exception as e:
logger.debug(
f"Couldn't load file {path} with driver " + f"{driver_name} and implementation {impl_type}.",
e,
)
if impl_list is None:
raise Exception(f"Couldn't load file {path}: unsupported format")
# Used if a dataset contains multiple subdatasets and we want to load all of them
outer_datasets = []
for impl in impl_list:
func = self._format_loaders[type(impl)]
datasets = func(impl, data_cache)
for ds in datasets:
files = ds.get_filepaths()
if files:
name = os.path.basename(files[0])
else:
name = os.path.basename(path)
subdataset_name = ds.get_subdataset_name()
if subdataset_name is not None:
name += ":" + subdataset_name.split(":")[-1]
ds.set_name(name)
outer_datasets.append(ds)
return outer_datasets
def get_save_filenames(self, path: str, format: str = "ENVI") -> List[str]:
if format == "ENVI":
return ENVI_GDALRasterDataImpl.get_save_filenames(path)
else:
raise ValueError(f'Unsupported format "{format}"')
def save_dataset_as(
self, dataset: RasterDataSet, path: str, format: str, config: Dict[str, Any]
) -> ENVI_GDALRasterDataImpl:
if format == "ENVI":
return ENVI_GDALRasterDataImpl.save_dataset_as(dataset, path, config)
else:
raise ValueError(f'Unsupported format "{format}"')
[docs]
def dataset_from_numpy_array(self, arr: np.ndarray, cache: "DataCache" = None) -> RasterDataSet:
"""
Given a NumPy ndarray, this function returns a RasterDataSet object
that uses the array for its raster data. The input ndarray must have
three dimensions; they are interpreted as
[spectral][spatial_y][spatial_x].
Raises a ValueError if the input array doesn't have 3 dimensions.
"""
if len(arr.shape) != 3:
raise ValueError("NumPy array must have 3 dimensions")
impl = NumPyRasterDataImpl(arr)
return RasterDataSet(impl, cache)
def dataset_from_gdal_dataset(self, dataset: gdal.Dataset, cache: "DataCache") -> RasterDataSet:
impl = ENVI_GDALRasterDataImpl(dataset)
return RasterDataSet(impl, cache)
# TODO(donnie): Not presently needed - can instantiate a NumPyArraySpectrum
# object from a NumPy array...
# def spectrum_from_numpy_array(self, arr: np.ndarray) -> Spectrum:
# return None