Skip to content

registry module

Sensor registry for common HyperCoast workflows.

SensorHandler dataclass

Describe a HyperCoast sensor workflow.

Parameters:

Name Type Description Default
name str

Canonical sensor name.

required
read Optional[Callable[..., xr.Dataset]]

Function used to read local sensor data.

None
to_image Optional[Callable[..., Any]]

Function used to convert sensor data to an image.

None
extract Optional[Callable[..., xr.DataArray]]

Function used to extract a point spectrum.

None
search Optional[Callable[..., Any]]

Function used to search remote sensor data.

None
download Optional[Callable[..., Any]]

Function used to download remote sensor data.

None
aliases tuple[str, ...]

Additional case-insensitive names accepted for the sensor.

()
extensions tuple[str, ...]

Supported file extensions.

()
default_rgb tuple[float, float, float]

Default RGB wavelengths in nanometers.

()
default_variable str

Default raster variable name.

''
crs str

Expected CRS behavior or CRS default.

''
qgis bool

Whether the sensor should be exposed in the QGIS plugin.

True
qgis_name str

Optional QGIS display key.

''
sample_data Dict[str, str]

Optional sample dataset metadata.

<factory>
description str

Short human-readable description.

''
Source code in hypercoast/registry.py
@dataclass(frozen=True)
class SensorHandler:
    """Describe a HyperCoast sensor workflow.

    Args:
        name: Canonical sensor name.
        read: Function used to read local sensor data.
        to_image: Function used to convert sensor data to an image.
        extract: Function used to extract a point spectrum.
        search: Function used to search remote sensor data.
        download: Function used to download remote sensor data.
        aliases: Additional case-insensitive names accepted for the sensor.
        extensions: Supported file extensions.
        default_rgb: Default RGB wavelengths in nanometers.
        default_variable: Default raster variable name.
        crs: Expected CRS behavior or CRS default.
        qgis: Whether the sensor should be exposed in the QGIS plugin.
        qgis_name: Optional QGIS display key.
        sample_data: Optional sample dataset metadata.
        description: Short human-readable description.
    """

    name: str
    read: Optional[Callable[..., xr.Dataset]] = None
    to_image: Optional[Callable[..., Any]] = None
    extract: Optional[Callable[..., xr.DataArray]] = None
    search: Optional[Callable[..., Any]] = None
    download: Optional[Callable[..., Any]] = None
    aliases: tuple[str, ...] = ()
    extensions: tuple[str, ...] = ()
    default_rgb: tuple[float, float, float] = ()
    default_variable: str = ""
    crs: str = ""
    qgis: bool = True
    qgis_name: str = ""
    sample_data: Dict[str, str] = field(default_factory=dict)
    description: str = ""

    def as_dict(self) -> Dict[str, Any]:
        """Return serializable sensor metadata.

        Returns:
            dict: Sensor metadata excluding function objects.
        """
        data = asdict(self)
        for key in ("read", "to_image", "extract", "search", "download"):
            data.pop(key, None)
        data["has_reader"] = self.read is not None
        data["has_image_converter"] = self.to_image is not None
        data["has_extractor"] = self.extract is not None
        data["has_search"] = self.search is not None
        data["has_download"] = self.download is not None
        data["qgis_name"] = self.qgis_name or self.name
        return data

as_dict(self)

Return serializable sensor metadata.

Returns:

Type Description
dict

Sensor metadata excluding function objects.

Source code in hypercoast/registry.py
def as_dict(self) -> Dict[str, Any]:
    """Return serializable sensor metadata.

    Returns:
        dict: Sensor metadata excluding function objects.
    """
    data = asdict(self)
    for key in ("read", "to_image", "extract", "search", "download"):
        data.pop(key, None)
    data["has_reader"] = self.read is not None
    data["has_image_converter"] = self.to_image is not None
    data["has_extractor"] = self.extract is not None
    data["has_search"] = self.search is not None
    data["has_download"] = self.download is not None
    data["qgis_name"] = self.qgis_name or self.name
    return data

download_sensor(sensor, items, **kwargs)

Download remote data using the registered download function.

Parameters:

Name Type Description Default
sensor str

Sensor name or alias.

required
items Iterable[Any]

Granules or STAC items accepted by the download function.

required
**kwargs Any

Download keyword arguments.

{}

Returns:

Type Description
Any

Download function result.

Exceptions:

Type Description
NotImplementedError

If the sensor has no registered download function.

Source code in hypercoast/registry.py
def download_sensor(sensor: str, items: Iterable[Any], **kwargs: Any) -> Any:
    """Download remote data using the registered download function.

    Args:
        sensor: Sensor name or alias.
        items: Granules or STAC items accepted by the download function.
        **kwargs: Download keyword arguments.

    Returns:
        Any: Download function result.

    Raises:
        NotImplementedError: If the sensor has no registered download function.
    """
    handler = get_sensor(sensor)
    if handler.download is None:
        raise NotImplementedError(
            f"{handler.name} has no registered download function."
        )
    return handler.download(items, **kwargs)

extract_sensor(sensor, data, lat, lon, **kwargs)

Extract a point spectrum using the registered extractor.

Parameters:

Name Type Description Default
sensor str

Sensor name or alias.

required
data Any

Dataset or local path accepted by the sensor reader.

required
lat float

Latitude of the point to extract.

required
lon float

Longitude of the point to extract.

required
**kwargs Any

Additional extractor keyword arguments.

{}

Returns:

Type Description
xr.DataArray

Extracted spectrum.

Exceptions:

Type Description
NotImplementedError

If the sensor has no registered extractor.

Source code in hypercoast/registry.py
def extract_sensor(
    sensor: str,
    data: Any,
    lat: float,
    lon: float,
    **kwargs: Any,
) -> xr.DataArray:
    """Extract a point spectrum using the registered extractor.

    Args:
        sensor: Sensor name or alias.
        data: Dataset or local path accepted by the sensor reader.
        lat: Latitude of the point to extract.
        lon: Longitude of the point to extract.
        **kwargs: Additional extractor keyword arguments.

    Returns:
        xr.DataArray: Extracted spectrum.

    Raises:
        NotImplementedError: If the sensor has no registered extractor.
    """
    handler = get_sensor(sensor)
    if handler.extract is None:
        raise NotImplementedError(f"{handler.name} has no registered extractor.")
    if isinstance(data, (str, bytes, os.PathLike)):
        data = read_sensor(sensor, data)
    return handler.extract(data, lat, lon, **kwargs)

get_sensor(name)

Return the handler for a sensor name or alias.

Parameters:

Name Type Description Default
name str

Sensor name or alias.

required

Returns:

Type Description
SensorHandler

Matching sensor handler.

Exceptions:

Type Description
KeyError

If the sensor is not registered.

Source code in hypercoast/registry.py
def get_sensor(name: str) -> SensorHandler:
    """Return the handler for a sensor name or alias.

    Args:
        name: Sensor name or alias.

    Returns:
        SensorHandler: Matching sensor handler.

    Raises:
        KeyError: If the sensor is not registered.
    """
    key = _normalize_name(name)
    if key not in _ALIASES:
        available = ", ".join(list_sensors())
        raise KeyError(f"Unsupported sensor '{name}'. Available sensors: {available}")
    return SENSOR_REGISTRY[_ALIASES[key]]

list_sensors()

Return registered sensor names.

Returns:

Type Description
list[str]

Sorted canonical sensor names.

Source code in hypercoast/registry.py
def list_sensors() -> List[str]:
    """Return registered sensor names.

    Returns:
        list[str]: Sorted canonical sensor names.
    """
    return sorted(SENSOR_REGISTRY)

qgis_data_types(include_generic=True)

Return QGIS data-type metadata derived from the registry.

Parameters:

Name Type Description Default
include_generic bool

Whether to include the generic fallback type.

True

Returns:

Type Description
dict

Mapping compatible with the QGIS plugin DATA_TYPES table.

Source code in hypercoast/registry.py
def qgis_data_types(include_generic: bool = True) -> Dict[str, Dict[str, Any]]:
    """Return QGIS data-type metadata derived from the registry.

    Args:
        include_generic: Whether to include the generic fallback type.

    Returns:
        dict: Mapping compatible with the QGIS plugin ``DATA_TYPES`` table.
    """
    data_types = {}
    for handler in SENSOR_REGISTRY.values():
        if not handler.qgis:
            continue
        key = handler.qgis_name or handler.name
        data_types[key] = {
            "extensions": list(handler.extensions),
            "description": handler.description,
            "variable": handler.default_variable or "data",
            "default_rgb": list(handler.default_rgb),
        }
    if include_generic:
        data_types["Generic"] = {
            "extensions": [".tif", ".tiff", ".nc", ".nc4"],
            "description": "Generic Hyperspectral (GeoTIFF/NetCDF)",
            "variable": "data",
            "default_rgb": [650, 550, 450],
        }
    return data_types

read_sensor(sensor, source, **kwargs)

Read sensor data using the registered reader.

Parameters:

Name Type Description Default
sensor str

Sensor name or alias.

required
source Any

Local source path or object accepted by the sensor reader.

required
**kwargs Any

Additional reader keyword arguments.

{}

Returns:

Type Description
xr.Dataset

Loaded sensor dataset.

Exceptions:

Type Description
NotImplementedError

If the sensor has no registered reader.

Source code in hypercoast/registry.py
def read_sensor(sensor: str, source: Any, **kwargs: Any) -> xr.Dataset:
    """Read sensor data using the registered reader.

    Args:
        sensor: Sensor name or alias.
        source: Local source path or object accepted by the sensor reader.
        **kwargs: Additional reader keyword arguments.

    Returns:
        xr.Dataset: Loaded sensor dataset.

    Raises:
        NotImplementedError: If the sensor has no registered reader.
    """
    handler = get_sensor(sensor)
    if handler.read is None:
        raise NotImplementedError(f"{handler.name} has no registered reader.")
    return handler.read(source, **kwargs)

register_sensor(handler)

Register a sensor handler.

Parameters:

Name Type Description Default
handler SensorHandler

The sensor handler to register.

required

Returns:

Type Description
SensorHandler

The registered handler.

Source code in hypercoast/registry.py
def register_sensor(handler: SensorHandler) -> SensorHandler:
    """Register a sensor handler.

    Args:
        handler: The sensor handler to register.

    Returns:
        SensorHandler: The registered handler.
    """
    key = _normalize_name(handler.name)
    SENSOR_REGISTRY[key] = handler
    _ALIASES[key] = key
    for alias in handler.aliases:
        _ALIASES[_normalize_name(alias)] = key
    return handler

registry_as_dict()

Return serializable metadata for all registered sensors.

Returns:

Type Description
dict

Mapping of sensor names to metadata dictionaries.

Source code in hypercoast/registry.py
def registry_as_dict() -> Dict[str, Dict[str, Any]]:
    """Return serializable metadata for all registered sensors.

    Returns:
        dict: Mapping of sensor names to metadata dictionaries.
    """
    return {name: SENSOR_REGISTRY[name].as_dict() for name in list_sensors()}

search_sensor(sensor, **kwargs)

Search remote data using the registered search function.

Parameters:

Name Type Description Default
sensor str

Sensor name or alias.

required
**kwargs Any

Search keyword arguments.

{}

Returns:

Type Description
Any

Search function result.

Exceptions:

Type Description
NotImplementedError

If the sensor has no registered search function.

Source code in hypercoast/registry.py
def search_sensor(sensor: str, **kwargs: Any) -> Any:
    """Search remote data using the registered search function.

    Args:
        sensor: Sensor name or alias.
        **kwargs: Search keyword arguments.

    Returns:
        Any: Search function result.

    Raises:
        NotImplementedError: If the sensor has no registered search function.
    """
    handler = get_sensor(sensor)
    if handler.search is None:
        raise NotImplementedError(f"{handler.name} has no registered search function.")
    return handler.search(**kwargs)

sensor_to_image(sensor, data, output=None, **kwargs)

Convert sensor data to an image using the registered converter.

Parameters:

Name Type Description Default
sensor str

Sensor name or alias.

required
data Any

Dataset or local path accepted by the image converter.

required
output Optional[str]

Optional output image path.

None
**kwargs Any

Additional image converter keyword arguments.

{}

Returns:

Type Description
Any

The sensor converter return value.

Exceptions:

Type Description
NotImplementedError

If the sensor has no registered image converter.

Source code in hypercoast/registry.py
def sensor_to_image(
    sensor: str,
    data: Any,
    output: Optional[str] = None,
    **kwargs: Any,
):
    """Convert sensor data to an image using the registered converter.

    Args:
        sensor: Sensor name or alias.
        data: Dataset or local path accepted by the image converter.
        output: Optional output image path.
        **kwargs: Additional image converter keyword arguments.

    Returns:
        Any: The sensor converter return value.

    Raises:
        NotImplementedError: If the sensor has no registered image converter.
    """
    handler = get_sensor(sensor)
    if handler.to_image is None:
        raise NotImplementedError(f"{handler.name} has no registered image converter.")
    return handler.to_image(data, output=output, **kwargs)