enmap module¶
enmap_to_image(dataset, wavelengths=None, method='nearest', output=None, scale_factor=10000.0, rgb_wavelengths=None, **kwargs)
¶
Converts an EnMAP dataset to an RGB image. Automatically masks NoData and scales reflectance if needed.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
dataset |
Union[xr.Dataset, str] |
Path or xarray Dataset. |
required |
wavelengths |
np.ndarray |
Wavelengths to select. |
None |
method |
str |
Selection method ("nearest" by default). |
'nearest' |
output |
str |
File path to save image. If None, returns PIL.Image. |
None |
scale_factor |
float |
Scale factor to convert integer reflectance to 0–1. |
10000.0 |
rgb_wavelengths |
list |
Wavelengths (nm) to use for RGB, e.g., [660, 560, 480]. |
None |
**kwargs |
Any |
Passed to leafmap.array_to_image(). |
{} |
Source code in hypercoast/enmap.py
def enmap_to_image(
dataset: Union[xr.Dataset, str],
wavelengths: Optional[np.ndarray] = None,
method: str = "nearest",
output: Optional[str] = None,
scale_factor: float = 10000.0,
rgb_wavelengths: Optional[List[float]] = None,
**kwargs: Any,
):
"""
Converts an EnMAP dataset to an RGB image.
Automatically masks NoData and scales reflectance if needed.
Args:
dataset (Union[xr.Dataset, str]): Path or xarray Dataset.
wavelengths (np.ndarray, optional): Wavelengths to select.
method (str): Selection method ("nearest" by default).
output (str, optional): File path to save image. If None, returns PIL.Image.
scale_factor (float): Scale factor to convert integer reflectance to 0–1.
rgb_wavelengths (list, optional): Wavelengths (nm) to use for RGB, e.g., [660, 560, 480].
**kwargs: Passed to leafmap.array_to_image().
"""
if isinstance(dataset, str):
dataset = read_enmap(dataset, method=method)
if wavelengths is not None:
dataset = dataset.sel(wavelength=wavelengths, method=method)
# Pick default RGB if not provided
if rgb_wavelengths is None and wavelengths is None:
rgb_wavelengths = [660, 560, 480] # Red, Green, Blue in nm
dataset = dataset.sel(wavelength=rgb_wavelengths, method="nearest")
arr = dataset["reflectance"]
if "_FillValue" in dataset.attrs:
nodata = dataset.attrs["_FillValue"]
arr = arr.where(arr != nodata)
if scale_factor and arr.max() > 1.5:
arr = arr / scale_factor
return array_to_image(
arr,
output=output,
transpose=False,
dtype=np.float32,
**kwargs,
)
extract_enmap(ds, lat, lon)
¶
Extracts EnMAP reflectance spectrum from a given xarray Dataset.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
ds |
xarray.Dataset |
The dataset containing the EnMAP data. |
required |
lat |
float |
The latitude of the point to extract. |
required |
lon |
float |
The longitude of the point to extract. |
required |
Returns:
Type | Description |
---|---|
xarray.DataArray |
The extracted spectrum with wavelength as coordinate. |
Source code in hypercoast/enmap.py
def extract_enmap(ds: xr.Dataset, lat: float, lon: float) -> xr.DataArray:
"""
Extracts EnMAP reflectance spectrum from a given xarray Dataset.
Args:
ds (xarray.Dataset): The dataset containing the EnMAP data.
lat (float): The latitude of the point to extract.
lon (float): The longitude of the point to extract.
Returns:
xarray.DataArray: The extracted spectrum with wavelength as coordinate.
"""
if "crs" not in ds.attrs:
raise ValueError("Dataset has no CRS in attributes.")
crs = ds.attrs["crs"]
x, y = convert_coords([[lat, lon]], "epsg:4326", crs)[0]
values = ds.sel(x=x, y=y, method="nearest")["reflectance"].values
da = xr.DataArray(
values,
dims=["wavelength"],
coords={"wavelength": ds.coords["wavelength"]},
name="reflectance",
)
return da
read_enmap(filepath, wavelengths=None, method='nearest', **kwargs)
¶
Reads EnMAP hyperspectral GeoTIFF (COG) and returns an xarray dataset. If wavelengths are not in the file, they will be extracted from the associated Metadata XML file in the same directory. Masks NoData values as NaN.
Source code in hypercoast/enmap.py
def read_enmap(
filepath: str,
wavelengths: Optional[List[float]] = None,
method: str = "nearest",
**kwargs: Any,
) -> xr.Dataset:
"""
Reads EnMAP hyperspectral GeoTIFF (COG) and returns an xarray dataset.
If wavelengths are not in the file, they will be extracted from the
associated Metadata XML file in the same directory.
Masks NoData values as NaN.
"""
ds = xr.open_dataset(filepath, engine="rasterio")
if wavelengths is None:
if "wavelength" in ds:
wavelength = ds["wavelength"].values.tolist()
elif "wavelength" in ds.attrs:
wavelength = [
float(w) for w in str(ds.attrs["wavelength"]).replace(",", " ").split()
]
else:
folder = os.path.dirname(filepath)
pattern_lower = os.path.join(folder, "*METADATA.xml")
pattern_upper = os.path.join(folder, "*METADATA.XML")
matches = glob.glob(pattern_lower) + glob.glob(pattern_upper)
if not matches:
raise ValueError(
f"No wavelength information in GeoTIFF and no matching METADATA XML found in {folder}"
)
xml_path = matches[0]
wavelength = _extract_enmap_wavelengths_from_xml(xml_path)
wavelength = [round(float(num), 2) for num in wavelength]
cols, rows = ds.x.size, ds.y.size
rio_transform = ds.rio.transform()
xres, _, xmin, _, yres, ymax = list(rio_transform)[:6]
xarr = np.array([xmin + i * xres for i in range(cols)])
yarr = np.array([ymax + i * yres for i in range(rows)])
ds["y"] = xr.DataArray(
yarr,
dims=("y",),
attrs={"units": "m", "standard_name": "projection_y_coordinate"},
)
ds["x"] = xr.DataArray(
xarr,
dims=("x",),
attrs={"units": "m", "standard_name": "projection_x_coordinate"},
)
# Dataset attributes
global_atts = ds.attrs
global_atts["Conventions"] = "CF-1.6"
ds.attrs = {
"units": "unitless",
"_FillValue": 0,
"grid_mapping": "crs",
"standard_name": "reflectance",
"long_name": "surface reflectance",
}
ds.attrs.update(global_atts)
ds = ds.transpose("y", "x", "band")
ds = ds.rename({"band": "wavelength", "band_data": "reflectance"})
ds.coords["wavelength"] = wavelength
if ds.rio.crs:
ds.attrs["crs"] = ds.rio.crs.to_string()
ds.rio.write_transform(rio_transform)
if wavelengths is not None:
ds = ds.sel(wavelength=wavelengths, method=method, **kwargs)
return ds