Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
1bc3ee5
Point to nwbep001 branch in nwb-schema
rly Aug 20, 2025
3d90adc
Implements TimestampVectorData, DurationVectorData, EventsTable
rly Jan 10, 2026
9fdcf8c
Add deprecations
rly Jan 12, 2026
eb55755
Clarify TimestampVectorData docstring
rly Jan 27, 2026
38a8feb
Require description arg for TimestampVectorData and DurationVectorData
rly Mar 24, 2026
98a9888
Merge branch 'dev' into nwbep001
rly Apr 2, 2026
1a0b5ea
Un-deprecate TimeSeries continuity="instantaneous"; soften Behavioral…
rly Apr 14, 2026
6acaec8
Add EventsTable examples to behavior and file basics galleries
rly Apr 14, 2026
04fc54b
Add changelog entry for EventsTable gallery examples
rly Apr 14, 2026
c7e6e11
Add changelog entries for NWBEP001 schema and deprecations
rly Apr 14, 2026
3bf95a9
Link NWBEP001 changelog entries to PR #2156
rly Apr 14, 2026
7e5db40
Remove erroneous PR #2156 link from HERD changelog entry
rly Apr 14, 2026
731d926
Revert errant replace_all in CHANGELOG and re-link only NWBEP001 entr…
rly Apr 14, 2026
2343bc1
Fix stale EventsTable test docstring and DurationVectorData resolutio…
rly Apr 14, 2026
f26dd2b
Replace tempfile.mktemp with TemporaryDirectory in EventsTable tests
rly Apr 14, 2026
b0a15bd
Use pathlib.Path for EventsTable test file path
rly Apr 14, 2026
e3924fe
Forward meanings_tables kwarg in DynamicTable subclass constructors
rly Apr 14, 2026
ee2c771
Use NWBH5IOFlexMixin for canonical EventsTable roundtrip test
rly Apr 14, 2026
06fa11c
Test meanings_tables on EventsTable; document __init__ override; bump…
rly Apr 14, 2026
75f052f
Clarify resolution docstrings on TimestampVectorData/DurationVectorData
rly Apr 14, 2026
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
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
# PyNWB Changelog

## PyNWB 3.2.0 (Upcoming)
## PyNWB 4.0.0 (Upcoming)

### Documentation and tutorial enhancements
- Added documentation for `ExternalImage` to the images tutorial. @h-mayorquin [#2159](https://github.com/NeurodataWithoutBorders/pynwb/pull/2159)
- Fixed broken and redirecting links in documentation. @bendichter [#2165](https://github.com/NeurodataWithoutBorders/pynwb/pull/2165)
- Added `EventsTable` examples to the NWB file basics and behavior tutorials. @rly [#2156](https://github.com/NeurodataWithoutBorders/pynwb/pull/2156)

### Added
- Added support for NWB Schema 2.10.0 ([NWBEP001](https://nwb-schema.readthedocs.io/)), which introduces the `EventsTable`, `TimestampVectorData`, and `DurationVectorData` neurodata types and a new `events` group on `NWBFile` for storing `EventsTable` instances. Use `NWBFile.add_events_table()` to add an `EventsTable` and `NWBFile.get_events_table()` to retrieve one. @rly [#2156](https://github.com/NeurodataWithoutBorders/pynwb/pull/2156)
- Added support for HERD (HDMF External Resources Data Structure) as the `external_resources` field on `NWBFile`, enabling users to associate external resource annotations (e.g., ontology term mappings) with their NWB files. `link_resources` and `get_external_resources` are inherited from `HERDManager` in hdmf. @mavaylon1, @rly [#2111](https://github.com/NeurodataWithoutBorders/pynwb/pull/2111)
- Added `get_starting_time()` and `get_duration()` methods to `TimeSeries` to get the starting time and duration of the time series. @h-mayorquin [#2146](https://github.com/NeurodataWithoutBorders/pynwb/pull/2146)
- Added `get_starting_time()` and `get_duration()` methods to `TimeIntervals` to get the earliest start time and total duration (span from earliest start to latest stop) of all intervals. @h-mayorquin [#2146](https://github.com/NeurodataWithoutBorders/pynwb/pull/2146)
Expand All @@ -19,6 +21,7 @@
- Added Python 3.14 support. @bendichter, @rly [#2168](https://github.com/NeurodataWithoutBorders/pynwb/pull/2168)
- Updated HDMF dependency to >=5.0.0, <6. @rly [#2171](https://github.com/NeurodataWithoutBorders/pynwb/issues/2171)
- Deprecated Python 3.9 support. (EOL was Oct 31, 2025) @bendichter [#2141](https://github.com/NeurodataWithoutBorders/pynwb/pull/2141)
- Deprecated `BehavioralEvents` and `AnnotationSeries` in favor of using an `EventsTable` in `NWBFile.events`. Creating a new instance of either type now emits a `UserWarning`; reading existing files containing these types continues to work without warnings. @rly [#2156](https://github.com/NeurodataWithoutBorders/pynwb/pull/2156)

## PyNWB 3.1.3 (December 9, 2025)

Expand Down
54 changes: 29 additions & 25 deletions docs/gallery/domain/plot_behavior.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@
* :py:class:`~pynwb.behavior.Position` for position measured over time
* :py:class:`~pynwb.behavior.CompassDirection` for view angle measured over time
* :py:class:`~pynwb.behavior.BehavioralTimeSeries` for continuous time series data
* :py:class:`~pynwb.behavior.BehavioralEvents` for behavioral events (e.g. reward amount)
* :py:class:`~pynwb.behavior.BehavioralEpochs` for behavioral intervals (e.g. sleep intervals)
* :py:class:`~pynwb.behavior.PupilTracking` for eye-tracking data of pupil size
* :py:class:`~pynwb.behavior.EyeTracking` for eye-tracking data of gaze direction
* :py:class:`~pynwb.event.EventsTable` for behavioral events (e.g. reward times)

* create a behavior processing module for the :py:class:`~pynwb.file.NWBFile` and add the interface object(s) to it

Expand All @@ -46,7 +46,6 @@
from pynwb import NWBHDF5IO, NWBFile, TimeSeries
from pynwb.behavior import (
BehavioralEpochs,
BehavioralEvents,
BehavioralTimeSeries,
CompassDirection,
EyeTracking,
Expand All @@ -55,6 +54,7 @@
SpatialSeries,
)
from pynwb.epoch import TimeIntervals
from pynwb.event import EventsTable
from pynwb.misc import IntervalSeries

####################
Expand Down Expand Up @@ -219,34 +219,38 @@
behavior_module.add(behavioral_time_series)

####################
# BehavioralEvents: Storing behavioral events
# -------------------------------------------
# EventsTable: Storing behavioral events
# --------------------------------------
#
# :py:class:`~pynwb.behavior.BehavioralEvents` is an interface for storing behavioral events.
# We can use it for storing the timing and amount of rewards (e.g. water amount) or lever press times.

reward_amount = [1.0, 1.5, 1.0, 1.5]
events_timestamps = [1.0, 2.0, 5.0, 6.0]

time_series = TimeSeries(
name="lever_presses",
data=reward_amount,
timestamps=events_timestamps,
description="The water amount the subject received as a reward.",
unit="ml",
# :py:class:`~pynwb.event.EventsTable` is for storing behavioral events such as the timing
# and amount of rewards (e.g., water amount) or lever press times. EventsTable is stored
# in ``NWBFile.events``.
#
# .. note::
# :py:class:`~pynwb.behavior.BehavioralEvents` is deprecated. Use
# :py:class:`~pynwb.event.EventsTable` instead.
#
# Create an :py:class:`~pynwb.event.EventsTable` to store reward delivery events.
# The required ``timestamp`` column stores the time of each event in seconds from the
# session start time. Additional columns can be added to store metadata about each
# event, such as the amount of reward delivered.

reward_events = EventsTable(
name="reward_events",
description="Times and amounts of water rewards delivered to the animal.",
)
reward_events.add_column(name="amount_ml", description="Volume of water reward in mL.")

behavioral_events = BehavioralEvents(time_series=time_series, name="BehavioralEvents")

behavior_module.add(behavioral_events)
reward_events.add_event(timestamp=12.5, amount_ml=0.05)
reward_events.add_event(timestamp=27.3, amount_ml=0.05)
reward_events.add_event(timestamp=44.1, amount_ml=0.10)

####################
# Storing only the timestamps of the events is possible with the `ndx-events <https://pypi.org/project/ndx-events/>`_
# NWB extension. You can also add labels associated with the events with this extension.
# You can find information about installation and example usage :nwb_extension:`here <ndx-events-record>`.
#
# .. seealso::
# You can learn more about using extensions in the :ref:`tutorial-extending-nwb` tutorial.
# Add the :py:class:`~pynwb.event.EventsTable` to the :py:class:`~pynwb.file.NWBFile`
# using :py:meth:`~pynwb.file.NWBFile.add_events_table`. Events tables are stored in
# ``NWBFile.events``.

nwbfile.add_events_table(reward_events)

####################
# BehavioralEpochs: Storing intervals of behavior data
Expand Down
40 changes: 21 additions & 19 deletions docs/gallery/general/plot_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@
* **Others:** :py:class:`~pynwb.ogen.OptogeneticSeries`,
:py:class:`~pynwb.behavior.SpatialSeries`,
:py:class:`~pynwb.misc.DecompositionSeries`,
:py:class:`~pynwb.misc.AnnotationSeries`,
:py:class:`~pynwb.misc.AbstractFeatureSeries`,
:py:class:`~pynwb.misc.IntervalSeries`.

Expand All @@ -83,13 +82,14 @@
For your reference, NWB defines the following main processing/analysis data types:

* **Behavior:** :py:class:`~pynwb.behavior.BehavioralEpochs`,
:py:class:`~pynwb.behavior.BehavioralEvents`,
:py:class:`~pynwb.behavior.BehavioralTimeSeries`,
:py:class:`~pynwb.behavior.CompassDirection`,
:py:class:`~pynwb.behavior.PupilTracking`,
:py:class:`~pynwb.behavior.Position`,
:py:class:`~pynwb.behavior.EyeTracking`.

* **Events:** :py:class:`~pynwb.event.EventsTable`.

* **Extracellular electrophysiology:** :py:class:`~pynwb.ecephys.EventDetection`,
:py:class:`~pynwb.ecephys.FeatureExtraction`,
:py:class:`~pynwb.ecephys.FilteredEphys`,
Expand Down Expand Up @@ -128,8 +128,8 @@

from pynwb import NWBHDF5IO, NWBFile, TimeSeries
from pynwb.behavior import Position, SpatialSeries
from pynwb.event import EventsTable
from pynwb.file import Subject
from pynwb.misc import AnnotationSeries

####################
# .. _basics_nwbfile:
Expand Down Expand Up @@ -289,24 +289,26 @@
# ^^^^^^^^^^^^^^^^^^^^^^^^^^
#
# As mentioned previously, there are many subtypes of :py:class:`~pynwb.base.TimeSeries` that are used to store
# different kinds of data. One example is :py:class:`~pynwb.misc.AnnotationSeries`, a subclass of
# :py:class:`~pynwb.base.TimeSeries` that stores text-based records about the experiment. Similarly to our
# :py:class:`~pynwb.base.TimeSeries` example above, we can create an :py:class:`~pynwb.misc.AnnotationSeries`
# object with text information about a stimulus and add it to the stimulus group in
# the :py:class:`~pynwb.file.NWBFile`.

annotations = AnnotationSeries(
name='airpuffs',
data=['Left Airpuff', 'Right Airpuff', 'Right Airpuff'],
description='Airpuff events delivered to the animal',
timestamps=[1.0, 3.0, 8.0],
# different kinds of data. The approach of creating a :py:class:`~pynwb.base.TimeSeries` object and adding it
# to the appropriate :py:class:`~pynwb.file.NWBFile` group can be used for all subtypes of
# :py:class:`~pynwb.base.TimeSeries` data.
#
# For storing events with annotations (e.g., behaviors scored from video), use
# :py:class:`~pynwb.event.EventsTable` in ``NWBFile.events``. The required ``timestamp``
# column stores the time of each event in seconds from the session start time. The
# optional built-in ``duration`` column stores the length of each event in seconds, and
# the optional built-in ``annotation`` column can be used to store a text label for each
# event.

behavior_events = EventsTable(
name="scored_behaviors",
description="Behaviors of the animal scored from video recordings.",
)
behavior_events.add_event(timestamp=10.2, duration=1.4, annotation="grooming")
behavior_events.add_event(timestamp=18.7, duration=0.6, annotation="rearing")
behavior_events.add_event(timestamp=25.0, duration=2.1, annotation="grooming")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This can be a separate issue, but this seems like a great place to also introduce a Meanings table to define what "grooming" and "rearing" means.


nwbfile.add_stimulus(annotations)

####################
# This approach of creating a :py:class:`~pynwb.base.TimeSeries` object and adding it to the appropriate
# :py:class:`~pynwb.file.NWBFile` group can be used for all subtypes of :py:class:`~pynwb.base.TimeSeries` data.
nwbfile.add_events_table(behavior_events)

####################
# .. _basic_spatialseries:
Expand Down
1 change: 1 addition & 0 deletions src/pynwb/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -639,6 +639,7 @@ def read_nwb(**kwargs):
from . import device # noqa: F401,E402
from . import ecephys # noqa: F401,E402
from . import epoch # noqa: F401,E402
from . import event # noqa: F401,E402
from . import icephys # noqa: F401,E402
from . import image # noqa: F401,E402
from . import misc # noqa: F401,E402
Expand Down
16 changes: 9 additions & 7 deletions src/pynwb/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,13 +192,15 @@ class TimeSeries(NWBDataInterface):
{'name': 'control_description', 'type': Iterable, 'doc': 'Description of each control value',
'default': None},
{'name': 'continuity', 'type': str, 'default': None, 'enum': ["continuous", "instantaneous", "step"],
'doc': 'Optionally describe the continuity of the data. Can be "continuous", "instantaneous", or'
'"step". For example, a voltage trace would be "continuous", because samples are recorded from a '
'continuous process. An array of lick times would be "instantaneous", because the data represents '
'distinct moments in time. Times of image presentations would be "step" because the picture '
'remains the same until the next time-point. This field is optional, but is useful in providing '
'information about the underlying data. It may inform the way this data is interpreted, the way it '
'is visualized, and what analysis methods are applicable.'},
'doc': 'Optionally describe the continuity of the data. Can be "continuous", "instantaneous", or '
'"step". For example, a voltage trace would be "continuous", because samples are recorded from '
'a continuous process. An array of lick times would be "instantaneous", because the data '
'represents distinct moments in time. Times of image presentations would be "step" because the '
'picture remains the same until the next time-point. This field is optional, but is useful in '
'providing information about the underlying data. It may inform the way this data is '
'interpreted, the way it is visualized, and what analysis methods are applicable. '
'For storing instantaneous event information, it is recommended to use an EventsTable instead '
'of a TimeSeries with continuity set to "instantaneous".'},
allow_positional=AllowPositional.WARNING,)
def __init__(self, **kwargs):
"""Create a TimeSeries object
Expand Down
16 changes: 15 additions & 1 deletion src/pynwb/behavior.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,9 @@ class BehavioralEpochs(MultiContainerInterface):
@register_class('BehavioralEvents', CORE_NAMESPACE)
class BehavioralEvents(MultiContainerInterface):
"""
TimeSeries for storing behavioral events. See description of BehavioralEpochs for more details.
DEPRECATED. TimeSeries for storing behavioral events. See description of BehavioralEpochs for more details.

BehavioralEvents is deprecated. Use an EventsTable in NWBFile.events instead for event data.
"""

__clsconf__ = {
Expand All @@ -109,6 +111,18 @@ class BehavioralEvents(MultiContainerInterface):
'attr': 'time_series'
}

@docval({'name': 'time_series', 'type': (list, tuple, dict, TimeSeries),
'doc': 'TimeSeries to store in this interface', 'default': dict()},
{'name': 'name', 'type': str, 'doc': 'the name of this container', 'default': 'BehavioralEvents'})
def __init__(self, **kwargs):
time_series = popargs('time_series', kwargs)
super().__init__(**kwargs)
self.add_timeseries(time_series)
self._warn_on_new_pass_on_construct(
"BehavioralEvents is deprecated. Use an EventsTable in NWBFile.events instead for event data. "
"Creating a new BehavioralEvents will not be allowed in a future version of PyNWB."
Comment thread
rly marked this conversation as resolved.
)


@register_class('BehavioralTimeSeries', CORE_NAMESPACE)
class BehavioralTimeSeries(MultiContainerInterface):
Expand Down
9 changes: 9 additions & 0 deletions src/pynwb/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,15 @@ def _error_on_new_pass_on_construct(self, error_msg: str):
if not self._in_construct_mode:
raise ValueError(error_msg)

def _warn_on_new_pass_on_construct(self, warn_msg: str):
"""
Issue a warning when a check is violated on instance creation.
When reading from a file, do nothing, ensuring that files with
deprecated neurodata types can be read without warnings.
"""
if not self._in_construct_mode:
warn(warn_msg)

def _get_type_map(self):
return get_type_map(copy=False)

Expand Down
2 changes: 1 addition & 1 deletion src/pynwb/ecephys.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ class ElectrodesTable(DynamicTable):
'for this electrode.'), 'required': False}
)

@docval(*get_docval(DynamicTable.__init__, 'id', 'columns', 'colnames', 'target_tables'),
@docval(*get_docval(DynamicTable.__init__, 'id', 'columns', 'colnames', 'target_tables', 'meanings_tables'),
allow_positional=AllowPositional.WARNING,)
def __init__(self, **kwargs):
kwargs['name'] = 'electrodes'
Expand Down
2 changes: 1 addition & 1 deletion src/pynwb/epoch.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class TimeIntervals(DynamicTable):
@docval({'name': 'name', 'type': str, 'doc': 'name of this TimeIntervals'}, # required
{'name': 'description', 'type': str, 'doc': 'Description of this TimeIntervals',
'default': "experimental intervals"},
*get_docval(DynamicTable.__init__, 'id', 'columns', 'colnames', 'target_tables'),
*get_docval(DynamicTable.__init__, 'id', 'columns', 'colnames', 'target_tables', 'meanings_tables'),
allow_positional=AllowPositional.WARNING,)
def __init__(self, **kwargs):
super().__init__(**kwargs)
Expand Down
Loading
Loading