Multi scale entropy

Demonstration code for performing a multi scale entropy analysis using MultiScaleEntropy().

Link to script: feature_extraction/multi_scale_entropy.py

import os
import matplotlib.pyplot as plt
import numpy as np

from nnsa import MultiScaleEntropy, print_object_summary, EdfReader, SUPPORTED_RESULT_FILE_TYPES, read_result_from_file, \
    assert_equal

plt.close('all')

Parameters.

# Print the default parameters of MultiScaleAnalysis():
print(MultiScaleEntropy().default_parameters())

# Descriptions of the parameters are documented in the default_parameters() code.

# Create an instance of the MultiScaleEntropy class with custom parameters, overruling some defaults:
mse = MultiScaleEntropy(artefact_criteria={'max_diff': 200},
                        segmentation={'segment_length': 5, 'overlap': 0},
                        mse={'max_scale': 20})

# See if the custom parameters were accepted:
print('\nCustom parameters:')
print(mse.parameters)

Main method: power_analysis.

# Now that we have initialized a MultiScaleEntropy object with certain parameters, we can run the multi_scale_entropy
# method, which performs the actual multi-scale entropy analysis:
# Create 2 random signals (5 segments) to simulate 2 channel EEG data (amplitude 50).
fs = 128
data_matrix = np.random.rand(2, 5 * mse.parameters['segmentation']['segment_length'] * fs)*100 - 50
result = mse.multi_scale_entropy(data_matrix, fs, channel_labels=None, verbose=1)  # Note: the kwargs are optional.

# The returned object is a MultiScaleEntropyResult object, which is a high-level interface for
# manipulating/visualizing/saving the result:
print('\nSummary of result object:')
print_object_summary(result)

MultiScaleEntropyResult attributes.

# The main attribute is 'mse', which contains the entropy values per scale, channel and segment. Therefore,
# 'mse' is a 3D array with dimensions corresponding to (scales, channels, segments), where mse[i, :, :] corresponds to
# scale i+1.
print('result.mse: {}'.format(result.mse))
print('result.mse.shape (scales, channels, segments): {}'.format(result.mse.shape))

# The labels of the channels are found in result.channels, such that mse[:, i, :] corresponds to result.channels[i].
print('result.channels: {}'.format(result.channel_labels))

# The time (in seconds) corresponding to the segments in the data in mse:
print('result.segment_times: {}'.format(result.segment_times))

# The average mse over all segments are computed (and subsequently stored) by the 'average_mse' property:
print('result.average_mse (scales, channels): {}'.format(result.average_mse))

MultiScaleEntropyResult methods.

# Compute the average slope of the mse curve in the small and large scales per channel, respectively:
slope_small, slope_large = result.compute_average_slopes()
print('Average slope of mse curve for small scales (channels, segments):\n{}'.format(slope_small))
print('Average slope of mse curve for large scales (channels, segments):\n{}'.format(slope_large))

# Compute the complexity index, i.e. the area under the mse curve, for each segment and each channel:
print('Complexity index (channels, segments):\n{}'.format(result.compute_complexity_index()))

# Get the maximum value of the multiscale entropy curve:
print('Maximum sample entropy (channels, segments):\n{}'.format(result.maximum_mse()))

# Plot average mse curves for all channels:
result.plot()

Save the result.

# We can save the result to any supported file. To list the supported file types/extensions:
print('Supported result file types: {}'.format(SUPPORTED_RESULT_FILE_TYPES))

# E.g. save as hdf5 (the file type to save is automatically detected from the extension of the filename).
filename = 'temp_result.hdf5'
result.save_to_file(filename)

# We can reload the result back to a PowerAnalysisResult object:
result_loaded = read_result_from_file(filename)

# Remove temporary file.
os.remove(filename)

# Verify that the loaded object is equal to the original object:
result._average_mse = None  # Properties won't be saved, so reset average mse to None before the assert_equal test.
assert_equal(actual=result_loaded, desired=result)  # Will raise an AssertionError if not equal.
print('Loaded result object is equal to original object.')

MultiScaleEntropy of EegDataset.

# Specify a file and open a reader to read the data (e.g. EdfReader to read an EDF(+) file).
filepath = 'C:/data_temp/test.edf'

# Read EEG channels in an EegDataset object.
with EdfReader(filepath) as r:
    ds = r.read_eeg_dataset(dtype=np.float32)  # We can specify the dtype, lower precision, means less memory usage.

ds_preprocessed = ds.reference('Cz').resample(fs_new=128).filter_saved_filter(filter_name='bandpassfir_a')

# We can do the mse analysis for multichannel EEG via the wrapper method 'multi_scale_entropy' of the EegDataset class.
# Specify the parameters to the MultiScaleEntropy class as kwargs.
if False:
    # Disable this line, since the multi_scale_entropy will take a long time to compute.
    result_ds = ds_preprocessed.multi_scale_entropy(segmentation={'segment_length': 5})

Perform on only some segments (the below code is basically what’s inside the EegDataset.multi_scale_entropy method).

# Initialize MultiScaleEntropy object with default arguments.
mse = MultiScaleEntropy()

# Prepare input matrix for multi_scale_entropy.
data_matrix, channel_labels = ds_preprocessed.asarray(return_channel_labels=True)

# Sample frequency of all EEG signals is the same if self.asarray() did not raise an error.
fs = next((ts.fs for ts in ds_preprocessed.time_series.values()))

# Select only some segments.
n_segments = 8
data_matrix = data_matrix[:, :n_segments * mse.parameters['segmentation']['segment_length'] * fs]

# Run multi_scale_entropy.
result_ds_ = mse.multi_scale_entropy(data_matrix, fs=fs,
                                     channel_labels=channel_labels)

print(result_ds_.mse)
result_ds_.plot(segments=[1], plot_fit=True)