def get_lcmtype_dictionary(root_path: str) -> Dict[bytes, Type]:
    """
    Searches recursively from `root_path` for any lcm type definitions
    and imports as python classes.

    Returns a dictionary of lcm type classes keyed by the type's fingerprint
    """
    # Search the given root path for lcm types
    lcmtypes_list = __find_lcmtypes(root_path)
    log.log(f"Found {len(lcmtypes_list)} lcm types")
    log.log("Importing lcm types")

    # Import each lcm type file as a python class & store keyed by fingerprint
    lcmtypes: Dict[bytes, Type] = {}
    for module_name in lcmtypes_list:
        log.if_verbose(f"Importing {module_name}", end="\t")
        try:
            # Import the lcm type file & get a reference to the module
            __import__(module_name)
            module = sys.modules[module_name]
            # Pick out the lcm type class from the module
            class_name = module_name.split(".")[-1]
            class_reference = getattr(module, class_name)
            # Store the class by its fingerprint
            fingerprint = class_reference._get_packed_fingerprint()
            lcmtypes[fingerprint] = class_reference
            log.if_verbose(f"-> {hexlify(fingerprint)}")
        except Exception as error:
            log.error(f"Error importing {module_name}")
            raise error

    return lcmtypes
def parse_lcm_log(file: str, lcm_types: Dict) -> Dict:
    log.log(f"Parsing lcm log file {file}")

    # Open log as an LCM EventLog object
    lcm_log = EventLog(file, "r")

    first_timestamp = None
    events: Dict[str, Dict[str, List]] = {
    }  # {CHANNEL: {FIELD1: [values], FIELD2: [values]}}

    missing_channels = []
    for event in lcm_log:
        assert isinstance(event, Event)
        # Record the time of the first event as start time
        if not first_timestamp:
            first_timestamp = event.timestamp

        # todo - ignored channels

        log.if_verbose(f"Event on channel: {event.channel}", end="\t")

        # Match the message to an lcm type
        fingerprint = event.data[:8]
        lcm_type = lcm_types.get(fingerprint, None)
        if not lcm_type:
            if event.channel not in missing_channels:
                missing_channels.append(event.channel)
                log.error(
                    f"Unable to find lcm type for events on channel {event.channel}"
                )
            continue
        log.if_verbose(f"-> {lcm_type.__name__}")

        # Decode the message into a python object
        try:
            message = lcm_type.decode(event.data)
        except:
            log.error(f"Error decoding event on channel {event.channel}")
            continue

        # Convert the message into loggable form & store
        message_dict = convert_to_primitive(message,
                                            event.timestamp - first_timestamp)

        # Convert to list of values for each field
        for field in message_dict.keys():
            events.setdefault(event.channel,
                              {}).setdefault(field,
                                             []).append(message_dict[field])

    return events
def __find_lcmtypes(root_path: str) -> List[str]:
    """
        Searches recursively from `root_path` for lcm types.

        Finds all python files matching the above regex of valid matlab names,
        imports their details using pyclbr and keeps a record of any which contain
        functions usually associated with lcm types.

        Returns a list of files containing lcm types as python modules to be imported
        e.g. root_path/dir/some_type.py -> dir.some_type
    """
    log.log(f"Searching for lcm types in root directory {root_path}")

    found_lcmtypes: List[str] = []
    for root, dirs, files in os.walk(root_path):
        log.if_verbose("Searching directory {}".format(root))

        # The python package will be the relative path to the file
        python_package = os.path.relpath(root, root_path).replace(os.sep, ".")
        if python_package == ".":
            python_package = ""

        for file in files:
            # Ensure file name (and hence lcm-type name) is importable to matlab
            if not filename_validator.fullmatch(file):
                continue

            log.if_verbose(f"Testing file: {file}", end=" ")

            lcmtype_name = file[:-3]
            module_name = f"{python_package}.{lcmtype_name}".strip(".")
            log.if_verbose(f"-> {module_name}")

            # Load python class from type definition & check validity as lcm type
            # To be valid, must have `_get_packed_fingerprint` and `decode` methods
            try:
                klass = pyclbr.readmodule(module_name)[lcmtype_name]
                if "decode" in klass.methods and "_get_packed_fingerprint" in klass.methods:
                    found_lcmtypes.append(module_name)
                    log.if_verbose(
                        f"Found lcm definition {klass.name} in file: {file}")

            except (ImportError, KeyError):
                continue

    # Raise error if no lcm type definitions found (nothing will be decoded)
    if not found_lcmtypes:
        raise FileNotFoundError("No lcm type definitions found")
    return found_lcmtypes
def dump_to_pickle(data: Dict, output_file: str):
    log.log(f"Writing .pkl file to {output_file}.pkl")
    with open(output_file + ".pkl", "wb") as file:
        pickle.dump(data, file, protocol=2)
def dump_to_matlab(data: Dict, output_file: str):
    """
        Creates a Matlab .mat file from teh given dictionary
    """
    log.log(f"Writing .mat file to {output_file}.mat")
    scipy.io.matlab.mio.savemat(output_file + ".mat", data, oned_as='column')