Multiwell MEA Data - multichannelsystems/McsMatlabDataTools GitHub Wiki

Working with Multiwell-MEA data

The Multiwell-MEA system generates 4 data files for each recording with the file extensions .mwc, .mwd, .mwr and .mws:

  • The .mwc files contain spike cutouts (for data acquired in Neuro mode) or averaged signal cutouts (for data acquired in Cardio mode) and event data.
  • The .mwd files contain the recorded electrode data, analog and digital input data, together with event data.
  • The .mwr and .mws files are XML files storing either just the experiment settings (.mws) or settings and analysis results (.mwr).

The .mwc and .mwd files can be exported via the Multi Channel DataManager to HDF5 and imported into Matlab via the McsMatlabDataTools toolbox:

raw_data = McsHDF5.McsData('PATH/TO/THE/CONVERTED/MWD/FILE_mwd.h5');
cutout_data = McsHDF5.McsData('PATH/TO/THE/CONVERTED/MWC/FILE_mwc.h5');

Analog Streams in .mwd files

The 3 AnalogStreams in an .mwd file consist of the electrode data stream (typically at index 1), the analog data stream (typically at index 2) and the digital data stream (typically at index 3). All of these streams contain a single block of channels x samples data (ChannelData), where the number of samples is identical between streams. They also have a field ChannelDataTimeStamps, which holds for each sample a timestamp in µs.

If Analog In or Electrode Data has been disabled in the Recorder Settings dialog of Multiwell-Screen, the respective Analog Stream will be missing from the data. To make sure that the streams are identified correctly, it can be helpful to check the DataSubType attribute of the streams:

for stream = raw_data.Recording{1}.AnalogStream
    sub_type = stream{1}.DataSubType;
    if strcmp(sub_type, 'Electrode')
        electrode_data = stream{1};
    elseif strcmp(sub_type, 'Auxiliary')
        analog_data = stream{1};
    elseif strcmp(sub_type, 'Digital')
        digital_data = stream{1};
    end
end

Recording Phases in AnalogStreams

The block of data in ChannelData in general does not represent only a single set of continuous data. Instead, it is a collection of data segments that are joined together. Each data segment is the data acquired during one recording phase in the experiment. If multiple recording phases had been used, the ChannelDataTimeStamps also consists of multiple discontinuous segments.

In order to get the data for a particular recording phase, you first have to identify the start and stop timestamp of the respective recording phase. See Recording Phases below for more information on how to get this information. These timestamps can then be used to get the data indices from the ChannelDataTimeStamps:

idx = raw_data.Recording{1}.AnalogStream{1}.ChannelDataTimeStamps >= start_timestamp & raw_data.Recording{1}.AnalogStream{1}.ChannelDataTimeStamps < end_timestamp;
raw_data_in_recording_phase = data.Recording{1}.AnalogStream{1}.ChannelData(:, idx);

Segment Streams in .mwc files

.mwc files contain a SegmentStream, but its contents depend on whether the data was acquired in Cardio or Neuro mode.

Neuronal Data

For neuronal data, the SegmentStream contains spike cutouts, organized in a samples x cutouts matrix. For each channel, a segment entity exists in the stream:

segment_data = cutout_data.Recording{1}.SegmentStream{1}.SegmentData{segment_id}; % samples x cutouts
segment_timestamps = cutout_data.Recording{1}.SegmentStream{1}.SegmentDataTimeStamps{segment_id}; % timestamp in µs, 1 x cutouts

All cutouts for a channel are stored in a single samples x cutouts matrix, even if the experiment consisted multiple recording phases. In order to get all cutouts for a specific recording phase, please use the information stored in the EventStreams to get the start and stop timestamps of the different recording phases (see section Recording Phases below) to select the appropriate segments:

idx = cutout_data.Recording{1}.SegmentStream{1}.SegmentDataTimeStamps{segment_id} >= start_timestamp & cutout_data.Recording{1}.SegmentStream{1}.SegmentDataTimeStamps{segment_id} < end_timestamp;
cutouts_in_recording_phase = cutout_data.Recording{1}.SegmentStream{1}.SegmentData{segment_id}(:, idx);

Cardiac Data

For cardiac data, the SegmentStream contains averaged signal cutouts. For each channel, a segment entity exists in the stream:

mean_data = cutout_data.Recording{1}.SegmentStream{1}.AverageDataMean{segment_id}; % cutout sample-wise average, samples x averages
std_data = cutout_data.Recording{1}.SegmentStream{1}.AverageDataStdDev{segment_id}; % cutout sample-wise stddev., samples x averages
count_data = cutout_data.Recording{1}.SegmentStream{1}.AverageDataCount{segment_id}; % no. of averaged cutouts, 1 x averages
segment_timestamps = cutout_data.Recording{1}.SegmentStream{1}.AverageDataTimeStamps{segment_id}; % start and stop timestamp in µs, 2 x averages

The information about the averages is divided up into 4 fields:

  • AverageDataMean: Sample-wise average over the cutouts
  • AverageDataStdDev: Sample-wise standard deviation over the cutouts
  • AverageDataCount: Number of averaged cutouts
  • AverageDataTimestamps: Start and stop timestamps in µs for the time window in which cutouts were averaged. The timestamps of the individual cutouts is not stored.

The data for each recording phase is stored in its own dimension in these fields. For example, the averaged data for the 3rd recording phase can be accessed as:

mean_data(:,3); % sample-wise average for the 3rd recording phase
std_data(:,3); % sample-wise standard deviation for the 3rd recording phase
count_data(3): % number of averaged cutouts in the 3rd recording phase
segment_timestamps(:,3); % start and stop timestamp for cutout averaging in the 3rd recording phase

Channel Positions in SegmentStreams

It is important to note that the sequence of segment entities does not necessarily correspond to the sequence of channels in an AnalogStream. It also doesn't need to correspond to the sequence of ChannelIDs of channels in an AnalogStream. Instead, in order to identify the AnalogStream channel responsible for creating the segments, it is necessary to check the SourceInfoChannel field:

channel_id = cutout_data.Recording{1}.SegmentStream{1}.SourceInfoChannel.ChannelID(segment_id);
well_id = cutout_data.Recording{1}.SegmentStream{1}.SourceInfoChannel.GroupID(segment_id);
channel_label = cutout_data.Recording{1}.SegmentStream{1}.SourceInfoChannel.Label(segment_id);

Event Streams

The EventStreams in .mwc and .mwd files contain timestamps of state changes on digital input channels, timestamps of electrical and optical stimulation pulses and timestamps that encode start and stop ofcertain phases in the experiment. These events are divided up into up to 4 different EventStreams which can be distinguished by their Label:

  • Experiment State Changes_...: This stream contains start and stop timestamps for (repeated) phases within the experiment, such as Control, Test, Pipet, WashIn, and many more. For each of these, a Start and a Stop event entity exists with a unique EventID. Check the Info field for a list of entities, their EventIDs and Labels. Depending on the experiment, only a subset of these entities will be active during the experiment. A typical sequence of events for an experiment with a Control and a Test phase with activated Pipetting and Wash-In could look like this:

ExperimentStart, ControlStart, RecordingAnalysisStart, RecordingAnalysisStop, PostDoseAnalysisStart, PostDoseAnalysisStop, ControlStop, ChangeDoseStart, ChangeDoseStop, PipetStart, PipetStop, WashInStart, WashInStop, TestStart, RecordingAnalysisStart, RecordingAnalysisStop, PostDoseAnalysisStart, PostDoseAnalysisStop, TestStop, ExperimentStop

  • Applied Dilution Series_...: This stream contains start and stop timestamps for each phase in the recording (for historical reasons, this is called the Dilution Series). For each recording phase a Start and Stop entity exists in the Info structure and its Label contains the name of the recording phase. In all files a Control Start and a Control Stop entity should be present. If the experiment was run to completion, there should be exactly one event for each event entity. The Stop timestamp of one recording phase conincides with the Start timestamp of the next phase.

  • Digital Events_...: This stream contains digital events received via the Digital-In ports of the IFB, digital events sent via the Digital-Out ports of the IFB and events generated by the electrical stimulator. Please check the Labels of the entities in the Info field to find out their purpose. Again, a Start and Stop entity exists for each event source. The Digital In and Digital Out entities denote rising (Start) and falling (Stop) flanks on the respective Digital-In or Digital-Out port. The Stimulator entities however denote different hardware state changes necessary for each electrical stimulation pulse:

    • Stimulator Marker Signal: The time window when the Sync-Out signal of the stimulator is active
    • Stimulator Selection Switch: The time window in which the stimulation electrode is connected to the stimulator
    • Stimulator Activation Switch: The time window in which the stimulator is active
    • Stimulator Blanking: The time window in which the blanking circuit is active. Please be aware that the actual duration for which the signal value is kept constant is longer than this duration. The total blanked duration depends on the stimulation mode (Voltage or Current controlled) and whether the signal was recorded from a stimulated or a non-stimulated channel.
  • LED Stimulation Events_...: As its Label suggests, this stream only exists if LED stimulation was used during the experiment. It encodes the start timestamps and duration of each pulse on one of the LEDs. The Label field of the event entities describes the LED color and the SourceChannelLabels contains a comma-separated list of stimulated well labels.

Channel Positions

The order of channels in the ChannelData matrix of AnalogStreams and the ChannelID or SegmentID does not directly correspond to the channel location - neither to its well nor to its position in the well. Instead, they are a reflection of the internal order of the channels during data acquisition.

Identifying the Well for a Channel

The well is encoded as a numeric ID in the GroupID property of a channel. GroupID 0 refers to well A1, GroupID 1 to well A2, and so on. The precise assignment depends on the plate type:

  • 24 well plate:
Wells Group IDs
A1 - A6 0 - 5
B1 - B6 6 - 11
C1 - C6 12 - 17
D1 - D6 18 - 23
  • 96 well plate:
Wells Group IDs
A1 - A12 0 - 11
B1 - B12 12 - 23
C1 - C12 24 - 35
D1 - D12 36 - 47
... ...
% channel_index is the row index in the ChannelData matrix
group_id = raw_data.Recording{1}.AnalogStream{1}.Info.GroupID(channel_index);

% 24 well plates
rows_24 = reshape(repmat(('A':'D')',1,6)',1,24);
wells_24 = arrayfun(@(x)({[rows_24(x) num2str(mod(x-1,6)+1)]}), 1:24);
well = wells_24(group_id + 1)

% 96 well plates
rows_96 = reshape(repmat(('A':'H')',1,12)',1,96);
wells_96 = arrayfun(@(x)({[rows_96(x) num2str(mod(x-1,12)+1)]}), 1:96);
well = wells_96(group_id + 1)

Channel Position in the Well

The position of the channel in the well is given by the Label property:

position = raw_data.Recording{1}.AnalogStream{1}.Info.Label(channel_index);

The Label is a string with two digits, the first one representing the (1-based) column, the second one the (1-based) row. So Label == '32' defines the channel position as the 3rd column, 2nd row within its well.

Getting the Data for all Channels in a Well

In order to retrieve the data for all channels in a well, first identify the corresponding GroupID for the well (see above), then select the appropriate rows in the ChannelData matrix:

% For example, we are looking for the channels in well B2 of a 96 well plate:
group_id_B2 = 13; % For a 24 well plate, the GroupID of B2 would be 7.
row_index_B2 = find(data.Recording{1}.AnalogStream{1}.Info.GroupID == group_id_B2);
% now, we can load just these channels from the file 
cfg = [];
cfg.channel = [min(row_index_B2) max(row_index_B2)];
data_stream_B2 = data.Recording{1}.AnalogStream{1}.readPartialChannelData(cfg);
data_B2 = data_stream_B2.ChannelData;
% or, if the data has already been loaded, access the ChannelData matrix
data_B2 = data.Recording{1}.AnalogStream{1}.ChannelData(row_index_B2,:);

Getting the Data for a Specific Channel

In order to retrieve the data for a channel in a particular position of one well, we need to identify the corresponding GroupID for the well (see above), as well as the channel Label and then find the correct row index in the ChannelData matrix.

% For example, we are looking for the channel in position 23 (2nd column, 3rd row) in well B2 of a 96 well plate:
group_id_B2 = 13; % For a 24 well plate, the GroupID of B2 would be 7.
label_23 = '23'; % The channel label is always a string of two digits, first the (1-based) column index, then the (1-based) row index.
row_index_B2_23 = find(data.Recording{1}.AnalogStream{1}.Info.GroupID == group_id_B2 & strcmp(data.Recording{1}.AnalogStream{1}.Info.Label, label_23));
data_B2_23 = data.Recording{1}.AnalogStream{1}.ChannelData(row_index_B2_23,:);

Stimulation Channels and Timepoints

For finding the stimulation timepoints, please check the Event Streams section above, because these timestamps are stored in the Digital Events and the LED Stimulation Events EventStreams.

Getting the stimulated channels and wells, however, is a bit more tricky. For LED stimulation, the stimulated wells for each color are SourceChannelLabels in the Info field of the LED Stimulation Events EventStream. For electrical stimulation, the stimulation electrodes are not stored in the HDF5 file. Instead, they can to be retrieved from either the .mwr or .mws file. The stimulation electrodes are defined in XML elements:

...
<StimulatorSettings>
  <StimulusPattern>
    <StimulationElectrodes>
      <DefinedPattern>
        <ChannelID>ID0</ChannelID>
        ...
      </DefinedPattern>
      <InvertedPattern>
        <ChannelID>ID1</ChannelID>
        ...
      </InvertedPattern>
    <StimulationElectrodes>
  <StimulusPattern>
<StimulatorSettings>
...

DefinedPattern lists all stimulation electrodes assigned to the "red", regular stimulator in Multiwell-Screen, whereas the InvertedPattern contains all stimulation electrodes assigned to the "green", inverted stimulator (active for bipolar stimulation). The numeric IDs stored in the <ChannelID> elements are the ChannelIDs listed in the Info fields of AnalogStreams. Please see the section Channel Positions on how to retrieve the channel position from a ChannelID.

Recording Phases

A Multiwell-Screen recording consists of 1 or more recording phases. These phases are tied to the Series definition for the experiment. The first recording phase always corresponds to the Control phase in the Series and if a Wash Out phase has been defined in the Series, it always corresponds to the last recording phase. How the rest of the Series is handled depends on whether the Dosing Protocol has been defined as Cumulative or as Single Dose per Well. In the former case, the Series entries are recorded one after the other, so each entry gets its own recording phase. For the Single Dose per Well dosing, all are recorded at the same time, so there is only a single recording phase.

For the duration of each recording phase, a continuous segment of raw data is acquired. The totality of the acquired raw data for each stream is stored in the HDF5 file as a single block of data in the ChannelData field of the AnalogStream. In order to find out, which parts of the ChannelData belong to which recording phase, we need to check the ChannelDataTimeStamps field that stores for each sample its timestamp in µs relative to the start of the recording. Most of the time there will be a pause between one recording phase and the next, so the start index of a recording phase can easily be identified by a jump in the timestamp from one sample to the next:

sample_timestamps = raw_data.Recording{1}.AnalogStream{1}.ChannelDataTimeStamps;
tick = data.Recording{1}.AnalogStream{1}.Info.Tick(1); % time difference between two samples
recording_phase_starts = [1 find(diff(sample_timestamps) > tick)];

The start and stop timestamps of recording phases are also stored in Event Streams. One can use either the entries in the Experiment State Changes_... stream:

for stream = raw_data.Recording{1}.EventStream
    label = stream{1}.Label;
    if strncmp(label, 'Experiment State Changes', length('Experiment State Changes'))
        state_change_stream = stream{1};
    elseif strncmp(label, 'Applied Dilution Series', length('Applied Dilution Series'))
        series_stream = stream{1};
    end
end

recording_start_events = state_change_stream.Events{find(strcmp(state_change_stream.Info.Label, 'RecordingStart'))};
recording_start_timestamps = recording_start_events(1,:); % start timestamps for each recording phase in µs

The label of each recording phase can be read from the Applied Dilution Series_... stream:

valid_events = find(cellfun(@(x)(length(x) > 0), series_stream.Events));
valid_timestamps = cellfun(@(x)(x(1)), series_stream.Events(valid_events));
recording_phase_labels = series_stream.Info.Label(valid_events);
recording_phase_labels = recording_phase_labels(1:2:end); % throw away the 'Stop' events
start_timestamps = valid_timestamps(1:2:end); % throw away the 'Stop' events
[start_timestamps, I] = sort(start_timestamps); % sort by timestamps
recording_phase_labels = recording_phase_labels(I);
recording_phase_labels = regexprep(recording_phase_labels, '(.*) Start', '$1'); % remove the ' Start'

.mwc Files Created by Multiwell-Analyzer

The Multiwell-Analyzer generates a new .mwc file when an analysis is saved via the "Generate Report" button. As long as only a single experiment was analyzed in the Multiwell-Analyzer, this file has the same structure as the .mwc files generated by Multiwell-Screen.

However, if multiple experiments had been merged in the Multiwell-Analyzer and analyzed together, the .mwc file needs to store all cutouts from the different experiments. This is done by creating multiple Recordings in the .mwc file, one for each experiment. Each of these recordings will contain a single segments stream and the file name of the original experiment is stored as the Label of the Recording:

% mwc file generated by the Multiwell-Analyzer with multiple merged experiments
cutout_data = McsHDF5.McsData('PATH/TO/THE/CONVERTED/ANALYZER/MWC/FILE_mwc.h5');
number_of_experiments = length(cutout_data.Recording);
for i = 1:number_of_experiments
    disp(['Recording ' num2str(i) ': ' cutout_data.Recording{i}.Label])
end
⚠️ **GitHub.com Fallback** ⚠️