Update README.md
Browse files
README.md
CHANGED
@@ -66,7 +66,7 @@ Fully compatible with Neuramax-6 Gen1.
|
|
66 |
-------------
|
67 |
|
68 |
<details>
|
69 |
-
<summary>bai-6 Emotion v1</summary>
|
70 |
|
71 |
# bai-6 Emotion v1 Yapısı / Structure
|
72 |
|
@@ -410,6 +410,617 @@ if __name__ == "__main__":
|
|
410 |
|
411 |
</details>
|
412 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
413 |
-------------
|
414 |
## Lisans/License
|
415 |
CC-BY-NC-SA-4.0
|
|
|
66 |
-------------
|
67 |
|
68 |
<details>
|
69 |
+
<summary><h3>bai-6 Emotion v1</h3></summary>
|
70 |
|
71 |
# bai-6 Emotion v1 Yapısı / Structure
|
72 |
|
|
|
410 |
|
411 |
</details>
|
412 |
|
413 |
+
<details>
|
414 |
+
<summary><h3>bai-6 Emotion v2</h3></summary>
|
415 |
+
|
416 |
+
# bai-6 Emotion v2 Yapısı / Structure
|
417 |
+
|
418 |
+
|Layer (type) | Output Shape | Param # |
|
419 |
+
| --- | --- | --- |
|
420 |
+
| dense_4 (Dense) | (None, 128) | 4,736 |
|
421 |
+
| batch_normalization_2 | (None, 128) | 512 |
|
422 |
+
| dropout_3 (Dropout) | (None, 128) | 0 |
|
423 |
+
| dense_5 (Dense) | (None, 64) | 8,256 |
|
424 |
+
| batch_normalization_3 | (None, 64) | 256 |
|
425 |
+
| dropout_4 (Dropout) | (None, 64) | 0 |
|
426 |
+
| dense_6 (Dense) | (None, 32) | 2,080 |
|
427 |
+
| dropout_5 (Dropout) | (None, 32) | 0 |
|
428 |
+
| dense_7 (Dense) | (None, 4) | 132 |
|
429 |
+
|
430 |
+
### Total params: 15,972 (62.39 KB)
|
431 |
+
|
432 |
+
**Trainable params: 15,588 (60.89 KB)**
|
433 |
+
|
434 |
+
**Non-trainable params: 384 (1.50 KB)**
|
435 |
+
|
436 |
+
# Kullanım / Usage
|
437 |
+
|
438 |
+
## Sentetik Veri ile / With Synthetic Data
|
439 |
+
```python
|
440 |
+
import numpy as np
|
441 |
+
import joblib
|
442 |
+
import time
|
443 |
+
import matplotlib.pyplot as plt
|
444 |
+
from matplotlib.animation import FuncAnimation
|
445 |
+
from tensorflow.keras.models import load_model
|
446 |
+
from datetime import datetime
|
447 |
+
import mne
|
448 |
+
import warnings
|
449 |
+
|
450 |
+
warnings.filterwarnings('ignore')
|
451 |
+
|
452 |
+
|
453 |
+
class EEGEmotionMonitorOptimized:
|
454 |
+
def __init__(self, model_path, scaler_path, selector_path=None, pca_path=None):
|
455 |
+
self.emotion_labels = {
|
456 |
+
0: "Mutlu (Happy)",
|
457 |
+
1: "Kızgın (Angry)",
|
458 |
+
2: "Üzgün (Sad)",
|
459 |
+
3: "Sakin (Calm)"
|
460 |
+
}
|
461 |
+
|
462 |
+
self.emotion_colors = ['#FFD700', '#FF4444', '#4169E1', '#32CD32']
|
463 |
+
|
464 |
+
# Kanal isimleri ve parametreler / Channel names and parameters
|
465 |
+
self.ch_names = ['T7', 'C3', 'Cz', 'C4', 'T8', 'Pz']
|
466 |
+
self.fs = 128 # Örnekleme hızı / Sampling rate
|
467 |
+
self.buffer_size = 640
|
468 |
+
self.update_interval = 200
|
469 |
+
|
470 |
+
# Buffer'ları başlat / Initialize buffers
|
471 |
+
self.raw_buffer = np.zeros((6, self.buffer_size))
|
472 |
+
self.prediction_history = []
|
473 |
+
self.confidence_history = []
|
474 |
+
self.time_history = []
|
475 |
+
self.max_history = 30
|
476 |
+
|
477 |
+
# Performance metrics tracking
|
478 |
+
self.performance_metrics = {
|
479 |
+
'total_predictions': 0,
|
480 |
+
'high_confidence_predictions': 0, # >0.8 confidence
|
481 |
+
'low_confidence_predictions': 0, # <0.5 confidence
|
482 |
+
'prediction_times': [], # Processing time per prediction
|
483 |
+
'emotion_transitions': 0, # Count of emotion changes
|
484 |
+
'stability_score': 0.0, # How stable predictions are
|
485 |
+
'average_confidence': 0.0,
|
486 |
+
'confidence_trend': [], # Last 10 confidence values for trend analysis
|
487 |
+
'processing_fps': 0.0, # Processing speed
|
488 |
+
'last_prediction': None
|
489 |
+
}
|
490 |
+
|
491 |
+
self.metrics_text = ""
|
492 |
+
self.start_time = None
|
493 |
+
|
494 |
+
try:
|
495 |
+
self.model = load_model(model_path)
|
496 |
+
self.scaler = joblib.load(scaler_path)
|
497 |
+
|
498 |
+
self.selector = None
|
499 |
+
self.pca = None
|
500 |
+
|
501 |
+
if selector_path:
|
502 |
+
try:
|
503 |
+
self.selector = joblib.load(selector_path)
|
504 |
+
print("Feature selector loaded")
|
505 |
+
except:
|
506 |
+
print("Feature selector not found, using raw features")
|
507 |
+
|
508 |
+
if pca_path:
|
509 |
+
try:
|
510 |
+
self.pca = joblib.load(pca_path)
|
511 |
+
print("PCA reducer loaded")
|
512 |
+
except:
|
513 |
+
print("PCA reducer not found, skipping dimensionality reduction")
|
514 |
+
|
515 |
+
print("Model and preprocessors successfully loaded!")
|
516 |
+
print(f"Model input shape: {self.model.input_shape}")
|
517 |
+
print(f"Output classes: {len(self.emotion_labels)}")
|
518 |
+
|
519 |
+
# Elektrot pozisyonları (10-20 sistemi) / Electrode positions (10-20 system)
|
520 |
+
self.montage = mne.channels.make_standard_montage('standard_1020')
|
521 |
+
|
522 |
+
except Exception as e:
|
523 |
+
print(f"Model/preprocessor loading error: {e}")
|
524 |
+
raise
|
525 |
+
|
526 |
+
self.fig = plt.figure(figsize=(14, 8))
|
527 |
+
self.fig.suptitle('EEG Duygu Tanıma Sistemi / EEG Emotion Analysis System', fontsize=16, fontweight='bold')
|
528 |
+
self.setup_plots()
|
529 |
+
|
530 |
+
self.animation = None
|
531 |
+
self.is_running = False
|
532 |
+
|
533 |
+
def setup_plots(self):
|
534 |
+
"""4 panelli görselleştirme arayüzünü hazırla (with performance metrics) / Setup 4-panel visualization interface (with performance metrics)"""
|
535 |
+
|
536 |
+
self.ax1 = self.fig.add_subplot(221)
|
537 |
+
self.ax1.set_title("Live EEG Signals", fontsize=10)
|
538 |
+
self.ax1.set_xlabel("Time (samples)", fontsize=9)
|
539 |
+
self.ax1.set_ylabel("Amplitude (µV)", fontsize=9)
|
540 |
+
self.ax1.grid(True, alpha=0.3)
|
541 |
+
|
542 |
+
self.ax2 = self.fig.add_subplot(222)
|
543 |
+
self.ax2.set_title("Emotion Probabilities", fontsize=10)
|
544 |
+
self.ax2.set_xlim(0, 1)
|
545 |
+
|
546 |
+
self.ax3 = self.fig.add_subplot(223)
|
547 |
+
self.ax3.set_title("Performance Metrics & Confidence Trend", fontsize=10)
|
548 |
+
|
549 |
+
self.ax4 = self.fig.add_subplot(224)
|
550 |
+
self.ax4.set_title("Electrode Contributions", fontsize=10)
|
551 |
+
self.ax4.set_xlim(0, 1)
|
552 |
+
|
553 |
+
plt.tight_layout(pad=1.0)
|
554 |
+
|
555 |
+
def generate_realistic_eeg_signal(self, emotion_bias=None):
|
556 |
+
noise = np.random.normal(0, 3e-6, (6, self.buffer_size))
|
557 |
+
|
558 |
+
t = np.linspace(0, self.buffer_size/self.fs, self.buffer_size)
|
559 |
+
|
560 |
+
alpha_freq = np.random.uniform(8, 12) # Alpha dominant
|
561 |
+
beta_freq = np.random.uniform(15, 25) # Beta
|
562 |
+
|
563 |
+
for ch in range(6):
|
564 |
+
if emotion_bias == 0: # Happy - higher beta
|
565 |
+
beta_amp = np.random.uniform(4e-6, 6e-6)
|
566 |
+
alpha_amp = np.random.uniform(2e-6, 3e-6)
|
567 |
+
elif emotion_bias == 1: # Angry - very high beta
|
568 |
+
beta_amp = np.random.uniform(5e-6, 7e-6)
|
569 |
+
alpha_amp = np.random.uniform(1e-6, 2e-6)
|
570 |
+
elif emotion_bias == 2: # Sad - lower activity
|
571 |
+
beta_amp = np.random.uniform(1e-6, 2e-6)
|
572 |
+
alpha_amp = np.random.uniform(3e-6, 5e-6)
|
573 |
+
elif emotion_bias == 3: # Calm - high alpha
|
574 |
+
beta_amp = np.random.uniform(1e-6, 3e-6)
|
575 |
+
alpha_amp = np.random.uniform(4e-6, 6e-6)
|
576 |
+
else: # Random
|
577 |
+
beta_amp = np.random.uniform(2e-6, 4e-6)
|
578 |
+
alpha_amp = np.random.uniform(2e-6, 4e-6)
|
579 |
+
|
580 |
+
noise[ch] += alpha_amp * np.sin(2 * np.pi * alpha_freq * t + np.random.random() * 2 * np.pi)
|
581 |
+
noise[ch] += beta_amp * np.sin(2 * np.pi * beta_freq * t + np.random.random() * 2 * np.pi)
|
582 |
+
|
583 |
+
return noise.astype(np.float32)
|
584 |
+
|
585 |
+
def update_buffer(self, new_data):
|
586 |
+
samples_to_add = min(new_data.shape[1], self.buffer_size // 4)
|
587 |
+
self.raw_buffer = np.roll(self.raw_buffer, -samples_to_add, axis=1)
|
588 |
+
self.raw_buffer[:, -samples_to_add:] = new_data[:, :samples_to_add]
|
589 |
+
|
590 |
+
def extract_lightweight_features(self, signal_data):
|
591 |
+
features = []
|
592 |
+
|
593 |
+
for channel_data in signal_data:
|
594 |
+
time_features = [
|
595 |
+
np.mean(channel_data),
|
596 |
+
np.std(channel_data),
|
597 |
+
np.ptp(channel_data),
|
598 |
+
np.median(channel_data),
|
599 |
+
np.mean(np.abs(channel_data)),
|
600 |
+
np.sqrt(np.mean(channel_data**2))
|
601 |
+
]
|
602 |
+
|
603 |
+
try:
|
604 |
+
fft_vals = np.abs(np.fft.rfft(channel_data[::4]))
|
605 |
+
freqs = np.fft.rfftfreq(len(channel_data)//4, 4/self.fs)
|
606 |
+
|
607 |
+
delta_power = np.sum(fft_vals[(freqs >= 0.5) & (freqs <= 4)])
|
608 |
+
theta_power = np.sum(fft_vals[(freqs >= 4) & (freqs <= 8)])
|
609 |
+
alpha_power = np.sum(fft_vals[(freqs >= 8) & (freqs <= 13)])
|
610 |
+
beta_power = np.sum(fft_vals[(freqs >= 13) & (freqs <= 30)])
|
611 |
+
total_power = np.sum(fft_vals) + 1e-10
|
612 |
+
|
613 |
+
freq_features = [
|
614 |
+
delta_power / total_power,
|
615 |
+
theta_power / total_power,
|
616 |
+
alpha_power / total_power,
|
617 |
+
beta_power / total_power
|
618 |
+
]
|
619 |
+
except:
|
620 |
+
freq_features = [0.25, 0.25, 0.25, 0.25]
|
621 |
+
|
622 |
+
nonlinear_features = [
|
623 |
+
np.std(np.diff(channel_data)) / (np.std(channel_data) + 1e-10),
|
624 |
+
np.mean(np.abs(np.diff(channel_data)))
|
625 |
+
]
|
626 |
+
|
627 |
+
channel_features = time_features + freq_features + nonlinear_features
|
628 |
+
features.extend(channel_features)
|
629 |
+
|
630 |
+
return np.array(features, dtype=np.float32)
|
631 |
+
|
632 |
+
def calculate_channel_contributions(self, signal_data):
|
633 |
+
contributions = np.zeros(6)
|
634 |
+
for i in range(6):
|
635 |
+
contributions[i] = np.sqrt(np.mean(signal_data[i]**2))
|
636 |
+
|
637 |
+
total = np.sum(contributions) + 1e-10
|
638 |
+
return contributions / total
|
639 |
+
|
640 |
+
def update_performance_metrics(self, predicted_class, confidence, processing_time):
|
641 |
+
metrics = self.performance_metrics
|
642 |
+
|
643 |
+
metrics['total_predictions'] += 1
|
644 |
+
|
645 |
+
if confidence > 0.8:
|
646 |
+
metrics['high_confidence_predictions'] += 1
|
647 |
+
elif confidence < 0.5:
|
648 |
+
metrics['low_confidence_predictions'] += 1
|
649 |
+
|
650 |
+
metrics['prediction_times'].append(processing_time)
|
651 |
+
if len(metrics['prediction_times']) > 50:
|
652 |
+
metrics['prediction_times'].pop(0)
|
653 |
+
|
654 |
+
if metrics['prediction_times']:
|
655 |
+
avg_time = np.mean(metrics['prediction_times'])
|
656 |
+
metrics['processing_fps'] = 1.0 / max(avg_time, 0.001)
|
657 |
+
|
658 |
+
if metrics['last_prediction'] is not None and metrics['last_prediction'] != predicted_class:
|
659 |
+
metrics['emotion_transitions'] += 1
|
660 |
+
metrics['last_prediction'] = predicted_class
|
661 |
+
|
662 |
+
metrics['confidence_trend'].append(float(confidence))
|
663 |
+
if len(metrics['confidence_trend']) > 10:
|
664 |
+
metrics['confidence_trend'].pop(0)
|
665 |
+
|
666 |
+
if self.confidence_history:
|
667 |
+
metrics['average_confidence'] = np.mean(self.confidence_history)
|
668 |
+
|
669 |
+
if metrics['total_predictions'] > 1:
|
670 |
+
transition_rate = metrics['emotion_transitions'] / metrics['total_predictions']
|
671 |
+
metrics['stability_score'] = max(0, 1.0 - transition_rate)
|
672 |
+
|
673 |
+
def update_plot(self, frame):
|
674 |
+
if not self.is_running:
|
675 |
+
return
|
676 |
+
|
677 |
+
start_time = time.time()
|
678 |
+
|
679 |
+
if frame % 2 == 0:
|
680 |
+
if np.random.random() < 0.2:
|
681 |
+
emotion_bias = np.random.randint(0, 4)
|
682 |
+
else:
|
683 |
+
emotion_bias = None
|
684 |
+
|
685 |
+
new_samples = self.buffer_size // 8
|
686 |
+
new_data = self.generate_realistic_eeg_signal(emotion_bias)[:, :new_samples]
|
687 |
+
self.update_buffer(new_data)
|
688 |
+
|
689 |
+
prediction_start = time.time()
|
690 |
+
features = self.extract_lightweight_features(self.raw_buffer)
|
691 |
+
|
692 |
+
try:
|
693 |
+
scaled_features = self.scaler.transform([features])
|
694 |
+
|
695 |
+
if self.selector is not None:
|
696 |
+
scaled_features = self.selector.transform(scaled_features)
|
697 |
+
|
698 |
+
if self.pca is not None:
|
699 |
+
scaled_features = self.pca.transform(scaled_features)
|
700 |
+
|
701 |
+
probs = self.model.predict(scaled_features, verbose=0)[0]
|
702 |
+
predicted_class = np.argmax(probs)
|
703 |
+
confidence = np.max(probs)
|
704 |
+
|
705 |
+
except Exception as e:
|
706 |
+
print(f"Prediction error: {e}")
|
707 |
+
probs = np.array([0.25, 0.25, 0.25, 0.25])
|
708 |
+
predicted_class = 0
|
709 |
+
confidence = 0.25
|
710 |
+
|
711 |
+
prediction_time = time.time() - prediction_start
|
712 |
+
|
713 |
+
self.update_performance_metrics(predicted_class, confidence, prediction_time)
|
714 |
+
|
715 |
+
self.prediction_history.append(predicted_class)
|
716 |
+
self.confidence_history.append(confidence)
|
717 |
+
self.time_history.append(datetime.now())
|
718 |
+
|
719 |
+
if len(self.prediction_history) > self.max_history:
|
720 |
+
self.prediction_history.pop(0)
|
721 |
+
self.confidence_history.pop(0)
|
722 |
+
self.time_history.pop(0)
|
723 |
+
|
724 |
+
contributions = self.calculate_channel_contributions(self.raw_buffer)
|
725 |
+
|
726 |
+
self.update_eeg_plot()
|
727 |
+
self.update_probabilities(probs)
|
728 |
+
self.update_performance_plot()
|
729 |
+
self.update_contributions(contributions)
|
730 |
+
|
731 |
+
emotion_name = self.emotion_labels[predicted_class]
|
732 |
+
metrics = self.performance_metrics
|
733 |
+
elapsed_time = time.time() - self.start_time if self.start_time else 0
|
734 |
+
|
735 |
+
print(f"\r{datetime.now().strftime('%H:%M:%S')} | "
|
736 |
+
f"Emotion: {emotion_name} | "
|
737 |
+
f"Conf: {confidence:.3f} | "
|
738 |
+
f"FPS: {metrics['processing_fps']:.1f} | "
|
739 |
+
f"Stab: {metrics['stability_score']:.2f} | "
|
740 |
+
f"Total: {metrics['total_predictions']} | "
|
741 |
+
f"Time: {elapsed_time:.0f}s", end='')
|
742 |
+
|
743 |
+
def update_eeg_plot(self):
|
744 |
+
self.ax1.clear()
|
745 |
+
|
746 |
+
colors = plt.cm.tab10(np.linspace(0, 1, 6))
|
747 |
+
display_samples = min(300, self.buffer_size) # Show fewer samples for performance
|
748 |
+
|
749 |
+
for i in range(6):
|
750 |
+
offset = i * 20e-6
|
751 |
+
signal = self.raw_buffer[i, -display_samples:] + offset
|
752 |
+
self.ax1.plot(signal, label=self.ch_names[i],
|
753 |
+
color=colors[i], linewidth=1.0, alpha=0.8)
|
754 |
+
|
755 |
+
self.ax1.set_title("Live EEG Signals", fontsize=12)
|
756 |
+
self.ax1.set_xlabel("Time (samples)")
|
757 |
+
self.ax1.set_ylabel("Amplitude (µV)")
|
758 |
+
self.ax1.legend(loc='upper right', fontsize=8)
|
759 |
+
self.ax1.grid(True, alpha=0.3)
|
760 |
+
|
761 |
+
def update_performance_plot(self):
|
762 |
+
self.ax3.clear()
|
763 |
+
|
764 |
+
metrics = self.performance_metrics
|
765 |
+
|
766 |
+
if metrics['total_predictions'] > 0:
|
767 |
+
high_conf_pct = (metrics['high_confidence_predictions'] / metrics['total_predictions']) * 100
|
768 |
+
low_conf_pct = (metrics['low_confidence_predictions'] / metrics['total_predictions']) * 100
|
769 |
+
|
770 |
+
metrics_text = f"""PERFORMANCE METRICS
|
771 |
+
|
772 |
+
Total Predictions: {metrics['total_predictions']}
|
773 |
+
Average Confidence: {metrics['average_confidence']:.3f}
|
774 |
+
High Confidence (>0.8): {high_conf_pct:.1f}%
|
775 |
+
Low Confidence (<0.5): {low_conf_pct:.1f}%
|
776 |
+
|
777 |
+
Processing Speed: {metrics['processing_fps']:.1f} FPS
|
778 |
+
Stability Score: {metrics['stability_score']:.3f}
|
779 |
+
Emotion Transitions: {metrics['emotion_transitions']}
|
780 |
+
|
781 |
+
Model Accuracy: {high_conf_pct:.1f}%
|
782 |
+
Response Time: {np.mean(metrics['prediction_times'])*1000:.1f}ms"""
|
783 |
+
|
784 |
+
self.ax3.text(0.02, 0.98, metrics_text,
|
785 |
+
transform=self.ax3.transAxes,
|
786 |
+
fontsize=8, verticalalignment='top',
|
787 |
+
fontfamily='monospace',
|
788 |
+
bbox=dict(boxstyle="round,pad=0.3", facecolor="lightblue", alpha=0.7))
|
789 |
+
|
790 |
+
if len(metrics['confidence_trend']) > 1:
|
791 |
+
trend_x = np.arange(len(metrics['confidence_trend']))
|
792 |
+
trend_data = np.array(metrics['confidence_trend'], dtype=np.float64)
|
793 |
+
self.ax3.plot(trend_x + 0.6, trend_data * 0.4 + 0.1,
|
794 |
+
'g-o', markersize=3, linewidth=2, label='Confidence Trend')
|
795 |
+
|
796 |
+
# Add trend analysis
|
797 |
+
if len(metrics['confidence_trend']) > 3:
|
798 |
+
try:
|
799 |
+
x_data = np.array(range(len(trend_data[-5:])), dtype=np.float64)
|
800 |
+
y_data = np.array(trend_data[-5:], dtype=np.float64)
|
801 |
+
recent_trend = np.polyfit(x_data, y_data, 1)[0]
|
802 |
+
trend_direction = "↗" if recent_trend > 0.01 else "↘" if recent_trend < -0.01 else "→"
|
803 |
+
self.ax3.text(0.7, 0.9, f"Trend: {trend_direction}",
|
804 |
+
transform=self.ax3.transAxes, fontsize=10, fontweight='bold')
|
805 |
+
except:
|
806 |
+
# Fallback if polyfit fails
|
807 |
+
self.ax3.text(0.7, 0.9, f"Trend: →",
|
808 |
+
transform=self.ax3.transAxes, fontsize=10, fontweight='bold')
|
809 |
+
|
810 |
+
self.ax3.set_xlim(0, 1)
|
811 |
+
self.ax3.set_ylim(0, 1)
|
812 |
+
self.ax3.set_title("Performance Metrics & Confidence Trend", fontsize=10)
|
813 |
+
|
814 |
+
if metrics['average_confidence'] > 0.8:
|
815 |
+
title_color = 'green'
|
816 |
+
elif metrics['average_confidence'] > 0.6:
|
817 |
+
title_color = 'orange'
|
818 |
+
else:
|
819 |
+
title_color = 'red'
|
820 |
+
self.ax3.title.set_color(title_color)
|
821 |
+
|
822 |
+
def update_contributions(self, contributions):
|
823 |
+
self.ax4.clear()
|
824 |
+
|
825 |
+
colors = plt.cm.viridis(contributions)
|
826 |
+
bars = self.ax4.barh(self.ch_names, contributions, color=colors)
|
827 |
+
|
828 |
+
for i, (bar, v) in enumerate(zip(bars, contributions)):
|
829 |
+
if v > 0.05:
|
830 |
+
self.ax4.text(v + 0.02, i, f"{v*100:.1f}%",
|
831 |
+
va='center', fontsize=9)
|
832 |
+
|
833 |
+
self.ax4.set_title("Electrode Contributions", fontsize=10)
|
834 |
+
self.ax4.set_xlabel("Contribution Rate", fontsize=9)
|
835 |
+
self.ax4.set_xlim(0, 0.6)
|
836 |
+
self.ax4.grid(True, alpha=0.3, axis='x')
|
837 |
+
|
838 |
+
def update_probabilities(self, probs):
|
839 |
+
self.ax2.clear()
|
840 |
+
|
841 |
+
emotions = [self.emotion_labels[i] for i in range(4)]
|
842 |
+
bars = self.ax2.barh(emotions, probs, color=self.emotion_colors)
|
843 |
+
|
844 |
+
max_idx = np.argmax(probs)
|
845 |
+
bars[max_idx].set_edgecolor('black')
|
846 |
+
bars[max_idx].set_linewidth(2)
|
847 |
+
|
848 |
+
for bar, prob in zip(bars, probs):
|
849 |
+
width = bar.get_width()
|
850 |
+
if width > 0.05:
|
851 |
+
self.ax2.text(width + 0.02, bar.get_y() + bar.get_height()/2,
|
852 |
+
f"{width*100:.1f}%", ha='left', va='center',
|
853 |
+
fontsize=9, fontweight='bold')
|
854 |
+
|
855 |
+
self.ax2.set_title("Emotion Probabilities", fontsize=12)
|
856 |
+
self.ax2.set_xlabel("Probability")
|
857 |
+
self.ax2.set_xlim(0, 1)
|
858 |
+
self.ax2.grid(True, alpha=0.3, axis='x')
|
859 |
+
|
860 |
+
current_emotion = emotions[max_idx]
|
861 |
+
confidence = probs[max_idx]
|
862 |
+
self.ax2.text(0.5, 1.05, f"Current: {current_emotion} ({confidence*100:.1f}%)",
|
863 |
+
transform=self.ax2.transAxes, ha='center',
|
864 |
+
fontsize=10, fontweight='bold', color=self.emotion_colors[max_idx])
|
865 |
+
|
866 |
+
def start_monitoring(self):
|
867 |
+
print("\n" + "="*60)
|
868 |
+
print(" OPTIMIZED EEG EMOTION RECOGNITION MONITOR")
|
869 |
+
print("="*60)
|
870 |
+
print("\nPress 'X' to close the window...")
|
871 |
+
print("Real-time performance metrics will be displayed")
|
872 |
+
print("-"*60)
|
873 |
+
|
874 |
+
self.is_running = True
|
875 |
+
self.start_time = time.time()
|
876 |
+
|
877 |
+
self.animation = FuncAnimation(
|
878 |
+
self.fig,
|
879 |
+
self.update_plot,
|
880 |
+
interval=self.update_interval,
|
881 |
+
blit=False,
|
882 |
+
cache_frame_data=False
|
883 |
+
)
|
884 |
+
|
885 |
+
plt.show()
|
886 |
+
|
887 |
+
self.is_running = False
|
888 |
+
print("\n\nMonitoring stopped.")
|
889 |
+
|
890 |
+
if self.prediction_history:
|
891 |
+
self.print_summary_statistics()
|
892 |
+
|
893 |
+
def print_summary_statistics(self):
|
894 |
+
print("\n" + "="*80)
|
895 |
+
print(" DETAILED PERFORMANCE & STATISTICS SUMMARY")
|
896 |
+
print("="*80)
|
897 |
+
|
898 |
+
if not self.prediction_history:
|
899 |
+
print("No data collected.")
|
900 |
+
return
|
901 |
+
|
902 |
+
metrics = self.performance_metrics
|
903 |
+
total_time = time.time() - self.start_time if self.start_time else 0
|
904 |
+
|
905 |
+
print("\n📊 PERFORMANCE METRICS:")
|
906 |
+
print(f" Total Predictions: {metrics['total_predictions']}")
|
907 |
+
print(f" Total Runtime: {total_time:.1f} seconds")
|
908 |
+
print(f" Average Processing Speed: {metrics['processing_fps']:.1f} FPS")
|
909 |
+
print(f" Average Response Time: {np.mean(metrics['prediction_times'])*1000:.1f}ms")
|
910 |
+
print(f" Model Stability Score: {metrics['stability_score']:.3f} (0-1, higher=better)")
|
911 |
+
print(f" Emotion Transitions: {metrics['emotion_transitions']}")
|
912 |
+
|
913 |
+
print(f"\n🎯 CONFIDENCE ANALYSIS:")
|
914 |
+
total = len(self.prediction_history)
|
915 |
+
high_conf_count = metrics['high_confidence_predictions']
|
916 |
+
low_conf_count = metrics['low_confidence_predictions']
|
917 |
+
medium_conf_count = max(0, total - high_conf_count - low_conf_count)
|
918 |
+
|
919 |
+
print(f" Average Confidence: {metrics['average_confidence']:.3f}")
|
920 |
+
print(f" Confidence Std Dev: {np.std(self.confidence_history):.3f}")
|
921 |
+
print(f" High Confidence (>0.8): {high_conf_count} ({high_conf_count/total*100:.1f}%)")
|
922 |
+
print(f" Medium Confidence (0.5-0.8): {medium_conf_count} ({medium_conf_count/total*100:.1f}%)")
|
923 |
+
print(f" Low Confidence (<0.5): {low_conf_count} ({low_conf_count/total*100:.1f}%)")
|
924 |
+
|
925 |
+
accuracy_score = high_conf_count / total * 100 if total > 0 else 0
|
926 |
+
print(f"\n🏆 MODEL QUALITY ASSESSMENT:")
|
927 |
+
print(f" Estimated Accuracy: {accuracy_score:.1f}% (based on high confidence predictions)")
|
928 |
+
|
929 |
+
if accuracy_score >= 80:
|
930 |
+
quality = "EXCELLENT 🌟"
|
931 |
+
elif accuracy_score >= 70:
|
932 |
+
quality = "GOOD ✅"
|
933 |
+
elif accuracy_score >= 60:
|
934 |
+
quality = "FAIR ⚠️"
|
935 |
+
else:
|
936 |
+
quality = "POOR ❌"
|
937 |
+
print(f" Model Quality Rating: {quality}")
|
938 |
+
|
939 |
+
emotion_counts = {i: 0 for i in range(4)}
|
940 |
+
for pred in self.prediction_history:
|
941 |
+
emotion_counts[pred] += 1
|
942 |
+
|
943 |
+
print(f"\n😊 EMOTION DISTRIBUTION:")
|
944 |
+
for emotion_id, count in emotion_counts.items():
|
945 |
+
percentage = (count / total) * 100
|
946 |
+
bar = "█" * int(percentage / 5)
|
947 |
+
print(f" {self.emotion_labels[emotion_id]:<15}: {count:>3} ({percentage:>5.1f}%) {bar}")
|
948 |
+
|
949 |
+
dominant_emotion = max(emotion_counts, key=emotion_counts.get)
|
950 |
+
dominant_percentage = emotion_counts[dominant_emotion] / total * 100
|
951 |
+
print(f"\n Dominant Emotion: {self.emotion_labels[dominant_emotion]} ({dominant_percentage:.1f}%)")
|
952 |
+
|
953 |
+
if len(metrics['confidence_trend']) > 3:
|
954 |
+
try:
|
955 |
+
x_data = np.array(range(len(metrics['confidence_trend'])), dtype=np.float64)
|
956 |
+
y_data = np.array(metrics['confidence_trend'], dtype=np.float64)
|
957 |
+
trend_slope = np.polyfit(x_data, y_data, 1)[0]
|
958 |
+
print(f"\n📈 TREND ANALYSIS:")
|
959 |
+
if trend_slope > 0.01:
|
960 |
+
trend_desc = "IMPROVING ↗"
|
961 |
+
elif trend_slope < -0.01:
|
962 |
+
trend_desc = "DECLINING ↘"
|
963 |
+
else:
|
964 |
+
trend_desc = "STABLE →"
|
965 |
+
print(f" Recent Confidence Trend: {trend_desc} (slope: {trend_slope:.4f})")
|
966 |
+
except Exception as e:
|
967 |
+
print(f"\n📈 TREND ANALYSIS:")
|
968 |
+
print(f" Recent Confidence Trend: STABLE → (analysis unavailable)")
|
969 |
+
|
970 |
+
print(f"\n💡 RECOMMENDATIONS:")
|
971 |
+
if accuracy_score < 70:
|
972 |
+
print(" • Consider retraining the model with more data")
|
973 |
+
print(" • Check data quality and preprocessing steps")
|
974 |
+
if metrics['stability_score'] < 0.7:
|
975 |
+
print(" • Model predictions are unstable - review signal quality")
|
976 |
+
if metrics['processing_fps'] < 5:
|
977 |
+
print(" • Processing speed is slow - consider model optimization")
|
978 |
+
if accuracy_score >= 80 and metrics['stability_score'] >= 0.8:
|
979 |
+
print(" • Model performance is excellent! ✨")
|
980 |
+
|
981 |
+
print("\n" + "="*80)
|
982 |
+
|
983 |
+
|
984 |
+
def main():
|
985 |
+
model_path = 'path/to/bai-6 EmotionOptimized.h5'
|
986 |
+
scaler_path = 'path/to/bai-6 ScalerOptimized.pkl'
|
987 |
+
selector_path = 'path/to/bai-6_feature_selector_opt.pkl'
|
988 |
+
pca_path = 'path/to/bai-6_pca_reducer_opt.pkl'
|
989 |
+
|
990 |
+
try:
|
991 |
+
monitor = EEGEmotionMonitorOptimized(
|
992 |
+
model_path, scaler_path, selector_path, pca_path
|
993 |
+
)
|
994 |
+
|
995 |
+
monitor.start_monitoring()
|
996 |
+
|
997 |
+
except FileNotFoundError as e:
|
998 |
+
print(f"Model or preprocessor file not found: {e}")
|
999 |
+
print("Please ensure the model has been trained and saved.")
|
1000 |
+
print("Available fallback: Using basic model without feature selection/PCA")
|
1001 |
+
|
1002 |
+
try:
|
1003 |
+
basic_model_path = 'path/to/bai-6 EmotionOptimized.h5'
|
1004 |
+
basic_scaler_path = 'path/to/bai-6 ScalerOptimized.pkl'
|
1005 |
+
|
1006 |
+
monitor = EEGEmotionMonitorOptimized(basic_model_path, basic_scaler_path)
|
1007 |
+
monitor.start_monitoring()
|
1008 |
+
|
1009 |
+
except Exception as e2:
|
1010 |
+
print(f"Fallback also failed: {e2}")
|
1011 |
+
|
1012 |
+
except Exception as e:
|
1013 |
+
print(f"Error: {e}")
|
1014 |
+
import traceback
|
1015 |
+
traceback.print_exc()
|
1016 |
+
|
1017 |
+
|
1018 |
+
if __name__ == "__main__":
|
1019 |
+
main()
|
1020 |
+
```
|
1021 |
+
|
1022 |
+
</details>
|
1023 |
+
|
1024 |
-------------
|
1025 |
## Lisans/License
|
1026 |
CC-BY-NC-SA-4.0
|