Skip to main content

SeFEF: Seizure Forecasting Evaluation Framework

Project description

SeFEF logo

SeFEF is a Seizure Forecasting Evaluation Framework written in Python. The framework standardizes the development, evaluation, and reporting of individualized algorithms for seizure likelihood forecast. SeFEF aims to decrease development time and minimize implementation errors by automating key procedures within data preparation, training/testing, and computation of evaluation metrics.

Highlights:

  • evaluation module: implements time series cross-validation.

  • labeling module: automatically labels samples according to the desired pre-ictal duration and prediction latency.

  • postprocessing module: processes individual predicted probabilities into a unified forecast according to the desired forecast horizon.

  • scoring module: computes both deterministic and probabilistic metrics according to the horizon of the forecast.

Installation

Installation can be easily done with pip:

$ pip install sefef

Example

The code below loads the metadata from an existing dataset from the examples folder, splits creates a Dataset instance, and creates an adequate split for a time series cross-validation. It also provides an example of model development and evaluation through a simple probabilistic estimator that leverages periodicity in event data.

This example dataset contains synthesized event occurrence timestamps spanning 2.5 years, starting from January 1, 2020. Events occur periodically, with an initial cycle of 28 days (in seconds), subject to a small random variation of ±1 day.

# built-in
import os

# third-party
import h5py
import numpy as np
import pandas as pd

# local
from config import forecast_horizon, directory_information
from seizureforecast.optimize_threshold import optimize_thr_GMM
from seizureforecast.prepare_data import create_events_dataset
from seizureforecast.model_periodicity_analysis import VonMisesEstimator

# SeFEF
from sefef import labeling, evaluation, postprocessing, visualization, scoring

# Data preparation - read files
event_times_metadata = pd.read_csv(os.path.join(directory_information['data_folder_path'], 'event_times_metadata.csv'))
with open(os.path.join(directory_information['data_folder_path'], 'synthetic_onsets.txt'), 'r') as f:
    event_onsets = [float(line.strip()) for line in f]

create_events_dataset(event_onsets, freq=['D', 'h'][forecast_horizon < 60*60*24], dataset_filepath=os.path.join(
    directory_information['preprocessed_data_path'], f'event_times_dataset.h5'))

# SeFEF - labeling module
with h5py.File(os.path.join(directory_information['preprocessed_data_path'], f'event_times_dataset.h5'), 'r+') as h5dataset:
    if 'annotations' not in h5dataset.keys():
        labeling.add_annotations(
            h5dataset, sz_onsets_ts=event_onsets, preictal_duration=forecast_horizon, prediction_latency=0)
    if 'sz_onsets' not in h5dataset.keys():
        labeling.add_sz_onsets(
            h5dataset, sz_onsets_ts=event_onsets)

try:
    event_times_dataset = h5py.File(os.path.join(
        directory_information['preprocessed_data_path'], f'event_times_dataset.h5'), 'r')

    # SeFEF - evaluation module
    tscv = evaluation.TimeSeriesCV(
        preictal_duration=forecast_horizon,
        prediction_latency=0,
        post_sz_interval=1*60*60,
        pre_lead_sz_interval=4*60*60,
    )
    dataset = evaluation.Dataset(timestamps=event_times_dataset['timestamps'][(
    )], samples_duration=[forecast_horizon]*len(event_times_dataset['timestamps'][(
    )]), sz_onsets=event_times_dataset['sz_onsets'][()])
    tscv.split(dataset)
    tscv.plot(dataset)

    # Operationalizing CV
    for ifold, (train_data, test_data) in enumerate(tscv.iterate(event_times_dataset)):
        print(
            f'\n---------------------\nStarting TSCV fold {ifold+1}/{tscv.n_folds}\n---------------------')

        X_train, y_train, ts_train, sz_onsets_train = train_data
        X_test, _, ts_test, sz_onsets_test = test_data

        seizure_hist_freq = pd.to_datetime(pd.Series(sz_onsets_train), unit='s').dt.floor(['D', 'h'][forecast_horizon < 3600*24]).nunique(
        ) / pd.to_datetime(pd.Series(ts_train), unit='s').dt.floor(['D', 'h'][forecast_horizon < 3600*24]).nunique()
        print(f'Historical seizure frequency: {seizure_hist_freq}')

        # List underlying cycles with periods ranging from 2-periods to 60-periods
        total_duration = pd.to_timedelta(
            (ts_train[-1] - ts_train[0]) + forecast_horizon, unit='s')
        fast_cycles = [pd.Timedelta(hours=t) for t in [6, 12, 24]]
        slow_cycles = [pd.Timedelta(days=t) for t in list(
            range(3, min([60, int(np.floor(total_duration.days * 0.5)+1)])))]
        candidate_cycles = fast_cycles + slow_cycles
        candidate_cycles = [cycle for cycle in candidate_cycles if cycle > pd.to_timedelta(
            forecast_horizon, unit='s')]

        # Compute likelihoods for phase bins, according to significant cycles.
        estimator = VonMisesEstimator(forecast_horizon=forecast_horizon)
        try:
            estimator.train(train_ts=X_train, train_labels=y_train,
                            candidate_cycles=[cycle.total_seconds() for cycle in candidate_cycles], si_thr=0.6)
            estimator.plot_fit_dist(X_train, y_train)
        except ValueError as e:
            print(e)
            continue

        #  Optimize high-probability threshold
        high_likelihood_thr = optimize_thr_GMM(np.reshape(
            estimator.predict(test_ts=X_train), (-1, 1)))

        # Compute probability estimates given samples' timestamps
        pred = estimator.predict(test_ts=X_test)

        # SeFEF - postprocessing module
        forecast = postprocessing.Forecast(pred, ts_test)
        forecasts, ts = forecast.postprocess(
            forecast_horizon=forecast_horizon, smooth_win=2*60*60, origin='clock-time')

        # SeFEF - visualization module
        fig = visualization.plot_forecasts(
            forecasts, ts,  sz_onsets_test, high_likelihood_thr, forecast_horizon, title='Daily seizure probability')

        # SeFEF - scoring module
        scorer = scoring.Scorer(metrics2compute=['Sen', 'FPR', 'TiW', 'AUC_TiW', 'resolution', 'reliability', 'BS', 'skill'],
                                sz_onsets=sz_onsets_test,
                                forecast_horizon=forecast_horizon,
                                reference_method='prior_prob',
                                hist_prior_prob=seizure_hist_freq)

        fold_performance = scorer.compute_metrics(
            forecasts, ts, binning_method='uniform', num_bins=5, draw_diagram=True, threshold=high_likelihood_thr)

        # Print results
        for metric in fold_performance:
            fold_performance[metric] = f'{fold_performance[metric]:0.3f}'
        print(fold_performance)

except KeyboardInterrupt:
    print('Interrupted by user.')
except Exception as e:
    print(e)
finally:
    event_times_dataset.close()

The example methodology (available in the examples folder) results in a daily forecast as the one below (with synthetic data), generated with SeFEF’s visualization module.

Example forecast with synthetic data

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

sefef-3.0.0.tar.gz (127.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

sefef-3.0.0-py3-none-any.whl (26.1 kB view details)

Uploaded Python 3

File details

Details for the file sefef-3.0.0.tar.gz.

File metadata

  • Download URL: sefef-3.0.0.tar.gz
  • Upload date:
  • Size: 127.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.12.9

File hashes

Hashes for sefef-3.0.0.tar.gz
Algorithm Hash digest
SHA256 ef487a35a5b4bf05877bb2b1d53959b2bb45b3df8ed67fc75b06b8b22cdc1de9
MD5 bad5bb7fb169ab542750e2053687c91d
BLAKE2b-256 c99359f552d3fcedc6920d0b675ff06c03a239d3ac2c8d1c4077760d389f2b4b

See more details on using hashes here.

Provenance

The following attestation bundles were made for sefef-3.0.0.tar.gz:

Publisher: release.yml on anascacais/SeFEF

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file sefef-3.0.0-py3-none-any.whl.

File metadata

  • Download URL: sefef-3.0.0-py3-none-any.whl
  • Upload date:
  • Size: 26.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.12.9

File hashes

Hashes for sefef-3.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 5272212d79fa35e78a4421f53dba9676ba0d761a4430be6a8439df459617ba93
MD5 5056ba4ab8b8d07c81f53c1f4eba3568
BLAKE2b-256 258b4ef78c1e97f50b2db4947d341f390d07c6ce9112fe0b078bc2ee3d6a41f8

See more details on using hashes here.

Provenance

The following attestation bundles were made for sefef-3.0.0-py3-none-any.whl:

Publisher: release.yml on anascacais/SeFEF

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page