|
| 1 | +# Licensed to the Apache Software Foundation (ASF) under one |
| 2 | +# or more contributor license agreements. See the NOTICE file |
| 3 | +# distributed with this work for additional information |
| 4 | +# regarding copyright ownership. The ASF licenses this file |
| 5 | +# to you under the Apache License, Version 2.0 (the |
| 6 | +# "License"); you may not use this file except in compliance |
| 7 | +# with the License. You may obtain a copy of the License at |
| 8 | +# |
| 9 | +# http://www.apache.org/licenses/LICENSE-2.0 |
| 10 | +# |
| 11 | +# Unless required by applicable law or agreed to in writing, |
| 12 | +# software distributed under the License is distributed on an |
| 13 | +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| 14 | +# KIND, either express or implied. See the License for the |
| 15 | +# specific language governing permissions and limitations |
| 16 | +# under the License. |
| 17 | +# pylint: disable=invalid-name, unused-argument, too-many-lines, import-outside-toplevel |
| 18 | +# pylint: disable=broad-exception-raised, use-list-literal |
| 19 | +"""Tensorflow lite frontend helper to parse custom options in Flexbuffer format.""" |
| 20 | + |
| 21 | +import struct |
| 22 | +from enum import IntEnum |
| 23 | + |
| 24 | + |
| 25 | +class BitWidth(IntEnum): |
| 26 | + """Flexbuffer bit width schema from flexbuffers.h""" |
| 27 | + |
| 28 | + BIT_WIDTH_8 = 0 |
| 29 | + BIT_WIDTH_16 = 1 |
| 30 | + BIT_WIDTH_32 = 2 |
| 31 | + BIT_WIDTH_64 = 3 |
| 32 | + |
| 33 | + |
| 34 | +class FlexBufferType(IntEnum): |
| 35 | + """Flexbuffer type schema from flexbuffers.h""" |
| 36 | + |
| 37 | + FBT_NULL = 0 |
| 38 | + FBT_INT = 1 |
| 39 | + FBT_UINT = 2 |
| 40 | + FBT_FLOAT = 3 |
| 41 | + # Types above stored inline, types below store an offset. |
| 42 | + FBT_KEY = 4 |
| 43 | + FBT_STRING = 5 |
| 44 | + FBT_INDIRECT_INT = 6 |
| 45 | + FBT_INDIRECT_UINT = 7 |
| 46 | + FBT_INDIRECT_FLOAT = 8 |
| 47 | + FBT_MAP = 9 |
| 48 | + FBT_VECTOR = 10 # Untyped. |
| 49 | + FBT_VECTOR_INT = 11 # Typed any size (stores no type table). |
| 50 | + FBT_VECTOR_UINT = 12 |
| 51 | + FBT_VECTOR_FLOAT = 13 |
| 52 | + FBT_VECTOR_KEY = 14 |
| 53 | + FBT_VECTOR_STRING = 15 |
| 54 | + FBT_VECTOR_INT2 = 16 # Typed tuple (no type table, no size field). |
| 55 | + FBT_VECTOR_UINT2 = 17 |
| 56 | + FBT_VECTOR_FLOAT2 = 18 |
| 57 | + FBT_VECTOR_INT3 = 19 # Typed triple (no type table, no size field). |
| 58 | + FBT_VECTOR_UINT3 = 20 |
| 59 | + FBT_VECTOR_FLOAT3 = 21 |
| 60 | + FBT_VECTOR_INT4 = 22 # Typed quad (no type table, no size field). |
| 61 | + FBT_VECTOR_UINT4 = 23 |
| 62 | + FBT_VECTOR_FLOAT4 = 24 |
| 63 | + FBT_BLOB = 25 |
| 64 | + FBT_BOOL = 26 |
| 65 | + FBT_VECTOR_BOOL = 36 # To Allow the same type of conversion of type to vector type |
| 66 | + |
| 67 | + |
| 68 | +class FlexBufferDecoder(object): |
| 69 | + """ |
| 70 | + This implements partial flexbuffer deserialization to be able |
| 71 | + to read custom options. It is not intended to be a general |
| 72 | + purpose flexbuffer deserializer and as such only supports a |
| 73 | + limited number of types and assumes the data is a flat map. |
| 74 | + """ |
| 75 | + |
| 76 | + def __init__(self, buffer): |
| 77 | + self.buffer = buffer |
| 78 | + |
| 79 | + def indirect_jump(self, offset, byte_width): |
| 80 | + """Helper function to read the offset value and jump""" |
| 81 | + unpack_str = "" |
| 82 | + if byte_width == 1: |
| 83 | + unpack_str = "<B" |
| 84 | + elif byte_width == 4: |
| 85 | + unpack_str = "<i" |
| 86 | + assert unpack_str != "" |
| 87 | + back_jump = struct.unpack(unpack_str, self.buffer[offset : offset + byte_width])[0] |
| 88 | + return offset - back_jump |
| 89 | + |
| 90 | + def decode_keys(self, end, size, byte_width): |
| 91 | + """Decodes the flexbuffer type vector. Map keys are stored in this form""" |
| 92 | + # Keys are strings here. The format is all strings separated by null, followed by back |
| 93 | + # offsets for each of the string. For example, (str1)\0(str1)\0(offset1)(offset2) The end |
| 94 | + # pointer is pointing at the end of all strings |
| 95 | + keys = list() |
| 96 | + for i in range(0, size): |
| 97 | + offset_pos = end + i * byte_width |
| 98 | + start_index = self.indirect_jump(offset_pos, byte_width) |
| 99 | + str_size = self.buffer[start_index:].find(b"\0") |
| 100 | + assert str_size != -1 |
| 101 | + s = self.buffer[start_index : start_index + str_size].decode("utf-8") |
| 102 | + keys.append(s) |
| 103 | + return keys |
| 104 | + |
| 105 | + def decode_vector(self, end, size, byte_width): |
| 106 | + """Decodes the flexbuffer vector""" |
| 107 | + # Each entry in the vector can have different datatype. Each entry is of fixed length. The |
| 108 | + # format is a sequence of all values followed by a sequence of datatype of all values. For |
| 109 | + # example - (4)(3.56)(int)(float) The end here points to the start of the values. |
| 110 | + values = list() |
| 111 | + for i in range(0, size): |
| 112 | + value_type_pos = end + size * byte_width + i |
| 113 | + value_type = FlexBufferType(self.buffer[value_type_pos] >> 2) |
| 114 | + value_bytes = self.buffer[end + i * byte_width : end + (i + 1) * byte_width] |
| 115 | + if value_type == FlexBufferType.FBT_BOOL: |
| 116 | + value = bool(value_bytes[0]) |
| 117 | + elif value_type == FlexBufferType.FBT_INT: |
| 118 | + value = struct.unpack("<i", value_bytes)[0] |
| 119 | + elif value_type == FlexBufferType.FBT_UINT: |
| 120 | + value = struct.unpack("<I", value_bytes)[0] |
| 121 | + elif value_type == FlexBufferType.FBT_FLOAT: |
| 122 | + value = struct.unpack("<f", value_bytes)[0] |
| 123 | + else: |
| 124 | + raise Exception |
| 125 | + values.append(value) |
| 126 | + return values |
| 127 | + |
| 128 | + def decode_map(self, end, byte_width, parent_byte_width): |
| 129 | + """Decodes the flexbuffer map and returns a dict""" |
| 130 | + mid_loc = self.indirect_jump(end, parent_byte_width) |
| 131 | + map_size = struct.unpack("<i", self.buffer[mid_loc - byte_width : mid_loc])[0] |
| 132 | + |
| 133 | + # Find keys |
| 134 | + keys_offset = mid_loc - byte_width * 3 |
| 135 | + keys_end = self.indirect_jump(keys_offset, byte_width) |
| 136 | + keys = self.decode_keys(keys_end, map_size, 1) |
| 137 | + |
| 138 | + # Find values |
| 139 | + values_end = self.indirect_jump(end, parent_byte_width) |
| 140 | + values = self.decode_vector(values_end, map_size, byte_width) |
| 141 | + return dict(zip(keys, values)) |
| 142 | + |
| 143 | + def decode(self): |
| 144 | + """Decode the buffer. Decoding is partially implemented""" |
| 145 | + root_end = len(self.buffer) - 1 |
| 146 | + root_byte_width = self.buffer[root_end] |
| 147 | + root_end -= 1 |
| 148 | + root_packed_type = self.buffer[root_end] |
| 149 | + root_end -= root_byte_width |
| 150 | + |
| 151 | + root_type = FlexBufferType(root_packed_type >> 2) |
| 152 | + byte_width = 1 << BitWidth(root_packed_type & 3) |
| 153 | + |
| 154 | + if root_type == FlexBufferType.FBT_MAP: |
| 155 | + return self.decode_map(root_end, byte_width, root_byte_width) |
| 156 | + raise NotImplementedError("Flexbuffer Decoding is partially imlpemented.") |
0 commit comments