Skip to content

Conversation

@apdavison
Copy link
Member

@apdavison apdavison commented Oct 23, 2018

As discussed in #125, the parent-child relationships between objects are currently handled manually, e.g. when you add a SpikeTrain to the segment.spiketrains list, the reverse relationship spiketrain.segment is not automatically created, leading to potential inconsistencies.

The aim of this PR, when complete, is to automatically create these relationships by replacing the various child lists (Segment.spiketrains, Segment.analogsignals, Block.segments, etc) by pseudo-list objects, and the reverse relationships Segment.block, etc., by properties).

The pseudo-list classes also provide the opportunity for additional logic and optimisations, for example, a multiplexed representation of spike trains (all times in a single array, with a second array indicating which neuron/channel the spike is from) which will be much more efficient for data coming from simulators.

  • SpikeTrainList class
  • AnalogSignalList class
  • EventList and EpochList
  • child.parent properties
  • ChannelIndexList (or whatever ChannelIndex turns into)

Comments welcome!

@pep8speaks
Copy link

pep8speaks commented Oct 23, 2018

Hello @apdavison! Thanks for updating the PR.

Line 239:9: E265 block comment should start with '# '

Line 125:21: E127 continuation line over-indented for visual indent
Line 475:17: E131 continuation line unaligned for hanging indent

Comment last updated on October 23, 2018 at 07:55 Hours UTC

@apdavison apdavison added this to the 0.9.0 milestone Oct 23, 2018
@samuelgarcia
Copy link
Contributor

Hi @apdavison ,
What I understand here is that you need a list that internaly interleave or not spiketrain depending the situation.

My first impression is that we should first implement in a generic class that do the bidirectional relationship for all object (at least the one to many) and then hinerits from that generic class.

Then this SpikeTrainList would inherits this generic class.

Naively, I would inherits this generic class from the python list but I don't known the consequence.

Cheers,

Samuel

}
return obj

def _spiketrains_from_array(self):
Copy link
Member

Choose a reason for hiding this comment

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

This function is quite expensive, as it generates all Spiketrain objects explicitly. As it is used in many of Spiketrainlist methods, this would cause quite a bit of overhead. Maybe it would be good to use it in only as few places as possible?

Copy link
Member Author

Choose a reason for hiding this comment

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

It's only ever used once, because the generated SpikeTrain objects are cached in self._items

Copy link
Member

Choose a reason for hiding this comment

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

Yes, you are right. So in this PR the SpikeTrain representation is still the reference all data will be converted to eventually. Even when merging two SpikeTrainLists coming from a spike_time_array representation. Is there any way we can avoid that? Maybe having a default operation mode that can switch between SpikeTrain and spike_time_list? Or would it be worth to have the _spike_time_array and the corresponding masks for individual channels / units as base representation and only generate SpikeTrains when required (without) caching / duplicating the data? This would rely even stronger on numpy for inserting / reordering of the spike_times lists and masks. I am not sure which method would be more performant in the end. @apdavison Do you have a better intuition there?

Copy link
Member Author

Choose a reason for hiding this comment

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

you're right that merging two array-representation SpikeTrainLists should avoid creating SpikeTrains. I think that's easy enough to do.

The general idea is to keep data in the representation they arrive in as long as possible, to avoid unecessary transformations, i.e. the "reference/base" representation depends on how the object was initialized.

Note that the multiplexed property (not yet implemented in this branch, but you can see it in the spiketrainlist branch which will replace this PR) allows users to access the array representation.

else:
self._items.extend(other._items)
return self
elif other and isinstance(other[0], SpikeTrain):
Copy link
Member

Choose a reason for hiding this comment

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

In principle this would need to check if other is iterable and assert that all contained items are SpikeTrain instances, no?

Copy link
Member Author

Choose a reason for hiding this comment

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

yes, this was a shortcut, but it would be more robust to do a full check

self._spiketrains_from_array()
for obj in iterable:
obj.segment = self.segment
self._items.extend(iterable)
Copy link
Member

Choose a reason for hiding this comment

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

maybe it's necessary to also verify that the items of iterable are instances of SpikeTrain? Otherwise arbitrary objects might be in the SpikeTrainList...

Copy link
Member Author

Choose a reason for hiding this comment

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

yes, more robust checks are needed

}
return obj

def _spiketrains_from_array(self):
Copy link
Member

Choose a reason for hiding this comment

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

Yes, you are right. So in this PR the SpikeTrain representation is still the reference all data will be converted to eventually. Even when merging two SpikeTrainLists coming from a spike_time_array representation. Is there any way we can avoid that? Maybe having a default operation mode that can switch between SpikeTrain and spike_time_list? Or would it be worth to have the _spike_time_array and the corresponding masks for individual channels / units as base representation and only generate SpikeTrains when required (without) caching / duplicating the data? This would rely even stronger on numpy for inserting / reordering of the spike_times lists and masks. I am not sure which method would be more performant in the end. @apdavison Do you have a better intuition there?

@apdavison
Copy link
Member Author

SpikeTrainList now implemented in #1000.

Closing this now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants