Multiwell MEA Data - multichannelsystems/McsMatlabDataTools GitHub Wiki
- Analog Streams in .mwd files
- Segment Streams in .mwc files
- Event Streams
- Channel Positions
- Stimulation Channels and Timepoints
- Recording Phases
- .mwc Files Created by Multiwell-Analyzer
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');
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
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);
.mwc files contain a SegmentStream, but its contents depend on whether the data was acquired in Cardio or Neuro mode.
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);
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
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);
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 asControl
,Test
,Pipet
,WashIn
, and many more. For each of these, aStart
and aStop
event entity exists with a uniqueEventID
. Check theInfo
field for a list of entities, theirEventID
s andLabel
s. 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 aStart
andStop
entity exists in theInfo
structure and itsLabel
contains the name of the recording phase. In all files aControl Start
and aControl Stop
entity should be present. If the experiment was run to completion, there should be exactly one event for each event entity. TheStop
timestamp of one recording phase conincides with theStart
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 theLabel
s of the entities in theInfo
field to find out their purpose. Again, aStart
andStop
entity exists for each event source. TheDigital In
andDigital Out
entities denote rising (Start
) and falling (Stop
) flanks on the respective Digital-In or Digital-Out port. TheStimulator
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 itsLabel
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. TheLabel
field of the event entities describes the LED color and theSourceChannelLabels
contains a comma-separated list of stimulated well labels.
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.
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)
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.
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,:);
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,:);
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 ChannelID
s listed in the Info
fields of AnalogStreams. Please see the section Channel Positions on how to retrieve the channel position from a ChannelID
.
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'
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