Ceed-MCS data merging

During an experiment both Ceed and MCS record their respective data to separate files. Ceed records the projected frames and all its shapes to an H5 file, while MCS records its electrode and digital data to a proprietary file format that can be exported to H5.

This module merges and temporally aligns the Ceed frames to the MCS electrode data and creates a single H5 file containing the data from both files. This new file can be read with CeedDataReader

Temporal alignment protocol

As explained in DataSerializerBase, during a Ceed experiment Ceed generates upto 24-bits per projector frame that is recorded into the MCS data stream. Following is a basic summary as it relates to merging.

Each frame contains upto 24-bits of data labeling the frame. There’s a clock that alternates high/low for each frame. There’s a short counter of a few bits that increments by one for each displayed frame (not including dropped frames) and overflows back to zero. This allows us to detect bad frames/data. And finally there’s a large n-bit counter int broken up into groups of a few bits, and each frame contains the next group of these bits until the full int was sent so it can start on the next counter value.

E.g. if it’s 32-bit and we have 2 bits per frame, then naively it takes 16 frames to send the full int, broken up into groups of two. However, each set of bits is sent twice, the bits followed by their one’s complement. So it takes 32 frames to send the full int as follows: b0-1, b0-1_comp, b2-3, b2-3_comp, …, b30-31, b30-31_comp. This allows us to further detect data corruption.

So, in an experiment as we send the global counter, starting from one it may look as follows. At frame zero we start sending the counter of 1. This takes 32 frames to send. So, at frame 32 when we’re done with the int we start sending the next current value of the counter - 33. This is sent over the next 32 frames etc.

Handshake

There’s one further caveat. At the start of the experiment Ceed sends some handshaking data before starting to send the counter ints. Specifically, the first int value is the number of ints in the subsequent handshake. The subsequent handshake is a sequence of ints, composed of bytes that are a unique signature of that experiment. This is how we can later locate a Ceed experiment in the MCS data that contains multiple experiments.

Once the handshake is complete, Ceed starts sending the counter values of the frames as explained above. However, unlike the counter ints that are sent along with their complement, for the initial length int and handshake data the groups are sent twice. I.e. b0-1, b0-1, b2-3, b2-3, …, b30-31, b30-31. This allows us to locate the start of an experiment in the MCS data.

Sub-frames

When Ceed is running in quad mode where each frame has sub-frames (e.g. quad4x each frame is actually 4 frames), the digital IO is only updated for each major frame and it’s the same for all the frame’s sub-frames. So although Ceed records data for each sub frame, the serializer digital IO is only updated for each major frame. So the counter increments with the number of sub-frames times the number of frames it took to transmit the int - because that’s how may frames completed while sending a int.

However, MCS can only see when a major frame changes because that’s when the digital IO changes. So we have to infer and break each main frame into sub-frames to find where in the electrode data the sub-frames were rendered.

Dropped frames

As explained in Frame rate and dropped frames, Ceed will sometimes drop a frame. The Ceed frame counter (denoting the time as count / frame rate) still increments for the frame, but the frame is not displayed. Dropping a frame has no effect on the digital IO and it is continuous like normal (we don’t update the IO for the dropped frame). The only visible difference is that the counter int will have incremented by a larger values than normal when we send the next counter int after finishing the current int. That’s is how MCS can detect a dropped frame, in addition to observing that a Ceed frame went too long because the IO is not updated at the normal rate, like the TeensyFrameEstimation detection approach.

Parsing Ceed data

Ceed logs all its data into the H5 file. When parsing an experiment using CeedDigitalData, it takes the digital IO data logged by Ceed, including the data for all the sub-frames and dropped frames, and it extracts the frame clock, short and long counter values for each frame. This gives us the expected value for each of these three signals for every frame.

Parsing MCS data

Unlike parsing the Ceed data, the MCS data contains data for multiple experiments so when parsing we first have to split them into experiments and then we parse each experiment. Furthermore, each Ceed main frame is approximately 1 / 120 seconds long. MCS generally samples at multiple kHz. So MCS will typically read many duplicate values for each Ceed frame. This needs to be de-duplicated before parsing.

Once the MCS has been de-duplicated and split into experiments, we can parse it like we parse the Ceed data and get the corresponding counter value for each detected frame.

When parsing the MCS data we assume that there’s a pause of at least 10 frames between experiments, during which the clock is constant. This simplifies locating the start of experiments. Additionally, we cannot parse experiments which are shorter than a couple of frames.

Merging

When we have a Ceed experiment parsed both for the Ceed and MCS data, we can then relate and find exactly where in the electrode data a Ceed frame is displayed. This is returned by CeedMCSDataMerger.get_alignment() and is stored in the new file.

Typically we’d search in the parsed MCS data for the unique experiment’s handshake value as provided by Ceed’s data. However, if an experiment was stopped early and is too short to have sent the full handshake. So multiple experiments could potentially share the same handshake, if the initial bytes were the same. In that case the user would need to disambiguate between them somehow.

Merging example

The typical steps for merging is to instantiate a CeedMCSDataMerger with the Ceed and MCS H5 files. Then CeedMCSDataMerger.read_mcs_data(), CeedMCSDataMerger.read_ceed_data(), and CeedMCSDataMerger.parse_mcs_data().

Typically a Ceed and MCS file will contain multiple experiments. Using CeedMCSDataMerger.get_experiment_numbers() you’d get all the experiments in the Ceed file, ignoring any experiment which are not in the MCS file, or bad experiments. For each of the experiments we’d CeedMCSDataMerger.read_ceed_experiment_data() and CeedMCSDataMerger.parse_ceed_experiment_data() for that experiment.

Once parsed we can get the alignment with CeedMCSDataMerger.get_alignment() and save it in a dict. Finally, once we have the alignment for all the experiments we can create the output file with all the data using CeedMCSDataMerger.merge_data().

Following is a worked example:

import logging
ceed_file = 'ceed_data.h5'
mcs_file = 'mcs_data.h5'
output_file = 'ceed_mcs_merged.h5'
notes = ''
notes_filename = None  # no notes
debug = False

# create merge object
merger = CeedMCSDataMerger(
    ceed_filename=ceed_file, mcs_filename=mcs_file, debug=debug)

# read ceed and MCS data and parse MCS metadata
merger.read_mcs_data()
merger.read_ceed_data()
merger.parse_mcs_data()

print(merger.get_skipped_frames_summary_header)

alignment = {}
# get alignment for all interesting experiments
for experiment in merger.get_experiment_numbers(ignore_list=[]):
    # read and parse the data
    merger.read_ceed_experiment_data(experiment)
    merger.parse_ceed_experiment_data()

    # try to get the Ceed-MCS alignment
    try:
        align = alignment[experiment] = merger.get_alignment(
            ignore_additional_ceed_frames=True)
        # print experiment summary, see method for column meaning
        print(merger.get_skipped_frames_summary(align, experiment))
    except Exception as e:
        print(
            "Couldn't align MCS and ceed data for experiment "
            "{} ({})".format(experiment, e))
        if debug:
            logging.exception(e)

# finally create new file from alignment and existing files
merger.merge_data(
    output_file, alignment, notes=notes, notes_filename=notes_filename)
class ceed.analysis.merge_data.CeedMCSDataMerger(ceed_filename, mcs_filename, debug=False)

Bases: object

Merges a MCS file containing electrode and digital data recorded during a Ceed experiment into a new Ceed file that contains both the original Ceed and MCS data.

See Parsing Ceed data for an example.

ceed_global_config = {}

Dict containing the global app_settings found in the Ceed file.

This the global app config as present when the file was last saved as opposed to the app settings of a specific experiment as stored in ceed_config_orig.

ceed_version: str = ''

The Ceed version string read from the Ceed file.

ceed_config_orig = {}

Dict of the app_settings used for the opened experiment.

It is updated for each experiment read with read_ceed_experiment_data().

ceed_data = {}

Dict with the raw Ceed data for the opened experiment.

It is updated for each experiment read with read_ceed_experiment_data().

ceed_data_container: Optional[ceed.analysis.merge_data.CeedDigitalData] = None

The CeedDigitalData used to parse the Ceed data.

mcs_dig_data: numpy.ndarray = None

The raw array containing the digital (16-bits) data recorded by MCS at the same frequency and time as the electrode data.

mcs_dig_config = {}

MCS configuration data read from the MCS H5 file.

mcs_data_container: Optional[ceed.analysis.merge_data.MCSDigitalData] = None

The MCSDigitalData used to parse the MCS data.

ceed_filename: str = ''

The H5 filename of the file containing the Ceed data.

mcs_filename: str = ''

The H5 filename of the file containing the MCS data.

debug = False

Whether to print full traces when alignment or other merging related events fail. By default it just drops the failed alignment and tries the next alignment.

property n_sub_frames

The number of sub-frames in each Ceed frame (e.g. for quad modes it’s 4 or 12).

It’s specific to the current experiment.

get_experiment_numbers(ignore_list=None) List[str]

Returns list of experiments in the ceed_filename.

ignore_list, if provided, is a list of experiment numbers to skip.

Each experiment name is a number, represented as a str.

This method is very light and does not load the data, so it can be called before read_ceed_data().

read_mcs_data()

Reads the MCS digital data and metadata from the file and updates the corresponding properties.

read_ceed_data()

Reads the overall Ceed metadata from the file and updates the corresponding properties.

After loading the overall data, use read_ceed_experiment_data() to load a specific experiment.

read_ceed_experiment_data(experiment: Union[str, int])

Reads the data and metadata for the experiment and updates the corresponding properties.

Parameters

experiment – The experiment to read. It’s a number (or string representing the number) for the experiment, as would be returned by get_experiment_numbers().

create_or_reuse_ceed_data_container() ceed.analysis.merge_data.CeedDigitalData

Ensures that the ceed_data_container is ready to be used to parse the data.

create_or_reuse_mcs_data_container() ceed.analysis.merge_data.MCSDigitalData

Ensures that the mcs_data_container is ready to be used to parse the data.

parse_ceed_experiment_data()

Parses the Ceed experiment data previously read with read_ceed_experiment_data().

parse_mcs_data(find_start_from_ceed_time: bool = False, pre_estimated_start: float = 0, estimated_start: Optional[datetime.datetime] = None)

Parses the MCS data previously read with read_mcs_data().

Parameters
  • find_start_from_ceed_time – Whether to locate the Ceed experiment in the MCS data using a Ceed time estimate or by finding the handshaking pattern in the digital data. The time based approach is not well tested, but can tuned if handshaking is not working.

  • estimated_start – The estimated time when the Ceed experiment started. You can get this with self.ceed_data['start_t'].

  • pre_estimated_start – A fudge factor for estimated_start. We look for the experiment by pre_estimated_start before estimated_start.

get_alignment(ignore_additional_ceed_frames=True) numpy.ndarray

After reading and parsing the Ceed and MCS data you can compute the alignment between the current Ceed experiment and the MCS file.

This returns an array of indices, where each item in the array is an index into the raw MCS electrode data and corresponds to a Ceed frame. The index is the start index in the electrode data corresponding to when the ith Ceed frame was beginning to be displayed.

Parameters

ignore_additional_ceed_frames – Whether to chop OFF the Ceed data to the MCS data. I.e. if There are more Ceed frames than recorded in the MCS data, e.g. because MCS was stopped recording while Ceed was still projecting frames, then those final Ceed frames will not be recorded by MCS in the digital stream. By default we will drop these remaining Ceed frames as not rendered, but if False we will raise an exception instead.

estimate_skipped_frames(ceed_mcs_alignment)

Given an alignment from get_alignment(), it returns information about the Ceed frames to help compute whether Ceed dropped any frames, as observed by the recorded MCS data.

See get_skipped_frames_summary() and compute_long_and_skipped_frames().

get_skipped_frames_summary(ceed_mcs_alignment, experiment_num)

Given an alignment from get_alignment() and the experiment number, it returns a string that when printed will display some information about any dropped Ceed frames as well as overall information about the alignment.

There are 8 numbers in the summary.

  1. The experiment number.

  2. The start index index in the MCS electrode data corresponding to the experiment.

  3. The end index in the MCS electrode data.

  4. The number of electrode data samples during the experiment.

  5. The number of Ceed frames that went long (i.e. the number of frames whose data was not the next frame, but an older frame that the GPU displayed again because the CPU didn’t update in time.

  6. The number of major frames Ceed dropped.

  7. In parenthesis, the number of total frames, including sub-frames Ceed dropped.

  8. The maximum delay in terms of frames between Ceed repeating a frame because the CPU was too slow, until Ceed realized it needs to drop a frame to compensate. I.e. this is the largest number of bad frames Ceed ever displayed before correcting.

merge_data(filename: str, alignment_indices: Dict[str, numpy.ndarray], notes='', notes_filename=None)

Takes the Ceed and MCS data files and copies them over to a new Ceed H5 file. It also adds the alignment data for all the experiments as computed with get_alignment() and optional notes and stores it in the new file.

Parameters
  • filename – The target file to create.

  • alignment_indices – Dict whose keys are experiment numbers and whose values is the alignment for that experiment as computed by get_alignment().

  • notes – Optional notes string to be added to the new file.

  • notes_filename – A optional filename, which if provided contains more notes that is appended to notes and added to the file.

exception ceed.analysis.merge_data.AlignmentException

Bases: Exception

Raised if the Ceed-MCS data cannot be aligned.

class ceed.analysis.merge_data.BitMapping32(bits: List[int])

Bases: object

Maps 32-bit integers to other 32-bit integers, assuming each bit of the input is in the output, but perhaps at a different index.

E.g. if we have a 32-bit int whose bits at indices 5 and 8 are exchanged, or if bit 5 should be moved to bit 0, and all other bits ignored, this can achieve it.

As input it takes a list of indices that are less than 32, the bit index represented by each item in the list is mapped to the bit-index represented by the index of the item in the input list.

E.g. to shift all bits upto 10 to the right by two bits (chopping off the first two bits which don’t get mapped):

>>> mapping = BitMapping32([i + 2 for i in range(10)])
>>> data = np.array([0, 1, 2, 4, 8, 16], dtype=np.int32)
>>> mapping.map(data)
array([0, 0, 0, 1, 2, 4], dtype=uint32)

This mapped bits 0-1 to nothing (they are not on the list), bit 2 to 0, 3 to 1 etc.

Note

This computes the mapping as 4 16-bit int arrays, each containing 2 ^ 16 items (stored internally). This could be aproblem on low memory devices.

l_to_l: numpy.ndarray = None

Maps the lower 16-bits of the input to the lower 16-bits of the output.

h_to_l: numpy.ndarray = None

Maps the upper 16-bits of the input to the lower 16-bits of the output.

l_to_h: numpy.ndarray = None

Maps the lower 16-bits of the input to the upper 16-bits of the output.

h_to_h: numpy.ndarray = None

Maps the upper 16-bits of the input to the upper 16-bits of the output.

map(data: numpy.ndarray) numpy.ndarray

Maps the 32-bit integer input data to the 32-bit integer output data according to the given mapping. See BitMapping32.

class ceed.analysis.merge_data.DigitalDataStore(short_count_indices, count_indices, clock_index, counter_bit_width, debug=False)

Bases: object

The class that can parse the MCS and Ceed upto 24-bit data pattern associated with each Ceed frame.

It takes the recorded data and parses and decomposes it into the clock bit, the short_count_indices bits, and the count_indices.

This lets us then compare the decomposed data between the Ceed data output through the Propixx system and recorded by Ceed for each frame and the MCS data recorded through the Propixx-MCS data link. Then we can align each frame to its location in the MCS data by comparing them (especially since Ceed sends frame numbers over the counter bits).

The indices passed in to __init__ should have first been mapped through projector_to_aquisition_map when creating the class, if this is the MCS store.

short_count_data: numpy.ndarray = None

The short counter data parsed and mapped so it’s represented as a correct number that can just be read for its value.

count_data: numpy.ndarray = None

The long counter data parsed and mapped so it’s represented as a correct number that can just be read for its value.

Keep in mind, that although it has been mapped to the lower n-bits, the long counter is a counter_bit_width integer split over multiple frames so this value is not quite a complete number.

clock_data: numpy.ndarray = None

The clock data parsed and mapped so it is represented as either zero or one, depending on the state of the clock.

data: numpy.ndarray = None

The raw input data that contains the clock, short, and long counter data across its various bits.

When frames are dropped by Ceed, the DataSerializerBase will be correctly incremented. This means the long counter will skip those frames. But the data will not be corrupt because we split the long counter over multiple frames and we still correctly send each count even if frames are globally skipped. The only difference is that when the next count value is split over the frames, that value will have incremented more than normal.

The short counter’s behavior doesn’t change when frames are dropped because it increments for each frame displayed.

expected_handshake_len: int = 0

The number of bytes required to send the full handshake as described in DataSerializerBase.

The handshake always begins with the number of ints of size counter_bit_width that will make up the handshake (see Temporal alignment protocol). This stores the number of bytes and is computed from that (assuming we were able to parse the first handshake int).

It is automatically set.

handshake_data: bytes = b''

The parsed handshake (config_bytes) data sent at the start of each experiment as described in get_bits().

It is automatically parsed from the data.

counter: numpy.ndarray = None

The long counter value associated with each frame. This is provided in the Ceed data, since Ceed knows this as it records it for each frame.

short_count_indices: List[int] = None

See short_count_indices.

count_indices: List[int] = None

See count_indices.

clock_index: int = None

See clock_idx.

counter_bit_width: int = 32

The number of bits in the counter sent over the long counter. See counter_bit_width.

debug = False

Whether to print debug traces or ignore alignment issues. Automatically set from CeedMCSDataMerger.debug.

short_map: ceed.analysis.merge_data.BitMapping32 = None

The mapping that maps the short_count_indices of the recorded data to a contiguous bit pattern representing the number.

I.e. the recorded data could store the n-bit counter (e.g. 4-bits) over some random bit arrangement (e.g. bits 5, 7, 2, 9). This will map it to the lower n-bits in the correct order (e.g. bits 0-3) so the value can just be read off.

count_map: ceed.analysis.merge_data.BitMapping32 = None

Same as short_map, but for count_indices.

compare(short_count_indices, count_indices, clock_index)

Compares the similarly named properties of this instance to thee given values and returns if they are the same.

If it is the same, we assume we can just re-use this instance and its mappings (which are expensive to compute).

property n_parts_per_int

The number of parts into which a complete int of the long counter (of size counter_bit_width) will have to be split into so we can send it over the bits available for the long counter.

property n_bytes_per_int

The number of bytes (rounded down) required to represent the long counter counter_bit_width bits.

get_count_ints(count_data: numpy.ndarray, contains_sub_frames: bool, n_sub_frames: int)

Takes raw count_data, bool indicating whether the count data contains sub-frames data as well as the number of sub-frames and it parses it into complete counter data.

In quad mode, Ceed can generate 4 or 12 frames for each GPU frame. However, the count data may contain data for each whole frame only, or for all frames, including sub-frames. contains_sub_frames is True for the latter and n_sub_frames indicates the number of sub-frames per frame (1 if there are no sub-frames). See Sub-frames.

It returns a three tuple, (count, count_2d, count_inverted_2d). As explained in Temporal alignment protocol, each long counter int is sent broken across multiple frames, in addition to being sent inverted (except for handshake that is not inverted). So each int is sent in parts as k1, k1_inv, k2, k2_inv, …, kn, kn_inv.

Defining M = len(count_data) // n // 2 (the number of whole ints). count is the collated up-to 64-bit int that contains all the 1..n components and is size M. count_2d is a 2D array of Mxn containing all the parts. count_inverted_2d similarly of size Mxn contains only the inverted parts.

check_counter_consistency(count_data, count_2d, count_inverted_2d, n_handshake_ints, contains_sub_frames, n_sub_frames, exclude_last_value)

Takes the parsed data and verifies that the data is consistent and doesn’t contain errors as best as it can.

get_handshake(count: numpy.ndarray, contains_sub_frames, n_sub_frames)

Takes the count data as returned by get_count_ints() and extracts the handshake bytes from it.

It returns a 4-tuple of (handshake_data, handshake_len, n_handshake_ints, n_config_frames).

handshake_data is like handshake_data, handshake_len is like expected_handshake_len, n_handshake_ints is the number of ints in the handshake, n_config_frames is the total number of configuration frames including the initial count and the handshake.

If handshake is incomplete it returns empty bytes and all zeros for the numbers.

check_missing_frames(short_count_data, contains_sub_frames, n_sub_frames)

Given the short counter data it checks that no frames was missed since it should be contiguous.

class ceed.analysis.merge_data.MCSDigitalData(short_count_indices, count_indices, clock_index, counter_bit_width, debug=False)

Bases: ceed.analysis.merge_data.DigitalDataStore

Parses the MCS data recorded into the MCS file.

In addition to parsing the 24-bits for each frame, for the MCS we first need to collapse the data because it’s sampling at a much higher rate than the Ceed (projector) is generating data (see reduce_samples()).

Finally, as opposed to the Ceed data that is separately stored for each experiment, the MCS data contains data for many experiments, which needs to be individually parsed (see Parsing MCS data).

The per-experiment parsed data is stored in experiments.

data_indices_start: numpy.ndarray = None

An array of indices into the raw DigitalDataStore.data indicating the start of each (potential) projector frame. We use the clock signal to compute this.

This lets us find align the start of a frame with e-phys data for the frame.

data_indices_end: numpy.ndarray = None

The same as data_indices_start, but it’s the index of the last sample of the (potential) frame.

It is the same length as data_indices_start, because each start index has a end index, even if it’s single sample long, in which case they point to the same index.

experiments: Dict[bytes, List[Tuple[numpy.ndarray, numpy.ndarray, int, numpy.ndarray, numpy.ndarray]]] = {}

A dict of potential experiments found in the MCS parsed DigitalDataStore.data.

Each experiment is uniquely identified by the handshake key Ceed sends at the start of that experiment. This key is parsed from DigitalDataStore.data for each experiment found in the data, ignoring experiments that are too short (a couple of frames).

Typically the handshake is 16 bytes of data. When pad_to_stage_handshake is False or if the experiment was ended early, an experiment could still be short enough so that the full handshake was not transmitted. So, e.g. you may have two experiments whose unique keys are “ABCD” and “ABGH”, respectively, but only “AB” was transmitted before the experiment ended.

Therefore, each key in the dict maps to a list of experiments, in case a single key (“AB”) maps to multiple experiments in the MCS data. The user would then need to disambiguate between them.

Each item in the value is a 5-tuple of (start, end, handshake_len, count_data, count) specific to the experiment. start and end are the slice from data_indices_start and data_indices_end for the experiment. count_data is the slice from DigitalDataStore.count_data for the experiment and count is the DigitalDataStore.get_count_ints() parsed count from that. handshake_len is the the corresponding value from DigitalDataStore.get_handshake(), parsed from count.

Note

This is only populated for experiments created by Ceed versions greater than 1.0.0.dev0.

parse(ceed_version, data, t_start: datetime.datetime, f, find_start_from_ceed_time=False, estimated_start: Optional[datetime.datetime] = None, pre_estimated_start: float = 0)

Parses the MCS data into the individual experiments.

Parameters
  • ceed_version – The file’s Ceed version string.

  • data – the upto 24-bit digital data recorded by MCS at the MCS sampling rate (>> the Ced rate) along with the electrode data. It is saved as DigitalDataStore.data.

  • t_start – The date/time that corresponds to the first data sample index in data. That’s when the MCS data recording started.

  • f – The MCS recording sampling rate.

  • find_start_from_ceed_time – Whether to locate the Ceed experiment in the MCS data using a Ceed time estimate or by finding the handshaking pattern in the digital data. The time based approach is not well tested, but can tuned if handshaking is not working.

  • estimated_start – The estimated time when the Ceed experiment started.

  • pre_estimated_start – A fudge factor for estimated_start. We look for the experiment by pre_estimated_start before estimated_start.

reduce_samples(t_start: datetime.datetime, f, find_start_from_ceed_time=False, estimated_start: Optional[datetime.datetime] = None, pre_estimated_start: float = 0)

Reduces the data from multiple samples per-frame, to one sample per frame, using the clock. See parse().

Ceed (projector) generates data at about 120Hz, while MCS records data at many thousands of hertz. So each Ceed frame is saved repeatedly over many samples. This collapses it into a single sample per frame.

We can do this because Ceed toggles the clock for each frame.

Once reduced, it extracts the clock, short and long counter data and saves them into the class properties ready for use by the super class methods.

parse_experiments()

Given the data that has been reduced and split into the components with reduce_samples(), it extracts the individual experiments into experiments.

class ceed.analysis.merge_data.CeedDigitalData(short_count_indices, count_indices, clock_index, counter_bit_width, debug=False)

Bases: ceed.analysis.merge_data.DigitalDataStore

Parses the Ceed data for an individual experiment as recorded in the Ceed H5 file.

parse(ceed_version, frame_bits: numpy.ndarray, frame_counter: numpy.ndarray, start_t: datetime.datetime, n_sub_frames: int, rendered_frames: numpy.ndarray) None

Parses the Ceed data into the class properties from the raw data.

Parameters
  • ceed_version – The Ceed version string.

  • frame_bits – Array containing the 24 bits sent to MCS for each Ceed frame.

  • frame_counter – Array containing the global Ceed counter corresponding to each frame. This array includes frames that are not actually rendered but dropped by Ceed.

  • start_t – The date/time when the experiment started.

  • n_sub_frames – The number of sub-frames in each frame (e.g. for quad modes it’s 4 or 12).

  • rendered_frames – Logical array of the same size as frame_counter but only those frames that were rendered are True.

parse_data_v1_0_0_dev0(data: numpy.ndarray) None

Parses the data (from parse()) for Ceed version 1.0.0.dev0.

parse_data(frame_bits: numpy.ndarray, frame_counter: numpy.ndarray, start_t: datetime.datetime, n_sub_frames: int, rendered_frames: numpy.ndarray) None

Parses the data (from parse()) for Ceed versions greater than 1.0.0.dev0.