Source code for handwriting_features.features.base

import sys
import logging
import numpy
from handwriting_features.features.configuration.settings import HandwritingFeaturesSettings
from handwriting_features.data.descriptors.statistics import Statistics
from handwriting_features.data.containers.sample import HandwritingSampleWrapper
from handwriting_features.features.validation import HandwritingFeaturesFusion, HandwritingFeaturesValidation


# Set the logging configuration
logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)
logger = logging.getLogger("FeatureExtractor")


[docs] class HandwritingFeaturesBase(object): """Base class for handwriting features""" def __init__(self, sample_wrapper, **config): """Constructor method""" # Set the sample and the extractor configuration self.wrapper = sample_wrapper self.config = config if config else {} # Set the features to be skipped during preparation self.skip_features = self.config.pop("skip_features", ("statistics", )) # Set the logging self.logging_settings = self.config.pop("logging_settings", {}) # ------------------------------- # # Alternative constructor methods # # ------------------------------- #
[docs] @classmethod def from_sample(cls, sample, **config): """ Initializes HandwritingFeatures object from a sample. :param sample: handwriting sample object :type sample: HandwritingSample :param config: common configuration :type config: **kwargs :return: HandwritingFeatures object :rtype: HandwritingFeatures """ return cls(HandwritingSampleWrapper(sample), **config)
[docs] @classmethod def from_list(cls, values, labels=None, **config): """ Initializes HandwritingFeatures object from a list. :param values: data values :type values: list :param labels: labels for the data values :type labels: list, optional :param config: common configuration :type config: **kwargs :return: HandwritingFeatures object :rtype: HandwritingFeatures """ return cls(HandwritingSampleWrapper.from_list(values, labels), **config)
[docs] @classmethod def from_numpy_array(cls, values, labels=None, **config): """ Initializes HandwritingFeatures object from a numpy array. :param values: data values :type values: numpy.ndarray :param labels: labels for the data values :type labels: list, optional :param config: common configuration :type config: **kwargs :return: HandwritingFeatures object :rtype: HandwritingFeatures """ return cls(HandwritingSampleWrapper.from_numpy_array(values, labels), **config)
[docs] @classmethod def from_pandas_dataframe(cls, values, labels=None, **config): """ Initializes HandwritingFeatures object from a pandas DataFrame. :param values: data values :type values: pandas.DataFrame :param labels: labels for the data values :type labels: list, optional :param config: common configuration :type config: **kwargs :return: HandwritingFeatures object :rtype: HandwritingFeatures """ return cls(HandwritingSampleWrapper.from_pandas_dataframe(values, labels), **config)
[docs] @classmethod def from_json(cls, path, labels=None, **config): """ Initializes HandwritingFeatures object from a JSON file. :param path: path to a JSON file :type path: str :param labels: labels for the data values :type labels: list, optional :param config: common configuration :type config: **kwargs :return: HandwritingFeatures object :rtype: HandwritingFeatures """ return cls(HandwritingSampleWrapper.from_json(path, labels), **config)
[docs] @classmethod def from_svc(cls, path, labels=None, **config): """ Initializes HandwritingFeatures object from an SVC file. :param path: path to an SVC file :type path: str :param labels: labels for the data values :type labels: list, optional :param config: common configuration :type config: **kwargs :return: HandwritingFeatures object :rtype: HandwritingFeatures """ return cls(HandwritingSampleWrapper.from_svc(path, labels), **config)
# ----------------- # # Computation hooks # # ----------------- #
[docs] @classmethod def inject_common_configuration(cls, method, kwargs, common, skip_features=None): """ Injects the common configuration into the kwargs. :param method: feature computation method to be applied :type method: callable :param kwargs: feature computation-specific kwargs :type kwargs: dict :param skip_features: features to be skipped during injection, defaults to None :type skip_features: Any[list, tuple], optional :param common: common configuration :type common: dict :return: kwargs :rtype: dict """ return HandwritingFeaturesFusion.fuze(method.__name__, kwargs, common, skip_features)
[docs] @classmethod def before_computation(cls, method, kwargs=None, skip_features=None): """ Applies the before-computation hook. :param method: feature computation method to be applied :type method: callable :param kwargs: feature computation-specific kwargs :type kwargs: dict :param skip_features: features to be skipped during validation, defaults to None :type skip_features: Any[list, tuple], optional :return: kwargs :rtype: dict """ return HandwritingFeaturesValidation.validate(method.__name__, kwargs, skip_features)
[docs] @classmethod def after_computation(cls, feature, statistics=None): """ Applies the after-computation hook. :param feature: computed feature :type feature: numpy.ndarray :param statistics: statistics to be computed, defaults to None :type statistics: Any[str, list, tuple], optional :return: features :rtype: numpy.ndarray """ # Handle the statistics options statistics = [statistics] if isinstance(statistics, str) else statistics # Compute the statistics if statistics: feature = numpy.array([Statistics.compute(feature, stat) for stat in statistics]) else: if not isinstance(feature, numpy.ndarray): feature = numpy.array(feature).reshape((1,)) # Return the feature return feature
[docs] def compute(self, sample_wrapper, method, statistics=None, **kwargs): """ Applies the computation (including before-after computational hooks). :param sample_wrapper: sample wrapper object :type sample_wrapper: HandwritingSampleWrapper :param method: feature computation method to be applied :type method: callable :param statistics: statistics to be computed, defaults to None :type statistics: iterable, optional :param kwargs: feature computation-specific kwargs :type kwargs: **kwargs :return: computed features :rtype: numpy.ndarray """ # Prepare the method name method_name = method.__name__ # Prepare the configuration settings = self.inject_common_configuration(method, kwargs, self.config, self.skip_features) # Apply the before-computation hook settings = self.before_computation(method, settings, self.skip_features) # Apply the computation try: features = method(sample_wrapper, **settings) except Exception as e: if self.logging_settings.get("soft_validation"): logger.warning(f"Failure '{method_name}' for {sample_wrapper}: {e.__class__.__name__} - {e}") if HandwritingFeaturesSettings.is_feature_multivalued(method_name): features = numpy.array([]) else: features = numpy.nan else: raise e # Apply the after-computation hook features = self.after_computation(features, statistics) # Return the features return features