Source code for arcade.features.base
"""Base class and registry for feature extractors."""
from __future__ import annotations
import importlib
from abc import ABC, abstractmethod
from typing import Any
import numpy as np
_EXTRACTOR_REGISTRY: dict[str, type["BaseFeatureExtractor"]] = {}
[docs]
def register_extractor(name: str) -> Any:
"""Register a :class:`BaseFeatureExtractor` subclass under *name*.
Args:
name: Registry key used when selecting extractors from config.
Returns:
A class decorator that stores the class in the registry.
"""
def decorator(cls: type["BaseFeatureExtractor"]) -> type["BaseFeatureExtractor"]:
_EXTRACTOR_REGISTRY[name] = cls
cls.name = name
return cls
return decorator
[docs]
def get_extractor(name: str) -> type["BaseFeatureExtractor"]:
"""Resolve an extractor class by registry name or dotted import path.
Args:
name: Registered key or ``package.module.ClassName``.
Returns:
The extractor class.
Raises:
KeyError: Unknown registry name.
TypeError: Dotted path is not a ``BaseFeatureExtractor`` subclass.
"""
if name in _EXTRACTOR_REGISTRY:
return _EXTRACTOR_REGISTRY[name]
if "." in name:
module_path, cls_name = name.rsplit(".", 1)
mod = importlib.import_module(module_path)
cls = getattr(mod, cls_name)
if isinstance(cls, type) and issubclass(cls, BaseFeatureExtractor):
return cls
raise TypeError(f"{name} resolved to {cls!r}, not a BaseFeatureExtractor subclass.")
raise KeyError(
f"Unknown extractor {name!r}. "
f"Registered: {sorted(_EXTRACTOR_REGISTRY)}. "
f"For external extractors, use a dotted import path."
)
[docs]
def list_extractors() -> list[str]:
"""Return sorted names of all registered feature extractors."""
return sorted(_EXTRACTOR_REGISTRY)
[docs]
class BaseFeatureExtractor(ABC):
"""Contract for turning IQ / ADC traces into a feature matrix.
Matched filters, NG-RC polynomials, and path signatures all implement this
interface. Subclass and decorate with :func:`register_extractor`.
Attributes:
name: Registry name (set by the decorator).
feature_kind: Short tag describing the output feature family.
Example::
@register_extractor("my_features")
class MyExtractor(BaseFeatureExtractor):
feature_kind = "custom"
def extract(self, traces, **kwargs):
return np.zeros((len(traces), 10))
"""
name: str = "base"
feature_kind: str = "generic"
[docs]
def fit(self, train_traces: np.ndarray, train_labels: np.ndarray, **kwargs: Any) -> "BaseFeatureExtractor":
"""Optionally fit templates / statistics on training traces.
Args:
train_traces: Training IQ or ADC array (layout is extractor-specific).
train_labels: Integer labels aligned with *train_traces*.
**kwargs: Extractor-specific options.
Returns:
``self``. Default implementation is a no-op.
"""
return self
[docs]
@abstractmethod
def extract(self, traces: np.ndarray, **kwargs: Any) -> np.ndarray:
"""Extract features from traces.
Args:
traces: Input traces; shape depends on extractor type.
**kwargs: Extractor-specific options.
Returns:
Feature matrix shaped ``(n_shots, feature_dim)``.
"""
...