Skip to content

Speed up .edf export with edfio#12218

Merged
cbrnr merged 31 commits intomne-tools:mainfrom
hofaflo:edfio
Nov 22, 2023
Merged

Speed up .edf export with edfio#12218
cbrnr merged 31 commits intomne-tools:mainfrom
hofaflo:edfio

Conversation

@hofaflo
Copy link
Copy Markdown
Contributor

@hofaflo hofaflo commented Nov 16, 2023

Reference issue

Partially addresses #9883 (first and second issue), might supersede #12085

What does this implement/fix?

As suggested by @cbrnr here, edfio could be used to speed up the export to .edf files.

Additional information

  • This also fixes a bug where sometimes unrelated channels were used to determine the physical min and max for physical_range="auto" (see here).
  • Two sections of test_rawarray_edf are removed, because
    • the patient birth date is formatted as "dd-MMM-yyyy" in EDF+, so a date like 20-JAN-1700 is actually valid
    • the export always uses EDF+C, which allows to store dates outside the range valid in "original" Edf (1985-01-01 to 2084-12-31) formatted as "dd-MMM-yyyy" in the "local recording identification" field

@welcome
Copy link
Copy Markdown

welcome bot commented Nov 16, 2023

Hello! 👋 Thanks for opening your first pull request here! ❤️ We will try to get back to you soon. 🚴

@hofaflo
Copy link
Copy Markdown
Contributor Author

hofaflo commented Nov 16, 2023

Conda+mamba checks fail since edfio is not on conda-forge yet, I'll work on that.

@larsoner
Copy link
Copy Markdown
Member

edfio is not on conda-forge yet, I'll work on that.

Let me know if you need help, I did this for pysnirf2 just yesterday so it's fresh in my mind. But the short version is something like:

  1. clone staged-recipes
  2. pip install grayskull (or conda install it)
  3. cd to staged-recipes/recipes
  4. grayskull pypi edfio
  5. Open PR to staged-recipes

@hofaflo
Copy link
Copy Markdown
Contributor Author

hofaflo commented Nov 16, 2023

Thank you for the simplified instructions!
Review is pending at conda-forge/staged-recipes#24511

@Teuniz
Copy link
Copy Markdown

Teuniz commented Nov 16, 2023

* Two sections of `test_rawarray_edf` are removed, because
  
  * the patient birth date is formatted as "dd-MMM-yyyy" in EDF+, so a date like 20-JAN-1700 is actually valid
  * the export always uses EDF+C, which allows to store dates outside the range valid in "original" Edf (1985-01-01 to 2084-12-31) formatted as "dd-MMM-yyyy" in the "local recording identification" field

Pay attention. Allowing a measurement date outside the range 1985-01-01 to 2084-12-31 breaks backwards compatibility with EDF!
In order to protect this backwards compatibility, EDFbrowser will refuse to open an EDF+ file that has a startdate like, for example, 1700-01-01 or 2090-01-01.
(Ofcourse, this limitation in EDFbrowser will be removed before the year 2085 arrives.)

@hofaflo
Copy link
Copy Markdown
Contributor Author

hofaflo commented Nov 16, 2023

Thank you for the heads up @Teuniz! edfio currently stores non-EDF-compatible startdates according to EDF+ specs in the "local recording identification" field and sets the EDF startdate field to "01.01.85". On read, it prefers the EDF+ startdate to the EDF startdate, but of course that only makes sense if no other software is used. Since EDFbrowser does not open files with such a mismatch, we will update edfio to refuse writing them :)

@Teuniz
Copy link
Copy Markdown

Teuniz commented Nov 16, 2023

Since EDFbrowser does not open files with such a mismatch, we will update edfio to refuse writing them :)

Thanks! In an "ideal" world where all EDF software has been updated to EDF+, this shouldn't be necessary,
but ofcourse this is not the case.
The goal is to avoid confusion when two different viewers (one EDF, the other EDF+) show different startdates.
Thanks again for your consideration. It's much appreciated.

@hofaflo
Copy link
Copy Markdown
Contributor Author

hofaflo commented Nov 16, 2023

Conda installs should work now, but I'll mark this as draft until the-siesta-group/edfio#5 is merged and released.

@hofaflo hofaflo marked this pull request as draft November 16, 2023 16:36
@sappelhoff
Copy link
Copy Markdown
Member

note: once this is merged, we'll have to make adjustments in mne-bids:

https://github.com/sappelhoff/mne-bids/blob/781fc577dc0da0419f7eb5c40d537b51834cb802/doc/install.rst?plain=1#L27

@cbrnr
Copy link
Copy Markdown
Contributor

cbrnr commented Nov 16, 2023

Just to be clear, even EDF+ mandates that the date range is between 1985–2084 (https://www.edfplus.info/specs/edfplus.html#additionalspecs). So regardless of EDFbrowser not opening it, this is non-standard and should therefore not be supported.

(Ofcourse, this limitation in EDFbrowser will be removed before the year 2085 arrives.)

The spec also says: "After 2084, yy must be 'yy' and only item 4 of this paragraph defines the date."

And this actually made my day 😄!

@cbrnr
Copy link
Copy Markdown
Contributor

cbrnr commented Nov 16, 2023

Another comment: we don't actually require all dependencies to be on conda-forge, so installing via pip would have also worked. But I guess it doesn't hurt to have it available over there as well.

@cbrnr
Copy link
Copy Markdown
Contributor

cbrnr commented Nov 17, 2023

There's a problem: edfio requires Python >= 3.9, but MNE currently still supports Python 3.8.

This should be relatively easy to fix though by dropping support for Python 3.8. Actually, since we're already adopting SPECS, SPEC 0 says we should drop 3.8 and 3.9 (and support only 3.10, 3.11, and 3.12).

@hoechenberger
Copy link
Copy Markdown
Member

hoechenberger commented Nov 17, 2023

Mind you our installers are often lagging behind by 1 Python release or so, because of the complicated mix of dependencies. Otherwise I'm all for cutting off things that are holding us back.

@hofaflo
Copy link
Copy Markdown
Contributor Author

hofaflo commented Nov 19, 2023

Is there already a timeline for dropping Python 3.8 support? If not – or if the plan is not to do that anytime soon – it should be relatively simple to support 3.8 in edfio. Let me know if that would be your preferred solution!

A few more things are fixed now:

  • The dictionary mapping integers indicating sex in mne to letters used in EDF uses the mapping given here
  • Empty subfields in patient/recording identification are substituted with "X" to ensure compatibility with EDF+
  • Tolerances for comparing signal values and sample times to their values before exporting have been decreased to make passing the respective assertions non-trivial.
  • Slicing signals to their original length has been removed where no padding occurs in the first place.

@cbrnr
Copy link
Copy Markdown
Contributor

cbrnr commented Nov 19, 2023

I'd prefer that we drop support for Python 3.8. This should be no problem, and we don't have to go full in and also drop 3.9. @drammock @larsoner OK?

@larsoner
Copy link
Copy Markdown
Member

We try to follow the scientific Python community standards for this and I think that means it's okay to drop 3.8 according to a quick look at https://numpy.org/neps/nep-0029-deprecation_policy.html#support-table

Let's push 1.6 out the door Monday or Tuesday (it's overdue) then drop 3.8 it while bumping dependency versions to 2-years-old. In the meantime you can ignore the "old" 3.8 CI

@cbrnr
Copy link
Copy Markdown
Contributor

cbrnr commented Nov 19, 2023

There's a problem: edfio requires Python >= 3.9, but MNE currently still supports Python 3.8.

This should be relatively easy to fix though by dropping support for Python 3.8. Actually, since we're already adopting SPECS, SPEC 0 says we should drop 3.8 and 3.9 (and support only 3.10, 3.11, and 3.12).

SPEC0 is even more aggressive, but I'm fine with dropping only 3.8 after the 1.6 release.

@hofaflo hofaflo marked this pull request as ready for review November 19, 2023 13:47
@cbrnr
Copy link
Copy Markdown
Contributor

cbrnr commented Nov 20, 2023

One more thing before I'm happy: exporting stimulus channels are not supported, right? For example, this test involves a Raw object with a stim channel, but it doesn't get exported. If this is intentional (I guess it is, but maybe we can take another look in another PR), I think we should at least emit a warning.

@hofaflo
Copy link
Copy Markdown
Contributor Author

hofaflo commented Nov 20, 2023

Good idea! Added a warning and removed the stim channel from all but one test to avoid checking for that warning in unrelated tests.

@cbrnr
Copy link
Copy Markdown
Contributor

cbrnr commented Nov 20, 2023

Great! Can you maybe remove this comment (# check channel types except for 'bio', which loses its type), which is incorrect (bio stays bio)?

This test is a bit convoluted, feel free to clean up a bit if it makes sense. For example, there's actually a third export (the very last line), which I'm not sure what it actually tests (just a smoke test, but for what). And the test is called test_double_export_edf(), which is not true because of the third export...

@Teuniz
Copy link
Copy Markdown

Teuniz commented Nov 20, 2023

One more thing before I'm happy: exporting stimulus channels are not supported, right? For example, this test involves a Raw object with a stim channel, but it doesn't get exported. If this is intentional (I guess it is, but maybe we can take another look in another PR), I think we should at least emit a warning.

Because I'm curious, what's different about a stim channel that it cannot be saved in EDF+?

@cbrnr
Copy link
Copy Markdown
Contributor

cbrnr commented Nov 20, 2023

Because I'm curious, what's different about a stim channel that it cannot be saved in EDF+?

Good question, there's no reason why this cannot be done. In fact, this would be a perfectly valid and useful thing to do, but for some reason MNE-Python has excluded stim channels. We did not want to mess with that in this PR, but we'll tackle it in another PR. I guess the main difference between "normal" (physiological) and stim channels is that the latter contain exact (integer) values, but this can be dealt with by setting the physical and digital minima/maxima appropriately.

@hofaflo
Copy link
Copy Markdown
Contributor Author

hofaflo commented Nov 21, 2023

Done!

@cbrnr
Copy link
Copy Markdown
Contributor

cbrnr commented Nov 21, 2023

Perfect, thanks!

@cbrnr cbrnr mentioned this pull request Nov 21, 2023
Copy link
Copy Markdown
Member

@larsoner larsoner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just minor stuff from me, happy to defer to @cbrnr's EDF expertise so if he's happy I think we can merge 👍

You'll want to merge in main shortly, though, because @cbrnr dropped 3.8 support in #12229 which is about to land, and I had to adjust the merge requirements to adapt (and merging main into your branch or rebasing will get you the correct CI names/runs to fulfill merge requirements)

Comment on lines +144 to +147
def _pytest_mark_skipif_edfio_not_installed():
return pytest.mark.skipif(
not _check_edfio_installed(strict=False), reason="edfio not installed"
)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A simpler pattern is just to do:

edfio_mark = pytest.mark.skipif(...)

then each function can just be decorated with

@edfio_mark
def test...

Enhancements
~~~~~~~~~~~~
- None yet
- Speed up export to .edf in :func:`mne.export.export_raw` (:gh:`12218` by :newcontrib:`Florian Hofer`)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should mention the switch from EDFlib-Python to edfio explicitly because people might need to pip install stuff to adjust

@cbrnr
Copy link
Copy Markdown
Contributor

cbrnr commented Nov 22, 2023

I agree with your comments, so let's wait until they are addressed.

BTW, will this land in 1.6.1 (backported), or do we wait until 1.7? I'd prefer backporting.

@hofaflo
Copy link
Copy Markdown
Contributor Author

hofaflo commented Nov 22, 2023

Done, thanks for the suggestions @larsoner!

Backporting to 1.6.1 would require support for Python3.8 in edfio, right?

@cbrnr
Copy link
Copy Markdown
Contributor

cbrnr commented Nov 22, 2023

Backporting to 1.6.1 would require support for Python3.8 in edfio, right?

True, I forgot. So I guess this will have to wait for v1.7 then (about 3 months).

@cbrnr cbrnr enabled auto-merge (squash) November 22, 2023 09:28
@hoechenberger
Copy link
Copy Markdown
Member

We should have na "Dependencies" section in the changelog to mention changes of dependencies. @larsoner WDYT?

@cbrnr cbrnr merged commit 6c6e6ec into mne-tools:main Nov 22, 2023
@welcome
Copy link
Copy Markdown

welcome bot commented Nov 22, 2023

🎉 Congrats on merging your first pull request! 🥳 Looking forward to seeing more from you in the future! 💪

@cbrnr
Copy link
Copy Markdown
Contributor

cbrnr commented Nov 22, 2023

Thanks @hofaflo!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants