Skip to content
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

add time series check #1426

Merged
merged 10 commits into from
Feb 7, 2022
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion src/pynwb/base.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
from warnings import warn
from collections.abc import Iterable
import numpy as np
from typing import NamedTuple

import numpy as np

from hdmf.utils import docval, getargs, popargs, call_docval_func, get_docval
from hdmf.common import DynamicTable, VectorData
from hdmf.utils import get_data_shape

from . import register_class, CORE_NAMESPACE
from .core import NWBDataInterface, MultiContainerInterface, NWBData
Expand Down Expand Up @@ -146,6 +148,24 @@ def __init__(self, **kwargs):
"control",
"control_description",
"continuity")

data_shape = get_data_shape(data=kwargs["data"], strict_no_data_load=True)
timestamps_shape = get_data_shape(data=kwargs["timestamps"], strict_no_data_load=True)
if (
# check that the shape is known
data_shape is not None and timestamps_shape is not None

# check for scalars. Should never happen
and (len(data_shape) > 0 and len(timestamps_shape) > 0)

# check that the length of the first dimension is known
and (data_shape[0] is not None and timestamps_shape[0] is not None)

# check that the data and timestamps match
and (data_shape[0] != timestamps_shape[0])
):
warn("Length of data does not match length of timestamps. Your data may be transposed. Time should be on "
"the 0th dimension")
for key in keys:
val = kwargs.get(key)
if val is not None:
Expand Down
15 changes: 15 additions & 0 deletions src/pynwb/ecephys.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from collections.abc import Iterable
import warnings

from hdmf.utils import docval, getargs, popargs, call_docval_func, get_docval
from hdmf.data_utils import DataChunkIterator, assertEqualShape
from hdmf.utils import get_data_shape

from . import register_class, CORE_NAMESPACE
from .base import TimeSeries
Expand Down Expand Up @@ -78,6 +80,19 @@ class ElectricalSeries(TimeSeries):
def __init__(self, **kwargs):
name, electrodes, data, channel_conversion, filtering = popargs('name', 'electrodes', 'data',
'channel_conversion', 'filtering', kwargs)
data_shape = get_data_shape(data, strict_no_data_load=True)
if (
data_shape is not None
and len(data_shape) == 2
and data_shape[1] != len(electrodes.data)
):
if data_shape[0] == len(electrodes.data):
warnings.warn("The second dimension of data does not match the length of electrodes, but instead the "
"first does. Data is oriented incorrectly and should be transposed.")
else:
warnings.warn("The second dimension of data does not match the length of electrodes. Your data may be "
"transposed.")

super(ElectricalSeries, self).__init__(name, data, 'volts', **kwargs)
self.electrodes = electrodes
self.channel_conversion = channel_conversion
Expand Down
22 changes: 21 additions & 1 deletion src/pynwb/ophys.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import numpy as np
import warnings

from hdmf.utils import docval, getargs, popargs, call_docval_func, get_docval
from hdmf.utils import docval, getargs, popargs, call_docval_func, get_docval, get_data_shape

from . import register_class, CORE_NAMESPACE
from .base import TimeSeries
Expand Down Expand Up @@ -342,6 +342,26 @@ class RoiResponseSeries(TimeSeries):
'comments', 'description', 'control', 'control_description'))
def __init__(self, **kwargs):
rois = popargs('rois', kwargs)

data_shape = get_data_shape(data=kwargs["data"], strict_no_data_load=True)
rois_shape = get_data_shape(data=rois.data, strict_no_data_load=True)
if (
data_shape is not None and rois_shape is not None

# check that data is 2d and rois is 1d
and len(data_shape) == 2 and len(rois_shape) == 1

# check that key dimensions are known
and data_shape[1] is not None and rois_shape[0] is not None

and data_shape[1] != rois_shape
):
if data_shape[0] == rois_shape[0]:
warnings.warn("The second dimension of data does not match the length of rois, but instead the "
"first does. Data is oriented incorrectly and should be transposed.")
else:
warnings.warn("The second dimension of data does not match the length of rois. Your data may be "
"transposed.")
call_docval_func(super(RoiResponseSeries, self).__init__, kwargs)
self.rois = rois

Expand Down
18 changes: 18 additions & 0 deletions tests/unit/test_base.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import warnings

import numpy as np

from pynwb.base import ProcessingModule, TimeSeries, Images, Image, TimeSeriesReferenceVectorData, TimeSeriesReference
Expand Down Expand Up @@ -203,6 +205,22 @@ def test_conflicting_time_args(self):
TimeSeries('test_ts2', [10, 11, 12, 13, 14, 15], 'grams',
starting_time=30., timestamps=[.3, .4, .5, .6, .7, .8])

def test_dimension_warning(self):
with warnings.catch_warnings(record=True) as w:
# Cause all warnings to always be triggered.
warnings.simplefilter("always")
TimeSeries(
name='test_ts2',
data=[10, 11, 12],
unit='grams',
timestamps=[.3, .4, .5, .6, .7, .8],
)
assert len(w) == 1
assert (
"Length of data does not match length of timestamps. Your data may be "
"transposed. Time should be on the 0th dimension"
) in str(w[-1].message)


class TestImage(TestCase):

Expand Down
34 changes: 34 additions & 0 deletions tests/unit/test_ecephys.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import warnings

import numpy as np

from pynwb.ecephys import ElectricalSeries, SpikeEventSeries, EventDetection, Clustering, EventWaveform,\
Expand Down Expand Up @@ -66,6 +68,38 @@ def test_invalid_data_shape(self):
"2)', expected '((None,), (None, None), (None, None, None))')")):
ElectricalSeries('test_ts1', np.ones((2, 2, 2, 2)), region, timestamps=[0.0, 0.1, 0.2, 0.3, 0.4, 0.5])

def test_dimensions_warning(self):
table = make_electrode_table()
region = DynamicTableRegion('electrodes', [0, 2], 'the first and third electrodes', table)
with warnings.catch_warnings(record=True) as w:
# Cause all warnings to always be triggered.
warnings.simplefilter("always")
ElectricalSeries(
name="test_ts1",
data=np.ones((6, 3)),
electrodes=region,
timestamps=[0.0, 0.1, 0.2, 0.3, 0.4, 0.5]
)
self.assertEqual(len(w), 1)
assert (
"The second dimension of data does not match the length of electrodes. Your data may be transposed."
) in str(w[-1].message)

with warnings.catch_warnings(record=True) as w:
# Cause all warnings to always be triggered.
warnings.simplefilter("always")
ElectricalSeries(
name="test_ts1",
data=np.ones((2, 6)),
electrodes=region,
rate=30000.,
)
self.assertEqual(len(w), 1)
assert (
"The second dimension of data does not match the length of electrodes, but instead the first does. Data "
"is oriented incorrectly and should be transposed."
) in str(w[-1].message)


class SpikeEventSeriesConstructor(TestCase):

Expand Down
37 changes: 37 additions & 0 deletions tests/unit/test_ophys.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import warnings

import numpy as np

from pynwb.base import TimeSeries
Expand Down Expand Up @@ -289,6 +291,41 @@ def test_init(self):
self.assertEqual(ts.unit, 'unit')
self.assertEqual(ts.rois, rt_region)

def test_warnings(self):
ps = create_plane_segmentation()
rt_region = ps.create_roi_table_region(description='the second ROI', region=[0, 1])

with warnings.catch_warnings(record=True) as w:
# Cause all warnings to always be triggered.
warnings.simplefilter("always")
RoiResponseSeries(
name="test_ts1",
data=np.ones((6, 3)),
rois=rt_region,
unit="n.a.",
timestamps=[0.0, 0.1, 0.2, 0.3, 0.4, 0.5]
)
self.assertEqual(len(w), 1)
assert (
"The second dimension of data does not match the length of rois. Your data may be transposed."
) in str(w[-1].message)

with warnings.catch_warnings(record=True) as w:
# Cause all warnings to always be triggered.
warnings.simplefilter("always")
RoiResponseSeries(
name="test_ts1",
data=np.ones((2, 6)),
rois=rt_region,
rate=30000.,
unit="n.a.",
)
self.assertEqual(len(w), 1)
assert (
"The second dimension of data does not match the length of rois, but instead the first does. "
"Data is oriented incorrectly and should be transposed."
) in str(w[-1].message)


class DfOverFConstructor(TestCase):
def test_init(self):
Expand Down