|
35 | 35 | REFERENCE_NAMES = ("VREF", "Vertex Reference") |
36 | 36 |
|
37 | 37 |
|
| 38 | +def _get_mff_reader(input_fname): |
| 39 | + """Open an mffpy.Reader for the MFF directory, with raw (uncalibrated) EEG. |
| 40 | +
|
| 41 | + mffpy applies its own GCAL scaling by default, but MNE's ``cals`` already |
| 42 | + incorporate that gain (see ``_get_eeg_calibration_info``), so it is |
| 43 | + disabled here to avoid applying it twice. |
| 44 | + """ |
| 45 | + mffpy = _import_mffpy("read EGI MFF data") |
| 46 | + mff_reader = mffpy.Reader(input_fname) |
| 47 | + mff_reader.set_calibration("EEG", None) |
| 48 | + return mff_reader |
| 49 | + |
| 50 | + |
| 51 | +def _disk_range_to_epochs(egi_info, disk_start, disk_stop): |
| 52 | + """Map a contiguous range of on-disk EEG samples to mffpy epoch windows. |
| 53 | +
|
| 54 | + EGI MFF signal blocks are stored back-to-back on disk with no gaps |
| 55 | + between recording epochs. This generator yields, for each recording epoch, |
| 56 | + the epoch index plus the relative ``(t0, dt)`` window (in seconds) and the |
| 57 | + corresponding ``[out_start, out_stop)`` slice into the caller's output |
| 58 | + array. |
| 59 | + """ |
| 60 | + first_samps = np.asarray(egi_info["first_samps"]) |
| 61 | + last_samps = np.asarray(egi_info["last_samps"]) |
| 62 | + sfreq = egi_info["sfreq"] |
| 63 | + epoch_lengths = last_samps - first_samps |
| 64 | + epoch_disk_offsets = np.concatenate([[0], np.cumsum(epoch_lengths)]) |
| 65 | + |
| 66 | + for ei in range(len(epoch_lengths)): |
| 67 | + ep_disk_start = epoch_disk_offsets[ei] |
| 68 | + ep_disk_stop = epoch_disk_offsets[ei + 1] |
| 69 | + ov_start = max(disk_start, ep_disk_start) |
| 70 | + ov_stop = min(disk_stop, ep_disk_stop) |
| 71 | + if ov_start >= ov_stop: |
| 72 | + continue |
| 73 | + t0 = (ov_start - ep_disk_start) / sfreq |
| 74 | + dt = (ov_stop - ov_start) / sfreq |
| 75 | + yield ei, t0, dt, ov_start - disk_start, ov_stop - disk_start |
| 76 | + |
| 77 | + |
38 | 78 | def _read_mff_header(filepath): |
39 | 79 | """Read mff header.""" |
40 | 80 | _soft_import("mffpy", "reading EGI MFF data") |
@@ -489,6 +529,7 @@ def __init__( |
489 | 529 |
|
490 | 530 | file_bin = op.join(input_fname, egi_info["eeg_fname"]) |
491 | 531 | egi_info["egi_events"] = egi_events |
| 532 | + egi_info["mff_path"] = input_fname |
492 | 533 |
|
493 | 534 | # Check how many channels to read are from EEG |
494 | 535 | keys = ("eeg", "sti", "pns") |
@@ -584,10 +625,6 @@ def _read_segment_file(self, data, idx, fi, start, stop, cals, mult): |
584 | 625 | egi_info = self._raw_extras[fi] |
585 | 626 | one = np.zeros((egi_info["kind_bounds"][-1], stop - start)) |
586 | 627 |
|
587 | | - # info about the binary file structure |
588 | | - n_channels = egi_info["n_channels"] |
589 | | - samples_block = egi_info["samples_block"] |
590 | | - |
591 | 628 | # Check how many channels to read are from each type |
592 | 629 | bounds = egi_info["kind_bounds"] |
593 | 630 | if isinstance(idx, slice): |
@@ -620,61 +657,18 @@ def _read_segment_file(self, data, idx, fi, start, stop, cals, mult): |
620 | 657 | stop = disk_samps[disk_use_idx[-1]] + 1 |
621 | 658 | assert len(disk_use_idx) == stop - start |
622 | 659 |
|
623 | | - # Get starting/stopping block/samples |
624 | | - block_samples_offset = np.cumsum(samples_block) |
625 | | - offset_blocks = np.sum(block_samples_offset <= start) |
626 | | - offset_samples = start - ( |
627 | | - block_samples_offset[offset_blocks - 1] if offset_blocks > 0 else 0 |
628 | | - ) |
629 | | - |
630 | | - # TODO: Refactor this reading with the PNS reading in a single function |
631 | | - # (DRY) |
632 | | - samples_to_read = stop - start |
633 | | - with open(self.filenames[fi], "rb", buffering=0) as fid: |
634 | | - # Go to starting block |
635 | | - current_block = 0 |
636 | | - current_block_info = None |
637 | | - current_data_sample = 0 |
638 | | - while current_block < offset_blocks: |
639 | | - this_block_info = _block_r(fid) |
640 | | - if this_block_info is not None: |
641 | | - current_block_info = this_block_info |
642 | | - fid.seek(current_block_info["block_size"], 1) |
643 | | - current_block += 1 |
644 | | - |
645 | | - # Start reading samples |
646 | | - while samples_to_read > 0: |
647 | | - logger.debug(f" Reading from block {current_block}") |
648 | | - this_block_info = _block_r(fid) |
649 | | - current_block += 1 |
650 | | - if this_block_info is not None: |
651 | | - current_block_info = this_block_info |
652 | | - |
653 | | - to_read = current_block_info["nsamples"] * current_block_info["nc"] |
654 | | - block_data = np.fromfile(fid, dtype, to_read) |
655 | | - block_data = block_data.reshape(n_channels, -1, order="C") |
656 | | - |
657 | | - # Compute indexes |
658 | | - samples_read = block_data.shape[1] |
659 | | - logger.debug(f" Read {samples_read} samples") |
660 | | - logger.debug(f" Offset {offset_samples} samples") |
661 | | - if offset_samples > 0: |
662 | | - # First block read, skip to the offset: |
663 | | - block_data = block_data[:, offset_samples:] |
664 | | - samples_read = samples_read - offset_samples |
665 | | - offset_samples = 0 |
666 | | - if samples_to_read < samples_read: |
667 | | - # Last block to read, skip the last samples |
668 | | - block_data = block_data[:, :samples_to_read] |
669 | | - samples_read = samples_to_read |
670 | | - logger.debug(f" Keep {samples_read} samples") |
671 | | - |
672 | | - s_start = current_data_sample |
673 | | - s_end = s_start + samples_read |
674 | | - |
675 | | - one[eeg_one, disk_use_idx[s_start:s_end]] = block_data[eeg_in] |
676 | | - samples_to_read = samples_to_read - samples_read |
677 | | - current_data_sample = current_data_sample + samples_read |
| 660 | + if len(eeg_one): |
| 661 | + mff_reader = _get_mff_reader(egi_info["mff_path"]) |
| 662 | + mff_epochs = mff_reader.epochs |
| 663 | + for ei, t0, dt, out_start, out_stop in _disk_range_to_epochs( |
| 664 | + egi_info, start, stop |
| 665 | + ): |
| 666 | + logger.debug(f" Reading from epoch {ei} t0={t0} dt={dt}") |
| 667 | + eeg_chunk, _ = mff_reader.get_physical_samples_from_epoch( |
| 668 | + mff_epochs[ei], t0=t0, dt=dt, channels=["EEG"] |
| 669 | + )["EEG"] |
| 670 | + cols = disk_use_idx[out_start:out_stop] |
| 671 | + one[eeg_one, cols] = eeg_chunk[eeg_in] |
678 | 672 |
|
679 | 673 | if len(pns_one) > 0: |
680 | 674 | # PNS Data is present and should be read: |
|
0 commit comments