Repository: mne-tools/mne-python

Issue:
private attribute to detect if raw has been cropped?
In mne-bids, we were thinking of an API where there is a function called `write_raw_bids(raw, bids_fname, ...)` to convert existing data to BIDS format. This would work by essentially **copying** the original file in raw.filenames to the new destination and converting the metadata into json and tsv sidecar files (it's the only way we can support different file types since MNE can only write fif files). 

However, we need a way to safeguard against accidental changes to the data in the raw object, as this could render the accompanying meta-data incorrect (changes to the meta data -- like raw.info -- can be considered intentional). One way to enforce this is to force raw.preload=False. 

However, @agramfort pointed out to me that even with this, it is possible to crop the raw object. It appears that MNE keeps no history of the cropping and it's impossible for us to prevent this. Would it make sense to add a private attribute (e.g., `raw._is_cropped`) to mne that can be used by mne-bids to enforce this? Any other potential snafus that we should be aware of?


Discussion:
how about rather making sure you can clone the raw object by rereading from
disk and then check that raw.times is still the same?

I would be safer I think than tracking any possible change that can have
happened to the raw object besides the info attribute.

Eventually it would be ideal if other operations (filtering in particular) could be done without preloading, too. And such things are feasible to implement. So whatever solution we devise should be compatible with the idea that operations on the data other than cropping could eventually be implemented, too.
>
> Eventually it would be ideal if other operations (filtering in particular)
> could be done without preloading, too. And such things are feasible to
> implement. So whatever solution we devise should be compatible with the
> idea that operations on the data other than cropping could eventually be
> implemented, too.
>

yes this also says that one should be able to reread from disk and check
that object has not changed. I don't think it's hard to do provided you
keep with attributes all the constructor parameters.

okay that's fair enough. We can reload and do a check. It should be on `mne-bids` end for now I suppose?

> Eventually it would be ideal if other operations (filtering in particular) could be done without preloading, too

But this would not work with `preload=False` would it? It would be a memory mapped file, right? Otherwise you would have to modify the original file ...
It is possible to do it on the fly (no preload, memmap, or on disk modification), it would just be a huge pain to program
I would force to pass non-preloaded data and then just recreate the raw
from scratch. Think about the clone function in sklearn to clone any
estimator and get a fresh one. Makes sense?

hmm ... this would not fix your cropping issue though as you would also copy `raw.times`. Or do you propose reading the `raw` back from the file? For that to happen we would need all the params to the `read_raw_xxx` files as an attribute in `raw` somewhere. It would essentially reset your `raw` to the state just after it was read in -- thus we would lose any information that the user added to `raw.info` ...
>
> hmm ... this would not fix your cropping issue though as you would also
> copy raw.times. Or do you propose reading the raw back from the file?
>

yes but doing this without overwritting the current raw (ant its info) and
just to check that data have still the same shape etc.


>


Resolving patch:
diff --git a/mne/io/base.py b/mne/io/base.py
--- a/mne/io/base.py
+++ b/mne/io/base.py
@@ -41,7 +41,7 @@
                      check_fname, _get_stim_channel,
                      logger, verbose, _time_mask, warn, SizeMixin,
                      copy_function_doc_to_method_doc,
-                     _check_preload)
+                     _check_preload, _get_argvalues)
 from ..viz import plot_raw, plot_raw_psd, plot_raw_psd_topo
 from ..defaults import _handle_default
 from ..externals.six import string_types
@@ -406,6 +406,7 @@ def __init__(self, info, preload=False,
         self._update_times()
         if load_from_disk:
             self._preload_data(preload)
+        self._init_kwargs = _get_argvalues()
 
     @verbose
     def apply_gradient_compensation(self, grade, verbose=None):
diff --git a/mne/utils.py b/mne/utils.py
--- a/mne/utils.py
+++ b/mne/utils.py
@@ -16,6 +16,8 @@
 import inspect
 import json
 import logging
+import fnmatch
+
 from math import log, ceil
 import multiprocessing
 import operator
@@ -79,6 +81,22 @@ def nottest(f):
 # RANDOM UTILITIES
 
 
+def _get_argvalues():
+    """Return all arguments (except self) and values of read_raw_xxx."""
+    # call stack
+    # read_raw_xxx -> EOF -> verbose() -> BaseRaw.__init__ -> get_argvalues
+    frame = inspect.stack()[4][0]
+    fname = frame.f_code.co_filename
+    if not fnmatch.fnmatch(fname, '*/mne/io/*'):
+        return None
+    args, _, _, values = inspect.getargvalues(frame)
+    params = dict()
+    for arg in args:
+        params[arg] = values[arg]
+    params.pop('self', None)
+    return params
+
+
 def _ensure_int(x, name='unknown', must_be='an int'):
     """Ensure a variable is an integer."""
     # This is preferred over numbers.Integral, see:


Test patch:
diff --git a/mne/io/tests/test_raw.py b/mne/io/tests/test_raw.py
--- a/mne/io/tests/test_raw.py
+++ b/mne/io/tests/test_raw.py
@@ -91,6 +91,11 @@ def _test_raw_reader(reader, test_preloading=True, **kwargs):
     # gh-5604
     assert _handle_meas_date(raw.info['meas_date']) >= 0
 
+    # test resetting raw
+    raw2 = reader(**raw._init_kwargs)
+    assert set(raw.info.keys()) == set(raw2.info.keys())
+    assert_array_equal(raw.times, raw2.times)
+
     # Test saving and reading
     out_fname = op.join(tempdir, 'test_raw.fif')
     raw = concatenate_raws([raw])