Improved motor imagery BCI performance via task-unaware compression in the BELT Bayesian Edge-Cloud architecture.
The 9 matches
- [1] § Materials and methods › Implementation › Data augmentation. ↔ train_GAN.py, lines 137–177 · score 0.72 · latent vector, dense layer, tanh, cGAN, ReLU, concatenated
- [2] § Materials and methods › Implementation › Data augmentation. ↔ model_comparison.ipynb, lines 276–391 · score 0.68 · dense layers, EEG channels, norm, flattening, sigmoid, dropout
- [3] § Materials and methods › Implementation › Data augmentation. ↔ train_GAN.py, lines 75–135 · score 0.67 · EEG channels, leaky, flattening, dropout, bias, dense
- [4] § Materials and methods › Implementation › Detector. ↔ model_comparison.ipynb, lines 276–391 · score 0.62 · dropout layer, dense layer, Batch normalization, classifier
- [5] § Materials and methods › Implementation › Detector. ↔ train_GAN.py, lines 75–135 · score 0.58 · Conv1D, EEG channel, variable, padding, kernels, batch
- [6] § Materials and methods › Implementation › Detector. ↔ prior.ipynb, lines 13–65 · score 0.56 · Conv1D, spatial filter, activation, bias, kernels, linear
- [7] § Materials and methods › Implementation › Detector. ↔ compressor.ipynb, lines 355–419 · score 0.56 · Conv1D, spatial filter, activation, bias, kernels, linear
- [8] § Materials and methods › Model › Detector. ↔ model_comparison.ipynb, lines 57–123 · score 0.55 · Conv1D, spatial filters, activation, bias, stride, kernel
- [9] § Materials and methods › Implementation › EEG dataset and task framing. ↔ segmentation.ipynb, lines 254–305 · score 0.51 · cue onset, rejected, segments
Paper
Loaded from Europe PMC by your browser, not stored by OSCR: doi.org · Europe PMC
The paper is loaded when this pane is shown.
The authors' code
Python · 551 lines · 20 KB · GPL-3.0 · 3 matches
- import multiprocessing.process
- import sys
- k_fold = sys.argv
- PROJ_NAME = "DualDataset"
- def process_gan_training(k_fold: int, EPOCH_RESUME_FROM = 0, MAX_RAM_GB = 4, max_epochs: int = 300):
- global PROJ_NAME
- import TSA
- import tools
- # import analysis as A
- import importlib
- import numpy as np
- import os
- latent_dim = 100
- TSA.setup_proj(PROJ_NAME, 'ab', ignore_folds=False)
- # Data
- Segments = tools.load_segments('ab', splitSegments=TSA.cv_select_k_fold(k_fold))
- XY_spl = tools.seg2XY(Segments)
- X = [XY_spl['tr'][0], XY_spl['te'][0]]
- X = np.concatenate(X, axis=0)
- Y = [XY_spl['tr'][1], XY_spl['te'][1]]
- Y = np.concatenate(Y, axis=0)
- X_train = np.zeros((X.shape[0], X.shape[1], 5))
- X_train[:, :, 0:3] = X
- for i in range(1000):
- X_train[:, i, 3:] = Y
- Xv, Yv = XY_spl['val']
- X_val = np.zeros((Xv.shape[0], Xv.shape[1], 5))
- X_val[:, :, 0:3] = Xv
- for i in range(1000):
- X_val[:, i, 3:] = Yv
- # Models
- from tensorflow.keras.layers import Layer
- from keras.saving import register_keras_serializable
- import keras.models as KM
- import keras.layers as KL
- import tensorflow as tf
- from keras.models import Model
- from keras.layers import Input, Dense, Reshape, RepeatVector, LSTM, Concatenate
- if EPOCH_RESUME_FROM > 0:
- EPOCH_LAST = EPOCH_RESUME_FROM
- discriminator, _ = TSA.checkpoint_model_read(f"GAN2_{EPOCH_LAST}/disc", cv_k=k_fold, exp_name=None)
- generator, _ = TSA.checkpoint_model_read(f"GAN2_{EPOCH_LAST}/gen", cv_k=k_fold, exp_name=None)
- else:
- from tensorflow.keras.layers import Layer
- from keras.saving import register_keras_serializable
- import keras.models as KM
- import keras.layers as KL
- import tensorflow as tf
- input_shape = (1000, 5)
- @register_keras_serializable()
- class NTBSquareLayer(Layer):
- def call(self, x):
- return tf.square(x)
- @register_keras_serializable()
- class NTBLogLayer(Layer):
- def call(self, x):
- return tf.math.log(x + 1e-8)
- def build_discriminator(T, C, num_classes):
- # Input: Real or generated data (batch_size, T, C + num_subjects + num_classes)
- disc_input = KL.Input(shape=(T, C + num_classes)) # Shape: (T, C + 11)
- # Process EEG data (C channels)
- eeg_data = disc_input[:, :, :C]
- eeg_channels = KL.Conv1D(
- 18,
- kernel_size=25,
- #groups=3,
- use_bias=False,
- activation="linear",
- padding="same",
- name="LTI-BP-Filts",
- )(eeg_data)
- # Filts: Spatial filters
- eeg_channels = KL.Conv1D(
- 16,
- kernel_size=1,
- strides=1,
- use_bias=False,
- activation="linear",
- padding="valid",
- name="LTI-Spatial-Filts",
- )(eeg_channels)
- # NTB
- eeg_channels = KL.BatchNormalization()(eeg_channels)
- eeg_channels = NTBSquareLayer(name="NTB-square")(eeg_channels)
- eeg_channels = KL.AveragePooling1D(20, strides=13, name="NTB-avg")(eeg_channels)
- # KL.Lambda(lambda x: tf.math.log(x + 1e-8), name="NTB-log"),
- eeg_channels = NTBLogLayer(name="NTB-log")(eeg_channels)
- # Cfier
- eeg_flat = KL.Flatten()(eeg_channels)
- eeg_flat = KL.BatchNormalization()(eeg_flat)
- # Process condition variables (class and subject codes)
- condition_data = disc_input[:, :, C:] # Remaining channels
- condition_flat = KL.Flatten()(condition_data)
- # Concatenate EEG features and condition variables
- combined = KL.Concatenate()([eeg_flat, condition_flat])
- # Fully connected layers
- x = combined
- x = KL.Dense(256)(x)
- x = KL.LeakyReLU(alpha=0.2)(x)
- x = KL.Dropout(0.2)(x)
- x = KL.Dense(128)(x)
- x = KL.LeakyReLU(alpha=0.2)(x)
- x = KL.Dropout(0.2)(x)
- x = KL.Dense(64)(x)
- x = KL.LeakyReLU(alpha=0.2)(x)
- x = KL.Dropout(0.2)(x)
- x = KL.Dense(32)(x)
- x = KL.LeakyReLU(alpha=0.2)(x)
- x = KL.Dropout(0.2)(x)
- x = KL.Dense(16)(x)
- x = KL.LeakyReLU(alpha=0.2)(x)
- x = KL.Dropout(0.2)(x)
- x = KL.Dense(8)(x)
- x = KL.LeakyReLU(alpha=0.2)(x)
- x = KL.Dropout(0.2)(x)
- x = KL.Dense(4)(x)
- x = KL.LeakyReLU(alpha=0.2)(x)
- x = KL.Dropout(0.2)(x)
- # Output layer for binary classification (real/fake)
- disc_output = KL.Dense(1, activation="sigmoid")(x)
- # Create the model
- discriminator = KM.Model(disc_input, disc_output, name="Discriminator")
- return discriminator
- discriminator = build_discriminator(1000, 3, 2)
- from keras.models import Model
- from keras.layers import Input, Dense, Reshape, RepeatVector, LSTM, Concatenate
- def build_generator(latent_dim, T, C, num_classes):
- # Input: concatenated latent vector + subject label + class label
- gen_input = Input(shape=(latent_dim + num_classes,)) # Shape: (batch_size, latent_dim + 9 + 2)
- # Process the latent vector through dense layers
- x = Dense(32, activation='relu')(gen_input)
- x = Dense(32, activation='relu')(gen_input)
- x = Dense(32, activation='relu')(gen_input)
- x = Dense(T * C, activation='tanh')(x)
- x = Reshape((T, C))(x) # Shape: (batch_size, T, C)
- # Extract the subject and class labels from the input
- subject_and_class_labels = gen_input[:, - num_classes:] # Last (9 + 2) part of input
- # Repeat the subject and class labels across T time steps
- repeated_labels = RepeatVector(T)(subject_and_class_labels) # Shape: (batch_size, T, 9 + 2)
- # Concatenate the generated sequence and the repeated labels
- gen_output = Concatenate(axis=-1)([x, repeated_labels]) # Shape: (batch_size, T, C + 9 + 2)
- # Create the model
- generator = Model(gen_input, gen_output, name="Generator")
- return generator
- generator = build_generator(latent_dim, 1000, 3, 2)
- def build_cgan(generator, discriminator):
- # Make the discriminator non-trainable when training the CGAN
- discriminator.trainable = False
- # Generator inputs
- latent_input = Input(shape=(latent_dim + 2,), name="generator_input")
- # Generator output
- generated_data = generator(latent_input) # Output shape: (batch_size, T, C + 9 + 2)
- # Discriminator prediction on generated data
- validity = discriminator(generated_data) # Output shape: (batch_size, 1)
- # CGAN model (generator -> discriminator)
- cgan = Model(inputs=latent_input, outputs=validity, name="CGAN")
- return cgan
- cgan = build_cgan(generator, discriminator)
- cgan.summary()
- def compile_cgan(generator, discriminator, cgan_model):
- # Compile discriminator (real/fake classification)
- discriminator.trainable = True
- optimizer_D = TSA.KO.Adam(0.0002, 0.5)
- discriminator.compile(optimizer=optimizer_D, loss='binary_crossentropy', metrics=['accuracy'])
- # Compile CGAN (train the generator through the discriminator's feedback)
- discriminator.trainable = False
- generator.trainable = True
- optimizer_G = TSA.KO.Adam(0.0002, 0.5)
- cgan_model.compile(optimizer=optimizer_G, loss='binary_crossentropy', metrics=['accuracy'])
- return optimizer_D, optimizer_G
- opt_D, opt_G = compile_cgan(generator, discriminator, cgan)
- # Phase 1 - Train
- real_mixed = X_train.copy()
- real_mixed[:, :, 3:] = 1 - real_mixed[:, :, 3:]
- import numpy as np
- def generate_disc_data_uniform(real: bool, fake: bool, mixed_class_real: bool = False):
- real_data = X_train.copy()
- batch_size = X_train.shape[0]
- if fake:
- fake_data = np.random.normal(-1, 1, size=real_data.shape)
- fake_data[:, :, 3:] = 0
- fake_data[0::2, :, 3] = 1
- fake_data[1::2, :, 3] = 1
- if mixed_class_real:
- real_mixed = X_train.copy()
- real_mixed[:, :, 3:] = 1 - real_mixed[:, :, 3:]
- # Combine real and fake data
- DL = []
- LL = []
- if real:
- DL.append(real_data)
- LL.append(np.ones((batch_size, 1)))
- if fake:
- DL.append(fake_data)
- LL.append(np.zeros((batch_size, 1)))
- if mixed_class_real:
- DL.append(real_mixed)
- LL.append(np.zeros((batch_size, 1)))
- combined_data = np.concatenate(DL, axis=0)
- labels = np.concatenate(LL, axis=0) # Real = 1, Fake = 0
- # Add noise to labels (label smoothing)
- #labels += 0.05 * np.random.random(labels.shape)
- #labels = np.clip(labels, 0.0, 1.0) # Ensure labels stay within [0, 1] range
- return combined_data, labels
- X_val_c0 = X_val.copy()
- X_val_c0[:, :, 3] = 1
- X_val_c0[:, :, 4] = 0
- X_val_c1 = X_val.copy()
- X_val_c1[:, :, 3] = 0
- X_val_c1[:, :, 4] = 1
- def measure_classification_of_disc():
- Yhat_0 = discriminator.predict(X_val_c0)
- Yhat_1 = discriminator.predict(X_val_c1)
- Yv_hat = np.zeros_like(Yv)
- is_c_0 = Yhat_0 >= Yhat_1
- is_c_1 = Yhat_0 < Yhat_1
- Yv_hat[is_c_0.flatten(), 0] = 1
- Yv_hat[is_c_0.flatten(), 1] = 0
- Yv_hat[is_c_1.flatten(), 0] = 0
- Yv_hat[is_c_1.flatten(), 1] = 1
- Cv = np.argmax(Yv, axis=1)
- Cv_hat = np.argmax(Yv_hat, axis=1)
- return np.mean(Cv == Cv_hat)
- if EPOCH_RESUME_FROM == 0:
- discriminator.trainable = True
- X, Y = generate_disc_data_uniform(real=True, fake=False, mixed_class_real=True)
- for epoch in range(20):
- print("----------")
- discriminator.fit(X, Y, validation_data=(X_val, np.ones((X_val.shape[0], 1))), batch_size=16, epochs=1, shuffle=True)
- #print(discriminator.evaluate(X_val, np.ones((X_val.shape[0], 1)), batch_size=64, return_dict=True))
- print(measure_classification_of_disc())
- # Training Phase 2
- epoch = EPOCH_RESUME_FROM + 1
- import numpy as np
- import tensorflow as tf
- from keras.utils import disable_interactive_logging, enable_interactive_logging
- import time
- enable_interactive_logging()
- # Training parameters
- batch_size = X_train.shape[0]//5
- epochs = 2500
- acc_D_qualified = 0.9
- acc_G_prime = 0.8
- lr_D_max = 1e-2 / 100
- B_D_NQ = 1
- lr_D_NQ = 1e-3 / 100
- lr_G_NQ = 1e-7 / 100
- alpha = 0.2
- lr_D_base_QP = 1e-3 / 100
- lr_G_QP = 1e-3 / 100
- B_D_QP = 200
- lr_D_base_QNP = 1e-4 / 100
- lr_G_QNP = 1e-4 / 100
- B_D_QNP = 20
- B_G_samples_QNP = 5
- B_G_rep_QNP = 100
- prime = False
- qualified = False
- batches_per_epoch = len(X_train) // batch_size
- TR_LOGS = []
- #epoch = 2059
- rk = 1
- lr_d_prev = lr_D_base_QNP
- lr_d_next = lr_D_base_QNP
- lr_g = lr_G_QNP
- import gc
- import psutil
- gc.collect()
- def get_ram_usage_MB():
- process = psutil.Process()
- mem_info = process.memory_info()
- return mem_info.rss / 1024**2
- USED_RAM = {}
- tic = time.time()
- while epoch < epochs + 1:
- Primes = []
- Qualified = []
- used_ram_MB = get_ram_usage_MB()
- USED_RAM[epoch] = used_ram_MB
- print(f"@{epoch} - RAM usage: {get_ram_usage_MB():.2f} MB")
- disable_interactive_logging()
- if used_ram_MB > MAX_RAM_GB * 1024:
- print("The RAM usage exceeds maximum allowed RAM!")
- return
- # print("Relearn the uniform!")
- #X, Y = generate_disc_data_uniform(real=True, fake=True)
- #discriminator.fit(X, Y, batch_size=64, epochs=1, shuffle=True)
- for batch_no in range(batches_per_epoch):
- Primes.append(prime)
- Qualified.append(qualified)
- # Control the mode
- if not qualified: #NQ mode:
- print("NQ")
- lr_d = lr_D_NQ
- lr_g = lr_G_NQ
- lr_d_base = lr_D_NQ
- B_D = B_D_NQ
- B_G_samples = 5
- B_G_rep = 1
- elif prime: #QP mode:
- print("QP")
- lr_d = alpha*lr_d_prev + (1-alpha)*lr_d_next
- lr_d_base = lr_D_base_QP
- lr_g = lr_G_QP
- B_D = B_D_QP
- B_G_samples = B_D
- B_G_rep = 1
- else: #QNP mode:
- print("QNP")
- lr_d = alpha*lr_d_prev + (1-alpha)*lr_d_next
- lr_d_base = lr_D_base_QNP
- lr_g = lr_G_QNP
- B_D = B_D_QNP
- B_G_samples = B_G_samples_QNP
- B_G_rep = B_G_rep_QNP
- lr_d_prev = lr_d
- # Update learning rate of disc and gen
- opt_D.learning_rate = lr_d
- opt_G.learning_rate = lr_g
- print("Discriminator...")
- # 1. Train the discriminator
- real_data = X_train[batch_size*batch_no:batch_size*(batch_no+1),:,:]
- # Generate fake data
- noise = np.random.normal(0, 1, (batch_size, latent_dim))
- fake_class_labels = np.zeros((batch_size, 2)) # Replace with actual class labels
- fake_class_labels[0::2, 0] = 1
- fake_class_labels[1::2, 1] = 1
- generator_input = np.concatenate([noise, fake_class_labels], axis=-1)
- # fake_data = generate_disc_data_uniform(real=False, fake=True)[0][0:batch_size]#generator.predict(generator_input) # Shape: (batch_size, T, C+2)
- fake_data = generator.predict(generator_input) # Shape: (batch_size, T, C+2)
- # Combine real and fake data
- #print(real_data.shape, fake_data.shape, combined_data.shape)
- combined_data = np.concatenate([real_data, fake_data], axis=0)
- labels = np.concatenate(
- [np.ones((batch_size, 1)), np.zeros((batch_size, 1))]
- ) # Real = 1, Fake = 0
- #combined_data, labels = generate_disc_data_uniform(True, True, False)
- #print(labels.shape, np.mean(labels))
- # Add noise to labels (label smoothing)
- #labels += 0.05 * np.random.random(labels.shape)
- #labels = np.clip(labels, 0.0, 1.0) # Ensure labels stay within [0, 1] range
- # Train discriminator
- discriminator.trainable = True
- generator.trainable = False
- qualified = False
- for rep_d in range(B_D):
- #d_loss = discriminator.train_on_batch(combined_data, labels)
- #combined_data, labels = generate_disc_data_uniform(real=True, fake=True)
- dHist = discriminator.fit(combined_data, labels).history
- d_loss = dHist['loss']
- d_acc = dHist['accuracy'][0]
- if d_acc >= acc_D_qualified:
- qualified = True
- break
- del dHist
- print("Generator...")
- # 2. Train the generator (via the CGAN model)
- G_Loss = []
- discriminator.trainable = False
- generator.trainable = True
- prime = False
- noise = None
- for g_batch_n in range(B_G_samples):
- if noise is not None:
- del noise, generator_input, misleading_labels
- gc.collect()
- noise = None
- noise = np.random.normal(0, 1, (batch_size, latent_dim))
- generator_input = np.concatenate([noise, fake_class_labels], axis=-1)
- misleading_labels = np.ones((batch_size, 1)) # Generator tries to fool discriminator
- for g_batch_n_rep in range(B_G_rep):
- gHist = cgan.fit(generator_input, misleading_labels).history
- g_loss = gHist['loss'][0]
- g_acc = gHist['accuracy'][0]
- G_Loss.append(g_loss)
- del gHist
- if g_acc > acc_G_prime:
- prime = True
- break
- if prime:
- break
- # update rk and prime
- g_loss = np.mean(G_Loss)
- rk = 10**(1 - g_loss/d_loss[0])
- lr_d_next = min(lr_D_max, lr_d_base*rk)
- # freeup!
- del noise, fake_class_labels, generator_input, fake_data, combined_data, labels
- gc.collect()
- if epoch % 10 == 0:
- print("Discriminator recovery...")
- # discriminator recovery
- discAccBinBr = measure_classification_of_disc()
- X, Y = generate_disc_data_uniform(real=True, fake=False, mixed_class_real=True)
- discriminator.fit(X, Y, batch_size=16, epochs=1, shuffle=True)#, class_weight={0:1/3, 1:2/3})
- drHist = discriminator.evaluate(X, Y, batch_size=64, return_dict=True)
- print(drHist)
- del drHist, X, Y
- else:
- discAccBinBr = 0
- discAccBin = measure_classification_of_disc()
- # Print loss metrics
- if epoch % 1 == 0:
- took = time.time() - tic
- #print(epoch, took, d_loss, g_loss)
- _lrD = tf.keras.backend.get_value(opt_D.learning_rate)
- tr_log = f"Epoch {epoch}/{epochs}: Discriminator Loss: {d_loss[0]:.4f}, Generator Loss: {g_loss:.4f}, Took: {took:.1f}s, log10(lr_D): {np.log10(_lrD):.1f}, prime: {np.mean(Primes)}, qualified: {np.mean(Qualified)}, disc_bincla_power: {discAccBinBr*100:.1f}%%, {discAccBin*100:.1f}%%"
- print("\r \r", end='')
- print(tr_log)
- TR_LOGS.append(tr_log)
- tic = time.time()
- # Save network periodically
- if epoch % 50 == 0 or epoch <= 2 or epoch == max_epochs:
- print("SAVING MODELS")
- TSA.checkpoint_model_write(f"GAN2_{epoch}/gen", generator, cv_k=k_fold, exp_name=None)
- TSA.checkpoint_model_write(f"GAN2_{epoch}/disc", discriminator, cv_k=k_fold, exp_name=None)
- if epoch == max_epochs:
- return
- epoch += 1
- del Primes, Qualified
- TSA.KB.clear_session(True)
- gc.collect()
- if __name__ == "__main__":
- # k_fold = int(sys.argv[1])
- MAX_EPOCHS = 300
- MAX_RAM_GB = 5
- import multiprocessing
- import time
- for k_fold in range(0, 10):
- print("Working on fold %d" % k_fold)
- def find_last_saved_epoch():
- import os
- try:
- all = os.listdir(f"./Output/Models/{PROJ_NAME}/{k_fold}/None/Saved")
- except:
- all = []
- if len(all) == 0:
- return 0
- else:
- all_epochs = [int(name.split("_")[1]) for name in all if name[0] != '_']
- return max(all_epochs)
- while True:
- epoch_resume = find_last_saved_epoch()
- if epoch_resume >= MAX_EPOCHS:
- print("Maximum epochs is already reached")
- break
- print("Resuming from epoch %d" % epoch_resume)
- p = multiprocessing.Process(target=process_gan_training, args=(k_fold, epoch_resume, 5, 300))
- p.start()
- print("PROCESS STARTED!")
- p.join()
- print("PROCESS EXITED!")
- time.sleep(5)
train_GAN.py at commit 2f5270f, under GPL-3.0 · at the source
Overview
- School of Electrical and Computer Engineering, College of Engineering, University of Tehran, Tehran, Iran
- School of Cognitive Sciences, Institute for Research in Fundamental Sciences (IPM), Tehran, Iran
- Departments of Research Administration and Radiology, Henry Ford Health System, Detroit, Michigan, United States of America
Abstract
Brain–computer interface (BCI) systems have advanced with deep learning, but they are still limited by designs tied to specific applications, poor scalability, weak portability, the need for user-specific adaptation, and privacy concerns. We present BELT, a modular Bayesian Edge–Cloud architecture based on three principles: (i) Bayesian priors and posteriors to balance generalization and subject-specific learning, (ii) lightweight classifiers suitable for embedded devices, and (iii) task-aware compression to reduce bandwidth and improve privacy in edge–cloud communication. To show feasibility, we implement BELT-lite as an instantiation of BELT, a lightweight version built only from linear time-invariant operations, making it directly compatible with digital signal processing hardware. Using the BCI Competition IV-2a and IV-2b motor imagery datasets (18 subjects total, ten-fold cross-validation), BELT-lite achieved strong posterior performance after subject-specific fine-tuning: mean accuracy of 87.9%±6.8% on Dataset B and 80.6%±8.6% on Dataset A with data augmentation. After adaptation, four subjects from Dataset B and two from Dataset A exceeded 90% accuracy. On ARM Cortex-A7 hardware, BELT-lite achieved a mean latency of 6.75 ms per sample, significantly faster than EEGNet’s 8.36 ms (p < 10-17)—a 21% speed improvement—at the cost of a modest but statistically significant accuracy reduction of approximately 2.7 percentage points compared to EEGNet. Network Tuning Blocks allowed partial parameter freezing: classifier-only fine-tuning incurred a modest 2–5% accuracy drop while substantially reducing training cost. Compression via the task-unaware autoencoder reduced data size by 3.3× while maintaining high accuracy: prior-model performance stayed within ≈1% of the uncompressed baseline (with slight improvements in some configurations), full posterior fine-tuning showed a ≈1% drop, and classifier-only fine-tuning incurred a ≈3% drop—an acceptable trade-off for privacy-preserving edge–cloud communication, where only a compressed latent representation is transmitted instead of raw EEG. Notably, this task-unaware autoencoder (trained solely to reconstruct the input) consistently outperformed autoencoders that also incorporated classification objectives (task-aware or task-only), providing the best accuracy–compression trade-off across all fine-tuning scenarios. These findings show that BELT provides a principled design for modular and scalable BCIs, while BELT-lite demonstrates that the approach supports accurate, efficient, and portable implementations. Together, they point toward BCI systems that are more practical, mass-producible, and privacy-aware, enabling wider use in real-world settings.
Reproduced under the paper's license (CC BY), from the paper cited above.
Repository
Its files are read in the Code ↔ Paper reader above, with 9 matches between paragraphs and lines of code.
adanayi/BELT
2f5270fb5916581751e5614393c67874e1076208, 2 June 2026Availability: 1 check, the latest on 27 September 2026: the link answers
- 27 September 2026: the link answers
11 files
- TSA.py, Python, 166 lines
- compressor.ipynb, Jupyter, 1,235 lines, 1 match
- model_comparison.ipynb, Jupyter, 617 lines, 3 matches
- new_subject.ipynb, Jupyter, 715 lines
- posterior.ipynb, Jupyter, 1,299 lines
- prior.ipynb, Jupyter, 430 lines, 1 match
- segmentation.ipynb, Jupyter, 421 lines, 1 match
- tools.py, Python, 357 lines
- train_GAN.py, Python, 551 lines, 3 matches
- LICENSE, License, 674 lines
- README.md, Text, 22 lines
The paper's code and data availability statement is in the Data section.
Tracing map
Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.
What the map holds:
- 1 repository of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
- 9 scripts, each with its path and the digest of its content;
- 9 matches between paragraphs of the paper and lines of the code (method lexical-v1);
- neither the text of the paper nor the code itself.
Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.
Data
Datasets cited
- bbci.de/
competition/ , at bbci.de; found in “Data Availability”iv
Data Availability
All code underlying the findings is available at: https://
Reproduced under the paper's license (CC BY), from the paper cited above.
Versions
The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.
Version 1, 27 September 2026: the first record
Recorded: type, language, journal, volume, issue, pages, dates, 2 authors, 8 MeSH terms, 37 references.
Cite
This paper
Danayi, A., & Soltanian-Zadeh, H. (2026). Improved motor imagery BCI performance via task-unaware compression in the BELT Bayesian Edge-Cloud architecture. PloS one, 21(8), e0354976. https://
BibTeX
@article{danayi2026impro
author = {Danayi, Abolfazl and Soltanian-Zadeh, Hamid},
title = {{Improved motor imagery BCI performance via task-unaware compression in the BELT Bayesian Edge-Cloud architecture}},
journal = {PloS one},
year = {2026},
month = aug,
volume = {21},
number = {8},
pages = {e0354976},
publisher = {PLOS},
issn = {1932-6203},
doi = {10.1371/
url = {https://
pmid = {42627854},
pmcid = {PMC13497277}
}
RIS
TY - JOUR
AU - Danayi, Abolfazl
AU - Soltanian-Zadeh, Hamid
TI - Improved motor imagery BCI performance via task-unaware compression in the BELT Bayesian Edge-Cloud architecture
T2 - PloS one
J2 - PLoS One
PY - 2026
DA - 2026/
VL - 21
IS - 8
SP - e0354976
SN - 1932-6203
PB - PLOS
DO - 10.1371/
UR - https://
LA - en
ER -
CSL-JSON
{
"id": "10.1371/
"type": "article-journal",
"title": "Improved motor imagery BCI performance via task-unaware compression in the BELT Bayesian Edge-Cloud architecture",
"container-title": "PloS one",
"author": [
{
"family": "Danayi",
"given": "Abolfazl"
},
{
"family": "Soltanian-Zadeh",
"given": "Hamid"
}
],
"container-title-short":
"volume": "21",
"issue": "8",
"page": "e0354976",
"DOI": "10.1371/
"PMID": "42627854",
"PMCID": "PMC13497277",
"ISSN": "1932-6203",
"publisher": "PLOS",
"URL": "https://
"language": "en",
"issued": {
"date-parts": [
[
2026,
8,
21
]
]
}
}
The tracing map gets a citation of its own once an author has validated it and it has a DOI.
Similar papers
The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.
- [1] doi:10.1371/journal.pone.0347671 [code]
- RMETNet: A cross-subject motor imagery EEG signal classification model based on TSLANet and riemannian geometry features.Journal: PloS oneIn common: TensorFlow, MNE-Python, seaborn, 4 other tools, methods / tools, EEG, 2 references
- [2] doi:10.1038/s41746-026-02778-0 [code]
- Trust-gated synthetic EEG augmentation reduces performance drops when generalizing to new patients.Journal: NPJ digital medicineIn common: MNE-Python, pandas, SciPy, 2 other tools, bbci.de/competition/iv, EEG, 1 reference
- [3] doi:10.1038/s41598-026-52330-z [code]
- SHAP analysis of an improved EEG-based mental workload classification framework: utilizing data augmentation and explainable AI.Journal: Scientific reportsIn common: Keras, TensorFlow, MNE-Python, 4 other tools, methods / tools, EEG, 1 reference
- [4] doi:10.1038/s41597-025-05174-7 [code]
- A large-scale MEG and EEG dataset for object recognition in naturalistic scenesJournal: n/aIn common: Keras, TensorFlow, MNE-Python, 5 other tools, methods / tools, EEG
- [5] doi:10.1038/s41598-026-47627-y [code]
- QuantumNeuroXAI: a quantum-inspired deep learning framework with explainability for brain signal analysis and neurological disorder detection.Journal: Scientific reportsIn common: MNE-Python, pandas, SciPy, 2 other tools, bbci.de/competition/iv, methods / tools, EEG
- [6] doi:10.3390/s26175327 [code]
- Subject Identity Confounds qEEG Emotion Recognition on DEAP and DREAMER.Journal: Sensors (Basel, Switzerland)In common: Keras, TensorFlow, MNE-Python, 5 other tools, EEG
- [7] doi:10.3390/s26051730 [code]
- SFE-GAT: Structure-Feature Evolution Graph Attention Network for Motor Imagery Decoding.Journal: Sensors (Basel, Switzerland)In common: SciPy, Matplotlib, NumPy, bbci.de/competition/iv, EEG, 2 references
- [8] doi:10.3389/fpsyg.2026.1774068 [code]
- Analysis of cognitive mechanisms in phoneme perception and pronunciation errors among Korean language learners.Journal: Frontiers in psychologyIn common: Keras, TensorFlow, MNE-Python, 5 other tools, EEG
- [9] doi:10.3390/biomimetics11060377
- PG-MCTFormer: A Prior-Guided Multi-Scale Convolutional Transformer for Interpretable Motor Imagery EEG Classification.Journal: Biomimetics (Basel, Switzerland)In common: bbci.de/competition/iv, EEG, 3 references
- [10] doi:10.1038/s41598-026-52672-8 [code]
- Validation of portable, semi-dry electrode-based electroencephalography device for its application in brain-computer interface solutions.Journal: Scientific reportsIn common: Keras, SciPy, Matplotlib, 1 other tool, methods / tools, EEG, 2 references
Contribute
The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.
Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.
Claim this paper
Correct its record
Say what each link of this record is, remove the ones that are not the paper's, add the ones that are missing. The correction becomes a new version of the record, in its Versions section.
Validate its tracing map
You validate the map as this page shows it: 1 repository of the authors' code, each at its verified commit and with its license, 9 scripts, and 9 matches between paragraphs and code (see the Code and Map sections). It then receives a DOI on Zenodo, with you (your ORCID iD) and OSCR as its creators; the code itself is not deposited.
The map's fingerprint: sha256:2ff3ca1186be5acc…
Add the badge to its README
The badge links the code to this page. Copy one of these into the README of the paper's code: only you decide where it goes, and nothing is changed for you.
Markdown
[, paste the snippet at the top, then “Commit changes…” and, to review it first, “Create a new branch and start a pull request”. You open the pull request; OSCR asks for no permission.
Request its removal
To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).
Discussion, reproductions, activity
Discussion: questions and error reports about this paper and its code, from signed-in readers and its authors. It opens with sign-in.
Reproductions: reports from readers who ran the authors' code: what they reproduced, with which environment, commit and data. It opens with sign-in.
Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.
