Skip to content

Commit

Permalink
improvements after Ben review (#11)
Browse files Browse the repository at this point in the history
  • Loading branch information
h-mayorquin authored Apr 2, 2024
1 parent 17870ef commit 9b2e8f9
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 53 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ ecephys_processing_module.add(binned_aligned_spikes)
### Parameters and data structure
The structure of the bins are characterized with the following parameters:

* `milliseconds_from_event_to_first_bin`: The time in milliseconds from the event to the first bin. A negative value indicates that the first bin is before the event whereas a positive value indicates that the first bin is after the event.
* `milliseconds_from_event_to_first_bin`: The time in milliseconds from the event to the beginning of the first bin. A negative value indicates that the first bin is before the event whereas a positive value indicates that the first bin is after the event.
* `bin_width_in_milliseconds`: The width of each bin in milliseconds.


Expand All @@ -78,7 +78,7 @@ Note that in the diagram above, the `milliseconds_from_event_to_first_bin` is ne



The `data` argument passed to the `BinnedAlignedSpikes` stores counts across all the event timestamps for each of the units. The data is a 3D array where the first dimension indexes the units, the second dimension indexes the event timestamps, and the third dimension indexes the bins where the counts are stored. The shape of the data is `(number_of_units`, `number_of_event_repetitions`, `number_of_bins`).
The `data` argument passed to the `BinnedAlignedSpikes` stores counts across all the event timestamps for each of the units. The data is a 3D array where the first dimension indexes the units, the second dimension indexes the event timestamps, and the third dimension indexes the bins where the counts are stored. The shape of the data is `(number_of_units`, `number_of_events`, `number_of_bins`).


The `event_timestamps` is used to store the timestamps of the events and should have the same length as the second dimension of `data`.
Expand Down
24 changes: 17 additions & 7 deletions spec/ndx-binned-spikes.extensions.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,23 @@ groups:
doc: A data interface for binned spike data aligned to an event (e.g. a stimuli
or the beginning of a trial).
attributes:
- name: name
dtype: text
value: BinnedAlignedSpikes
doc: The name of this container
- name: description
dtype: text
value: Spikes data binned and aligned to event timestamps.
doc: A description of what the data represents
- name: bin_width_in_milliseconds
dtype: float64
doc: The length in milliseconds of the bins
- name: milliseconds_from_event_to_first_bin
dtype: float64
default_value: 0.0
doc: The time in milliseconds from the event (e.g. a stimuli or the beginning
of a trial),to the first bin. Note that this is a negative number if the first
bin is before the event.
doc: The time in milliseconds from the event to the beginning of the first bin.
A negative value indicatesthat the first bin is before the event whereas a positive
value indicates that the first bin is after the event.
required: false
- name: units
dtype:
Expand All @@ -26,17 +34,19 @@ groups:
dtype: numeric
dims:
- - num_units
- number_of_event_repetitions
- number_of_events
- number_of_bins
shape:
- - null
- null
- null
doc: TODO
doc: The binned data. It should be an array whose first dimension is the number
of units, the second dimension is the number of events, and the third dimension
is the number of bins.
- name: event_timestamps
dtype: float64
dims:
- number_of_event_repetitions
- number_of_events
shape:
- null
doc: The timestamps at which the event occurred.
doc: The timestamps at which the events occurred.
53 changes: 30 additions & 23 deletions src/pynwb/ndx_binned_spikes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from pynwb import load_namespaces, get_class
from pynwb import register_class
from pynwb.core import NWBDataInterface
from hdmf.utils import docval, popargs_to_dict
from hdmf.utils import docval

try:
from importlib.resources import files
Expand All @@ -25,10 +25,11 @@
# BinnedAlignedSpikes = get_class("BinnedAlignedSpikes", "ndx-binned-spikes")


@register_class(neurodata_type="BinnedAlignedSpikes", namespace="ndx-binned-spikes") #noqa
@register_class(neurodata_type="BinnedAlignedSpikes", namespace="ndx-binned-spikes") # noqa
class BinnedAlignedSpikes(NWBDataInterface):
__nwbfields__ = (
"name",
"description",
"bin_width_in_milliseconds",
"milliseconds_from_event_to_first_bin",
"data",
Expand All @@ -37,6 +38,7 @@ class BinnedAlignedSpikes(NWBDataInterface):
)

DEFAULT_NAME = "BinnedAlignedSpikes"
DEFAULT_DESCRIPTION = "Spikes data binned and aligned to event timestamps."

@docval(
{
Expand All @@ -45,6 +47,12 @@ class BinnedAlignedSpikes(NWBDataInterface):
"doc": "The name of this container",
"default": DEFAULT_NAME,
},
{
"name": "description",
"type": str,
"doc": "A description of what the data represents",
"default": DEFAULT_DESCRIPTION,
},
{
"name": "bin_width_in_milliseconds",
"type": float,
Expand All @@ -54,52 +62,51 @@ class BinnedAlignedSpikes(NWBDataInterface):
"name": "milliseconds_from_event_to_first_bin",
"type": float,
"doc": (
"The time in milliseconds from the event (e.g. a stimuli or the beginning of a trial),"
"to the first bin. Note that this is a negative number if the first bin is before the event."
"The time in milliseconds from the event to the beginning of the first bin. A negative value indicates"
"that the first bin is before the event whereas a positive value indicates that the first bin is "
"after the event."
),
"default": 0.0,
},
{
"name": "data",
"type": "array_data",
"shape": [(None, None, None)],
"doc": "The source of the data",
"doc": (
"The binned data. It should be an array whose first dimension is the number of units, "
"the second dimension is the number of events, and the third dimension is the number of bins."
),
},
{
"name": "event_timestamps",
"type": "array_data",
"doc": "The timestamps at which the event occurred.",
"doc": "The timestamps at which the events occurred.",
"shape": (None,),
},
{
"name": "units",
"type": ("DynamicTableRegion"),
"type": "DynamicTableRegion",
"doc": "A reference to the Units table region that contains the units of the data.",
"default": None,
},
)
def __init__(self, **kwargs):

keys_to_set = ("bin_width_in_milliseconds", "milliseconds_from_event_to_first_bin", "units")
args_to_set = popargs_to_dict(keys_to_set, kwargs)

keys_to_process = ("data", "event_timestamps")
args_to_process = popargs_to_dict(keys_to_process, kwargs)
super().__init__(**kwargs)

# Set the values
for key, val in args_to_set.items():
setattr(self, key, val)

# Post-process / post_init
data = args_to_process["data"]
event_timestamps = args_to_process["event_timestamps"]
data = kwargs["data"]
event_timestamps = kwargs["event_timestamps"]

if data.shape[1] != event_timestamps.shape[0]:
raise ValueError("The number of event timestamps must match the number of event repetitions in the data.")

self.fields["data"] = data
self.fields["event_timestamps"] = event_timestamps
super().__init__(name=kwargs["name"])

name = kwargs.pop("name")
super().__init__(name=name)

for key in kwargs:
setattr(self, key, kwargs[key])




# Remove these functions from the package
Expand Down
20 changes: 10 additions & 10 deletions src/pynwb/ndx_binned_spikes/testing/mock.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

def mock_BinnedAlignedSpikes(
number_of_units: int = 2,
number_of_event_repetitions: int = 4,
number_of_events: int = 4,
number_of_bins: int = 3,
bin_width_in_milliseconds: float = 20.0,
milliseconds_from_event_to_first_bin: float = 1.0,
Expand All @@ -21,7 +21,7 @@ def mock_BinnedAlignedSpikes(
----------
number_of_units : int, optional
The number of different units (channels, neurons, etc.) to simulate.
number_of_event_repetitions : int, optional
number_of_events : int, optional
The number of times an event is repeated.
number_of_bins : int, optional
The number of bins.
Expand All @@ -33,9 +33,9 @@ def mock_BinnedAlignedSpikes(
Seed for the random number generator to ensure reproducibility.
event_timestamps : np.ndarray, optional
An array of timestamps for each event. If not provided, it will be automatically generated.
It should have size `number_of_event_repetitions`.
It should have size `number_of_events`.
data : np.ndarray, optional
A 3D array of shape (number_of_units, number_of_event_repetitions, number_of_bins) representing
A 3D array of shape (number_of_units, number_of_events, number_of_bins) representing
the binned spike data. If provided, it overrides the generation of mock data based on other parameters.
Its shape should match the expected number of units, event repetitions, and bins.
Expand All @@ -62,21 +62,21 @@ def mock_BinnedAlignedSpikes(
"""

if data is not None:
number_of_units, number_of_event_repetitions, number_of_bins = data.shape
number_of_units, number_of_events, number_of_bins = data.shape
else:
rng = np.random.default_rng(seed=seed)
data = rng.integers(low=0, high=100, size=(number_of_units, number_of_event_repetitions, number_of_bins))
data = rng.integers(low=0, high=100, size=(number_of_units, number_of_events, number_of_bins))

if event_timestamps is None:
event_timestamps = np.arange(number_of_event_repetitions, dtype="float64")
event_timestamps = np.arange(number_of_events, dtype="float64")
else:
assert (
event_timestamps.shape[0] == number_of_event_repetitions
), "The shape of `event_timestamps` does not match `number_of_event_repetitions`."
event_timestamps.shape[0] == number_of_events
), "The shape of `event_timestamps` does not match `number_of_events`."
event_timestamps = np.array(event_timestamps, dtype="float64")

if event_timestamps.shape[0] != data.shape[1]:
raise ValueError("The shape of `event_timestamps` does not match `number_of_event_repetitions`.")
raise ValueError("The shape of `event_timestamps` does not match `number_of_events`.")

binned_aligned_spikes = BinnedAlignedSpikes(
bin_width_in_milliseconds=bin_width_in_milliseconds,
Expand Down
8 changes: 4 additions & 4 deletions src/pynwb/tests/test_binned_aligned_spikes.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def setUp(self):

self.number_of_units = 2
self.number_of_bins = 3
self.number_of_event_repetitions = 4
self.number_of_events = 4
self.bin_width_in_milliseconds = 20.0
self.milliseconds_from_event_to_first_bin = -100.0
self.rng = np.random.default_rng(seed=0)
Expand All @@ -31,12 +31,12 @@ def setUp(self):
high=100,
size=(
self.number_of_units,
self.number_of_event_repetitions,
self.number_of_events,
self.number_of_bins,
),
)

self.event_timestamps = np.arange(self.number_of_event_repetitions, dtype="float64")
self.event_timestamps = np.arange(self.number_of_events, dtype="float64")

self.nwbfile = mock_NWBFile()

Expand All @@ -58,7 +58,7 @@ def test_constructor(self):
)

self.assertEqual(binned_aligned_spikes.data.shape[0], self.number_of_units)
self.assertEqual(binned_aligned_spikes.data.shape[1], self.number_of_event_repetitions)
self.assertEqual(binned_aligned_spikes.data.shape[1], self.number_of_events)
self.assertEqual(binned_aligned_spikes.data.shape[2], self.number_of_bins)

def test_constructor_units_region(self):
Expand Down
30 changes: 23 additions & 7 deletions src/spec/create_extension_spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,21 @@ def main():

binned_aligned_spikes_data = NWBDatasetSpec(
name="data",
doc="TODO",
doc=(
"The binned data. It should be an array whose first dimension is the number of units, the second dimension "
"is the number of events, and the third dimension is the number of bins."
),
dtype="numeric", # TODO should this be a uint64?
shape=[(None, None, None)],
dims=[("num_units", "number_of_event_repetitions", "number_of_bins")],
dims=[("num_units", "number_of_events", "number_of_bins")],
)

event_timestamps = NWBDatasetSpec(
name="event_timestamps",
doc="The timestamps at which the event occurred.",
doc="The timestamps at which the events occurred.",
dtype="float64",
shape=(None,),
dims=("number_of_event_repetitions",),
dims=("number_of_events",),
)

binned_aligned_spikes = NWBGroupSpec(
Expand All @@ -48,6 +51,18 @@ def main():
doc="A data interface for binned spike data aligned to an event (e.g. a stimuli or the beginning of a trial).",
datasets=[binned_aligned_spikes_data, event_timestamps],
attributes=[
NWBAttributeSpec(
name="name",
doc="The name of this container",
dtype="text",
value="BinnedAlignedSpikes",
),
NWBAttributeSpec(
name="description",
doc="A description of what the data represents",
dtype="text",
value="Spikes data binned and aligned to event timestamps.",
),
NWBAttributeSpec(
name="bin_width_in_milliseconds",
doc="The length in milliseconds of the bins",
Expand All @@ -56,9 +71,10 @@ def main():
NWBAttributeSpec(
name="milliseconds_from_event_to_first_bin",
doc=(
"The time in milliseconds from the event (e.g. a stimuli or the beginning of a trial),"
"to the first bin. Note that this is a negative number if the first bin is before the event."
),
"The time in milliseconds from the event to the beginning of the first bin. A negative value indicates"
"that the first bin is before the event whereas a positive value indicates that the first bin is "
"after the event."
),
dtype="float64",
default_value=0.0,
),
Expand Down

0 comments on commit 9b2e8f9

Please sign in to comment.