Repository: torcheeg/torcheeg (language: python)

Issue:
TypeError: can only concatenate list (not "int") to list
I tried torcheeg with SEED dataset. My code as bellow:
```
dataset = SEEDDataset(io_path=f'./seed',
                              root_path='../../DATASET/SEED_EEG/Preprocessed_EEG',
                              offline_transform=transforms.Compose([
                                  transforms.BandDifferentialEntropy(),
                                  transforms.ToGrid(SEED_CHANNEL_LOCATION_DICT)
                              ]),
                              online_transform=transforms.ToTensor(),
                              label_transform=transforms.Compose([
                                  transforms.Select(['emotion']),
                                  transforms.Lambda(lambda x: x + 1)
                              ]),
                              num_worker=4)
print(dataset[0])

```
But when running the code, I met an error:
```
TypeError                                 Traceback (most recent call last)
Cell In[50], line 1
----> 1 print(dataset[0])

File ~/anaconda3/envs/eeg/lib/python3.8/site-packages/torcheeg/datasets/module/emotion_recognition/seed.py:184, in SEEDDataset.__getitem__(self, index)
    181     signal = self.online_transform(eeg=eeg)['eeg']
    183 if self.label_transform:
--> 184     label = self.label_transform(y=info)['y']
    186 return signal, label

File ~/anaconda3/envs/eeg/lib/python3.8/site-packages/torcheeg/transforms/any/compose.py:54, in Compose.__call__(self, *args, **kwargs)
     51     raise KeyError("Please pass data as named parameters.")
     53 for t in self.transforms:
---> 54     kwargs = t(**kwargs)
     55 return kwargs

File ~/anaconda3/envs/eeg/lib/python3.8/site-packages/torcheeg/transforms/any/lambd.py:50, in Lambda.__call__(self, *args, **kwargs)
     43 def __call__(self, *args, **kwargs) -> Dict[str, any]:
     44     r'''
     45     Args:
     46         x (any): The input.
     47     Returns:
     48         any: The transformed output.
     49     '''
...
---> 10                                   transforms.Lambda(lambda x: x + 1)
     11                               ]),
     12                               num_worker=4)

TypeError: can only concatenate list (not "int") to list
```

Can anyone help me how to resolve this problem?
Thank you very much!


Discussion / hints:
Greeting,

In TorchEEG version 1.0.11, when a list is passed in `transforms.Select()`, TorchEEG returns a list of labels. For example:
```
info = {'valence': 4.5, 'arousal': 5.5, 'subject_id': 7}
transformed_label = Select(key=['valence'])(y=info)
transformed_label['y'] == [4.5]
```
When a string is passed in `transforms.Select()`, TorchEEG returns a label. For example:
```
info = {'valence': 4.5, 'arousal': 5.5, 'subject_id': 7}
transformed_label = Select(key='valence')(y=info)
transformed_label['y'] == 4.5
```
Therefore, you can consider the following modifications:
```
dataset = SEEDDataset(io_path=f'./seed',
                               root_path='../../DATASET/SEED_EEG/Preprocessed_EEG',
                               offline_transform = transforms. Compose([
                                   transforms.BandDifferentialEntropy(),
                                   transforms.ToGrid(SEED_CHANNEL_LOCATION_DICT)
                               ]),
                               online_transform = transforms.ToTensor(),
                               label_transform = transforms. Compose([
                                   transforms. Select('emotion'), # see here!!!!!!!!!!!!!!!!!!
                                   transforms.Lambda(lambda x: x + 1)
                               ]),
                               num_worker=4)
```
You can also iterate over the list in the `Lambda` function if you don't want to modify `Select(['valence'])`.
We'll update this problem on the docs, and we can also adopt a PR if you'd like (*^▽^*)
Thank you, @tczhangzhi! I understood.
Could I add one more question?
After loading the SEED dataset with the above code, I want to train a classifier with a CCNN model with code sample from: https://pypi.org/project/torcheeg/
```
for i, (train_dataset, val_dataset) in enumerate(k_fold.split(dataset)):
    model = CCNN().to(device)
    optimizer = torch.optim.Adam(model.parameters(), lr=1e-4)

    train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
    val_loader = DataLoader(val_dataset, batch_size=batch_size, shuffle=True)
    
    epochs = 5
    for t in range(epochs):
        print(f'Epoch {t+1}\n -------------------')
        train(train_loader, model, loss_fn, optimizer)
        valid(val_loader, model, loss_fn)
    print('Done!')
```
but, I met an error:
```
ValueError                                Traceback (most recent call last)
Cell In[18], line 11
      9 for t in range(epochs):
     10     print(f'Epoch {t+1}\n -------------------')
---> 11     train(train_loader, model, loss_fn, optimizer)
     12     valid(val_loader, model, loss_fn)
     13 print('Done!')

Cell In[5], line 17, in train(dataloader, model, loss_fn, optimizer)
     11 # X = batch[0]
     12 # y = torch.from_numpy(np.asarray(batch[1])).to(device)
     13 # y = batch[1]
     14 
     15 # Computer prediction error
     16 pred = model(X)
---> 17 loss = loss_fn(pred, y)
     19 # backpropagation
     20 optimizer.zero_grad()
File ~/anaconda3/envs/eeg/lib/python3.8/site-packages/torch/nn/modules/module.py:1194, in Module._call_impl(self, *input, **kwargs)
   1190 # If we don't have any hooks, we want to skip the rest of the logic in
   1191 # this function, and just call forward.
   1192 if not (self._backward_hooks or self._forward_hooks or self._forward_pre_hooks or _global_backward_hooks
   1193         or _global_forward_hooks or _global_forward_pre_hooks):
...
   3024 if size_average is not None or reduce is not None:
   3025     reduction = _Reduction.legacy_get_string(size_average, reduce)
-> 3026 return torch._C._nn.cross_entropy_loss(input, target, weight, _Reduction.get_enum(reduction), ignore_index, label_smoothing)

ValueError: Expected input batch_size (64) to match target batch_size (0).
```

If I changed the loading of the SEED dataset as below:
```
dataset2 = SEEDDataset(io_path=f'./seed2',
                        root_path='../../DATASET/SEED_EEG/Preprocessed_EEG',
                        online_transform=transforms.Compose([
                            transforms.ToTensor(),
                            transforms.To2d()
                        ]),
                        label_transform=transforms.Compose([
                            transforms.Select('emotion'),
                            transforms.Lambda(lambda x: x + 1)
                        ]))
```
The error is:
```
RuntimeError                              Traceback (most recent call last)
Cell In[17], line 11
      9 for t in range(epochs):
     10     print(f'Epoch {t+1}\n -------------------')
---> 11     train(train_loader, model, loss_fn, optimizer)
     12     valid(val_loader, model, loss_fn)
     13 print('Done!')

Cell In[5], line 16, in train(dataloader, model, loss_fn, optimizer)
     10 y = batch[1][0].to(device)
     11 # X = batch[0]
     12 # y = torch.from_numpy(np.asarray(batch[1])).to(device)
     13 # y = batch[1]
     14 
     15 # Computer prediction error
---> 16 pred = model(X)
     17 loss = loss_fn(pred, y)
     19 # backpropagation
File ~/anaconda3/envs/eeg/lib/python3.8/site-packages/torch/nn/modules/module.py:1194, in Module._call_impl(self, *input, **kwargs)
   1190 # If we don't have any hooks, we want to skip the rest of the logic in
   1191 # this function, and just call forward.
   1192 if not (self._backward_hooks or self._forward_hooks or self._forward_pre_hooks or _global_backward_hooks
   1193         or _global_forward_hooks or _global_forward_pre_hooks):
...
    458                     _pair(0), self.dilation, self.groups)
--> 459 return F.conv2d(input, weight, bias, self.stride,
    460                 self.padding, self.dilation, self.groups)

RuntimeError: Given groups=1, weight of size [64, 4, 4, 4], expected input[64, 1, 65, 203] to have 4 channels, but got 1 channels instead
```
Can you show me how to overcome these errors?
Thank you very much!

For question 1, the error means that the length of `y` is 0, which does not match the length of the predicted result.

The possible cause of this problem is that the variable `y` has not been assigned a value correctly. Did you do something like `y = batch[1]`?  If no problem is found, you can also try to delete IO (./seed) and regenerate.

For question 2, these codes work well:
```
dataset2 = SEEDDataset(io_path=f'./seed2',
                        root_path='../../DATASET/SEED_EEG/Preprocessed_EEG',
                        online_transform=transforms.Compose([
                            transforms.ToTensor(),
                            transforms.To2d()
                        ]),
                        label_transform=transforms.Compose([
                            transforms.Select('emotion'),
                            transforms.Lambda(lambda x: x + 1)
                        ]))
```

The only problem you need to know is the shape of the input that the different models accept. You can refer to the original paper or the TorchEEG document for this part.

For example, the ideal input shape for CCNN is `[N, 4, 9, 9]` (https://torcheeg.readthedocs.io/en/latest/generated/torcheeg.models.CCNN.html#torcheeg.models.CCNN). The ideal input shape for TSCeption is `[N, 1, 28, 512]` (https://torcheeg.readthedocs.io/en/latest/generated/torcheeg.models.TSCeption.html#torcheeg.models.TSCeption). Your preprocessed results should conform to these shapes.

For your `online_transform`, you may want to apply a CNN-based model on raw EEG data, which is the aim of TSCeption. Thus, you can let:

```
model = TSCeption(num_classes=2,
                  num_electrodes=65, # see here [64, 1, *65*, 200]
                  sampling_rate=200, # see here [64, 1, 65, *200*]
                  num_T=15,
                  num_S=15,
                  hid_channels=32,
                  dropout=0.5)
```
Thank @tczhangzhi !
I applied your comment, but a new error occurred:
```
RuntimeError                              Traceback (most recent call last)
Cell In[42], line 19
     17 for t in range(epochs):
     18     print(f'Epoch {t+1}\n -------------------')
---> 19     train(train_loader, model, loss_fn, optimizer)
     20     valid(val_loader, model, loss_fn)
     21 print('Done!')

Cell In[22], line 9, in train(dataloader, model, loss_fn, optimizer)
      6 y = batch[1].to(device)
      8 # Computer prediction error
----> 9 pred = model(X)
     10 print(f'Pred: {pred}')
     11 print(f'Target: {y}')

File ~/anaconda3/envs/eeg/lib/python3.8/site-packages/torch/nn/modules/module.py:1194, in Module._call_impl(self, *input, **kwargs)
   1190 # If we don't have any hooks, we want to skip the rest of the logic in
   1191 # this function, and just call forward.
   1192 if not (self._backward_hooks or self._forward_hooks or self._forward_pre_hooks or _global_backward_hooks
   1193         or _global_forward_hooks or _global_forward_pre_hooks):
-> 1194     return forward_call(*input, **kwargs)
   1195 # Do not call functions when jit is used
   1196 full_backward_hooks, non_full_backward_hooks = [], []
...
    458                     _pair(0), self.dilation, self.groups)
--> 459 return F.conv2d(input, weight, bias, self.stride,
    460                 self.padding, self.dilation, self.groups)

RuntimeError: Calculated padded input size per channel: (62 x 52). Kernel size: (65 x 1). Kernel size can't be greater than actual input size
```
Btw, could you provide me a sample from loading the SEED dataset, reshaping, then training with a CNN model, such as CCNN, TSCeption, etc. Actually, I don't have the DEAP dataset. I have only the SEED dataset.  The torcheeg libraries, has only sample code for the DEAP dataset.
```
dataset = DEAPDataset(io_path=f'./deap',
                    root_path='./data_preprocessed_python',
                    offline_transform=transforms.Compose([
                        transforms.BandDifferentialEntropy(),
                        transforms.ToGrid(DEAP_CHANNEL_LOCATION_DICT)
                    ]),
                    online_transform=transforms.ToTensor(),
                    label_transform=transforms.Compose([
                        transforms.Select('valence'),
                        transforms.Binary(5.0),
                    ]))
        model = CCNN(num_classes=2, in_channels=4, grid_size=(9, 9))
```

I really appreciate your help!
Please refer to the documentation, e.g.,

https://torcheeg.readthedocs.io/en/latest/auto_examples/examples_seed_feature_ada.html#sphx-glr-auto-examples-examples-seed-feature-ada-py

Later today I will upload some new cases in the gallery:

https://torcheeg.readthedocs.io/en/latest/auto_examples/index.html

DONE. Please refer to the documentation, e.g.,

https://torcheeg.readthedocs.io/en/latest/auto_examples/examples_seed_ccnn.html#sphx-glr-auto-examples-examples-seed-ccnn-py

https://torcheeg.readthedocs.io/en/latest/auto_examples/examples_seed_tsception.html#sphx-glr-auto-examples-examples-seed-tsception-py

Feel free to ask any questions and pull requests!
Thank you, @tczhangzhi !
I will try your recommendation!

Fix patch:
diff --git a/.readthedocs.yaml b/.readthedocs.yaml
index 7c506f1..8f55313 100644
--- a/.readthedocs.yaml
+++ b/.readthedocs.yaml
@@ -5,7 +5,6 @@
 version: 2
 
 build:
-   image: latest
    os: ubuntu-22.04
    tools:
       python: "3.8"
@@ -14,9 +13,7 @@ sphinx:
    configuration: docs/source/conf.py
 
 python:
-   version: 3.8
-   system_packages: true
    install:
       - requirements: docs/requirements.txt
       - method: setuptools
-        path: .
\ No newline at end of file
+        path: .
diff --git a/docs/source/torcheeg.datasets.rst b/docs/source/torcheeg.datasets.rst
index 1de1431..9c17902 100644
--- a/docs/source/torcheeg.datasets.rst
+++ b/docs/source/torcheeg.datasets.rst
@@ -67,6 +67,16 @@ Steady-state Visual Evoked Potential Datasets
 
     TSUBenckmarkDataset
 
+HealthCare Datasets 
+---------------------------------------------------------
+
+.. autosummary::
+    :toctree: generated
+    :nosignatures:
+    :template: datasettemplate.rst
+
+    SleepEDFDataset
+
 Customized Datasets
 ---------------------------------------------------------
 
diff --git a/torcheeg/datasets/constants/motor_imagery/bciciv_2a.py b/torcheeg/datasets/constants/motor_imagery/bciciv_2a.py
index fef34ef..500fe18 100644
--- a/torcheeg/datasets/constants/motor_imagery/bciciv_2a.py
+++ b/torcheeg/datasets/constants/motor_imagery/bciciv_2a.py
@@ -1,3 +1,11 @@
+from ..utils import format_channel_location_dict
 BCICIV2A_CHANNEL_LIST = [
     'FZ', '0', '1', '2', '3', '4', '5', 'C3', '6', 'CZ', '7', 'C4', '8', '9', '10', '11', '12', '13', '14', 'PZ', '15', '16'
-]
\ No newline at end of file
+]
+BCICIV2A_LOCATION_LIST = [['-',  '-',  '-',  'FZ', '-',  '-',  '-'],
+                          ['-',  '0',  '1',  '2',  '3',  '4',  '-'],
+                          ['5',  'C3', '6',  'CZ', '7',  'C4', '8'],
+                          ['-',  '9',  '10', '11', '12', '13', '-'],
+                          ['-',  '-',  '14', 'PZ', '15', '-',  '-'],
+                          ['-',  '-',  '-',  '16', '-',  '-',  '-']]
+BCICIV2A_LOCATION_DICT = format_channel_location_dict(BCICIV2A_CHANNEL_LIST,BCICIV2A_LOCATION_LIST)
diff --git a/torcheeg/datasets/module/__init__.py b/torcheeg/datasets/module/__init__.py
index 322dc2b..1ace19d 100644
--- a/torcheeg/datasets/module/__init__.py
+++ b/torcheeg/datasets/module/__init__.py
@@ -1,8 +1,8 @@
 from .base_dataset import BaseDataset
 from .folder_dataset import FolderDataset
 from .csv_folder_dataset import CSVFolderDataset
-
-from .sspev import *
+from .healthcare import *
+from .ssvep import *
 from .motor_imagery import *
 from .emotion_recognition import *
 from .personal_identification import *
\ No newline at end of file
diff --git a/torcheeg/datasets/module/healthcare/__init__.py b/torcheeg/datasets/module/healthcare/__init__.py
new file mode 100644
index 0000000..5ca8510
--- /dev/null
+++ b/torcheeg/datasets/module/healthcare/__init__.py
@@ -0,0 +1,1 @@
+from .sleep import SleepEDFDataset
\ No newline at end of file
diff --git a/torcheeg/datasets/module/healthcare/sleep.py b/torcheeg/datasets/module/healthcare/sleep.py
new file mode 100644
index 0000000..fec8566
--- /dev/null
+++ b/torcheeg/datasets/module/healthcare/sleep.py
@@ -0,0 +1,251 @@
+import os
+import pickle as pkl
+import pandas as pd
+from typing import Any, Callable, Dict, Tuple, Union
+import mne
+from ..base_dataset import BaseDataset
+from ....utils import get_random_dir_path
+
+
+class SleepEDFDataset(BaseDataset):
+    r'''
+    A dataset for studying human sleep stages (expanded version), of which a small subset was previously contributed in 2002, is now available in PhysioNet. The database now includes 61 full-night polysomnograms of healthy subjects and of subjects with mild difficulty falling asleep, with accompanying expert annotations of sleep stages.
+    This class generates training samples and test samples according to the given parameters, and caches the generated results in a unified input and output format (IO). The relevant information of the dataset is as follows:
+    
+    - Author:B Kemp.
+    - Year: 2002
+    - Download URL: https://www.physionet.org/content/sleep-edfx/1.0.0/
+    - Reference: B Kemp, AH Zwinderman, B Tuk, HAC Kamphuisen, JJL Oberyé. Analysis of a sleep-dependent neuronal feedback loop: the slow-wave microcontinuity of the EEG. IEEE-BME 47(9):1185-1194 (2000)
+    - Signals: 197 whole-night PolySomnoGraphic sleep recordings, containing EEG, EOG, chin EMG, and event markers. Corresponding hypnograms (sleep patterns) were manually scored by well-trained technicians according to the Rechtschaffen and Kales manual, and are also available. 
+    - sleep stages: W, R, 1, 2, 3, 4, M (Movement time) and ? (not scored).
+    
+    In order to use this dataset, the download folder :obj:`sleep-edf-database-expanded-1.0.0` is required, containing the following files and directories:
+    
+    - sleep-cassette (dir)
+    - sleep-telemetry (dir)
+    - SC-subjects.xls (file)
+    - ST-subjects.xls (file)
+    - ...
+
+    Here is a configure setting to obtain raw eeg examples:
+
+    .. code-block:: python
+    
+        dataset =SleepEDFDataset(root_path="D:\datasets\sleep-edf-database-expanded-1.0.0",io_path=".torcheeg\datasets_1709471408553_wDTQD",
+                num_channels=4,
+                chunk_size = 3000,
+                remove_unclear_example = True,
+                online_transform=transforms.ToTensor(),
+                label_transform=transforms.Compose([ transforms.Select(key="stage"),
+                                                     transforms.Mapping( map_dict={"W":0,"1":1,"2":2,"3":3,"4":4,"R":5} ) ])))
+        
+        print(dataset[0])
+        # EEG signal (torch.Tensor[4,3000]),
+        # coresponding baseline signal (torch.Tensor[4, 9, 9]),
+        # label (int)
+
+    
+    In particular, TorchEEG utilizes the producer-consumer model to allow multi-process data preprocessing. If your data preprocessing is time consuming, consider increasing :obj:`num_worker` for higher speedup. If running under Windows, please use the proper idiom in the main module:
+
+    Args:
+        root_path (str): Downloaded data folder (unzipped sleep-edf-database-expanded-1.0.0.zip) (default: :obj:`'./sleep-edf-database-expanded-1.0.0'`)
+        chunk_size (int): Number of data points included in each EEG chunk as training or test samples.  (default: :obj:`3000`)
+        overlap (int): Number of overlapping data points between different chunks when dividing EEG chunks. (default: :obj:`0`)
+        num_channel (int): Number of channels used, of which the first 4 channels are EEG signals. (default: :obj:`4`)
+        version (str): There are two studies corresponding to two different dataset called "cassette" and "Telemetry" in the downloaded data folder. Available options are ['cassette','Telemetry'] (default: :obj:`"cattesse"`)
+        remove_unclear_example (bool): Whether to remove the examples which are labels as "?". (default: :obj:`True`)
+        online_transform (Callable, optional): The transformation of the EEG signals and baseline EEG signals. The input is a :obj:`np.ndarray`, and the ouput is used as the first and second value of each element in the dataset. (default: :obj:`None`)
+        offline_transform (Callable, optional): The usage is the same as :obj:`online_transform`, but executed before generating IO intermediate results. (default: :obj:`None`)
+        label_transform (Callable, optional): The transformation of the label. The input is an information dictionary, and the ouput is used as the third value of each element in the dataset. (default: :obj:`None`)
+        before_trial (Callable, optional): The hook performed on the trial to which the sample belongs. It is performed before the offline transformation and thus typically used to implement context-dependent sample transformations, such as moving averages, etc. The input of this hook function is a 2D EEG signal with shape (number of electrodes, number of data points), whose ideal output shape is also (number of electrodes, number of data points).
+        after_trial (Callable, optional): The hook performed on the trial to which the sample belongs. It is performed after the offline transformation and thus typically used to implement context-dependent sample transformations, such as moving averages, etc. The input and output of this hook function should be a sequence of dictionaries representing a sequence of EEG samples. Each dictionary contains two key-value pairs, indexed by :obj:`eeg` (the EEG signal matrix) and :obj:`key` (the index in the database) respectively.
+        io_path (str): The path to generated unified data IO, cached as an intermediate result. If set to None, a random path will be generated. (default: :obj:`None`)
+        io_size (int): Maximum size database may grow to; used to size the memory mapping. If database grows larger than ``map_size``, an exception will be raised and the user must close and reopen. (default: :obj:`1048576`)
+        io_mode (str): Storage mode of EEG signal. When io_mode is set to :obj:`lmdb`, TorchEEG provides an efficient database (LMDB) for storing EEG signals. LMDB may not perform well on limited operating systems, where a file system based EEG signal storage is also provided. When io_mode is set to :obj:`pickle`, pickle-based persistence files are used. When io_mode is set to :obj:`memory`, memory are used. (default: :obj:`lmdb`)
+        num_worker (int): Number of subprocesses to use for data loading. 0 means that the data will be loaded in the main process. (default: :obj:`0`)
+        verbose (bool): Whether to display logs during processing, such as progress bars, etc. (default: :obj:`True`)
+    '''
+
+    def __init__(self,
+                 root_path: str = './sleep-edf-database-expanded-1.0.0',
+                 chunk_size: int = 3000,
+                 overlap: int = 0,
+                 num_channel: int = 4,
+                 version: str= "cassette",
+                 remove_unclear_example: bool = True,
+                 online_transform: Union[None, Callable] = None,
+                 offline_transform: Union[None, Callable] = None,
+                 label_transform: Union[None, Callable] = None,
+                 before_trial: Union[None, Callable] = None,
+                 after_trial: Union[Callable, None] = None,
+                 after_session: Union[Callable, None] = None,
+                 after_subject: Union[Callable, None] = None,
+                 io_path: Union[None, str] = None,
+                 io_size: int = 1048576,
+                 io_mode: str = 'lmdb',
+                 num_worker: int = 0,
+                 verbose: bool = True):
+        if io_path is None:
+            io_path = get_random_dir_path(dir_prefix='datasets')
+
+        assert version in ["cassette","Telemetry"],f"please choose \"version\" in ['cassette','Telemetry'].{version}(current setting) is not available. "
+        # pass all arguments to super class
+        params = {
+            'root_path': root_path,
+            'chunk_size': chunk_size,
+            'overlap': overlap,
+            'num_channel':num_channel,
+            'version':version,
+            'remove_unclear_example':remove_unclear_example,
+            'online_transform': online_transform,
+            'offline_transform': offline_transform,
+            'label_transform': label_transform,
+            'before_trial': before_trial,
+            'after_trial': after_trial,
+            'after_session': after_session,
+            'after_subject': after_subject,
+            'io_path': io_path,
+            'io_size': io_size,
+            'io_mode': io_mode,
+            'num_worker': num_worker,
+            'verbose': verbose
+        }
+        super().__init__(**params)
+        # save all arguments to __dict__
+        self.__dict__.update(params)
+
+    @staticmethod
+    def process_record(file: Any = None,
+                       root_path: str = './sleep-edf-database-expanded-1.0.0',
+                       version:str = "cassette",
+                       chunk_size: int = 3000,
+                       overlap: int = 0,
+                       num_channel: int =4,
+                       remove_unclear_example: bool =  True,
+                       before_trial: Union[None, Callable] = None,
+                       offline_transform: Union[None, Callable] = None,
+                       **kwargs):
+        file_name = file  # an element from file name list
+        # derive the given arguments (kwargs)
+        eeg_dir = "sleep-"+version
+        file_path = os.path.join(root_path,eeg_dir, file_name)
+        
+        eeg = mne.io.read_raw_edf(file_path).get_data()
+        eeg = eeg[:num_channel]
+
+        ann_file_name = list(filter(lambda x:x[-13:]=="Hypnogram.edf" and x[:7] == file_name[:7] ,os.listdir(os.path.join(root_path,eeg_dir))))[0]
+        ann = mne.read_annotations(os.path.join(root_path,eeg_dir,ann_file_name))
+    
+        subject_id = int(file_name[3:5])
+        trial_id = int(file_name[5])-1
+        meta_info = {'subject_id': subject_id, 'trial_id': trial_id}
+        if eeg_dir == "sleep-cassette":
+            suject_info = pd.read_excel(os.path.join(root_path ,"SC-subjects.xls"))  
+            query = suject_info[(suject_info["subject"]== subject_id) & (suject_info["night"] == trial_id+1)]
+            meta_info["age"],meta_info["sex"],meta_info["light_off"] = query.values[0][-3:]
+        else:
+            subject_info = pd.read_excel(os.path.join(root_path ,"ST-subjects.xls"))
+            query = subject_info[subject_info["Subject - age - sex"]==subject_id]
+            meta_info["age"],meta_info["sex"] = query.values[0,1:3]
+            Placebo_night = query[ ['Placebo night']].values[0,0]
+            meta_info["night"] = 'Placebo night' if trial_id+1 == Placebo_night else 'Temazepam night'
+            meta_info["light_off"] = query['Unnamed: 4'].values[0] if trial_id +1 == Placebo_night else query['Unnamed: 6'].values[0]
+
+        write_pointer = 0
+        
+        if before_trial:
+            eeg = before_trial(eeg)
+            # record the common meta info
+
+        start_at = 0
+        assert chunk_size > 0 and chunk_size <=eeg.shape[1] ,f"please set the chunk_size  correctly.(current chunk_size = {chunk_size})"
+        end_at = start_at + chunk_size
+        step = chunk_size - overlap
+        
+
+        ann_cur = 0
+        cur_stage_time = 0
+        while ann_cur<len(ann.duration):
+            stage = ann.description[ann_cur][-1]
+            if cur_stage_time + chunk_size > ann.duration[ann_cur]*100 :
+                start_at += int(ann.duration[ann_cur])-cur_stage_time
+                end_at = start_at + chunk_size
+                ann_cur += 1
+                cur_stage_time = 0
+                continue
+            
+            if stage == "?" and remove_unclear_example:
+                start_at += step
+                end_at = start_at + chunk_size
+                cur_stage_time+=step
+                continue
+
+            clip_sample = eeg[:, start_at:end_at]
+            t_eeg = clip_sample
+    
+            if not offline_transform is None:
+                t = offline_transform(eeg=clip_sample)
+                t_eeg = t['eeg']
+
+            clip_id = f'subject{subject_id}_night{trial_id}_{write_pointer}'
+            write_pointer += 1
+                # record meta info for each signal
+            record_info = {
+                'start_at': start_at,
+                'end_at': end_at,
+                'clip_id': clip_id,
+                'stage':stage
+            }
+            record_info.update(meta_info)
+
+            yield {'eeg': t_eeg, 'key': clip_id, 'info': record_info}
+
+            start_at += step
+            cur_stage_time += step
+            end_at = start_at + chunk_size
+    
+    def set_records(self,
+                    root_path: str ='D:\datasets\sleep-edf-database-expanded-1.0.0' ,
+                    **kwargs):
+        assert os.path.exists(
+            root_path
+        ), f'root_path ({root_path}) does not exist. Please download the dataset and set the root_path to the downloaded path.'
+
+        self.eeg_dir ="sleep-"+ kwargs["version"] 
+        return list(filter(lambda item:item[-7:]=="PSG.edf",os.listdir(os.path.join(root_path,self.eeg_dir))))
+
+    def __getitem__(self, index: int) -> Tuple:
+        info = self.read_info(index)
+        eeg_index = str(info['clip_id'])
+        eeg_record = str(info['_record_id'])
+        eeg = self.read_eeg(eeg_record, eeg_index)
+
+        signal = eeg
+        label = info
+
+        if self.online_transform:
+            signal = self.online_transform(eeg=eeg)['eeg']
+
+        if self.label_transform:
+            label = self.label_transform(y=info)['y']
+
+        return signal, label
+
+    @property
+    def repr_body(self) -> Dict:
+        return dict(
+            super().repr_body, **{
+                'root_path': self.root_path,
+                'chunk_size': self.chunk_size,
+                'overlap': self.overlap,
+                'num_channel': self.num_channel,
+                'version':self.version,
+                'online_transform': self.online_transform,
+                'offline_transform': self.offline_transform,
+                'label_transform': self.label_transform,
+                'before_trial': self.before_trial,
+                'after_trial': self.after_trial,
+                'num_worker': self.num_worker,
+                'verbose': self.verbose,
+                'io_size': self.io_size
+            })
diff --git a/torcheeg/datasets/module/sspev/__init__.py b/torcheeg/datasets/module/ssvep/__init__.py
similarity index 100%
rename from torcheeg/datasets/module/sspev/__init__.py
rename to torcheeg/datasets/module/ssvep/__init__.py
diff --git a/torcheeg/datasets/module/sspev/tsu_benchmark.py b/torcheeg/datasets/module/ssvep/tsu_benchmark.py
similarity index 100%
rename from torcheeg/datasets/module/sspev/tsu_benchmark.py
rename to torcheeg/datasets/module/ssvep/tsu_benchmark.py
diff --git a/torcheeg/models/cnn/eegnet.py b/torcheeg/models/cnn/eegnet.py
index c42b60a..8b58530 100644
--- a/torcheeg/models/cnn/eegnet.py
+++ b/torcheeg/models/cnn/eegnet.py
@@ -27,7 +27,7 @@ class EEGNet(nn.Module):
         dataset = DEAPDataset(io_path=f'./deap',
                     root_path='./data_preprocessed_python',
                     online_transform=transforms.Compose([
-                        transforms.To2d()
+                        transforms.To2d(),
                         transforms.ToTensor(),
                     ]),
                     label_transform=transforms.Compose([
diff --git a/torcheeg/models/transformer/__init__.py b/torcheeg/models/transformer/__init__.py
index 7b5ddf6..05e614d 100644
--- a/torcheeg/models/transformer/__init__.py
+++ b/torcheeg/models/transformer/__init__.py
@@ -3,4 +3,4 @@
 from .vanilla_transformer import VanillaTransformer
 from .vit import ViT
 from .conformer import Conformer
-from .atcnet import ATCNet
\ No newline at end of file
+from .atcnet import ATCNet


Test patch:
diff --git a/test/datasets/test_sleep.py b/test/datasets/test_sleep.py
new file mode 100644
index 0000000..e2d4ec4
--- /dev/null
+++ b/test/datasets/test_sleep.py
@@ -0,0 +1,40 @@
+import os
+import random
+import shutil
+import unittest
+
+import numpy as np
+
+from torcheeg import transforms
+from torcheeg.datasets import SleepEDFDataset
+
+
+def mean_reduce(eeg_list):
+    return np.array(eeg_list).mean(axis=0)
+
+
+class TestSleepEDFDataset(unittest.TestCase):
+
+    def setUp(self):
+        if os.path.exists('./tmp_out/'):
+            shutil.rmtree('./tmp_out/')
+        os.mkdir('./tmp_out/')
+
+    def test_tsu_benchmark_dataset(self):
+        io_path = f'./tmp_out/SleepEDF_{"".join(random.sample("zyxwvutsrqponmlkjihgfedcba", 20))}'
+        root_path = './tmp_in/sleep-edf-database-expanded-1.0.0'
+
+        dataset = SleepEDFDataset(io_path=io_path,
+                                      root_path=root_path,
+                                      online_transform=transforms.ToTensor(),
+                                      num_worker=4)
+
+        self.assertEqual(len(dataset), 415593)
+        first_item = dataset[0]
+        self.assertEqual(first_item[0].shape, (4, 3000))
+        last_item = dataset[50399]
+        self.assertEqual(last_item[0].shape, (4, 3000))
+
+
+if __name__ == '__main__':
+    unittest.main()