Skip to content
Achilleas Koutsou edited this page Feb 9, 2018 · 2 revisions

Data model mapping between Neo and NIX

This document describes how the Neo data model is mapped into NIX when writing using the Neo-NixIO.

General mapping convention

Each Neo object maps to at least one instance of a NIX object. Each object's attributes are mapped into the corresponding attributes in NIX. If there is no NIX attribute that holds the same meaning as the Neo attribute, the object's metadata section is used.

Metadata

Each NIX object refers to one metadata Section. This section shares the NIX object's name. The type is the same as the type attribute with the suffix .metadata. Metadata Sections are created when the object is created, even if there are no properties to be stored.

The NIX metadata tree reflects the Neo object tree.

Quantities and Units

Since Version 1.4.3, NIXPY allows any string to be used as a unit. Previously, NIX restricted dimensions to SI units. With this change, the Neo-NixIO can store any unit specified in Neo into the file without much trouble. In some situations, however, there is a small change that needs to be made for the data in the NIX file to be consistent.

Certain attribute values are scaled to fit a particular unit when writing. The clearest example of this is t_start for AnalogSignal. When written to NIX, this attribute is stored in the time dimension, a dimension object of type SampledDimension. The unit of this attribute must match the signal's time dimension unit (the offset, which is the t_start counterpart, does not have its own unit; instead it is stored in the time dimension and shares the same unit as the sampling frequency). In the cases where it does not match, the attribute is rescaled to match, the original unit is stored as metadata, and it is restored to its original value when reading.

Example behaviour:

  • AnalogSignal with:
    • sampling_period = 0.1 * ms
    • t_start = 1 * s
  • When writing to NIX this will be converted to a DataArray with a SampledDimension to represent the time axis with:
    • sampling_interval = 0.1
    • offset = 1000
    • unit = "ms"
  • In order to losslessly recover the state of the original AnalogSignal, the following property is stored in the DataArray's metadata:
    • da.metadata["t_start"] = 1
    • da.metadata["t_start"].unit = "s"

Object names

Objects in Neo do not require names to be unique or non-empty. This is an issue for NIX since NIX requires non-empty, unique names. To work around this, the following naming scheme is implemented during writing:

  1. When a Neo object is written to NIX, a unique nix_name is generated.
  2. The NIX name is added to the Neo object as an annotation (nix_name).
  3. The Neo object's original name is stored in the NIX object's metadata (neo_name).
  4. When reading the file, the Neo object's original name is restored and the autogenerated NIX name is stored as an annotation.

During step 1., if the Neo object already has a nix_name annotation, that name is used instead of generating a new one.

See the Naming Example for an example of this behaviour.

Rewriting existing objects

When a Block is written to a file, the NixIO checks whether the neo.Block has a nix_name annotation. If it does and it matches the name of a nix.Block already in the file, the entire Block is deleted and rewritten. If no conflict exists, the Block is written alongside existing ones.

See the Naming Example for an example of this behaviour.

Object mapping details

neo.Block

Maps to nix.Block.

  • Attributes

    Neo NIX
    Block.name(string) Block.name(string)
    Block.description(string) Block.definition(string)
    Block.rec_datetime(datetime) Block.created_at(int)
    Block.file_datetime(datetime) Block.metadata(Section) [1]
    Block.file_origin(string) Block.metadata(Section) [1]
  • Objects

    • neo.Block.segments(Segment[]):
      • Maps to nix.Block.groups(Group[]).
      • See the neo.Segment section for details.
    • neo.Block.channel_indexes(ChannelIndex[]):
      • Maps to nix.Block.sources(Source[]) with type = "neo.channelindex".
      • See the neo.ChannelIndex section for details.

neo.Segment

Maps to nix.Group.

  • Attributes

    Neo NIX
    Segment.name(string) Group.name(string)
    Segment.description(string) Group.definition(string)
    Segment.rec_datetime(datetime) Group.created_at(int)
    Segment.file_datetime(datetime) Group.metadata(Section) [1]
    Segment.file_origin(string) Group.metadata(Section) [1]
  • Objects

    • Segment.analogsignals(AnalogSignal[]) & Segment.irregularlysampledsignals(IrregularlySampledSignal[]): For each item in both lists, a nix.DataArray is created which holds the signal data and attributes. The type attribute of the DataArray is set to neo.analogsignal or neo.irregularlysampledsignal accordingly. These are stored in the Group.data_arrays list. See the neo.AnalogSignal and neo.IrregularlySampledSignal sections for details.
      • Signal objects in Neo can be grouped, e.g., Segment.analogsignals is a list of AnalogSignal objects, each of which can hold multiple signals. In order to be able to reconstruct the original signal groupings, all DataArray objects that belong to the same AnalogSignal (or IrregularlySampledSignal) have their metadata attribute point to the same Section.
    • Segment.epochs(Epoch[]): For each item in Segment.epochs, a nix.MultiTag is created with type = neo.epoch. This is stored in the Group.multi_tags list. See the neo.Epoch section for details.
    • Segment.events(Event[]): For each item in Segment.events, a nix.MultiTag is created with type = neo.event. This is stored in the Group.multi_tags list. See the neo.Event section for details.
    • Segment.spiketrains(SpikeTrain[]): For each item in Segment.spiketrains, a nix.MultiTag is created with type = neo.spiketrain. This is stored in the Group.multi_tags list. See the neo.SpikeTrain section for details.

neo.ChannelIndex

Maps to nix.Source with type = neo.channelindex.

  • Attributes

    Neo NIX
    ChannelIndex.name(string) Source.name(string)
    ChannelIndex.description(string) Source.definition(string)
  • Objects

    • For each channel in ChannelIndex, determined by the index array, a nix.Source is created with type = neo.channelindex.
      • The name of each channel is the NIX name of the ChannelIndex object with ChannelIndex<N> to it (where <N> is an incremental index).
      • Each channel has a metadata section which stores:
        • A property that specifies its channel index, from channel_indexes.
        • A property that stores the corresponding name, from channel_names (if available).
        • A property that stores the corresponding id, from channel_ids (if available).
        • A property that stores the corresponding (x, y, z) coordinate, with unit, from coordinates (if available).
    • ChannelIndex.units Maps to nix.Source with type = neo.unit. See the neo.Unit section for details.
  • AnalogSignal and IrregularlySampledSignal references

    • The nix.DataArray objects which represent the neo.AnalogSignal and neo.IrregularlySampedSignal objects referenced by the neo.ChannelIndex, reference the respective nix.Source object (the reference direction is reversed).

neo.AnalogSignal

Maps to a nix.DataArray with type = neo.analogsignal.

  • Attributes

    Neo NIX
    AnalogSignal.name(string) DataArray.name(string)
    AnalogSignal.description(string) DataArray.definition(string)
  • Objects

    • AnalogSignal.signal(Quantity 2D):
      • Maps to a set of DataArray.data(DataType[]), one for each signal in the signal group.
      • DataArray.unit(string) is set based on the units of the Quantity array (AnalogSignal.signal).
      • DataArray.dimensions(Dimension[]) contains two objects:
        • A SampledDimension to denote that the signals are regularly sampled. The attributes of this dimension are:
          • sampling_interval assigned from the value of AnalogSignal.sampling_rate(Quantity scalar).
          • offset assigned from the value of AnalogSignal.t_start(Quantity scalar).
          • unit inheriting the value of the DataArray.unit.
        • A SetDimension to denote that the second dimension represents a set (collection) of signals.
  • ChannelIndex references

    • If a neo.AnalogSignal is referenced by a neo.ChannelIndex, the nix.DataArrays each reference the corresponding nix.Source.

neo.IrregularlySampledSignal

Maps to a nix.DataArray with type = neo.irregularlysampledsignal.

  • Attributes

    Neo NIX
    IrregularlySampledSignal.name(string) DataArray.name(string)
    IrregularlySampledSignal.description(string) DataArray.definition(string)
  • Objects

    • IrregularlySampledSignal.signal(Quantity 2D):
      • Maps to a set of DataArray.data(DataType[]), one for each signal in the signal group.
      • DataArray.unit(string) is set based on the units of the Quantity array (IrregularlySampledSignal.signal).
      • DataArray.dimensions(Dimension[]) contains two objects:
        • A RangeDimension to denote that the signals are irregularly sampled. The attributes of this dimension are:
          • ticks assigned from the value of IrregularlySampledSignal.times(Quantity 1D).
          • unit inheriting the value of the DataArray.unit.
        • A SetDimension to denote that the second dimension represents a set (collection) of signals.
  • ChannelIndex references

    • If a neo.IrregularlySampedSignal is referenced by a neo.ChannelIndex, the nix.DataArrays each reference the corresponding nix.Source.

neo.Epoch

Maps to a nix.MultiTag with type = neo.epoch.

  • Attributes

    Neo NIX
    Epoch.name(string) MultiTag.name(string)
    Epoch.description(string) MultiTag.definition(string)
  • Objects

    • Epoch.times(Quantity 1D) maps to MultiTag.positions(DataArray) with type neo.epoch.times.
    • Epoch.durations(Quantity 1D) maps to MultiTag.extents(DataArray) with type neo.epoch.durations.
    • Epoch.labels(string[]) maps to the labels attribute of a SetDimension of the positions DataArray. The SetDimension is also referenced by the extents DataArray.
    • The references attribute of the nix.MultiTag points to all the AnalogSignal and IrregularlySampledSignal objects that exist in the same neo.Segment as the epoch.

neo.Event

Maps to a nix.MultiTag with type = neo.event.

  • Attributes

    Neo NIX
    Event.name(string) MultiTag.name(string)
    Event.description(string) MultiTag.definition(string)
  • Objects

    • Event.times(Quantity 1D) maps to MultiTag.positions(DataArray) with type neo.event.times.
    • Event.labels(string[]) maps to the labels attribute of a SetDimension of the positions DataArray.
    • The references attribute of the nix.MultiTag points to all the AnalogSignal and IrregularlySampledSignal objects that exist in the same neo.Segment as the event.

neo.SpikeTrain

Maps to a nix.MultiTag with type = neo.spiketrain.

  • Attributes

    Neo NIX
    SpikeTrain.name(string) MultiTag.name(string)
    SpikeTrain.description(string) MultiTag.definition(string)
    SpikeTrain.t_start(Quantity scalar) MultiTag.metadata(Section) [1]
    SpikeTrain.t_stop(Quantity scalar) MultiTag.metadata(Section) [1]
    SpikeTrain.left_sweep(Quantity scalar) MultiTag.metadata(Section) [1]
  • Objects

    • SpikeTrain.times(Quantity 1D):
      • Maps to MultiTag.positions(DataArray).
      • The positions DataArray is of type neo.spiketrain and has a single SetDimension.
    • SpikeTrain.waveforms(Quantity 3D):
      • Waveform data and metadata associated with spikes are stored in a DataArray of type neo.waveforms.
      • The DataArray is associated with the spiketrain MultiTag via a nix.Feature. Specifically, MultiTag.features holds a reference to a single Feature with link_type = indexed.
      • Feature.data refers to the DataArray where the waveforms are stored.
      • The DataArray also refers to a metadata Section that stores the left_sweep value.
      • The DataArray has 3 dimensions:
        1. SetDimension.
        2. SetDimension.
        3. SampledDimension: The SpikeTrain.sampling_rate is stored in this dimension's sampling_interval and the unit is set accordingly.
  • Unit references

    • If a neo.SpikeTrain is referenced by a neo.Unit, the nix.MultiTag references the corresponding nix.Source as well as the Unit's parent neo.ChannelIndex.

neo.Unit

Maps to a nix.Source with type = neo.unit.

  • Attributes

    Neo NIX
    Unit.name(string) Source.name(string)
    Unit.description(string) Source.definition(string)
  • nix.Source objects that represent neo.Units are created on the corresponding nix.Source which represents the original neo.RecordingChannelGroup.

  • SpikeTrain references

    • The nix.MultiTag objects which represent the neo.SpikeTrain objects referenced by the neo.Unit, reference the respective nix.Source object (the reference direction is reversed).