eegdash 0.3.4.dev70__py3-none-any.whl → 0.3.5.dev77__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of eegdash might be problematic. Click here for more details.
- eegdash/__init__.py +1 -1
- eegdash/api.py +177 -54
- {eegdash-0.3.4.dev70.dist-info → eegdash-0.3.5.dev77.dist-info}/METADATA +2 -1
- {eegdash-0.3.4.dev70.dist-info → eegdash-0.3.5.dev77.dist-info}/RECORD +7 -7
- {eegdash-0.3.4.dev70.dist-info → eegdash-0.3.5.dev77.dist-info}/WHEEL +0 -0
- {eegdash-0.3.4.dev70.dist-info → eegdash-0.3.5.dev77.dist-info}/licenses/LICENSE +0 -0
- {eegdash-0.3.4.dev70.dist-info → eegdash-0.3.5.dev77.dist-info}/top_level.txt +0 -0
eegdash/__init__.py
CHANGED
eegdash/api.py
CHANGED
|
@@ -9,6 +9,7 @@ import numpy as np
|
|
|
9
9
|
import xarray as xr
|
|
10
10
|
from dotenv import load_dotenv
|
|
11
11
|
from joblib import Parallel, delayed
|
|
12
|
+
from mne_bids import get_bids_path_from_fname, read_raw_bids
|
|
12
13
|
from pymongo import InsertOne, UpdateOne
|
|
13
14
|
from s3fs import S3FileSystem
|
|
14
15
|
|
|
@@ -34,6 +35,19 @@ class EEGDash:
|
|
|
34
35
|
|
|
35
36
|
"""
|
|
36
37
|
|
|
38
|
+
_ALLOWED_QUERY_FIELDS = {
|
|
39
|
+
"data_name",
|
|
40
|
+
"dataset",
|
|
41
|
+
"subject",
|
|
42
|
+
"task",
|
|
43
|
+
"session",
|
|
44
|
+
"run",
|
|
45
|
+
"modality",
|
|
46
|
+
"sampling_frequency",
|
|
47
|
+
"nchans",
|
|
48
|
+
"ntimes",
|
|
49
|
+
}
|
|
50
|
+
|
|
37
51
|
def __init__(self, *, is_public: bool = True, is_staging: bool = False) -> None:
|
|
38
52
|
"""Create new instance of the EEGDash Database client.
|
|
39
53
|
|
|
@@ -71,34 +85,59 @@ class EEGDash:
|
|
|
71
85
|
anon=True, client_kwargs={"region_name": "us-east-2"}
|
|
72
86
|
)
|
|
73
87
|
|
|
74
|
-
def find(
|
|
75
|
-
|
|
88
|
+
def find(
|
|
89
|
+
self, query: dict[str, Any] = None, /, **kwargs
|
|
90
|
+
) -> list[Mapping[str, Any]]:
|
|
91
|
+
"""Find records in the MongoDB collection.
|
|
92
|
+
|
|
93
|
+
This method can be called in two ways:
|
|
94
|
+
1. With a pre-built MongoDB query dictionary (positional argument):
|
|
95
|
+
>>> eegdash.find({"dataset": "ds002718", "subject": {"$in": ["012", "013"]}})
|
|
96
|
+
2. With user-friendly keyword arguments for simple and multi-value queries:
|
|
97
|
+
>>> eegdash.find(dataset="ds002718", subject="012")
|
|
98
|
+
>>> eegdash.find(dataset="ds002718", subject=["012", "013"])
|
|
76
99
|
|
|
77
100
|
Parameters
|
|
78
101
|
----------
|
|
79
|
-
query: dict
|
|
80
|
-
A
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
https://pymongo.readthedocs.io/en/stable/api/pymongo/collection.html#pymongo.collection.Collection.find
|
|
85
|
-
kwargs:
|
|
86
|
-
Additional keyword arguments for the MongoDB find() method.
|
|
102
|
+
query: dict, optional
|
|
103
|
+
A complete MongoDB query dictionary. This is a positional-only argument.
|
|
104
|
+
**kwargs:
|
|
105
|
+
Keyword arguments representing field-value pairs for the query.
|
|
106
|
+
Values can be single items (str, int) or lists of items for multi-search.
|
|
87
107
|
|
|
88
108
|
Returns
|
|
89
109
|
-------
|
|
90
110
|
list:
|
|
91
111
|
A list of DB records (string-keyed dictionaries) that match the query.
|
|
92
112
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
113
|
+
Raises
|
|
114
|
+
------
|
|
115
|
+
ValueError
|
|
116
|
+
If both a `query` dictionary and keyword arguments are provided.
|
|
97
117
|
|
|
98
118
|
"""
|
|
99
|
-
|
|
119
|
+
if query is not None and kwargs:
|
|
120
|
+
raise ValueError(
|
|
121
|
+
"Provide either a positional 'query' dictionary or keyword arguments, not both."
|
|
122
|
+
)
|
|
100
123
|
|
|
101
|
-
|
|
124
|
+
final_query = {}
|
|
125
|
+
if query is not None:
|
|
126
|
+
final_query = query
|
|
127
|
+
elif kwargs:
|
|
128
|
+
final_query = self._build_query_from_kwargs(**kwargs)
|
|
129
|
+
else:
|
|
130
|
+
# By default, an empty query {} returns all documents.
|
|
131
|
+
# This can be dangerous, so we can either allow it or raise an error.
|
|
132
|
+
# Let's require an explicit query for safety.
|
|
133
|
+
raise ValueError(
|
|
134
|
+
"find() requires a query dictionary or at least one keyword argument. "
|
|
135
|
+
"To find all documents, use find({})."
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
results = self.__collection.find(final_query)
|
|
139
|
+
|
|
140
|
+
return list(results)
|
|
102
141
|
|
|
103
142
|
def exist(self, query: dict[str, Any]) -> bool:
|
|
104
143
|
"""Return True if at least one record matches the query, else False.
|
|
@@ -184,6 +223,35 @@ class EEGDash:
|
|
|
184
223
|
|
|
185
224
|
return record
|
|
186
225
|
|
|
226
|
+
def _build_query_from_kwargs(self, **kwargs) -> dict[str, Any]:
|
|
227
|
+
"""Builds and validates a MongoDB query from user-friendly keyword arguments.
|
|
228
|
+
|
|
229
|
+
Translates list values into MongoDB's `$in` operator.
|
|
230
|
+
"""
|
|
231
|
+
# 1. Validate that all provided keys are allowed for querying
|
|
232
|
+
unknown_fields = set(kwargs.keys()) - self._ALLOWED_QUERY_FIELDS
|
|
233
|
+
if unknown_fields:
|
|
234
|
+
raise ValueError(
|
|
235
|
+
f"Unsupported query field(s): {', '.join(sorted(unknown_fields))}. "
|
|
236
|
+
f"Allowed fields are: {', '.join(sorted(self._ALLOWED_QUERY_FIELDS))}"
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
# 2. Construct the query dictionary
|
|
240
|
+
query = {}
|
|
241
|
+
for key, value in kwargs.items():
|
|
242
|
+
if isinstance(value, (list, tuple)):
|
|
243
|
+
if not value:
|
|
244
|
+
raise ValueError(
|
|
245
|
+
f"Received an empty list for query parameter '{key}'. This is not supported."
|
|
246
|
+
)
|
|
247
|
+
# If the value is a list, use the `$in` operator for multi-search
|
|
248
|
+
query[key] = {"$in": value}
|
|
249
|
+
else:
|
|
250
|
+
# Otherwise, it's a direct match
|
|
251
|
+
query[key] = value
|
|
252
|
+
|
|
253
|
+
return query
|
|
254
|
+
|
|
187
255
|
def load_eeg_data_from_s3(self, s3path: str) -> xr.DataArray:
|
|
188
256
|
"""Load an EEGLAB .set file from an AWS S3 URI and return it as an xarray DataArray.
|
|
189
257
|
|
|
@@ -218,14 +286,15 @@ class EEGDash:
|
|
|
218
286
|
Parameters
|
|
219
287
|
----------
|
|
220
288
|
bids_file : str
|
|
221
|
-
Path to the file on the local filesystem.
|
|
289
|
+
Path to the BIDS-compliant file on the local filesystem.
|
|
222
290
|
|
|
223
291
|
Notes
|
|
224
292
|
-----
|
|
225
293
|
Currently, only non-epoched .set files are supported.
|
|
226
294
|
|
|
227
295
|
"""
|
|
228
|
-
|
|
296
|
+
bids_path = get_bids_path_from_fname(bids_file, verbose=False)
|
|
297
|
+
raw_object = read_raw_bids(bids_path=bids_path, verbose=False)
|
|
229
298
|
eeg_data = raw_object.get_data()
|
|
230
299
|
|
|
231
300
|
fs = raw_object.info["sfreq"]
|
|
@@ -521,8 +590,8 @@ class EEGDashDataset(BaseConcatDataset):
|
|
|
521
590
|
def __init__(
|
|
522
591
|
self,
|
|
523
592
|
query: dict | None = None,
|
|
524
|
-
|
|
525
|
-
dataset: str |
|
|
593
|
+
cache_dir: str = "~/eegdash_cache",
|
|
594
|
+
dataset: str | None = None,
|
|
526
595
|
description_fields: list[str] = [
|
|
527
596
|
"subject",
|
|
528
597
|
"session",
|
|
@@ -532,36 +601,55 @@ class EEGDashDataset(BaseConcatDataset):
|
|
|
532
601
|
"gender",
|
|
533
602
|
"sex",
|
|
534
603
|
],
|
|
535
|
-
cache_dir: str = "~/eegdash_cache",
|
|
536
604
|
s3_bucket: str | None = None,
|
|
605
|
+
data_dir: str | None = None,
|
|
537
606
|
eeg_dash_instance=None,
|
|
607
|
+
records: list[dict] | None = None,
|
|
538
608
|
**kwargs,
|
|
539
609
|
):
|
|
540
610
|
"""Create a new EEGDashDataset from a given query or local BIDS dataset directory
|
|
541
611
|
and dataset name. An EEGDashDataset is pooled collection of EEGDashBaseDataset
|
|
542
612
|
instances (individual recordings) and is a subclass of braindecode's BaseConcatDataset.
|
|
543
613
|
|
|
614
|
+
|
|
615
|
+
Querying Examples:
|
|
616
|
+
------------------
|
|
617
|
+
# Find by single subject
|
|
618
|
+
>>> ds = EEGDashDataset(dataset="ds005505", subject="NDARCA153NKE")
|
|
619
|
+
|
|
620
|
+
# Find by a list of subjects and a specific task
|
|
621
|
+
>>> subjects = ["NDARCA153NKE", "NDARXT792GY8"]
|
|
622
|
+
>>> ds = EEGDashDataset(dataset="ds005505", subject=subjects, task="RestingState")
|
|
623
|
+
|
|
624
|
+
# Use a raw MongoDB query for advanced filtering
|
|
625
|
+
>>> raw_query = {"dataset": "ds005505", "subject": {"$in": subjects}}
|
|
626
|
+
>>> ds = EEGDashDataset(query=raw_query)
|
|
627
|
+
|
|
544
628
|
Parameters
|
|
545
629
|
----------
|
|
546
630
|
query : dict | None
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
631
|
+
A raw MongoDB query dictionary. If provided, keyword arguments for filtering are ignored.
|
|
632
|
+
**kwargs : dict
|
|
633
|
+
Keyword arguments for filtering (e.g., `subject="X"`, `task=["T1", "T2"]`) and/or
|
|
634
|
+
arguments to be passed to the EEGDashBaseDataset constructor (e.g., `subject=...`).
|
|
635
|
+
cache_dir : str
|
|
636
|
+
A directory where the dataset will be cached locally.
|
|
637
|
+
data_dir : str | None
|
|
638
|
+
Optionally a string specifying a local BIDS dataset directory from which to load the EEG data files. Exactly one
|
|
552
639
|
of query or data_dir must be provided.
|
|
553
|
-
dataset : str |
|
|
554
|
-
If data_dir is given, a name
|
|
640
|
+
dataset : str | None
|
|
641
|
+
If data_dir is given, a name for the dataset to be loaded.
|
|
555
642
|
description_fields : list[str]
|
|
556
643
|
A list of fields to be extracted from the dataset records
|
|
557
644
|
and included in the returned data description(s). Examples are typical
|
|
558
645
|
subject metadata fields such as "subject", "session", "run", "task", etc.;
|
|
559
646
|
see also data_config.description_fields for the default set of fields.
|
|
560
|
-
cache_dir : str
|
|
561
|
-
A directory where the dataset will be cached locally.
|
|
562
647
|
s3_bucket : str | None
|
|
563
648
|
An optional S3 bucket URI (e.g., "s3://mybucket") to use instead of the
|
|
564
649
|
default OpenNeuro bucket for loading data files
|
|
650
|
+
records : list[dict] | None
|
|
651
|
+
Optional list of pre-fetched metadata records. If provided, the dataset is
|
|
652
|
+
constructed directly from these records without querying MongoDB.
|
|
565
653
|
kwargs : dict
|
|
566
654
|
Additional keyword arguments to be passed to the EEGDashBaseDataset
|
|
567
655
|
constructor.
|
|
@@ -569,20 +657,47 @@ class EEGDashDataset(BaseConcatDataset):
|
|
|
569
657
|
"""
|
|
570
658
|
self.cache_dir = cache_dir
|
|
571
659
|
self.s3_bucket = s3_bucket
|
|
572
|
-
self.eeg_dash = eeg_dash_instance
|
|
573
|
-
_owns_client =
|
|
660
|
+
self.eeg_dash = eeg_dash_instance
|
|
661
|
+
_owns_client = False
|
|
662
|
+
if self.eeg_dash is None and records is None:
|
|
663
|
+
self.eeg_dash = EEGDash()
|
|
664
|
+
_owns_client = True
|
|
665
|
+
|
|
666
|
+
# Separate query kwargs from other kwargs passed to the BaseDataset constructor
|
|
667
|
+
query_kwargs = {
|
|
668
|
+
k: v for k, v in kwargs.items() if k in EEGDash._ALLOWED_QUERY_FIELDS
|
|
669
|
+
}
|
|
670
|
+
base_dataset_kwargs = {k: v for k, v in kwargs.items() if k not in query_kwargs}
|
|
671
|
+
|
|
672
|
+
if query and query_kwargs:
|
|
673
|
+
raise ValueError(
|
|
674
|
+
"Provide either a 'query' dictionary or keyword arguments for filtering, not both."
|
|
675
|
+
)
|
|
574
676
|
|
|
575
677
|
try:
|
|
576
|
-
if
|
|
577
|
-
datasets =
|
|
678
|
+
if records is not None:
|
|
679
|
+
datasets = [
|
|
680
|
+
EEGDashBaseDataset(
|
|
681
|
+
record,
|
|
682
|
+
self.cache_dir,
|
|
683
|
+
self.s3_bucket,
|
|
684
|
+
**base_dataset_kwargs,
|
|
685
|
+
)
|
|
686
|
+
for record in records
|
|
687
|
+
]
|
|
578
688
|
elif data_dir:
|
|
689
|
+
# This path loads from a local directory and is not affected by DB query logic
|
|
579
690
|
if isinstance(data_dir, str):
|
|
580
691
|
datasets = self.load_bids_dataset(
|
|
581
|
-
dataset,
|
|
692
|
+
dataset,
|
|
693
|
+
data_dir,
|
|
694
|
+
description_fields,
|
|
695
|
+
s3_bucket,
|
|
696
|
+
**base_dataset_kwargs,
|
|
582
697
|
)
|
|
583
698
|
else:
|
|
584
699
|
assert len(data_dir) == len(dataset), (
|
|
585
|
-
"Number of datasets and
|
|
700
|
+
"Number of datasets and directories must match"
|
|
586
701
|
)
|
|
587
702
|
datasets = []
|
|
588
703
|
for i, _ in enumerate(data_dir):
|
|
@@ -592,27 +707,28 @@ class EEGDashDataset(BaseConcatDataset):
|
|
|
592
707
|
data_dir[i],
|
|
593
708
|
description_fields,
|
|
594
709
|
s3_bucket,
|
|
595
|
-
**
|
|
710
|
+
**base_dataset_kwargs,
|
|
596
711
|
)
|
|
597
712
|
)
|
|
713
|
+
elif query or query_kwargs:
|
|
714
|
+
# This is the DB query path that we are improving
|
|
715
|
+
datasets = self.find_datasets(
|
|
716
|
+
query=query,
|
|
717
|
+
description_fields=description_fields,
|
|
718
|
+
query_kwargs=query_kwargs,
|
|
719
|
+
base_dataset_kwargs=base_dataset_kwargs,
|
|
720
|
+
)
|
|
721
|
+
# We only need filesystem if we need to access S3
|
|
722
|
+
self.filesystem = S3FileSystem(
|
|
723
|
+
anon=True, client_kwargs={"region_name": "us-east-2"}
|
|
724
|
+
)
|
|
598
725
|
else:
|
|
599
726
|
raise ValueError(
|
|
600
|
-
"
|
|
727
|
+
"You must provide either 'records', a 'data_dir', or a query/keyword arguments for filtering."
|
|
601
728
|
)
|
|
602
729
|
finally:
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
try:
|
|
606
|
-
self.eeg_dash.close()
|
|
607
|
-
except Exception:
|
|
608
|
-
# Don't let close errors break construction
|
|
609
|
-
pass
|
|
610
|
-
|
|
611
|
-
self.filesystem = S3FileSystem(
|
|
612
|
-
anon=True, client_kwargs={"region_name": "us-east-2"}
|
|
613
|
-
)
|
|
614
|
-
|
|
615
|
-
self.eeg_dash.close()
|
|
730
|
+
if _owns_client and self.eeg_dash is not None:
|
|
731
|
+
self.eeg_dash.close()
|
|
616
732
|
|
|
617
733
|
super().__init__(datasets)
|
|
618
734
|
|
|
@@ -630,7 +746,11 @@ class EEGDashDataset(BaseConcatDataset):
|
|
|
630
746
|
return None
|
|
631
747
|
|
|
632
748
|
def find_datasets(
|
|
633
|
-
self,
|
|
749
|
+
self,
|
|
750
|
+
query: dict[str, Any],
|
|
751
|
+
description_fields: list[str],
|
|
752
|
+
query_kwargs: dict,
|
|
753
|
+
base_dataset_kwargs: dict,
|
|
634
754
|
) -> list[EEGDashBaseDataset]:
|
|
635
755
|
"""Helper method to find datasets in the MongoDB collection that satisfy the
|
|
636
756
|
given query and return them as a list of EEGDashBaseDataset objects.
|
|
@@ -652,7 +772,10 @@ class EEGDashDataset(BaseConcatDataset):
|
|
|
652
772
|
|
|
653
773
|
"""
|
|
654
774
|
datasets: list[EEGDashBaseDataset] = []
|
|
655
|
-
|
|
775
|
+
|
|
776
|
+
records = self.eeg_dash.find(query, **query_kwargs)
|
|
777
|
+
|
|
778
|
+
for record in records:
|
|
656
779
|
description = {}
|
|
657
780
|
for field in description_fields:
|
|
658
781
|
value = self.find_key_in_nested_dict(record, field)
|
|
@@ -664,7 +787,7 @@ class EEGDashDataset(BaseConcatDataset):
|
|
|
664
787
|
self.cache_dir,
|
|
665
788
|
self.s3_bucket,
|
|
666
789
|
description=description,
|
|
667
|
-
**
|
|
790
|
+
**base_dataset_kwargs,
|
|
668
791
|
)
|
|
669
792
|
)
|
|
670
793
|
return datasets
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: eegdash
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.5.dev77
|
|
4
4
|
Summary: EEG data for machine learning
|
|
5
5
|
Author-email: Young Truong <dt.young112@gmail.com>, Arnaud Delorme <adelorme@gmail.com>, Aviv Dotan <avivd220@gmail.com>, Oren Shriki <oren70@gmail.com>, Bruno Aristimunha <b.aristimunha@gmail.com>
|
|
6
6
|
License-Expression: GPL-3.0-only
|
|
@@ -38,6 +38,7 @@ Requires-Dist: tqdm
|
|
|
38
38
|
Requires-Dist: xarray
|
|
39
39
|
Requires-Dist: h5io>=0.2.4
|
|
40
40
|
Requires-Dist: pymatreader
|
|
41
|
+
Requires-Dist: eeglabio
|
|
41
42
|
Requires-Dist: tabulate
|
|
42
43
|
Provides-Extra: tests
|
|
43
44
|
Requires-Dist: pytest; extra == "tests"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
eegdash/__init__.py,sha256=
|
|
2
|
-
eegdash/api.py,sha256=
|
|
1
|
+
eegdash/__init__.py,sha256=kEfROPte5G7SgYy3bdM6r3KrWm_XuXcvS-tNPMVBIr4,240
|
|
2
|
+
eegdash/api.py,sha256=M4QX93iqOneqO9zz6ruwGKpmU9h40K8lqYnICqAaj5o,32158
|
|
3
3
|
eegdash/data_config.py,sha256=OS6ERO-jHrnEOfMJUehY7ieABdsRw_qWzOKJ4pzSfqw,1323
|
|
4
4
|
eegdash/data_utils.py,sha256=_dycnPmGfTbYs7bc6edHxUn_m01dLYtp92_k44ffEoY,26475
|
|
5
5
|
eegdash/dataset.py,sha256=ooLoxMFy2I8BY9gJl6ncTp_Gz-Rq0Z-o4NJyyomxLcU,2670
|
|
@@ -23,8 +23,8 @@ eegdash/features/feature_bank/dimensionality.py,sha256=j_Ds71Y1AbV2uLFQj8EuXQ4kz
|
|
|
23
23
|
eegdash/features/feature_bank/signal.py,sha256=3Tb8z9gX7iZipxQJ9DSyy30JfdmW58kgvimSyZX74p8,3404
|
|
24
24
|
eegdash/features/feature_bank/spectral.py,sha256=bNB7skusePs1gX7NOU6yRlw_Gr4UOCkO_ylkCgybzug,3319
|
|
25
25
|
eegdash/features/feature_bank/utils.py,sha256=DGh-Q7-XFIittP7iBBxvsJaZrlVvuY5mw-G7q6C-PCI,1237
|
|
26
|
-
eegdash-0.3.
|
|
27
|
-
eegdash-0.3.
|
|
28
|
-
eegdash-0.3.
|
|
29
|
-
eegdash-0.3.
|
|
30
|
-
eegdash-0.3.
|
|
26
|
+
eegdash-0.3.5.dev77.dist-info/licenses/LICENSE,sha256=asisR-xupy_NrQBFXnx6yqXeZcYWLvbAaiETl25iXT0,931
|
|
27
|
+
eegdash-0.3.5.dev77.dist-info/METADATA,sha256=1-0zRsLKzuM36yIDIlpbxTSCbCSp75XQoi2MC0C2pdA,10388
|
|
28
|
+
eegdash-0.3.5.dev77.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
29
|
+
eegdash-0.3.5.dev77.dist-info/top_level.txt,sha256=zavO69HQ6MyZM0aQMR2zUS6TAFc7bnN5GEpDpOpFZzU,8
|
|
30
|
+
eegdash-0.3.5.dev77.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|