execution/run_utils.py
def parse_animation_csv(
    csv_path: str, number_of_leds: int, channel_order="RGB"
) -> Sequence:
    """
    Parse a CSV animation file into python objects.

    :param csv_path: The path to the csv animation file
    :param number_of_leds: The number of LEDs that the device supports
    :param channel_order: The order the channels should be loaded. Must be "RGB" or "GRB"
    :return: A Sequence namedtuple containing frame data  and frame times
    """
    if channel_order not in ("RGB", "GRB"):
        raise ValueError(f"Unsupported channel order {channel_order}")
    # parse the CSV file
    # The example files in this repository start with \xEF\xBB\xBF See UTF-8 BOM
    # If read normally these become part of the first header name
    # utf-8-sig reads this correctly and also handles the case when they don't exist
    with open(csv_path, "r", encoding="utf-8-sig") as csv_file:
        # pass the file object to reader() to get the reader object
        csv_reader = csv.reader(csv_file)

        # this is a list of strings containing the column names
        header = next(csv_reader)

        # read in the remaining data
        data = list(csv_reader)

    # create a dictionary mapping the header name to the index of the header
    header_indexes = dict(zip(header, range(len(header))))

    # find the column numbers of each required header
    # we should not assume that the columns are in a known order. Isn't that the point of column names?
    # If a column does not exist it is set to None which is handled at the bottom and populates the column with 0.0
    led_columns: List[Tuple[Optional[int], Optional[int], Optional[int]]] = [
        tuple(
            header_indexes.pop(f"{channel}_{led_index}", None)
            for channel in channel_order
        )
        for led_index in range(number_of_leds)
    ]

    if "FRAME_ID" in header_indexes:
        # get the frame id column index
        frame_id_column = header_indexes.pop("FRAME_ID")
        # don't assume that the frames are in chronological order. Isn't that the point of storing the frame index?
        # sort the frames by the frame index
        data = sorted(data, key=lambda frame_data: int(frame_data[frame_id_column]))
        # There may be a case where a frame is missed eg 1, 2, 4, 5, ...
        # Should we duplicate frame 2 in this case?
        # For now it can go straight from frame 2 to 4

    if "FRAME_TIME" in header_indexes:
        # Add the ability for the CSV file to specify how long the frame should remain for
        # This will allow users to customise the frame rate and even have variable frame rates
        # Note that frame rate is hardware limited because the method that pushes changes to the tree takes a while.
        frame_time_column = header_indexes.pop("FRAME_TIME")
        frame_times = [float(frame_data[frame_time_column])/1000 for frame_data in data]
    else:
        # if the frame time column is not defined then run as fast as possible like the old code.
        frame_times = [0] * len(data)

    frames = [
        [
            tuple(
                # Get the LED value or populate with 0.0 if the column did not exist
                0.0 if channel is None else float(frame_data[channel])
                # for each channel in the LED
                for channel in channels
            )
            # for each LED in the chain
            for channels in led_columns
        ]
        # for each frame in the data
        for frame_data in data
    ]
    return Sequence(frames, frame_times)

    Parse a CSV animation file into python objects.

    :param csv_path: The path to the csv animation file
    :param number_of_leds: The number of LEDs that the device supports
    :param channel_order: The order the channels should be loaded. Must be "RGB" or "GRB"
    :return: A Sequence namedtuple containing frame data  and frame times
    
Parse a CSV animation file into python objects.
Parse a CSV animation file into python objects .
def parse_animation_csv(
    csv_path: str, number_of_leds: int, channel_order="RGB"
) -> Sequence:
    if channel_order not in ("RGB", "GRB"):
        raise ValueError(f"Unsupported channel order {channel_order}")
    with open(csv_path, "r", encoding="utf-8-sig") as csv_file:
        csv_reader = csv.reader(csv_file)
        header = next(csv_reader)
        data = list(csv_reader)
    header_indexes = dict(zip(header, range(len(header))))
    led_columns: List[Tuple[Optional[int], Optional[int], Optional[int]]] = [
        tuple(
            header_indexes.pop(f"{channel}_{led_index}", None)
            for channel in channel_order
        )
        for led_index in range(number_of_leds)
    ]
    if "FRAME_ID" in header_indexes:
        frame_id_column = header_indexes.pop("FRAME_ID")
        data = sorted(data, key=lambda frame_data: int(frame_data[frame_id_column]))
    if "FRAME_TIME" in header_indexes:
        frame_time_column = header_indexes.pop("FRAME_TIME")
        frame_times = [float(frame_data[frame_time_column])/1000 for frame_data in data]
    else:
        frame_times = [0] * len(data)
    frames = [
        [
            tuple(
                0.0 if channel is None else float(frame_data[channel])
                for channel in channels
            )
            for channels in led_columns
        ]
        for frame_data in data
    ]
    return Sequence(frames, frame_times)
def parse_animation_csv ( csv_path : str , number_of_leds : int , channel_order = "RGB" ) -> Sequence : if channel_order not in ( "RGB" , "GRB" ) : raise ValueError ( f"Unsupported channel order {channel_order}" ) with open ( csv_path , "r" , encoding = "utf-8-sig" ) as csv_file : csv_reader = csv . reader ( csv_file ) header = next ( csv_reader ) data = list ( csv_reader ) header_indexes = dict ( zip ( header , range ( len ( header ) ) ) ) led_columns : List [ Tuple [ Optional [ int ] , Optional [ int ] , Optional [ int ] ] ] = [ tuple ( header_indexes . pop ( f"{channel}_{led_index}" , None ) for channel in channel_order ) for led_index in range ( number_of_leds ) ] if "FRAME_ID" in header_indexes : frame_id_column = header_indexes . pop ( "FRAME_ID" ) data = sorted ( data , key = lambda frame_data : int ( frame_data [ frame_id_column ] ) ) if "FRAME_TIME" in header_indexes : frame_time_column = header_indexes . pop ( "FRAME_TIME" ) frame_times = [ float ( frame_data [ frame_time_column ] ) / 1000 for frame_data in data ] else : frame_times = [ 0 ] * len ( data ) frames = [ [ tuple ( 0.0 if channel is None else float ( frame_data [ channel ] ) for channel in channels ) for channels in led_columns ] for frame_data in data ] return Sequence ( frames , frame_times )
Parse a CSV animation file into python objects.
Parse a CSV animation file into python objects .
"""
    Parse a CSV animation file into python objects.

    :param csv_path: The path to the csv animation file
    :param number_of_leds: The number of LEDs that the device supports
    :param channel_order: The order the channels should be loaded. Must be "RGB" or "GRB"
    :return: A Sequence namedtuple containing frame data  and frame times
    """ # parse the CSV file # The example files in this repository start with \xEF\xBB\xBF See UTF-8 BOM # If read normally these become part of the first header name # utf-8-sig reads this correctly and also handles the case when they don't exist # pass the file object to reader() to get the reader object # this is a list of strings containing the column names # read in the remaining data # create a dictionary mapping the header name to the index of the header # find the column numbers of each required header # we should not assume that the columns are in a known order. Isn't that the point of column names? # If a column does not exist it is set to None which is handled at the bottom and populates the column with 0.0 # get the frame id column index # don't assume that the frames are in chronological order. Isn't that the point of storing the frame index? # sort the frames by the frame index # There may be a case where a frame is missed eg 1, 2, 4, 5, ... # Should we duplicate frame 2 in this case? # For now it can go straight from frame 2 to 4 # Add the ability for the CSV file to specify how long the frame should remain for # This will allow users to customise the frame rate and even have variable frame rates # Note that frame rate is hardware limited because the method that pushes changes to the tree takes a while. # if the frame time column is not defined then run as fast as possible like the old code. # Get the LED value or populate with 0.0 if the column did not exist # for each channel in the LED # for each LED in the chain # for each frame in the data
A Sequence namedtuple containing frame data  and frame times
A Sequence namedtuple containing frame data and frame times
csv_path
str
The path to the csv animation file
The path to the csv animation file
number_of_leds
int
The number of LEDs that the device supports
The number of LEDs that the device supports
channel_order
The order the channels should be loaded. Must be "RGB" or "GRB"
The order the channels should be loaded . Must be " RGB " or " GRB "