| 100 | } |
| 101 | |
| 102 | void NpyReader::parseHeader() { |
| 103 | // The first 6 bytes are a magic string: exactly \x93NUMPY |
| 104 | char* magicString = (char*)mmapRegion; |
| 105 | const char* expectedMagicString = "\x93NUMPY"; |
| 106 | if (memcmp(magicString, expectedMagicString, 6) != 0) { |
| 107 | throw CopyException("Invalid NPY file"); |
| 108 | } |
| 109 | |
| 110 | // The next 1 byte is an unsigned byte: the major version number of the file |
| 111 | // format, e.g. x01. |
| 112 | char* majorVersion = magicString + 6; |
| 113 | if (*majorVersion != 1) { |
| 114 | throw CopyException("Unsupported NPY file version."); |
| 115 | } |
| 116 | // The next 1 byte is an unsigned byte: the minor version number of the file |
| 117 | // format, e.g. x00. Note: the version of the file format is not tied to the |
| 118 | // version of the numpy package. |
| 119 | char* minorVersion = majorVersion + 1; |
| 120 | if (*minorVersion != 0) { |
| 121 | throw CopyException("Unsupported NPY file version."); |
| 122 | } |
| 123 | // The next 2 bytes form a little-endian unsigned short int: the length of |
| 124 | // the header data HEADER_LEN. |
| 125 | auto headerLength = *(unsigned short int*)(minorVersion + 1); |
| 126 | if (!isLittleEndian()) { |
| 127 | headerLength = ((headerLength & 0xff00) >> 8) | ((headerLength & 0x00ff) << 8); |
| 128 | } |
| 129 | |
| 130 | // The next HEADER_LEN bytes form the header data describing the array's |
| 131 | // format. It is an ASCII string which contains a Python literal expression |
| 132 | // of a dictionary. It is terminated by a newline ('n') and padded with |
| 133 | // spaces ('x20') to make the total length of the magic string + 4 + |
| 134 | // HEADER_LEN be evenly divisible by 16 for alignment purposes. |
| 135 | auto metaInfoLength = strlen(expectedMagicString) + 4; |
| 136 | char* header = (char*)mmapRegion + metaInfoLength; |
| 137 | auto headerEnd = std::find(header, header + headerLength, '}'); |
| 138 | |
| 139 | std::string headerString(header, headerEnd + 1); |
| 140 | std::unordered_map<std::string, std::string> headerMap = |
| 141 | pyparse::parse_dict(headerString, {"descr", "fortran_order", "shape"}); |
| 142 | auto isFortranOrder = pyparse::parse_bool(headerMap["fortran_order"]); |
| 143 | if (isFortranOrder) { |
| 144 | throw CopyException("Fortran-order NPY files are not currently supported."); |
| 145 | } |
| 146 | auto descr = pyparse::parse_str(headerMap["descr"]); |
| 147 | parseType(descr); |
| 148 | auto shapeV = pyparse::parse_tuple(headerMap["shape"]); |
| 149 | for (auto const& item : shapeV) { |
| 150 | shape.emplace_back(std::stoul(item)); |
| 151 | } |
| 152 | dataOffset = metaInfoLength + headerLength; |
| 153 | } |
| 154 | |
| 155 | void NpyReader::parseType(std::string descr) { |
| 156 | if (descr[0] == '<' || descr[0] == '>') { |
nothing calls this directly
no test coverage detected