eegdash 0.3.7.dev105__py3-none-any.whl → 0.3.7.dev107__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 CHANGED
@@ -8,4 +8,4 @@ _init_mongo_client()
8
8
 
9
9
  __all__ = ["EEGDash", "EEGDashDataset", "EEGChallengeDataset", "preprocessing"]
10
10
 
11
- __version__ = "0.3.7.dev105"
11
+ __version__ = "0.3.7.dev107"
eegdash/api.py CHANGED
@@ -18,13 +18,21 @@ from s3fs import S3FileSystem
18
18
 
19
19
  from braindecode.datasets import BaseConcatDataset
20
20
 
21
- from .bids_eeg_metadata import build_query_from_kwargs, load_eeg_attrs_from_bids_file
21
+ from .bids_eeg_metadata import (
22
+ build_query_from_kwargs,
23
+ load_eeg_attrs_from_bids_file,
24
+ merge_participants_fields,
25
+ normalize_key,
26
+ )
22
27
  from .const import (
23
28
  ALLOWED_QUERY_FIELDS,
24
29
  RELEASE_TO_OPENNEURO_DATASET_MAP,
25
30
  )
26
31
  from .const import config as data_config
27
- from .data_utils import EEGBIDSDataset, EEGDashBaseDataset
32
+ from .data_utils import (
33
+ EEGBIDSDataset,
34
+ EEGDashBaseDataset,
35
+ )
28
36
  from .mongodb import MongoConnectionManager
29
37
 
30
38
  logger = logging.getLogger("eegdash")
@@ -784,20 +792,49 @@ class EEGDashDataset(BaseConcatDataset):
784
792
  f"Offline mode is enabled, but local data_dir {self.data_dir} does not exist."
785
793
  )
786
794
  records = self._find_local_bids_records(self.data_dir, self.query)
787
- datasets = [
788
- EEGDashBaseDataset(
789
- record=record,
790
- cache_dir=self.cache_dir,
791
- s3_bucket=self.s3_bucket,
792
- description={
793
- k: record.get(k)
794
- for k in description_fields
795
- if record.get(k) is not None
796
- },
797
- **base_dataset_kwargs,
795
+ # Try to enrich from local participants.tsv to restore requested fields
796
+ try:
797
+ bids_ds = EEGBIDSDataset(
798
+ data_dir=str(self.data_dir), dataset=self.query["dataset"]
799
+ ) # type: ignore[index]
800
+ except Exception:
801
+ bids_ds = None
802
+
803
+ datasets = []
804
+ for record in records:
805
+ # Start with entity values from filename
806
+ desc: dict[str, Any] = {
807
+ k: record.get(k)
808
+ for k in ("subject", "session", "run", "task")
809
+ if record.get(k) is not None
810
+ }
811
+
812
+ if bids_ds is not None:
813
+ try:
814
+ rel_from_dataset = Path(record["bidspath"]).relative_to(
815
+ record["dataset"]
816
+ ) # type: ignore[index]
817
+ local_file = (self.data_dir / rel_from_dataset).as_posix()
818
+ part_row = bids_ds.subject_participant_tsv(local_file)
819
+ desc = merge_participants_fields(
820
+ description=desc,
821
+ participants_row=part_row
822
+ if isinstance(part_row, dict)
823
+ else None,
824
+ description_fields=description_fields,
825
+ )
826
+ except Exception:
827
+ pass
828
+
829
+ datasets.append(
830
+ EEGDashBaseDataset(
831
+ record=record,
832
+ cache_dir=self.cache_dir,
833
+ s3_bucket=self.s3_bucket,
834
+ description=desc,
835
+ **base_dataset_kwargs,
836
+ )
798
837
  )
799
- for record in records
800
- ]
801
838
  elif self.query:
802
839
  # This is the DB query path that we are improving
803
840
  datasets = self._find_datasets(
@@ -882,23 +919,16 @@ class EEGDashDataset(BaseConcatDataset):
882
919
  else:
883
920
  matching_args[finder_key] = [entity_val]
884
921
 
885
- paths = find_matching_paths(
922
+ matched_paths = find_matching_paths(
886
923
  root=str(dataset_root),
887
924
  datatypes=["eeg"],
888
925
  suffixes=["eeg"],
889
926
  ignore_json=True,
890
927
  **matching_args,
891
928
  )
929
+ records_out: list[dict] = []
892
930
 
893
- records: list[dict] = []
894
- seen_files: set[str] = set()
895
-
896
- for bids_path in paths:
897
- fpath = str(Path(bids_path.fpath).resolve())
898
- if fpath in seen_files:
899
- continue
900
- seen_files.add(fpath)
901
-
931
+ for bids_path in matched_paths:
902
932
  # Build bidspath as dataset_id / relative_path_from_dataset_root (POSIX)
903
933
  rel_from_root = (
904
934
  Path(bids_path.fpath)
@@ -915,29 +945,37 @@ class EEGDashDataset(BaseConcatDataset):
915
945
  "session": (bids_path.session or None),
916
946
  "task": (bids_path.task or None),
917
947
  "run": (bids_path.run or None),
918
- # minimal fields to satisfy BaseDataset
948
+ # minimal fields to satisfy BaseDataset from eegdash
919
949
  "bidsdependencies": [], # not needed to just run.
920
950
  "modality": "eeg",
921
- # this information is from eegdash schema but not available locally
922
- "sampling_frequency": 1.0,
923
- "nchans": 1,
924
- "ntimes": 1,
951
+ # minimal numeric defaults for offline length calculation
952
+ "sampling_frequency": None,
953
+ "nchans": None,
954
+ "ntimes": None,
925
955
  }
926
- records.append(rec)
956
+ records_out.append(rec)
927
957
 
928
- return records
958
+ return records_out
929
959
 
930
960
  def _find_key_in_nested_dict(self, data: Any, target_key: str) -> Any:
931
- """Helper to recursively search for a key in a nested dictionary structure; returns
932
- the value associated with the first occurrence of the key, or None if not found.
961
+ """Recursively search for target_key in nested dicts/lists with normalized matching.
962
+
963
+ This makes lookups tolerant to naming differences like "p-factor" vs "p_factor".
964
+ Returns the first match or None.
933
965
  """
966
+ norm_target = normalize_key(target_key)
934
967
  if isinstance(data, dict):
935
- if target_key in data:
936
- return data[target_key]
937
- for value in data.values():
938
- result = self._find_key_in_nested_dict(value, target_key)
939
- if result is not None:
940
- return result
968
+ for k, v in data.items():
969
+ if normalize_key(k) == norm_target:
970
+ return v
971
+ res = self._find_key_in_nested_dict(v, target_key)
972
+ if res is not None:
973
+ return res
974
+ elif isinstance(data, list):
975
+ for item in data:
976
+ res = self._find_key_in_nested_dict(item, target_key)
977
+ if res is not None:
978
+ return res
941
979
  return None
942
980
 
943
981
  def _find_datasets(
@@ -969,11 +1007,20 @@ class EEGDashDataset(BaseConcatDataset):
969
1007
  self.records = self.eeg_dash_instance.find(query)
970
1008
 
971
1009
  for record in self.records:
972
- description = {}
1010
+ description: dict[str, Any] = {}
1011
+ # Requested fields first (normalized matching)
973
1012
  for field in description_fields:
974
1013
  value = self._find_key_in_nested_dict(record, field)
975
1014
  if value is not None:
976
1015
  description[field] = value
1016
+ # Merge all participants.tsv columns generically
1017
+ part = self._find_key_in_nested_dict(record, "participant_tsv")
1018
+ if isinstance(part, dict):
1019
+ description = merge_participants_fields(
1020
+ description=description,
1021
+ participants_row=part,
1022
+ description_fields=description_fields,
1023
+ )
977
1024
  datasets.append(
978
1025
  EEGDashBaseDataset(
979
1026
  record,
@@ -1,16 +1,18 @@
1
1
  import logging
2
+ import re
2
3
  from pathlib import Path
3
4
  from typing import Any
4
5
 
5
6
  from .const import ALLOWED_QUERY_FIELDS
6
7
  from .const import config as data_config
7
- from .data_utils import EEGBIDSDataset
8
8
 
9
9
  logger = logging.getLogger("eegdash")
10
10
 
11
11
  __all__ = [
12
12
  "build_query_from_kwargs",
13
13
  "load_eeg_attrs_from_bids_file",
14
+ "merge_participants_fields",
15
+ "normalize_key",
14
16
  ]
15
17
 
16
18
 
@@ -70,7 +72,7 @@ def build_query_from_kwargs(**kwargs) -> dict[str, Any]:
70
72
  return query
71
73
 
72
74
 
73
- def _get_raw_extensions(bids_file: str, bids_dataset: EEGBIDSDataset) -> list[str]:
75
+ def _get_raw_extensions(bids_file: str, bids_dataset) -> list[str]:
74
76
  """Helper to find paths to additional "sidecar" files that may be associated
75
77
  with a given main data file in a BIDS dataset; paths are returned as relative to
76
78
  the parent dataset path.
@@ -92,9 +94,7 @@ def _get_raw_extensions(bids_file: str, bids_dataset: EEGBIDSDataset) -> list[st
92
94
  ]
93
95
 
94
96
 
95
- def load_eeg_attrs_from_bids_file(
96
- bids_dataset: EEGBIDSDataset, bids_file: str
97
- ) -> dict[str, Any]:
97
+ def load_eeg_attrs_from_bids_file(bids_dataset, bids_file: str) -> dict[str, Any]:
98
98
  """Build the metadata record for a given BIDS file (single recording) in a BIDS dataset.
99
99
 
100
100
  Attributes are at least the ones defined in data_config attributes (set to None if missing),
@@ -182,3 +182,73 @@ def load_eeg_attrs_from_bids_file(
182
182
  attrs[field] = None
183
183
 
184
184
  return attrs
185
+
186
+
187
+ def normalize_key(key: str) -> str:
188
+ """Normalize a metadata key for robust matching.
189
+
190
+ Lowercase and replace non-alphanumeric characters with underscores, then strip
191
+ leading/trailing underscores. This allows tolerant matching such as
192
+ "p-factor" ≈ "p_factor" ≈ "P Factor".
193
+ """
194
+ return re.sub(r"[^a-z0-9]+", "_", str(key).lower()).strip("_")
195
+
196
+
197
+ def merge_participants_fields(
198
+ description: dict[str, Any],
199
+ participants_row: dict[str, Any] | None,
200
+ description_fields: list[str] | None = None,
201
+ ) -> dict[str, Any]:
202
+ """Merge participants.tsv fields into a dataset description dictionary.
203
+
204
+ - Preserves existing entries in ``description`` (no overwrites).
205
+ - Fills requested ``description_fields`` first, preserving their original names.
206
+ - Adds all remaining participants columns generically using normalized keys
207
+ unless a matching requested field already captured them.
208
+
209
+ Parameters
210
+ ----------
211
+ description : dict
212
+ Current description to be enriched in-place and returned.
213
+ participants_row : dict | None
214
+ A mapping of participants.tsv columns for the current subject.
215
+ description_fields : list[str] | None
216
+ Optional list of requested description fields. When provided, matching is
217
+ performed by normalized names; the original requested field names are kept.
218
+
219
+ Returns
220
+ -------
221
+ dict
222
+ The enriched description (same object as input for convenience).
223
+
224
+ """
225
+ if not isinstance(description, dict) or not isinstance(participants_row, dict):
226
+ return description
227
+
228
+ # Normalize participants keys and keep first non-None value per normalized key
229
+ norm_map: dict[str, Any] = {}
230
+ for part_key, part_value in participants_row.items():
231
+ norm_key = normalize_key(part_key)
232
+ if norm_key not in norm_map and part_value is not None:
233
+ norm_map[norm_key] = part_value
234
+
235
+ # Ensure description_fields is a list for matching
236
+ requested = list(description_fields or [])
237
+
238
+ # 1) Fill requested fields first using normalized matching, preserving names
239
+ for key in requested:
240
+ if key in description:
241
+ continue
242
+ requested_norm_key = normalize_key(key)
243
+ if requested_norm_key in norm_map:
244
+ description[key] = norm_map[requested_norm_key]
245
+
246
+ # 2) Add remaining participants columns generically under normalized names,
247
+ # unless a requested field already captured them
248
+ requested_norm = {normalize_key(k) for k in requested}
249
+ for norm_key, part_value in norm_map.items():
250
+ if norm_key in requested_norm:
251
+ continue
252
+ if norm_key not in description:
253
+ description[norm_key] = part_value
254
+ return description
eegdash/data_utils.py CHANGED
@@ -1,9 +1,11 @@
1
+ import io
1
2
  import json
2
3
  import logging
3
4
  import os
4
5
  import re
5
6
  import traceback
6
7
  import warnings
8
+ from contextlib import redirect_stderr
7
9
  from pathlib import Path
8
10
  from typing import Any
9
11
 
@@ -91,19 +93,8 @@ class EEGDashBaseDataset(BaseDataset):
91
93
  root=self.bids_root,
92
94
  datatype="eeg",
93
95
  suffix="eeg",
94
- # extension='.bdf',
95
96
  **self.bids_kwargs,
96
97
  )
97
- # TO-DO: remove this once find a better solution using mne-bids or update competition dataset
98
- try:
99
- _ = str(self.bidspath)
100
- except RuntimeError:
101
- try:
102
- self.bidspath = self.bidspath.update(extension=".bdf")
103
- self.filecache = self.filecache.with_suffix(".bdf")
104
- except Exception as e:
105
- logger.error(f"Error while updating BIDS path: {e}")
106
- raise e
107
98
 
108
99
  self.s3file = self._get_s3path(record["bidspath"])
109
100
  self.bids_dependencies = record["bidsdependencies"]
@@ -182,8 +173,11 @@ class EEGDashBaseDataset(BaseDataset):
182
173
  dep_local = Path(self.dataset_folder) / dep_path
183
174
  filepath = self.cache_dir / dep_local
184
175
  if not self.s3_open_neuro:
176
+ if filepath.suffix == ".set":
177
+ filepath = filepath.with_suffix(".bdf")
185
178
  if self.filecache.suffix == ".set":
186
179
  self.filecache = self.filecache.with_suffix(".bdf")
180
+
187
181
  # here, we download the dependency and it is fine
188
182
  # in the case of the competition.
189
183
  if not filepath.exists():
@@ -218,6 +212,12 @@ class EEGDashBaseDataset(BaseDataset):
218
212
 
219
213
  def _ensure_raw(self) -> None:
220
214
  """Download the S3 file and BIDS dependencies if not already cached."""
215
+ # TO-DO: remove this once is fixed on the our side
216
+ # for the competition
217
+ if not self.s3_open_neuro:
218
+ self.bidspath = self.bidspath.update(extension=".bdf")
219
+ self.filecache = self.filecache.with_suffix(".bdf")
220
+
221
221
  if not os.path.exists(self.filecache): # not preload
222
222
  if self.bids_dependencies:
223
223
  self._download_dependencies()
@@ -226,13 +226,50 @@ class EEGDashBaseDataset(BaseDataset):
226
226
  # capturing any warnings
227
227
  # to-do: remove this once is fixed on the mne-bids side.
228
228
  with warnings.catch_warnings(record=True) as w:
229
+ # Ensure all warnings are captured into 'w' and not shown to users
230
+ warnings.simplefilter("always")
229
231
  try:
230
- # TO-DO: remove this once is fixed on the our side
231
- if not self.s3_open_neuro:
232
- self.bidspath = self.bidspath.update(extension=".bdf")
233
- self._raw = mne_bids.read_raw_bids(
234
- bids_path=self.bidspath, verbose="ERROR"
235
- )
232
+ # mne-bids emits RuntimeWarnings to stderr; silence stderr during read
233
+ _stderr_buffer = io.StringIO()
234
+ with redirect_stderr(_stderr_buffer):
235
+ self._raw = mne_bids.read_raw_bids(
236
+ bids_path=self.bidspath, verbose="ERROR"
237
+ )
238
+ # Parse unmapped participants.tsv fields reported by mne-bids and
239
+ # inject them into Raw.info and the dataset description generically.
240
+ extras = self._extract_unmapped_participants_from_warnings(w)
241
+ if extras:
242
+ # 1) Attach to Raw.info under subject_info.participants_extras
243
+ try:
244
+ subject_info = self._raw.info.get("subject_info") or {}
245
+ if not isinstance(subject_info, dict):
246
+ subject_info = {}
247
+ pe = subject_info.get("participants_extras") or {}
248
+ if not isinstance(pe, dict):
249
+ pe = {}
250
+ # Merge without overwriting
251
+ for k, v in extras.items():
252
+ pe.setdefault(k, v)
253
+ subject_info["participants_extras"] = pe
254
+ self._raw.info["subject_info"] = subject_info
255
+ except Exception:
256
+ # Non-fatal; continue
257
+ pass
258
+
259
+ # 2) Also add to this dataset's description, if possible, so
260
+ # targets can be selected later without naming specifics.
261
+ try:
262
+ import pandas as _pd # local import to avoid top-level cost
263
+
264
+ if isinstance(self.description, dict):
265
+ for k, v in extras.items():
266
+ self.description.setdefault(k, v)
267
+ elif isinstance(self.description, _pd.Series):
268
+ for k, v in extras.items():
269
+ if k not in self.description.index:
270
+ self.description.loc[k] = v
271
+ except Exception:
272
+ pass
236
273
  except Exception as e:
237
274
  logger.error(
238
275
  f"Error while reading BIDS file: {self.bidspath}\n"
@@ -242,10 +279,60 @@ class EEGDashBaseDataset(BaseDataset):
242
279
  logger.error(f"Exception: {e}")
243
280
  logger.error(traceback.format_exc())
244
281
  raise e
245
- for warning in w:
246
- logger.warning(
247
- f"Warning while reading BIDS file: {warning.message}"
248
- )
282
+ # Filter noisy mapping notices from mne-bids; surface others
283
+ for captured_warning in w:
284
+ try:
285
+ msg = str(captured_warning.message)
286
+ except Exception:
287
+ continue
288
+ # Suppress verbose participants mapping messages
289
+ if "Unable to map the following column" in msg and "MNE" in msg:
290
+ logger.debug(
291
+ "Suppressed mne-bids mapping warning while reading BIDS file: %s",
292
+ msg,
293
+ )
294
+ continue
295
+ logger.warning("Warning while reading BIDS file: %s", msg)
296
+
297
+ def _extract_unmapped_participants_from_warnings(
298
+ self, warnings_list: list[Any]
299
+ ) -> dict[str, Any]:
300
+ """Scan captured warnings from mne-bids and extract unmapped participants.tsv
301
+ entries in a generic way.
302
+
303
+ Optionally, the column name can carry a note in parentheses that we ignore
304
+ for key/value extraction. Returns a mapping of column name -> raw value.
305
+ """
306
+ extras: dict[str, Any] = {}
307
+ header = "Unable to map the following column(s) to MNE:"
308
+ for wr in warnings_list:
309
+ try:
310
+ msg = str(wr.message)
311
+ except Exception:
312
+ continue
313
+ if header not in msg:
314
+ continue
315
+ lines = msg.splitlines()
316
+ # Find the header line, then parse subsequent lines as entries
317
+ try:
318
+ idx = next(i for i, ln in enumerate(lines) if header in ln)
319
+ except StopIteration:
320
+ idx = -1
321
+ for line in lines[idx + 1 :]:
322
+ line = line.strip()
323
+ if not line:
324
+ continue
325
+ # Pattern: <col>(optional note): <value>
326
+ # Examples: "gender: F", "Ethnicity: Indian", "foo (ignored): bar"
327
+ m = re.match(r"^([^:]+?)(?:\s*\([^)]*\))?\s*:\s*(.*)$", line)
328
+ if not m:
329
+ continue
330
+ col = m.group(1).strip()
331
+ val = m.group(2).strip()
332
+ # Keep original column names as provided to stay agnostic
333
+ if col and col not in extras:
334
+ extras[col] = val
335
+ return extras
249
336
 
250
337
  # === BaseDataset and PyTorch Dataset interface ===
251
338
 
@@ -264,11 +351,16 @@ class EEGDashBaseDataset(BaseDataset):
264
351
  def __len__(self) -> int:
265
352
  """Return the number of samples in the dataset."""
266
353
  if self._raw is None:
267
- # FIXME: this is a bit strange and should definitely not change as a side effect
268
- # of accessing the data (which it will, since ntimes is the actual length but rounded down)
269
- return int(self.record["ntimes"] * self.record["sampling_frequency"])
270
- else:
271
- return len(self._raw)
354
+ if (
355
+ self.record["ntimes"] is None
356
+ or self.record["sampling_frequency"] is None
357
+ ):
358
+ self._ensure_raw()
359
+ else:
360
+ # FIXME: this is a bit strange and should definitely not change as a side effect
361
+ # of accessing the data (which it will, since ntimes is the actual length but rounded down)
362
+ return int(self.record["ntimes"] * self.record["sampling_frequency"])
363
+ return len(self._raw)
272
364
 
273
365
  @property
274
366
  def raw(self):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: eegdash
3
- Version: 0.3.7.dev105
3
+ Version: 0.3.7.dev107
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
@@ -1,8 +1,8 @@
1
- eegdash/__init__.py,sha256=yplAyK95jkzGdCrV7kNsoqxXC0jxOJzkD87WI29VA5A,285
2
- eegdash/api.py,sha256=s6dG52Or7YlEsJFR_BJQ8Az2-BwyuUScFoy8UZLgsdY,38097
3
- eegdash/bids_eeg_metadata.py,sha256=iAHJKxeleDWzeei4sNqc-9NlZJk4QY2BYk6D1t2mkOI,6984
1
+ eegdash/__init__.py,sha256=UHfMZV_pHqDbXX4q_0FLvnQkrQG5kbXG_nS3iekTts8,285
2
+ eegdash/api.py,sha256=YyJrs2vTiJ67UPV5aK_xbr5wCa1onX9Jv4o6S3oVDsE,40091
3
+ eegdash/bids_eeg_metadata.py,sha256=LZrGPGVdnGUbZlD4M_aAW4kEItzwTTeZFicH-jyqDyc,9712
4
4
  eegdash/const.py,sha256=qdFBEL9kIrsj9CdxbXhBkR61R3CrTGSaj5Iq0YOACIs,7313
5
- eegdash/data_utils.py,sha256=sPglGH1w3USBuDn6uNpsfPxji8NVy2QmVg789uMhe_E,29739
5
+ eegdash/data_utils.py,sha256=U8QbqpDvZeHsC2shk1_A2gUBEm8GrzVT8vHN_VZpY_g,34257
6
6
  eegdash/mongodb.py,sha256=GD3WgA253oFgpzOHrYaj4P1mRjNtDMT5Oj4kVvHswjI,2006
7
7
  eegdash/utils.py,sha256=7TfQ9D0LrAJ7FgnSXEvWgeHWK2QqaqS-_WcWXD86ObQ,408
8
8
  eegdash/dataset/__init__.py,sha256=Qmzki5G8GaFlzTb10e4SmC3WkKuJyo1Ckii15tCEHAo,157
@@ -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.7.dev105.dist-info/licenses/LICENSE,sha256=asisR-xupy_NrQBFXnx6yqXeZcYWLvbAaiETl25iXT0,931
27
- eegdash-0.3.7.dev105.dist-info/METADATA,sha256=Eyu7l05oYbcZVSJGfZ2H3sC9a4Ekjc3oc4Z9fuZrk48,10053
28
- eegdash-0.3.7.dev105.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
29
- eegdash-0.3.7.dev105.dist-info/top_level.txt,sha256=zavO69HQ6MyZM0aQMR2zUS6TAFc7bnN5GEpDpOpFZzU,8
30
- eegdash-0.3.7.dev105.dist-info/RECORD,,
26
+ eegdash-0.3.7.dev107.dist-info/licenses/LICENSE,sha256=asisR-xupy_NrQBFXnx6yqXeZcYWLvbAaiETl25iXT0,931
27
+ eegdash-0.3.7.dev107.dist-info/METADATA,sha256=KjKTOdpKaADi0ZuBTScvYRRV4V0YoIQPd9ZpnALfmZ0,10053
28
+ eegdash-0.3.7.dev107.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
29
+ eegdash-0.3.7.dev107.dist-info/top_level.txt,sha256=zavO69HQ6MyZM0aQMR2zUS6TAFc7bnN5GEpDpOpFZzU,8
30
+ eegdash-0.3.7.dev107.dist-info/RECORD,,