An abandoned work-in-progress for a high level Signal type with a common timebase (in seconds) and groups of channels. Deprecated in favor of AxisArrays.jl.
Diagramming these complicated nested structures makes thinking about this much more concrete. Let's start with a regularly sampled Signal with three channels:
(t) chan1 chan2 chan3
| | | |
| | | |
| | | |
| | | |
| | | |
V V V V
When we take windowed repetitions of it, the result is a Signal (often irregularly sampled), with each channel being its own regularly sampled signal that contains a two-dimensional 4x3 SignalMatrix. The only disconnect here is that each channel calls its nested repetitions "channels," and the nested time vectors are unnecessarily repeated—they're all the same. But, really, this works fairly well in practice. This is a signal containing a vector of three regularly-sampled signals, all with the same time vector: When the outer signal slices across the inner signal to access each channel ([:,i]), the inner signal returns a SignalVector.
(t) chan1 chan2 chan3
o ----> ----> ---->
o ----> ----> ---->
o ----> ----> ---->
o ----> ----> ---->
(t)-> (t)-> (t)->
Typical analyses on this kind of signal will subsequently work across each channel independently, e.g., to find the mean response, so re-arranging the data to fit this paradigm makes sense.
Windowing repetitions of an irregularly sampled signal, however, must behave very differently. Each repetition has its own time vector, and so we can no longer share time across all dimensions or store the array in a densely packed format. In the example below, there are 12 independent signals! This could be represented by a signal containing a matrix of 1-element signals. Slicing across a channel ([:,i]) would yield a vector of 1-element Signals.
(t) chan1 chan2 chan3
o * * * * * * * * *
(t)o o o (t)o o o (t)o o o
o * ** * ** * **
(t)o oo (t)o oo (t)o oo
o ** ** ** ** ** **
(t)oo oo (t)oo oo (t)oo oo
o * ** * ** * **
(t)o oo (t)o oo (t)o oo
Again, typical analyses will work channel-by-channel, so there's not much to be gained by sharing the common time-points.
It's type-stable, but there is an asymmetry here. Grabbing a channel of the windowed regular signal will yield a Signal, whereas the windowed irregular signal gives us a Vector{Signal}. This feels wrong, but it completely represents the difference in complexity. I'll be wanting to create methods for those Vector{Signal}s (e.g., PSTH, raster, etc), which further makes me think that I want a new type here.
This is compounded by the fact that the kinds of analyses done on repetitions is very different from those done on independent channels.
Here's a brainstorm of one possible solution:
New types: abstract Repetition<: AbstractSignal, with subtypes RegularRepetition and IrregularRepetition. These types would be useful in dispatch. Plotting is a good motivation: repetitions are typically overlaid, whereas independent signals are typically shown independently (subplot/offset/something). Each channel would have its own Repetition object.
RegularRepetition would be almost exactly a Signal with a different name and reps instead of channels. It could be simpler, though. There's no need for channel names, but there's still use for a meta field (e.g., attaching sort codes to snippets).
IrregularRepetition would be much more specialized. But could it act like a Signal?
What is its time-base? The whole point of Signals.jl is to have multiple channels on the same time-base, and this breaks that. It can't have a timebase as each channel is different, so it can't be a signal.
Now, for its channels, we'd have to change some things. Indexing any AbstractSignal would always have to return a Signal with just one channel and its time vector. This might be worth doing in any case: it'd always keep time and data together, but it makes it harder to get at the raw data. I think it could just contain Signals, and it'd be fine on that front. The trouble is time.
Other ideas:
What if the Repetition type encapsulated the entire windowing result object, covering all channels and their subsequent reps. I don't like this as that's not where the difficulty lies, and Signals already represent time-locked channels very well. I'd still need to create per-channel repetition types.
I really like the idea of restricting Signals to only contain one matrix as data storage. Is there a way we could have the repetitions be a matrix? This would be tough, and probably problematic, as I'd want reps[:,i] to return a specialized repetition vector type. It could be made to be consistent, but it'd require totally re-inventing slice, sub and getindex.
These all seem to be special cases of a variable-rank SignalArray{N}, with a data array of rank N+1 and a broadcasting time array.
In the regular window case above, the data array can be described as a SignalArray{2}: it's data array is a three dimensional Nx4x3, and it indexes and iterates over the 4x3 part. Were the outer signal to index its data like a matrix (as [:,i]), it would return a SignalArray{1} with data Nx4. The time vector could be abstracted to an array that is treated as though it is broadcast to the dimensions of the SignalArray. In this case, it'd just be the one-element [(1:N)/fs].
For the irregular window, it's similarly a SignalArray{2}, but it's trickier because its data cannot be a densely packed Nx4x3 array - N is variable across the reps. Perhaps it could behave this way, though. It's time array is simply a 4-element vector that broadcasts across the channels. It would probably have to be its own type; a RaggedSignal.
Diagramming these complicated nested structures makes thinking about this much more concrete. Let's start with a regularly sampled Signal with three channels:
When we take windowed repetitions of it, the result is a Signal (often irregularly sampled),
with each channel being its own regularly sampled signalthat contains a two-dimensional 4x3 SignalMatrix.The only disconnect here is that each channel calls its nested repetitions "channels," and the nested time vectors are unnecessarily repeated—they're all the same. But, really, this works fairly well in practice. This is a signal containing a vector of three regularly-sampled signals, all with the same time vector:When the outer signal slices across the inner signal to access each channel ([:,i]
), the inner signal returns a SignalVector.Typical analyses on this kind of signal will subsequently work across each channel independently, e.g., to find the mean response, so re-arranging the data to fit this paradigm makes sense.
Windowing repetitions of an irregularly sampled signal, however, must behave very differently. Each repetition has its own time vector, and so we can no longer share time across all dimensions or store the array in a densely packed format. In the example below, there are 12 independent signals! This could be represented by a signal containing a matrix of 1-element signals. Slicing across a channel (
[:,i]
) would yield a vector of 1-element Signals.Again, typical analyses will work channel-by-channel, so there's not much to be gained by sharing the common time-points.
It's type-stable, but there is an asymmetry here. Grabbing a channel of the windowed regular signal will yield a
Signal
, whereas the windowed irregular signal gives us aVector{Signal}
. This feels wrong, but it completely represents the difference in complexity. I'll be wanting to create methods for thoseVector{Signal}
s (e.g., PSTH, raster, etc), which further makes me think that I want a new type here.This is compounded by the fact that the kinds of analyses done on repetitions is very different from those done on independent channels.
Here's a brainstorm of one possible solution:
abstract Repetition
, with subtypes<: AbstractSignal
RegularRepetition
andIrregularRepetition
. These types would be useful in dispatch. Plotting is a good motivation: repetitions are typically overlaid, whereas independent signals are typically shown independently (subplot/offset/something). Each channel would have its own Repetition object.RegularRepetition
would be almost exactly aSignal
with a different name and reps instead of channels. It could be simpler, though. There's no need for channel names, but there's still use for a meta field (e.g., attaching sort codes to snippets).IrregularRepetition
would be much more specialized. But could it act like a Signal?Now, for its channels, we'd have to change some things. Indexing any AbstractSignal would always have to return aI think it could just contain Signals, and it'd be fine on that front. The trouble is time.Signal
with just one channel and its time vector. This might be worth doing in any case: it'd always keep time and data together, but it makes it harder to get at the raw data.Other ideas:
What if the Repetition type encapsulated the entire windowing result object, covering all channels and their subsequent reps.I don't like this as that's not where the difficulty lies, and Signals already represent time-locked channels very well. I'd still need to create per-channel repetition types.I really like the idea of restricting Signals to only contain one matrix as data storage. Is there a way we could have the repetitions be a matrix? This would be tough, and probably problematic, as I'd want
reps[:,i]
to return a specialized repetition vector type. It could be made to be consistent, but it'd require totally re-inventing slice, sub and getindex.SignalArray{N}
, with a data array of rank N+1 and a broadcasting time array.SignalArray{2}
: it's data array is a three dimensionalNx4x3
, and it indexes and iterates over the 4x3 part. Were the outer signal to index its data like a matrix (as[:,i]
), it would return aSignalArray{1}
with dataNx4
. The time vector could be abstracted to an array that is treated as though it is broadcast to the dimensions of the SignalArray. In this case, it'd just be the one-element[(1:N)/fs]
.SignalArray{2}
, but it's trickier because its data cannot be a densely packedNx4x3
array - N is variable across the reps. Perhaps it could behave this way, though. It's time array is simply a 4-element vector that broadcasts across the channels. It would probably have to be its own type; aRaggedSignal
.