Skip to content

neon module

This module contains functions to read and process NEON AOP hyperspectral data. More info about the data can be found at https://bit.ly/3Rfszdc. The source code is adapted from https://bit.ly/3KwyZkn. Credit goes to the original authors.

extract_neon(ds, lat, lon)

Extracts NEON AOP data from a given xarray Dataset.

Parameters:

Name Type Description Default
ds xarray.Dataset

The dataset containing the NEON AOP 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 data.

Source code in hypercoast/neon.py
def extract_neon(ds, lat, lon):
    """
    Extracts NEON AOP data from a given xarray Dataset.

    Args:
        ds (xarray.Dataset): The dataset containing the NEON AOP data.
        lat (float): The latitude of the point to extract.
        lon (float): The longitude of the point to extract.

    Returns:
        xarray.DataArray: The extracted data.
    """

    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"]}
    )

    return da

list_neon_datasets(filepath, print_node=False)

Lists all the datasets in an HDF5 file.

Parameters:

Name Type Description Default
filepath str

The path to the HDF5 file.

required
print_node bool

If True, prints the node object of each dataset. If False, prints the name of each dataset. Defaults to False.

False
Source code in hypercoast/neon.py
def list_neon_datasets(filepath: str, print_node: bool = False) -> None:
    """
    Lists all the datasets in an HDF5 file.

    Args:
        filepath (str): The path to the HDF5 file.
        print_node (bool, optional): If True, prints the node object of each dataset.
            If False, prints the name of each dataset. Defaults to False.
    """

    f = h5py.File(filepath, "r")

    if print_node:

        def list_dataset(_, node):
            if isinstance(node, h5py.Dataset):
                print(node)

    else:

        def list_dataset(name, node):
            if isinstance(node, h5py.Dataset):
                print(name)

    f.visititems(list_dataset)

neon_to_image(dataset, wavelengths=None, method='nearest', output=None, **kwargs)

Converts an NEON dataset to an image.

Parameters:

Name Type Description Default
dataset Union[xr.Dataset, str]

The dataset containing the NEON data or the file path to the dataset.

required
wavelengths np.ndarray

The specific wavelengths to select. If None, all wavelengths are selected. Defaults to None.

None
method str

The method to use for data interpolation. Defaults to "nearest".

'nearest'
output str

The file path where the image will be saved. If None, the image will be returned as a PIL Image object. Defaults to None.

None
**kwargs Any

Additional keyword arguments to be passed to leafmap.array_to_image.

{}

Returns:

Type Description
Optional[rasterio.Dataset]

The image converted from the dataset. If output is provided, the image will be saved to the specified file and the function will return None.

Source code in hypercoast/neon.py
def neon_to_image(
    dataset: Union[xr.Dataset, str],
    wavelengths: Optional[np.ndarray] = None,
    method: str = "nearest",
    output: Optional[str] = None,
    **kwargs: Any,
):
    """
    Converts an NEON dataset to an image.

    Args:
        dataset (Union[xr.Dataset, str]): The dataset containing the NEON data
            or the file path to the dataset.
        wavelengths (np.ndarray, optional): The specific wavelengths to select. If None, all
            wavelengths are selected. Defaults to None.
        method (str, optional): The method to use for data interpolation.
            Defaults to "nearest".
        output (str, optional): The file path where the image will be saved. If
            None, the image will be returned as a PIL Image object. Defaults to None.
        **kwargs (Any): Additional keyword arguments to be passed to
            `leafmap.array_to_image`.

    Returns:
        Optional[rasterio.Dataset]: The image converted from the dataset. If
            `output` is provided, the image will be saved to the specified file
            and the function will return None.
    """
    from leafmap import array_to_image

    if isinstance(dataset, str):
        dataset = read_neon(dataset, method=method)

    if wavelengths is not None:
        dataset = dataset.sel(wavelength=wavelengths, method=method)

    return array_to_image(
        dataset["reflectance"],
        output=output,
        transpose=False,
        dtype=np.float32,
        **kwargs,
    )

read_neon(filepath, wavelengths=None, method='nearest', **kwargs)

Reads NEON AOP hyperspectral hdf5 files and returns an xarray dataset.

Parameters:

Name Type Description Default
filepath str

The path to the hdf5 file.

required
wavelengths List[float]

The wavelengths to select. If None, all wavelengths are selected. Defaults to None.

None
method str

The method to use for selection. Defaults to "nearest".

'nearest'
**kwargs Any

Additional arguments to pass to the selection method.

{}

Returns:

Type Description
xr.Dataset

The dataset containing the reflectance data.

Source code in hypercoast/neon.py
def read_neon(
    filepath: str,
    wavelengths: Optional[List[float]] = None,
    method: str = "nearest",
    **kwargs: Any,
) -> xr.Dataset:
    """
    Reads NEON AOP hyperspectral hdf5 files and returns an xarray dataset.

    Args:
        filepath (str): The path to the hdf5 file.
        wavelengths (List[float], optional): The wavelengths to select. If None,
            all wavelengths are selected. Defaults to None.
        method (str, optional): The method to use for selection. Defaults to
            "nearest".
        **kwargs (Any): Additional arguments to pass to the selection method.

    Returns:
        xr.Dataset: The dataset containing the reflectance data.
    """
    with h5py.File(filepath, "r") as f:
        # Extract site code dynamically from NEON HDF file metadata
        # At the root of `keys` NEON stores the site code, which is the `root` folder at the [0] index of object `keys`
        site_code = list(f.keys())[0]

        # Access the reflectance data using the site code
        site_refl = f[site_code]["Reflectance"]

        # Extract wavelengths
        wavelengths_list = site_refl["Metadata"]["Spectral_Data"]["Wavelength"][
            ()
        ].tolist()
        wavelengths_list = [round(num, 2) for num in wavelengths_list]

        # Extract EPSG code
        epsg_code = site_refl["Metadata"]["Coordinate_System"]["EPSG Code"][()]
        epsg_code_number = int(epsg_code.decode("utf-8"))

        # Extract map info
        mapInfo_string = site_refl["Metadata"]["Coordinate_System"]["Map_Info"][
            ()
        ].decode("utf-8")
        mapInfo_split = mapInfo_string.split(",")

        res = float(mapInfo_split[5]), float(mapInfo_split[6])

        # Extract reflectance array and shape
        site_reflArray = site_refl["Reflectance_Data"]
        refl_shape = site_reflArray.shape

        # Calculate coordinates
        xMin = float(mapInfo_split[3])
        yMax = float(mapInfo_split[4])

        xMax = xMin + (refl_shape[1] * res[0])
        yMin = yMax - (refl_shape[0] * res[1])

        # Handle scale factor and no-data value
        scaleFactor = site_reflArray.attrs["Scale_Factor"]
        noDataValue = site_reflArray.attrs["Data_Ignore_Value"]

        da = site_reflArray[:, :, :].astype(float)
        da[da == int(noDataValue)] = np.nan
        da[da < 0] = np.nan
        da[da > 10000] = np.nan
        da = da / scaleFactor

        coords = {
            "y": np.linspace(yMax, yMin, da.shape[0]),
            "x": np.linspace(xMin, xMax, da.shape[1]),
            "wavelength": wavelengths_list,
        }

        xda = xr.DataArray(
            da,
            coords=coords,
            dims=["y", "x", "wavelength"],
            attrs={
                "scale_factor": scaleFactor,
                "no_data_value": noDataValue,
                "crs": f"EPSG:{epsg_code_number}",
                "transform": (res[0], 0.0, xMin, 0.0, -res[1], yMax),
            },
        )

        if wavelengths is not None:
            xda = xda.sel(wavelength=wavelengths, method=method, **kwargs)

        dataset = xda.to_dataset(name="reflectance")
        dataset.attrs = dataset["reflectance"].attrs

    return dataset