New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[MRG] FIX: make all annotate_* funcs return annots with consistent orig_time #10067
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok, seems to be a little more tricky than expected ... and I find the docs on how annots.orig_time
and raw.meas_date
interact a bit hard to understand (the "figure" in the docstr)
If anyone knows what's going on with these two failing tests, I'd be happy about pointers:
pytest mne/preprocessing/tests/test_maxwell.py::test_find_bads_maxwell_flat
pytest mne/preprocessing/tests/test_flat.py::test_annotate_flat
+100 Also not sure if ASCII art is really the best choice here |
The problem occurs when the annotations are concatenated in the test: raw_time.set_annotations(raw_time.annotations + annot) > annot.onset
array([10.])
> raw_time.annotations.onset
array([], dtype=float64) This produces an annotation with onset at 10 seconds. However: > raw.times.max()
0.999 Now during new_annotations.crop(0, self.times[-1] + delta,
emit_warning=emit_warning)
new_annotations.onset += self._first_time So we crop away the annotation at 10 seconds To make it work, I think we'd need to do something like this instead (not tested) new_annotations.onset -= self._first_time
new_annotations.crop(0, self.times[-1] + delta,
emit_warning=emit_warning) But this will probably break other use cases? |
personally for me this is a regression. these annotations can contain thinks like sleep spindles, eye movements and you want to know I would rather use meas_date for all annotations |
I guess that would also be fine. My problem was arising when I wanted to concatenate
both of the annots were created on the same It's just that the current inconsistency is suboptimal. |
b1c1fa9
to
1763999
Compare
1763999
to
5b09651
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this and I think this should be backported too |
I'll push a tiny commit with tests after CIs come back green, then this is ready from my side as well. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good that I added the more thorough approach ... test_movement_annotation_head_correction
is actually failing ...
IIRC the plan is to release 1.0 in January, so another point release is unlikely before then, so not sure we need to backport. I guess we could mark things as candidates but not bother actually backporting until we decide we want to do another point release. |
So with this PR, all For one test, this leads to errors (
I think the reason why this fails is the interaction when calling When going into the code with (Pdb) annot_all = annot_rot + annot_tra + annot_dis
(Pdb) annot_all.onset+annot_all.duration
array([ 6. , 7.96, 8.34, 8.25, 11. , 13. , 13.49, 16.07])
(Pdb) raw
<Raw | test_move_anon_raw.fif, 392 x 20400 (17.0 s), ~68.7 MB, data loaded>
(Pdb) raw.first_samp
10800
(Pdb) raw.info["sfreq"]
1200.0 Basically:
Lines 691 to 704 in 963eea2
this leads to: (Pdb) pass # drop orig_time
(Pdb) annot_all2 = mne.Annotations(annot_all.onset, annot_all.duration, annot_all.description)
(Pdb) pass # annot onsets and durations are still the same between both versions
(Pdb) annot_all2.onset+annot_all2.duration
array([ 6. , 7.96, 8.34, 8.25, 11. , 13. , 13.49, 16.07])
(Pdb) annot_all.onset+annot_all.duration
array([ 6. , 7.96, 8.34, 8.25, 11. , 13. , 13.49, 16.07])
(Pdb) pass # but setting to raw only works when orig_time=None
(Pdb) raw.set_annotations(annot_all2)
<Raw | test_move_anon_raw.fif, 392 x 20400 (17.0 s), ~68.7 MB, data loaded>
(Pdb) pass # check annots attached to raw
(Pdb) raw.annotations
<Annotations | 8 segments: BAD_mov_dist (1), BAD_mov_rotat_vel (5), ...>
(Pdb) pass # when taking first_samp into account, we see that the data is not from 0-17s, but from 9-26s (or similar)
(Pdb) raw.annotations.onset+raw.annotations.duration
array([15. , 16.96, 17.34, 17.25, 20. , 22. , 22.49, 25.07])
(Pdb) pass # 4 annots are <9s, so these 4 first annots are ommitted
(Pdb) pass # when setting them to raw and orig_time is NOT None
(Pdb) raw._first_time
9.0 |
@larsoner do you have an idea how to resolve this? |
I'll need to dig into the code a bit :( Hopefully I can look early next week |
babe3d7
to
8034400
Compare
Okay, I'll take a look hopefully after that then |
8034400
to
197b75f
Compare
Thanks! Should be ready now. My analysis from #10067 (comment) is still where I am stuck. |
hp_ts -= raw.first_samp / sfreq | ||
orig_time = raw.info['meas_date'] | ||
if orig_time is None: | ||
hp_ts -= raw.first_samp / sfreq |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sappelhoff this was the key change. We only need to shift the head_pos
time reference by first_samp
if times are absolute / orig_time is not None
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ahhhh yes, makes sense! Thanks for finding that!
I just think you are missing a "not" in your sentence? I.e., we only need to shift if times are NOT absolute / orig_time is None
?
thx @larsoner 🙏 @sappelhoff merge if happy |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for your help @larsoner
@sappelhoff looks like this broke an example:
Can you look? |
mh, weird. running it locally on up to date |
can you confirm that @hoechenberger ? |
|
ok, thanks. got it. Some >>> annot_muscle
<Annotations | 1 segment: BAD_muscle (1)>
>>> annot_muscle.onset
array([15.17])
>>> annot_muscle.duration
array([1.09666667])
>>> annot_muscle.description
array(['BAD_muscle'], dtype='<U10')
>>> annot_muscle.orig_time
datetime.datetime(2013, 12, 18, 9, 43, tzinfo=datetime.timezone.utc)
>>> raw.info["meas_date"]
datetime.datetime(2013, 12, 18, 9, 43, tzinfo=datetime.timezone.utc)
>>> raw.times
array([0.00000000e+00, 3.33333333e-03, 6.66666667e-03, ...,
2.99900000e+01, 2.99933333e+01, 2.99966667e+01])
>>> raw.times.max()
29.996666666666666
>>> raw.first_samp
39000
>>> raw |
Ohhh yes, our all-time favorite… |
It seems like this can be solved like this. Old: mne-python/mne/preprocessing/artifact_detection.py Lines 119 to 120 in 151b8a6
New: annot = _annotations_from_mask(raw_copy.times+raw._first_time, art_mask, 'BAD_muscle',
orig_time=raw.info['meas_date']) I suspect that this is an issue in all funcs that use Don't know if this is the right fix 🤷 |
I'll start a new PR, adding tests that fail .. and will then push commits to fix. |
closes #10060
Now all
annotate_*
functions will return annotations with a consistenorig_time
(first thoughtNone
would be a good idea, ... now changing it toraw.info["meas_date"]
for all)might be worth including in 0.24.1, cc @larsoner