Skip to content

Conversation

@mscheltienne
Copy link
Member

@mscheltienne mscheltienne commented Aug 14, 2024

Closes #3609
Getting the ball rolling now that a first version of antio is up on PyPI. Open items:

  • conda-forge release
  • I'm not finding a nice way to pull the function antio.io.read_raw_ant and its docstring from antio, which forces the signature and docstring copy/paste..
  • How to tests without duplicating the tests in antio

Maybe it wasn't that smart to define RawANT and read_raw_ant in antio.. the rationale was to move all maintenance of the reader to antio.

@mscheltienne mscheltienne requested a review from larsoner as a code owner August 14, 2024 14:34
@larsoner
Copy link
Member

One reasonable division would be to have antio deal with stuff like getting metadata (dict?) and data (ndarray? on demand?) in whatever way is most natural for the format, completely separate from the idea of an Info or BaseRaw etc. Then RawANT in MNE-Python gets those objects to conform to MNE standards. So all read_raw_ant type stuff would be in MNE. In antio it would just check that you can get the data from channel X and it matches what you got from MATLAB or whatever. There would be some duplication there but it's at least a logical split.

This is similar to we would almost certainly implement something like read_raw_egi using mffpy under the hood (replacing our own low-level I/O code) someday. This would also be similar to how read_raw_neuralynx uses neo under the hood to get metadata and pull data from disk.

@mscheltienne
Copy link
Member Author

That's the alternative.. A bit annoying now that I implemented both RawANT and read_raw_ant in antio.. I kinda liked the idea of moving all the reader-code for a specific system outside of MNE, but it seems unpractical.

@larsoner
Copy link
Member

A bit annoying now that I implemented both RawANT and read_raw_ant in antio

Not too late to yank that release :) And not too bad to copy the code over here, either.

But more seriously you could in theory keep the code over there if you wanted. But I think it would make some maintenance stuff harder like if we deprecate something, move some private functions around, change stuff about how Info works etc. then it might not be immediately obvious what the effects are for the ANT reader. So I think in theory it's cleaner to have it here?

@mscheltienne
Copy link
Member Author

But more seriously you could in theory keep the code over there if you wanted. But I think it would make some maintenance stuff harder like if we deprecate something, move some private functions around, change stuff about how Info works etc. then it might not be immediately obvious what the effects are for the ANT reader. So I think in theory it's cleaner to have it here?

True, the biggest downside is that a breakage won't be immediately seen, except if some tests are also run in mne. I'll pick this up tomorrow, I don't have the time left today to make the changes and cut a 0.2.0 release. The conda-forge package will wait until after the 0.2.0 release, as it means I will entirely remove mne from the optional dependencies of antio.

@drammock
Copy link
Member

drammock commented Aug 14, 2024

I'm not finding a nice way to pull the function antio.io.read_raw_ant and its docstring from antio, which forces the signature and docstring copy/paste.

not sure what exactly you want to do with it, but does this help?

import inspect
from types import FunctionType

from antio.io import read_raw_ant

params = inspect.signature(read_raw_ant).parameters
defaults = tuple(params[key].default for key in params)
func_in_mne_namespace = FunctionType(code=read_raw_ant.__code__, globals=globals(), argdefs=defaults)
func_in_mne_namespace.__doc__ = read_raw_ant.__doc__

and now func_in_mne_namespace? gives you the full docstring

@mscheltienne
Copy link
Member Author

I don't think so, still learning something new in this snippet, but my issue was that I wanted to expose the signature and docstring from a function in an optional dependency, which might be absent from the environment.

Anyway, I started moving the code/tests around in antio and will probably get this done tomorrow.

@drammock
Copy link
Member

expose the signature and docstring from a function in an optional dependency, which might be absent from the environment.

ah, yeah, I don't think that's possible. the antio function needs to be importable for that to work.

@hoechenberger
Copy link
Member

That's the alternative.. A bit annoying now that I implemented both RawANT and read_raw_ant in antio.. I kinda liked the idea of moving all the reader-code for a specific system outside of MNE, but it seems unpractical.

i think we've had this discussion previously for bvio oder eeglabio, i don't remember ... but @sappelhoff and @cbrnr were involved

@mscheltienne
Copy link
Member Author

mscheltienne commented Aug 16, 2024

This is now ready for review. Do we want to include it in mne 1.8 or in 1.9? The remaining points are:

  • Decide if we want to include the reader in 1.8, if yes, mark the correct versions in antio here and here.
  • Cut the release 0.2.0 for antio
  • Add mne.io.read_raw_ant to the API documentation and let the CIs run on this PR (tests green locally with antio 0.2.0.dev0)
  • Move antio dataset to mne-testing dataset Add CA_208 ANT test dataset mne-testing-data#115
  • Review this PR and merge it (I left the type-hints I had in antio 0.1.0, but maybe you would prefer to have them removed here)
  • And the last real blocker: build antio on conda-forge

@larsoner
Copy link
Member

This is now ready for review. Do we want to include it in mne 1.8 or in 1.9?

I would rather go with 1.9, gives us months to iron out bugs and actually try using it for a bit

@drammock drammock added this to the 1.9 milestone Aug 16, 2024
@cbrnr
Copy link
Contributor

cbrnr commented Aug 16, 2024

So we are going to include a binary extension now? I think this needs to be discussed with all devs.

@larsoner
Copy link
Member

No the binary extension lives in the antio package separately

Copy link
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.

Looks good, just a couple of comments!

Comment on lines 104 to 160
@pytest.fixture(scope="module")
def reader_excluded_from_read_raw() -> set[str]:
"""Set of excluded readers from read_raw."""
return {
"read_raw_bti",
"read_raw_hitachi",
"read_raw_neuralynx",
}


def test_all_reader_documented(reader_excluded_from_read_raw: set[str]):
"""Test that all the readers in the documentation are accepted by read_raw."""
readers = _get_supported()
# flatten the dictionaries and retrieve the function names
functions = [foo.__name__ for value in readers.values() for foo in value.values()]
# read documentation .rst source file
doc_file = Path(__file__).parents[3] / "doc" / "api" / "reading_raw_data.rst"
assert doc_file.exists()
with open(doc_file) as fid:
doc = fid.read()
reader_lines = [
line.strip() for line in doc.split("\n") if line.strip().startswith("read_raw_")
]
reader_lines = [
elt for elt in reader_lines if elt not in reader_excluded_from_read_raw
]
missing_from_read_raw = set(reader_lines) - set(functions)
missing_from_doc = set(functions) - set(reader_lines)
if len(missing_from_doc) != 0 or len(missing_from_read_raw) != 0:
raise AssertionError(
"Functions missing from documentation:\n\t"
f"{'\n\t'.join(missing_from_doc)}\n\n"
"Functions missing from read_raw:\n\t"
f"{'\n\t'.join(missing_from_read_raw)}"
)
if sorted(reader_lines) != list(reader_lines):
raise AssertionError(
"Functions in documentation are not sorted. Expected order:\n\t"
f"{'\n\t'.join(sorted(reader_lines))}"
)


def test_all_reader_documented_in_docstring():
"""Test that all the readers are documented in read_raw docstring."""
readers = _get_supported()
# flatten the dictionaries and retrieve the function names
functions = [foo.__name__ for value in readers.values() for foo in value.values()]
doc = read_raw.__doc__.split("Parameters")[0]
documented = [elt.strip().split("`")[0] for elt in doc.split("mne.io.")[1:]]
missing_from_docstring = set(functions) - set(documented)
if len(missing_from_docstring) != 0:
raise AssertionError(
"Functions missing from docstring:\n\t"
f"{'\n\t'.join(missing_from_docstring)}"
)
if sorted(documented) != documented:
raise AssertionError("Functions in docstring are not sorted.")
Copy link
Member Author

Choose a reason for hiding this comment

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

@larsoner I added those 2 tests, it did catch a couple of missing readers, 3 of which I excluded because they require a folder or an extension-less file as input.

@mscheltienne
Copy link
Member Author

Finally green 🎉

Copy link
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 some small optional ideas, feel free to change them or not then we can merge!

)
if sorted(reader_lines) != list(reader_lines):
raise AssertionError(
"Functions in documentation are not sorted. Expected order:\n\t"
Copy link
Member

Choose a reason for hiding this comment

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

Nice!

Co-authored-by: Eric Larson <larson.eric.d@gmail.com>
@mscheltienne mscheltienne enabled auto-merge (squash) August 21, 2024 07:54
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.

ENH: import ANT Neuro .CNT files (some code available)

5 participants