Source code for vbi.feature_extraction.features_settings

import json
import vbi
import types
from copy import deepcopy
from typing import Union

import inspect
import importlib


[docs] def load_json(path): """ Load json file Parameters ---------- path : string Path to json file Returns ------- json_data : dictionary Dictionary with the json data """ with open(path) as json_file: json_data = json.load(json_file) return json_data
[docs] def get_features_by_domain(domain=None, json_path=None): """ Create a dictionary of features in given domain(s). Parameters ---------- domain : list of strings or None, optional List of domains of features to extract. If None, all domains are returned. Valid domains are: 'hmm', 'spectral', 'connectivity', 'temporal', 'statistical', 'information', 'catch22'. json_path : string or None, optional Path to json file containing feature definitions. If None, uses the default features.json file in the package. Returns ------- dict Dictionary of features filtered by the specified domain(s). Keys are domain names, values are dictionaries of features in that domain. """ _domains = [ "hmm", "spectral", "connectivity", "temporal", "statistical", "information", 'catch22', ] if json_path is None: json_path = vbi.__path__[0] + "/feature_extraction/features.json" if not isinstance(domain, (list, tuple)): domain = [domain] domain = list(set(domain)) domain = [d.lower() for d in domain if d is not None] # lower case # for d in domain: # if d not in valid_domains: # raise SystemExit( # f'Domain not valid. Please choose between: {" ".join(valid_domains)}') dict_features = load_json(json_path) if len(domain) == 0: return dict_features for d in _domains: if d not in domain: dict_features.pop(d) return dict_features
[docs] def get_features_by_given_names(cfg, names=None): """ Filter features by given names from cfg (a dictionary of features). Parameters ---------- cfg : dict Dictionary of features organized by domain. Each domain contains features as key-value pairs. names : list of strings, tuple of strings, string, or None, optional Names of features to extract. Can be a single name (string) or multiple names (list/tuple). If None, returns the original cfg. Names are case-insensitive. Returns ------- dict Dictionary of features filtered by the specified names. Structure matches input cfg but only contains features with matching names. Notes ----- If a feature name is not found in the available features, a warning message is printed but processing continues. """ cfg = deepcopy(cfg) if names is None: return cfg if not isinstance(names, (list, tuple)): names = [names] names = [n.lower() for n in names] # lower case # check if names are valid avail_names = [] for d in cfg: avail_names += list(cfg[d].keys()) for n in names: if n not in avail_names: print(f"Warning: {n} is not a valid in provided feature names.") # filter cfg for d in cfg: for f in list(cfg[d].keys()): if f not in names: cfg[d].pop(f) return cfg
[docs] def get_features_by_tag(tag=None, json_path=None): #! TODO: not tested """ Create a dictionary of features in given tag. Parameters ---------- tag : string or None, optional Tag of features to extract. Valid tags include "fmri", "audio", "eeg", "ecg". If None, returns all features. json_path : string or None, optional Path to json file containing feature definitions. If None, uses the default features.json file in the package. Returns ------- dict Dictionary of features filtered by the specified tag. Keys are domain names, values are dictionaries of features in that domain that match the tag. Empty domains are removed from the result. Raises ------ SystemExit If tag is not one of the valid options: "audio", "eeg", "ecg", or None. """ available_tags = ["fmri", "audio", "eeg", "ecg", None] if path is None: path = vbi.__path__[0] + "/feature_extraction/features.json" if tag not in ["audio", "eeg", "ecg", None]: raise SystemExit( "Tag not valid. Please choose between: audio, eeg, ecg or None" ) features_tag = {} dict_features = load_json(json_path) if tag is None: return dict_features else: for domain in dict_features: features_tag[domain] = {} for feat in dict_features[domain]: if dict_features[domain][feat]["use"] == "no": continue # Check if tag is defined try: js_tag = dict_features[domain][feat]["tag"] if isinstance(js_tag, list): if any([tag in js_t for js_t in js_tag]): features_tag[domain].update( {feat: dict_features[domain][feat]} ) elif js_tag == tag: features_tag[domain].update({feat: dict_features[domain][feat]}) except KeyError: continue # To remove empty dicts return dict( [ [d, features_tag[d]] for d in list(features_tag.keys()) if bool(features_tag[d]) ] )
[docs] def add_feature( cfg, domain, name, function: str = None, features_path: Union[str, types.ModuleType] = None, # str or module parameters={}, tag=None, description="", ): """ Add a feature to the cfg dictionary Parameters ---------- cfg : dictionary Dictionary of features domain : string Domain of the feature name : string Name of the feature function : function Function to compute the feature parameters : dictionary Parameters of the feature tag : string Tag of the feature description : string Description of the feature Returns ------- cfg : dictionary Updated dictionary of features """ if isinstance(features_path, str): features_path = __import__(features_path) _path = features_path.__file__ # _path = getattr(feature_path, name) if function is None: function = name if domain not in cfg: cfg[domain] = {} cfg[domain][name] = {} cfg[domain][name]["parameters"] = parameters cfg[domain][name]["tag"] = tag cfg[domain][name]["description"] = description cfg[domain][name]["use"] = "yes" cfg[domain][name]["function"] = function cfg["features_path"] = _path # function.__module__ + "." + function.__name__ return cfg
[docs] def add_features_from_json(json_path, features_path, fea_dict={}): """ Add features from json file to cfg dictionary. Parameters ---------- json_path : string Path to json file containing feature definitions to load. features_path : string or module Path to the module containing the feature functions, or the module object itself. fea_dict : dict, optional Dictionary of features to add to. If empty, a new dictionary is created. Default is {}. Returns ------- dict Dictionary containing all features from the json file added to the input fea_dict. Notes ----- TODO: Check if features already exist in fea_dict to avoid conflicts. TODO: Check for conflicts in parameters and function definitions. """ #! TODO: if fea_dict is not empty, check if the feature is already in the dict #! check also for conflicts in the parameters, and function if json_path is None: json_path = vbi.__path__[0] + "/feature_extraction/features.json" dict_features = load_json(json_path) for domain in dict_features: for feat in dict_features[domain]: use = ( dict_features[domain][feat]["use"] if "use" in dict_features[domain][feat] else "yes" ) tag = ( dict_features[domain][feat]["tag"] if "tag" in dict_features[domain][feat] else "all" ) description = ( dict_features[domain][feat]["description"] if "description" in dict_features[domain][feat]["description"] else "" ) if use == "no": continue fea_dict = add_feature( fea_dict, domain=domain, name=feat, features_path=features_path, parameters=dict_features[domain][feat]["parameters"], tag=tag, description=description, ) return fea_dict
[docs] class Data_F(object): """ Data container class for feature extraction results. A simple container to store feature values along with their labels and additional information. Parameters ---------- values : array-like or None, optional Feature values, typically a numpy array or list. Default is None. labels : array-like or None, optional Labels corresponding to the feature values. Default is None. info : dict or None, optional Additional information about the features (e.g., parameters used, feature names, etc.). Default is None. Attributes ---------- values : array-like or None The feature values. labels : array-like or None The feature labels. info : dict or None Additional feature information. """
[docs] def __init__(self, values=None, labels=None, info=None): self.values = values self.labels = labels self.info = info
def __repr__(self): return f"Data_F(values={self.values}, labels={self.labels}, info={self.info})" def __str__(self): return f"Data_F(values={self.values}, labels={self.labels}, info={self.info})"
[docs] def update_cfg(cfg: dict, name: str, parameters: dict): """ Set parameters of a feature in the configuration dictionary. Parameters ---------- cfg : dict Dictionary of features organized by domain. Each domain contains features as key-value pairs. name : str Name of the feature to update. parameters : dict Parameters as key-value pairs to set for the feature. Returns ------- dict Updated dictionary of features with the specified feature's parameters modified. Notes ----- This function searches through all domains to find the feature by name and updates its parameters. If the feature is not found, the cfg is returned unchanged. """ # find domain of giving feature domain = None for d in cfg: if name in cfg[d]: domain = d break if domain is None: raise SystemExit(f"Feature {name} not found in the dictionary") _params = cfg[domain][name]["parameters"] for p in parameters: # check if parameter is valid if p not in _params: raise SystemExit(f"Parameter {p} not valid for feature {name}") _params[p] = parameters[p] return cfg
# not used in the code
[docs] def select_features_by_domain(module_name, domain): """ Select functions from a module that belong to a specific domain. Note: This function is not currently used in the codebase. Parameters ---------- module_name : str Name of the module to inspect for functions. domain : str Domain name to filter functions by. Functions must have a 'domain' attribute containing this value. Returns ------- list List of function objects that have the specified domain in their 'domain' attribute. """ selected_functions = [] module = importlib.import_module(module_name) functions = inspect.getmembers(module, inspect.isfunction) for name, f in functions: if hasattr(f, "domain"): domains = getattr(f, "domain") if domain in domains: selected_functions.append(f) return selected_functions
# not used in the code
[docs] def select_functions_by_tag(module_name, tag): """ Select functions from a module that have a specific tag. Note: This function is not currently used in the codebase. Parameters ---------- module_name : str Name of the module to inspect for functions. tag : str Tag name to filter functions by. Functions must have a 'tag' attribute containing this value. Returns ------- list List of function objects that have the specified tag in their 'tag' attribute. """ selected_functions = [] module = importlib.import_module(module_name) functions = inspect.getmembers(module, inspect.isfunction) for name, f in functions: if hasattr(f, "tag"): tags = getattr(f, "tag") if tag in tags: selected_functions.append(f) return selected_functions
# not used in the code
[docs] def select_functions_by_domain_and_tag(module_name, domain=None, tag=None): """ Select functions from a module that match both domain and tag criteria. Note: This function is not currently used in the codebase. Parameters ---------- module_name : str Name of the module to inspect for functions. domain : str or None, optional Domain name to filter functions by. Functions must have a 'domain' attribute containing this value. tag : str or None, optional Tag name to filter functions by. Functions must have a 'tag' attribute containing this value. Returns ------- list List of function objects that have both the specified domain in their 'domain' attribute and the specified tag in their 'tag' attribute. """ selected_functions = [] module = importlib.import_module(module_name) functions = inspect.getmembers(module, inspect.isfunction) for name, func in functions: if hasattr(func, "domain") and hasattr(func, "tag"): domains = getattr(func, "domain") tags = getattr(func, "tag") if (domain in domains) and (tag in tags): selected_functions.append(func) return selected_functions