eegdash 0.4.0.dev173498563__py3-none-any.whl → 0.4.1__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 +183 -88
- eegdash/bids_eeg_metadata.py +139 -39
- eegdash/const.py +25 -0
- eegdash/data_utils.py +333 -276
- eegdash/dataset/dataset.py +35 -13
- eegdash/dataset/dataset_summary.csv +255 -255
- eegdash/dataset/registry.py +69 -4
- eegdash/downloader.py +95 -9
- eegdash/features/datasets.py +325 -136
- eegdash/features/decorators.py +96 -3
- eegdash/features/extractors.py +212 -55
- eegdash/features/feature_bank/complexity.py +7 -3
- eegdash/features/feature_bank/dimensionality.py +1 -1
- eegdash/features/feature_bank/signal.py +11 -10
- eegdash/features/feature_bank/utils.py +8 -0
- eegdash/features/inspect.py +88 -5
- eegdash/features/serialization.py +51 -19
- eegdash/features/utils.py +80 -8
- eegdash/hbn/preprocessing.py +50 -17
- eegdash/hbn/windows.py +145 -32
- eegdash/logging.py +19 -0
- eegdash/mongodb.py +44 -27
- eegdash/paths.py +14 -5
- eegdash/utils.py +16 -1
- {eegdash-0.4.0.dev173498563.dist-info → eegdash-0.4.1.dist-info}/METADATA +6 -8
- eegdash-0.4.1.dist-info/RECORD +37 -0
- eegdash-0.4.0.dev173498563.dist-info/RECORD +0 -37
- {eegdash-0.4.0.dev173498563.dist-info → eegdash-0.4.1.dist-info}/WHEEL +0 -0
- {eegdash-0.4.0.dev173498563.dist-info → eegdash-0.4.1.dist-info}/licenses/LICENSE +0 -0
- {eegdash-0.4.0.dev173498563.dist-info → eegdash-0.4.1.dist-info}/top_level.txt +0 -0
eegdash/__init__.py
CHANGED
eegdash/api.py
CHANGED
|
@@ -16,7 +16,7 @@ from typing import Any, Mapping
|
|
|
16
16
|
|
|
17
17
|
import mne
|
|
18
18
|
from docstring_inheritance import NumpyDocstringInheritanceInitMeta
|
|
19
|
-
from
|
|
19
|
+
from mne.utils import _soft_import
|
|
20
20
|
from mne_bids import find_matching_paths
|
|
21
21
|
from pymongo import InsertOne, UpdateOne
|
|
22
22
|
from rich.console import Console
|
|
@@ -89,7 +89,8 @@ class EEGDash:
|
|
|
89
89
|
except Exception:
|
|
90
90
|
DB_CONNECTION_STRING = None
|
|
91
91
|
else:
|
|
92
|
-
|
|
92
|
+
dotenv = _soft_import("dotenv", "eegdash[full] is necessary.")
|
|
93
|
+
dotenv.load_dotenv()
|
|
93
94
|
DB_CONNECTION_STRING = os.getenv("DB_CONNECTION_STRING")
|
|
94
95
|
|
|
95
96
|
# Use singleton to get MongoDB client, database, and collection
|
|
@@ -212,17 +213,22 @@ class EEGDash:
|
|
|
212
213
|
return doc is not None
|
|
213
214
|
|
|
214
215
|
def _validate_input(self, record: dict[str, Any]) -> dict[str, Any]:
|
|
215
|
-
"""
|
|
216
|
+
"""Validate the input record against the expected schema.
|
|
216
217
|
|
|
217
218
|
Parameters
|
|
218
219
|
----------
|
|
219
|
-
record: dict
|
|
220
|
+
record : dict
|
|
220
221
|
A dictionary representing the EEG data record to be validated.
|
|
221
222
|
|
|
222
223
|
Returns
|
|
223
224
|
-------
|
|
224
|
-
dict
|
|
225
|
-
|
|
225
|
+
dict
|
|
226
|
+
The record itself on success.
|
|
227
|
+
|
|
228
|
+
Raises
|
|
229
|
+
------
|
|
230
|
+
ValueError
|
|
231
|
+
If the record is missing required keys or has values of the wrong type.
|
|
226
232
|
|
|
227
233
|
"""
|
|
228
234
|
input_types = {
|
|
@@ -252,20 +258,44 @@ class EEGDash:
|
|
|
252
258
|
return record
|
|
253
259
|
|
|
254
260
|
def _build_query_from_kwargs(self, **kwargs) -> dict[str, Any]:
|
|
255
|
-
"""
|
|
261
|
+
"""Build a validated MongoDB query from keyword arguments.
|
|
262
|
+
|
|
263
|
+
This delegates to the module-level builder used across the package.
|
|
264
|
+
|
|
265
|
+
Parameters
|
|
266
|
+
----------
|
|
267
|
+
**kwargs
|
|
268
|
+
Keyword arguments to convert into a MongoDB query.
|
|
269
|
+
|
|
270
|
+
Returns
|
|
271
|
+
-------
|
|
272
|
+
dict
|
|
273
|
+
A MongoDB query dictionary.
|
|
256
274
|
|
|
257
|
-
This delegates to the module-level builder used across the package and
|
|
258
|
-
is exposed here for testing and convenience.
|
|
259
275
|
"""
|
|
260
276
|
return build_query_from_kwargs(**kwargs)
|
|
261
277
|
|
|
262
|
-
|
|
263
|
-
|
|
278
|
+
def _extract_simple_constraint(
|
|
279
|
+
self, query: dict[str, Any], key: str
|
|
280
|
+
) -> tuple[str, Any] | None:
|
|
264
281
|
"""Extract a simple constraint for a given key from a query dict.
|
|
265
282
|
|
|
266
|
-
Supports
|
|
267
|
-
|
|
268
|
-
|
|
283
|
+
Supports top-level equality (e.g., ``{'subject': '01'}``) and ``$in``
|
|
284
|
+
(e.g., ``{'subject': {'$in': ['01', '02']}}``) constraints.
|
|
285
|
+
|
|
286
|
+
Parameters
|
|
287
|
+
----------
|
|
288
|
+
query : dict
|
|
289
|
+
The MongoDB query dictionary.
|
|
290
|
+
key : str
|
|
291
|
+
The key for which to extract the constraint.
|
|
292
|
+
|
|
293
|
+
Returns
|
|
294
|
+
-------
|
|
295
|
+
tuple or None
|
|
296
|
+
A tuple of (kind, value) where kind is "eq" or "in", or None if the
|
|
297
|
+
constraint is not present or unsupported.
|
|
298
|
+
|
|
269
299
|
"""
|
|
270
300
|
if not isinstance(query, dict) or key not in query:
|
|
271
301
|
return None
|
|
@@ -275,16 +305,28 @@ class EEGDash:
|
|
|
275
305
|
return ("in", list(val["$in"]))
|
|
276
306
|
return None # unsupported operator shape for conflict checking
|
|
277
307
|
else:
|
|
278
|
-
return
|
|
308
|
+
return "eq", val
|
|
279
309
|
|
|
280
310
|
def _raise_if_conflicting_constraints(
|
|
281
311
|
self, raw_query: dict[str, Any], kwargs_query: dict[str, Any]
|
|
282
312
|
) -> None:
|
|
283
|
-
"""Raise ValueError if
|
|
313
|
+
"""Raise ValueError if query sources have incompatible constraints.
|
|
314
|
+
|
|
315
|
+
Checks for mutually exclusive constraints on the same field to avoid
|
|
316
|
+
silent empty results.
|
|
317
|
+
|
|
318
|
+
Parameters
|
|
319
|
+
----------
|
|
320
|
+
raw_query : dict
|
|
321
|
+
The raw MongoDB query dictionary.
|
|
322
|
+
kwargs_query : dict
|
|
323
|
+
The query dictionary built from keyword arguments.
|
|
324
|
+
|
|
325
|
+
Raises
|
|
326
|
+
------
|
|
327
|
+
ValueError
|
|
328
|
+
If conflicting constraints are found.
|
|
284
329
|
|
|
285
|
-
We conservatively check only top-level fields with simple equality or $in
|
|
286
|
-
constraints. If a field appears in both queries and constraints are mutually
|
|
287
|
-
exclusive, raise an explicit error to avoid silent empty result sets.
|
|
288
330
|
"""
|
|
289
331
|
if not raw_query or not kwargs_query:
|
|
290
332
|
return
|
|
@@ -388,12 +430,31 @@ class EEGDash:
|
|
|
388
430
|
logger.info("Upserted: %s", result.upserted_count)
|
|
389
431
|
logger.info("Errors: %s ", result.bulk_api_result.get("writeErrors", []))
|
|
390
432
|
|
|
391
|
-
def _add_request(self, record: dict):
|
|
392
|
-
"""
|
|
433
|
+
def _add_request(self, record: dict) -> InsertOne:
|
|
434
|
+
"""Create a MongoDB insertion request for a record.
|
|
435
|
+
|
|
436
|
+
Parameters
|
|
437
|
+
----------
|
|
438
|
+
record : dict
|
|
439
|
+
The record to insert.
|
|
440
|
+
|
|
441
|
+
Returns
|
|
442
|
+
-------
|
|
443
|
+
InsertOne
|
|
444
|
+
A PyMongo ``InsertOne`` object.
|
|
445
|
+
|
|
446
|
+
"""
|
|
393
447
|
return InsertOne(record)
|
|
394
448
|
|
|
395
|
-
def add(self, record: dict):
|
|
396
|
-
"""Add a single record to the MongoDB collection.
|
|
449
|
+
def add(self, record: dict) -> None:
|
|
450
|
+
"""Add a single record to the MongoDB collection.
|
|
451
|
+
|
|
452
|
+
Parameters
|
|
453
|
+
----------
|
|
454
|
+
record : dict
|
|
455
|
+
The record to add.
|
|
456
|
+
|
|
457
|
+
"""
|
|
397
458
|
try:
|
|
398
459
|
self.__collection.insert_one(record)
|
|
399
460
|
except ValueError as e:
|
|
@@ -405,11 +466,23 @@ class EEGDash:
|
|
|
405
466
|
)
|
|
406
467
|
logger.debug("Add operation failed", exc_info=exc)
|
|
407
468
|
|
|
408
|
-
def _update_request(self, record: dict):
|
|
409
|
-
"""
|
|
469
|
+
def _update_request(self, record: dict) -> UpdateOne:
|
|
470
|
+
"""Create a MongoDB update request for a record.
|
|
471
|
+
|
|
472
|
+
Parameters
|
|
473
|
+
----------
|
|
474
|
+
record : dict
|
|
475
|
+
The record to update.
|
|
476
|
+
|
|
477
|
+
Returns
|
|
478
|
+
-------
|
|
479
|
+
UpdateOne
|
|
480
|
+
A PyMongo ``UpdateOne`` object.
|
|
481
|
+
|
|
482
|
+
"""
|
|
410
483
|
return UpdateOne({"data_name": record["data_name"]}, {"$set": record})
|
|
411
484
|
|
|
412
|
-
def update(self, record: dict):
|
|
485
|
+
def update(self, record: dict) -> None:
|
|
413
486
|
"""Update a single record in the MongoDB collection.
|
|
414
487
|
|
|
415
488
|
Parameters
|
|
@@ -429,58 +502,81 @@ class EEGDash:
|
|
|
429
502
|
logger.debug("Update operation failed", exc_info=exc)
|
|
430
503
|
|
|
431
504
|
def exists(self, query: dict[str, Any]) -> bool:
|
|
432
|
-
"""
|
|
505
|
+
"""Check if at least one record matches the query.
|
|
506
|
+
|
|
507
|
+
This is an alias for :meth:`exist`.
|
|
508
|
+
|
|
509
|
+
Parameters
|
|
510
|
+
----------
|
|
511
|
+
query : dict
|
|
512
|
+
MongoDB query to check for existence.
|
|
513
|
+
|
|
514
|
+
Returns
|
|
515
|
+
-------
|
|
516
|
+
bool
|
|
517
|
+
True if a matching record exists, False otherwise.
|
|
518
|
+
|
|
519
|
+
"""
|
|
433
520
|
return self.exist(query)
|
|
434
521
|
|
|
435
|
-
def remove_field(self, record, field):
|
|
436
|
-
"""Remove a
|
|
522
|
+
def remove_field(self, record: dict, field: str) -> None:
|
|
523
|
+
"""Remove a field from a specific record in the MongoDB collection.
|
|
437
524
|
|
|
438
525
|
Parameters
|
|
439
526
|
----------
|
|
440
527
|
record : dict
|
|
441
|
-
Record
|
|
528
|
+
Record-identifying object with a ``data_name`` key.
|
|
442
529
|
field : str
|
|
443
|
-
|
|
530
|
+
The name of the field to remove.
|
|
444
531
|
|
|
445
532
|
"""
|
|
446
533
|
self.__collection.update_one(
|
|
447
534
|
{"data_name": record["data_name"]}, {"$unset": {field: 1}}
|
|
448
535
|
)
|
|
449
536
|
|
|
450
|
-
def remove_field_from_db(self, field):
|
|
451
|
-
"""Remove a field from all records
|
|
537
|
+
def remove_field_from_db(self, field: str) -> None:
|
|
538
|
+
"""Remove a field from all records in the database.
|
|
539
|
+
|
|
540
|
+
.. warning::
|
|
541
|
+
This is a destructive operation and cannot be undone.
|
|
452
542
|
|
|
453
543
|
Parameters
|
|
454
544
|
----------
|
|
455
545
|
field : str
|
|
456
|
-
|
|
546
|
+
The name of the field to remove from all documents.
|
|
457
547
|
|
|
458
548
|
"""
|
|
459
549
|
self.__collection.update_many({}, {"$unset": {field: 1}})
|
|
460
550
|
|
|
461
551
|
@property
|
|
462
552
|
def collection(self):
|
|
463
|
-
"""
|
|
464
|
-
return self.__collection
|
|
553
|
+
"""The underlying PyMongo ``Collection`` object.
|
|
465
554
|
|
|
466
|
-
|
|
467
|
-
|
|
555
|
+
Returns
|
|
556
|
+
-------
|
|
557
|
+
pymongo.collection.Collection
|
|
558
|
+
The collection object used for database interactions.
|
|
468
559
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
Connections are managed by :class:`MongoConnectionManager`. Use
|
|
472
|
-
:meth:`close_all_connections` to explicitly close all clients.
|
|
560
|
+
"""
|
|
561
|
+
return self.__collection
|
|
473
562
|
|
|
563
|
+
def close(self) -> None:
|
|
564
|
+
"""Close the MongoDB connection.
|
|
565
|
+
|
|
566
|
+
.. deprecated:: 0.1
|
|
567
|
+
Connections are now managed globally by :class:`MongoConnectionManager`.
|
|
568
|
+
This method is a no-op and will be removed in a future version.
|
|
569
|
+
Use :meth:`EEGDash.close_all_connections` to close all clients.
|
|
474
570
|
"""
|
|
475
571
|
# Individual instances no longer close the shared client
|
|
476
572
|
pass
|
|
477
573
|
|
|
478
574
|
@classmethod
|
|
479
|
-
def close_all_connections(cls):
|
|
480
|
-
"""Close all MongoDB client connections managed by the singleton."""
|
|
575
|
+
def close_all_connections(cls) -> None:
|
|
576
|
+
"""Close all MongoDB client connections managed by the singleton manager."""
|
|
481
577
|
MongoConnectionManager.close_all()
|
|
482
578
|
|
|
483
|
-
def __del__(self):
|
|
579
|
+
def __del__(self) -> None:
|
|
484
580
|
"""Destructor; no explicit action needed due to global connection manager."""
|
|
485
581
|
# No longer needed since we're using singleton pattern
|
|
486
582
|
pass
|
|
@@ -775,45 +871,30 @@ class EEGDashDataset(BaseConcatDataset, metaclass=NumpyDocstringInheritanceInitM
|
|
|
775
871
|
) -> list[dict]:
|
|
776
872
|
"""Discover local BIDS EEG files and build minimal records.
|
|
777
873
|
|
|
778
|
-
|
|
779
|
-
``mne_bids.find_matching_paths`` and applies entity filters to produce
|
|
780
|
-
|
|
781
|
-
|
|
874
|
+
Enumerates EEG recordings under ``dataset_root`` using
|
|
875
|
+
``mne_bids.find_matching_paths`` and applies entity filters to produce
|
|
876
|
+
records suitable for :class:`EEGDashBaseDataset`. No network access is
|
|
877
|
+
performed, and files are not read.
|
|
782
878
|
|
|
783
879
|
Parameters
|
|
784
880
|
----------
|
|
785
881
|
dataset_root : Path
|
|
786
|
-
Local dataset directory
|
|
787
|
-
|
|
788
|
-
``
|
|
789
|
-
|
|
790
|
-
Query filters. Must include ``'dataset'`` with the dataset id (without
|
|
791
|
-
local suffixes). May include BIDS entities ``'subject'``,
|
|
792
|
-
``'session'``, ``'task'``, and ``'run'``. Each value can be a scalar
|
|
793
|
-
or a sequence of scalars.
|
|
882
|
+
Local dataset directory (e.g., ``/path/to/cache/ds005509``).
|
|
883
|
+
filters : dict
|
|
884
|
+
Query filters. Must include ``'dataset'`` and may include BIDS
|
|
885
|
+
entities like ``'subject'``, ``'session'``, etc.
|
|
794
886
|
|
|
795
887
|
Returns
|
|
796
888
|
-------
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
- ``'data_name'``
|
|
801
|
-
- ``'dataset'`` (dataset id, without suffixes)
|
|
802
|
-
- ``'bidspath'`` (normalized to start with the dataset id)
|
|
803
|
-
- ``'subject'``, ``'session'``, ``'task'``, ``'run'`` (may be None)
|
|
804
|
-
- ``'bidsdependencies'`` (empty list)
|
|
805
|
-
- ``'modality'`` (``"eeg"``)
|
|
806
|
-
- ``'sampling_frequency'``, ``'nchans'``, ``'ntimes'`` (minimal
|
|
807
|
-
defaults for offline usage)
|
|
889
|
+
list of dict
|
|
890
|
+
A list of records, one for each matched EEG file. Each record
|
|
891
|
+
contains BIDS entities, paths, and minimal metadata for offline use.
|
|
808
892
|
|
|
809
893
|
Notes
|
|
810
894
|
-----
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
first path component is the dataset id (without local cache suffixes).
|
|
815
|
-
- Minimal defaults are set for ``sampling_frequency``, ``nchans``, and
|
|
816
|
-
``ntimes`` to satisfy dataset length requirements offline.
|
|
895
|
+
Matching is performed for ``datatypes=['eeg']`` and ``suffixes=['eeg']``.
|
|
896
|
+
The ``bidspath`` is normalized to ensure it starts with the dataset ID,
|
|
897
|
+
even for suffixed cache directories.
|
|
817
898
|
|
|
818
899
|
"""
|
|
819
900
|
dataset_id = filters["dataset"]
|
|
@@ -875,10 +956,22 @@ class EEGDashDataset(BaseConcatDataset, metaclass=NumpyDocstringInheritanceInitM
|
|
|
875
956
|
return records_out
|
|
876
957
|
|
|
877
958
|
def _find_key_in_nested_dict(self, data: Any, target_key: str) -> Any:
|
|
878
|
-
"""Recursively search for
|
|
959
|
+
"""Recursively search for a key in nested dicts/lists.
|
|
960
|
+
|
|
961
|
+
Performs a case-insensitive and underscore/hyphen-agnostic search.
|
|
962
|
+
|
|
963
|
+
Parameters
|
|
964
|
+
----------
|
|
965
|
+
data : Any
|
|
966
|
+
The nested data structure (dicts, lists) to search.
|
|
967
|
+
target_key : str
|
|
968
|
+
The key to search for.
|
|
969
|
+
|
|
970
|
+
Returns
|
|
971
|
+
-------
|
|
972
|
+
Any
|
|
973
|
+
The value of the first matching key, or None if not found.
|
|
879
974
|
|
|
880
|
-
This makes lookups tolerant to naming differences like "p-factor" vs "p_factor".
|
|
881
|
-
Returns the first match or None.
|
|
882
975
|
"""
|
|
883
976
|
norm_target = normalize_key(target_key)
|
|
884
977
|
if isinstance(data, dict):
|
|
@@ -901,23 +994,25 @@ class EEGDashDataset(BaseConcatDataset, metaclass=NumpyDocstringInheritanceInitM
|
|
|
901
994
|
description_fields: list[str],
|
|
902
995
|
base_dataset_kwargs: dict,
|
|
903
996
|
) -> list[EEGDashBaseDataset]:
|
|
904
|
-
"""
|
|
905
|
-
|
|
997
|
+
"""Find and construct datasets from a MongoDB query.
|
|
998
|
+
|
|
999
|
+
Queries the database, then creates a list of
|
|
1000
|
+
:class:`EEGDashBaseDataset` objects from the results.
|
|
906
1001
|
|
|
907
1002
|
Parameters
|
|
908
1003
|
----------
|
|
909
|
-
query : dict
|
|
910
|
-
The query
|
|
911
|
-
description_fields : list
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
constructor.
|
|
1004
|
+
query : dict, optional
|
|
1005
|
+
The MongoDB query to execute.
|
|
1006
|
+
description_fields : list of str
|
|
1007
|
+
Fields to extract from each record for the dataset description.
|
|
1008
|
+
base_dataset_kwargs : dict
|
|
1009
|
+
Additional keyword arguments to pass to the
|
|
1010
|
+
:class:`EEGDashBaseDataset` constructor.
|
|
916
1011
|
|
|
917
1012
|
Returns
|
|
918
1013
|
-------
|
|
919
|
-
list
|
|
920
|
-
A list of
|
|
1014
|
+
list of EEGDashBaseDataset
|
|
1015
|
+
A list of dataset objects matching the query.
|
|
921
1016
|
|
|
922
1017
|
"""
|
|
923
1018
|
datasets: list[EEGDashBaseDataset] = []
|