diff --git a/docs/BuildBrainFlow.rst b/docs/BuildBrainFlow.rst index 30282c3f5..c7ca532cb 100644 --- a/docs/BuildBrainFlow.rst +++ b/docs/BuildBrainFlow.rst @@ -149,6 +149,11 @@ Rust cd brainflow cargo build --features generate_binding +Swift +------- + +You can build Swift binding for BrainFlow using xcode. Before that you need to compile C/C++ code :ref:`compilation-label` and ensure that native libraries are properly placed. Keep in mind that currently it supports only MacOS. + Docker Image -------------- diff --git a/docs/Examples.rst b/docs/Examples.rst index eae4c7e7d..1f2707275 100644 --- a/docs/Examples.rst +++ b/docs/Examples.rst @@ -602,6 +602,69 @@ Typescript ICA .. literalinclude:: ../nodejs_package/tests/ica.ts :language: javascript +Swift +------------ + +Swift Get Data from a Board +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. literalinclude:: ../swift_package/examples/tests/brainflow_get_data/brainflow_get_data.swift + :language: swift + +Swift Markers +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. literalinclude:: ../swift_package/examples/tests/markers/markers.swift + :language: swift + +Swift Read Write File +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. literalinclude:: ../swift_package/examples/tests/read_write_file/read_write_file.swift + :language: swift + +Swift Downsample Data +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. literalinclude:: ../swift_package/examples/tests/downsampling/downsampling.swift + :language: swift + +Swift Transforms +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. literalinclude:: ../swift_package/examples/tests/transforms/transforms.swift + :language: swift + +Swift Signal Filtering +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. literalinclude:: ../swift_package/examples/tests/signal_filtering/signal_filtering.swift + :language: swift + +Swift Denoising +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. literalinclude:: ../swift_package/examples/tests/denoising/denoising.swift + :language: swift + +Swift Band Power +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. literalinclude:: ../swift_package/examples/tests/band_power/band_power.swift + :language: swift + +Swift EEG Metrics +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. literalinclude:: ../swift_package/examples/tests/eeg_metrics/eeg_metrics.swift + :language: swift + +Swift ICA +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. literalinclude:: ../swift_package/examples/tests/ica/ica.swift + :language: swift + Notebooks ------------ .. toctree:: diff --git a/docs/UserAPI.rst b/docs/UserAPI.rst index 02a5a4ab9..642443a6b 100644 --- a/docs/UserAPI.rst +++ b/docs/UserAPI.rst @@ -163,3 +163,13 @@ Example: .. literalinclude:: ../nodejs_package/tests/brainflow_get_data.ts :language: javascript + +Swift +------ + +Swift binding calls C/C++ code as any other binding. Use Swift examples and API reference for other languaes as a starting point. + +Example: + +.. literalinclude:: ../swift_package/examples/tests/brainflow_get_data/brainflow_get_data.swift + :language: swift diff --git a/python_package/brainflow/board_shim.py b/python_package/brainflow/board_shim.py index 0dd9ceb13..4c6613f64 100644 --- a/python_package/brainflow/board_shim.py +++ b/python_package/brainflow/board_shim.py @@ -10,7 +10,6 @@ import pkg_resources from brainflow.exit_codes import BrainFlowExitCodes, BrainFlowError from brainflow.utils import LogLevels -from nptyping import NDArray, Float64, Shape from numpy.ctypeslib import ndpointer @@ -777,7 +776,7 @@ def get_board_presets(cls, board_id: int) -> List[str]: :type board_id: int :return: presets for this board id :rtype: List[str] - :raises BrainFlowError + :raises BrainFlowError: In case of internal error or invalid args """ num_presets = numpy.zeros(1).astype(numpy.int32) @@ -795,7 +794,7 @@ def get_version(cls) -> str: :return: version :rtype: str - :raises BrainFlowError + :raises BrainFlowError: In case of internal error or invalid args """ string = numpy.zeros(64).astype(numpy.ubyte) string_len = numpy.zeros(1).astype(numpy.int32) @@ -1266,7 +1265,7 @@ def release_session(self) -> None: if res != BrainFlowExitCodes.STATUS_OK.value: raise BrainFlowError('unable to release streaming session', res) - def get_current_board_data(self, num_samples: int, preset: int = BrainFlowPresets.DEFAULT_PRESET) -> NDArray[Shape["*, *"], Float64]: + def get_current_board_data(self, num_samples: int, preset: int = BrainFlowPresets.DEFAULT_PRESET): """Get specified amount of data or less if there is not enough data, doesnt remove data from ringbuffer :param num_samples: max number of samples @@ -1345,7 +1344,7 @@ def is_prepared(self) -> bool: raise BrainFlowError('unable to check session status', res) return bool(prepared[0]) - def get_board_data(self, num_samples=None, preset: int = BrainFlowPresets.DEFAULT_PRESET) -> NDArray[Shape["*, *"], Float64]: + def get_board_data(self, num_samples=None, preset: int = BrainFlowPresets.DEFAULT_PRESET): """Get board data and remove data from ringbuffer :param num_samples: number of packages to get diff --git a/python_package/brainflow/data_filter.py b/python_package/brainflow/data_filter.py index 40b841a8e..aeb243df8 100644 --- a/python_package/brainflow/data_filter.py +++ b/python_package/brainflow/data_filter.py @@ -9,7 +9,6 @@ import pkg_resources from brainflow.exit_codes import BrainFlowExitCodes, BrainFlowError from brainflow.utils import check_memory_layout_row_major, LogLevels -from nptyping import NDArray, Float64, Complex128, Shape from numpy.ctypeslib import ndpointer @@ -583,7 +582,7 @@ def set_log_file(cls, log_file: str) -> None: raise BrainFlowError('unable to redirect logs to a file', res) @classmethod - def perform_lowpass(cls, data: NDArray[Shape["*"], Float64], sampling_rate: int, cutoff: float, order: int, filter_type: int, + def perform_lowpass(cls, data, sampling_rate: int, cutoff: float, order: int, filter_type: int, ripple: float) -> None: """apply low pass filter to provided data @@ -611,7 +610,7 @@ def perform_lowpass(cls, data: NDArray[Shape["*"], Float64], sampling_rate: int, raise BrainFlowError('unable to perform low pass filter', res) @classmethod - def perform_highpass(cls, data: NDArray[Shape["*"], Float64], sampling_rate: int, cutoff: float, order: int, filter_type: int, + def perform_highpass(cls, data, sampling_rate: int, cutoff: float, order: int, filter_type: int, ripple: float) -> None: """apply high pass filter to provided data @@ -639,7 +638,7 @@ def perform_highpass(cls, data: NDArray[Shape["*"], Float64], sampling_rate: int raise BrainFlowError('unable to apply high pass filter', res) @classmethod - def perform_bandpass(cls, data: NDArray[Shape["*"], Float64], sampling_rate: int, start_freq: float, + def perform_bandpass(cls, data, sampling_rate: int, start_freq: float, stop_freq: float, order: int, filter_type: int, ripple: float) -> None: """apply band pass filter to provided data @@ -669,7 +668,7 @@ def perform_bandpass(cls, data: NDArray[Shape["*"], Float64], sampling_rate: int raise BrainFlowError('unable to apply band pass filter', res) @classmethod - def perform_bandstop(cls, data: NDArray[Shape["*"], Float64], sampling_rate: int, start_freq: float, + def perform_bandstop(cls, data, sampling_rate: int, start_freq: float, stop_freq: float, order: int, filter_type: int, ripple: float) -> None: """apply band stop filter to provided data @@ -699,7 +698,7 @@ def perform_bandstop(cls, data: NDArray[Shape["*"], Float64], sampling_rate: int raise BrainFlowError('unable to apply band stop filter', res) @classmethod - def remove_environmental_noise(cls, data: NDArray[Shape["*"], Float64], sampling_rate: int, noise_type: float) -> None: + def remove_environmental_noise(cls, data, sampling_rate: int, noise_type: float) -> None: """remove env noise using notch filter :param data: data to filter, filter works in-place @@ -719,7 +718,7 @@ def remove_environmental_noise(cls, data: NDArray[Shape["*"], Float64], sampling raise BrainFlowError('unable to apply notch filter', res) @classmethod - def perform_rolling_filter(cls, data: NDArray[Shape["*"], Float64], period: int, operation: int) -> None: + def perform_rolling_filter(cls, data, period: int, operation: int) -> None: """smooth data using moving average or median :param data: data to smooth, it works in-place @@ -739,7 +738,7 @@ def perform_rolling_filter(cls, data: NDArray[Shape["*"], Float64], period: int, raise BrainFlowError('unable to smooth data', res) @classmethod - def calc_stddev(cls, data: NDArray[Shape["*"], Float64]): + def calc_stddev(cls, data): """calc stddev :param data: input array @@ -755,7 +754,7 @@ def calc_stddev(cls, data: NDArray[Shape["*"], Float64]): return output[0] @classmethod - def get_railed_percentage(cls, data: NDArray[Shape["*"], Float64], gain: int): + def get_railed_percentage(cls, data, gain: int): """get railed percentage :param data: input array @@ -773,7 +772,7 @@ def get_railed_percentage(cls, data: NDArray[Shape["*"], Float64], gain: int): return output[0] @classmethod - def get_oxygen_level(cls, ppg_ir: NDArray[Shape["*"], Float64], ppg_red: NDArray[Shape["*"], Float64], sampling_rate: int, + def get_oxygen_level(cls, ppg_ir, ppg_red, sampling_rate: int, coef1=1.5958422, coef2=-34.6596622, coef3=112.6898759): """get oxygen level from ppg @@ -798,7 +797,7 @@ def get_oxygen_level(cls, ppg_ir: NDArray[Shape["*"], Float64], ppg_red: NDArray return output[0] @classmethod - def get_heart_rate(cls, ppg_ir: NDArray[Shape["*"], Float64], ppg_red: NDArray[Shape["*"], Float64], sampling_rate: int, fft_size: int): + def get_heart_rate(cls, ppg_ir, ppg_red, sampling_rate: int, fft_size: int): """get heart rate :param ppg_ir: input array @@ -824,7 +823,7 @@ def get_heart_rate(cls, ppg_ir: NDArray[Shape["*"], Float64], ppg_red: NDArray[S return output[0] @classmethod - def perform_downsampling(cls, data: NDArray[Shape["*"], Float64], period: int, operation: int) -> NDArray[Shape["*"], Float64]: + def perform_downsampling(cls, data, period: int, operation: int): """perform data downsampling, it doesnt apply lowpass filter for you, it just aggregates several data points :param data: initial data @@ -853,7 +852,7 @@ def perform_downsampling(cls, data: NDArray[Shape["*"], Float64], period: int, o return downsampled_data @classmethod - def perform_wavelet_transform(cls, data: NDArray[Shape["*"], Float64], wavelet: int, decomposition_level: int, + def perform_wavelet_transform(cls, data, wavelet: int, decomposition_level: int, extension_type=WaveletExtensionTypes.SYMMETRIC) -> Tuple: """perform wavelet transform @@ -881,7 +880,7 @@ def perform_wavelet_transform(cls, data: NDArray[Shape["*"], Float64], wavelet: return wavelet_coeffs[0: sum(lengths)], lengths @classmethod - def restore_data_from_wavelet_detailed_coeffs(cls, data: NDArray[Shape["*"], Float64], wavelet, decomposition_level, level_to_restore): + def restore_data_from_wavelet_detailed_coeffs(cls, data, wavelet, decomposition_level, level_to_restore): """restore data from a single wavelet coeff :param data: initial data @@ -906,7 +905,7 @@ def restore_data_from_wavelet_detailed_coeffs(cls, data: NDArray[Shape["*"], Flo return output @classmethod - def detect_peaks_z_score(cls, data: NDArray[Shape["*"], Float64], lag=5, threshold=3.5, influence=0.1): + def detect_peaks_z_score(cls, data, lag=5, threshold=3.5, influence=0.1): """z score algorithm for peak detection :param data: initial data @@ -932,8 +931,7 @@ def detect_peaks_z_score(cls, data: NDArray[Shape["*"], Float64], lag=5, thresho @classmethod def perform_inverse_wavelet_transform(cls, wavelet_output: Tuple, original_data_len: int, wavelet: int, - decomposition_level: int, extension_type=WaveletExtensionTypes.SYMMETRIC) -> \ - NDArray[Shape["*"], Float64]: + decomposition_level: int, extension_type=WaveletExtensionTypes.SYMMETRIC): """perform wavelet transform :param wavelet_output: tuple of wavelet_coeffs and array with lengths @@ -960,7 +958,7 @@ def perform_inverse_wavelet_transform(cls, wavelet_output: Tuple, original_data_ return original_data @classmethod - def perform_wavelet_denoising(cls, data: NDArray[Shape["*"], Float64], wavelet: int, decomposition_level: int, + def perform_wavelet_denoising(cls, data, wavelet: int, decomposition_level: int, wavelet_denoising=WaveletDenoisingTypes.SURESHRINK, threshold=ThresholdTypes.HARD, extension_type=WaveletExtensionTypes.SYMMETRIC, @@ -991,7 +989,7 @@ def perform_wavelet_denoising(cls, data: NDArray[Shape["*"], Float64], wavelet: raise BrainFlowError('unable to denoise data', res) @classmethod - def get_csp(cls, data: NDArray[Shape["*, *, *"], Float64], labels: NDArray[Shape["*"], Float64]) -> Tuple: + def get_csp(cls, data, labels) -> Tuple: """calculate filters and the corresponding eigenvalues using the Common Spatial Patterns :param data: [epochs x channels x times]-shaped 3D array of data for two classes @@ -1024,7 +1022,7 @@ def get_csp(cls, data: NDArray[Shape["*, *, *"], Float64], labels: NDArray[Shape return output_filters, output_eigenvalues @classmethod - def get_window(cls, window_function: int, window_len: int) -> NDArray[Shape["*"], Float64]: + def get_window(cls, window_function: int, window_len: int): """perform data windowing :param window_function: window function @@ -1041,7 +1039,7 @@ def get_window(cls, window_function: int, window_len: int) -> NDArray[Shape["*"] return window_data @classmethod - def perform_fft(cls, data: NDArray[Shape["*"], Float64], window: int) -> NDArray[Shape["*"], Complex128]: + def perform_fft(cls, data, window: int): """perform direct fft :param data: data for fft, len of data must be even @@ -1067,7 +1065,7 @@ def perform_fft(cls, data: NDArray[Shape["*"], Float64], window: int) -> NDArray return output @classmethod - def get_psd(cls, data: NDArray[Shape["*"], Float64], sampling_rate: int, window: int) -> Tuple: + def get_psd(cls, data, sampling_rate: int, window: int) -> Tuple: """calculate PSD :param data: data to calc psd, len of data must be even @@ -1091,7 +1089,7 @@ def get_psd(cls, data: NDArray[Shape["*"], Float64], sampling_rate: int, window: return ampls, freqs @classmethod - def get_psd_welch(cls, data: NDArray[Shape["*"], Float64], nfft: int, overlap: int, sampling_rate: int, window: int) -> Tuple: + def get_psd_welch(cls, data, nfft: int, overlap: int, sampling_rate: int, window: int) -> Tuple: """calculate PSD using Welch method :param data: data to calc psd @@ -1120,7 +1118,7 @@ def get_psd_welch(cls, data: NDArray[Shape["*"], Float64], nfft: int, overlap: i return ampls, freqs @classmethod - def detrend(cls, data: NDArray[Shape["*"], Float64], detrend_operation: int) -> None: + def detrend(cls, data, detrend_operation: int) -> None: """detrend data :param data: data to calc psd @@ -1156,7 +1154,7 @@ def get_band_power(cls, psd: Tuple, freq_start: float, freq_end: float) -> float return band_power[0] @classmethod - def get_avg_band_powers(cls, data: NDArray[Shape["*, *"], Float64], channels: List, sampling_rate: int, apply_filter: bool) -> Tuple: + def get_avg_band_powers(cls, data, channels: List, sampling_rate: int, apply_filter: bool) -> Tuple: """calculate avg and stddev of BandPowers across all channels, bands are 1-4,4-8,8-13,13-30,30-50 :param data: 2d array for calculation @@ -1175,7 +1173,7 @@ def get_avg_band_powers(cls, data: NDArray[Shape["*, *"], Float64], channels: Li return cls.get_custom_band_powers(data, bands, channels, sampling_rate, apply_filter) @classmethod - def get_custom_band_powers(cls, data: NDArray[Shape["*, *"], Float64], bands: List, channels: List, sampling_rate: int, + def get_custom_band_powers(cls, data, bands: List, channels: List, sampling_rate: int, apply_filter: bool) -> Tuple: """calculate avg and stddev of BandPowers across selected channels @@ -1218,7 +1216,7 @@ def get_custom_band_powers(cls, data: NDArray[Shape["*, *"], Float64], bands: Li return avg_bands, stddev_bands @classmethod - def perform_ica(cls, data: NDArray[Shape["*, *"], Float64], num_components: int, channels=None) -> Tuple: + def perform_ica(cls, data, num_components: int, channels=None) -> Tuple: """perform ICA :param data: 2d array for calculation @@ -1265,7 +1263,7 @@ def perform_ica(cls, data: NDArray[Shape["*, *"], Float64], num_components: int, return w, k, a, s @classmethod - def perform_ifft(cls, data: NDArray[Shape["*"], Complex128]) -> NDArray[Shape["*"], Float64]: + def perform_ifft(cls, data): """perform inverse fft :param data: data from fft @@ -1303,7 +1301,7 @@ def get_nearest_power_of_two(cls, value: int) -> int: return output[0] @classmethod - def write_file(cls, data: NDArray[Shape["*, *"], Float64], file_name: str, file_mode: str) -> None: + def write_file(cls, data, file_name: str, file_mode: str) -> None: """write data to file, in file data will be transposed :param data: data to store in a file @@ -1329,7 +1327,7 @@ def write_file(cls, data: NDArray[Shape["*, *"], Float64], file_name: str, file_ raise BrainFlowError('unable to write file', res) @classmethod - def read_file(cls, file_name: str) -> NDArray[Shape["*, *"], Float64]: + def read_file(cls, file_name: str): """read data from file :param file_name: file name to read diff --git a/python_package/brainflow/ml_model.py b/python_package/brainflow/ml_model.py index b245caf5d..3e5fd71d2 100644 --- a/python_package/brainflow/ml_model.py +++ b/python_package/brainflow/ml_model.py @@ -10,7 +10,6 @@ import pkg_resources from brainflow.board_shim import BrainFlowError, LogLevels from brainflow.exit_codes import BrainFlowExitCodes -from nptyping import NDArray, Float64, Shape from numpy.ctypeslib import ndpointer @@ -262,7 +261,7 @@ def release(self) -> None: if res != BrainFlowExitCodes.STATUS_OK.value: raise BrainFlowError('unable to release classifier', res) - def predict(self, data: NDArray[Shape["*"], Float64]) -> List: + def predict(self, data) -> List: """calculate metric from data :param data: input array diff --git a/python_package/setup.py b/python_package/setup.py index 2ebc01e1d..2ca0314f0 100644 --- a/python_package/setup.py +++ b/python_package/setup.py @@ -18,7 +18,6 @@ packages=find_packages(), install_requires=[ 'numpy', - 'nptyping>=2.0.0', 'setuptools' ], package_data={