|
8 | 8 |
|
9 | 9 | import struct
|
10 | 10 |
|
| 11 | +try: |
| 12 | + import queue |
| 13 | +except ImportError: |
| 14 | + import Queue as queue |
| 15 | + |
11 | 16 | import uproot
|
12 | 17 |
|
13 | 18 | _rntuple_format1 = struct.Struct(">IIQIIQIIQ")
|
14 | 19 |
|
| 20 | +# https://github.com/jblomer/root/blob/ntuple-binary-format-v1/tree/ntuple/v7/doc/specifications.md#envelopes |
| 21 | +_rntuple_frame_format = struct.Struct("<HHI") |
| 22 | +_rntuple_feature_flag_format = struct.Struct("<Q") |
| 23 | +_rntuple_num_bytes_fields = struct.Struct("<II") |
| 24 | + |
15 | 25 |
|
16 | 26 | class Model_ROOT_3a3a_Experimental_3a3a_RNTuple(uproot.model.Model):
|
17 | 27 | """
|
@@ -40,6 +50,107 @@ def read_members(self, chunk, cursor, context, file):
|
40 | 50 | self._members["fReserved"],
|
41 | 51 | ) = cursor.fields(chunk, _rntuple_format1, context)
|
42 | 52 |
|
| 53 | + seek, nbytes = self._members["fSeekHeader"], self._members["fNBytesHeader"] |
| 54 | + header_range = (seek, seek + nbytes) |
| 55 | + |
| 56 | + seek, nbytes = self._members["fSeekFooter"], self._members["fNBytesFooter"] |
| 57 | + footer_range = (seek, seek + nbytes) |
| 58 | + |
| 59 | + notifications = queue.Queue() |
| 60 | + compressed_header_chunk, compressed_footer_chunk = file.source.chunks( |
| 61 | + [header_range, footer_range], notifications=notifications |
| 62 | + ) |
| 63 | + |
| 64 | + if self._members["fNBytesHeader"] == self._members["fLenHeader"]: |
| 65 | + self._header_chunk = compressed_header_chunk |
| 66 | + self._header_cursor = uproot.source.cursor.Cursor( |
| 67 | + self._members["fSeekHeader"] |
| 68 | + ) |
| 69 | + else: |
| 70 | + self._header_chunk = uproot.compression.decompress( |
| 71 | + compressed_header_chunk, |
| 72 | + uproot.source.cursor.Cursor(self._members["fSeekHeader"]), |
| 73 | + context, |
| 74 | + self._members["fNBytesHeader"], |
| 75 | + self._members["fLenHeader"], |
| 76 | + ) |
| 77 | + self._header_cursor = uproot.source.cursor.Cursor(0) |
| 78 | + |
| 79 | + if self._members["fNBytesFooter"] == self._members["fLenFooter"]: |
| 80 | + self._footer_chunk = compressed_footer_chunk |
| 81 | + self._footer_cursor = uproot.source.cursor.Cursor( |
| 82 | + self._members["fSeekFooter"] |
| 83 | + ) |
| 84 | + else: |
| 85 | + self._footer_chunk = uproot.compression.decompress( |
| 86 | + compressed_footer_chunk, |
| 87 | + uproot.source.cursor.Cursor(self._members["fSeekFooter"]), |
| 88 | + context, |
| 89 | + self._members["fNBytesFooter"], |
| 90 | + self._members["fLenFooter"], |
| 91 | + ) |
| 92 | + self._footer_cursor = uproot.source.cursor.Cursor(0) |
| 93 | + |
| 94 | + self._header, self._footer = None, None |
| 95 | + |
| 96 | + @property |
| 97 | + def header(self): |
| 98 | + if self._header is None: |
| 99 | + cursor = self._header_cursor.copy() |
| 100 | + context = {} |
| 101 | + |
| 102 | + self._header = {} |
| 103 | + self._header["frame"] = self._frame(self._header_chunk, cursor, context) |
| 104 | + |
| 105 | + # https://github.com/jblomer/root/blob/ntuple-binary-format-v1/tree/ntuple/v7/doc/specifications.md#header-envelope |
| 106 | + self._header["feature_flag"] = cursor.field( |
| 107 | + self._header_chunk, _rntuple_feature_flag_format, context |
| 108 | + ) |
| 109 | + self._header["name"] = cursor.rntuple_string(self._header_chunk, context) |
| 110 | + self._header["description"] = cursor.rntuple_string( |
| 111 | + self._header_chunk, context |
| 112 | + ) |
| 113 | + self._header["author"] = cursor.rntuple_string(self._header_chunk, context) |
| 114 | + |
| 115 | + cursor.skip(68) # ??? |
| 116 | + num_fields_plus_one = cursor.field( |
| 117 | + self._header_chunk, struct.Struct("<Q"), context |
| 118 | + ) |
| 119 | + |
| 120 | + self._header["fields"] = [None] * (num_fields_plus_one - 1) |
| 121 | + while any(x is None for x in self._header["fields"]): |
| 122 | + field = {} |
| 123 | + |
| 124 | + pos = cursor.index |
| 125 | + field["num_bytes"] = cursor.field( |
| 126 | + self._header_chunk, struct.Struct("<I"), context |
| 127 | + ) |
| 128 | + field["id"] = cursor.field( |
| 129 | + self._header_chunk, struct.Struct("<Q"), context |
| 130 | + ) |
| 131 | + self._header["fields"][field["id"] - 1] = field |
| 132 | + |
| 133 | + cursor.skip(48) # ??? |
| 134 | + field["name"] = cursor.rntuple_string(self._header_chunk, context) |
| 135 | + field["description"] = cursor.rntuple_string( |
| 136 | + self._header_chunk, context |
| 137 | + ) |
| 138 | + field["type"] = cursor.rntuple_string(self._header_chunk, context) |
| 139 | + |
| 140 | + cursor.move_to(pos + field["num_bytes"]) |
| 141 | + |
| 142 | + return self._header |
| 143 | + |
| 144 | + @property |
| 145 | + def footer(self): |
| 146 | + raise NotImplementedError |
| 147 | + |
| 148 | + def _frame(self, chunk, cursor, context): |
| 149 | + version, min_version, num_bytes = cursor.fields( |
| 150 | + chunk, _rntuple_frame_format, context |
| 151 | + ) |
| 152 | + return {"version": version, "min_version": min_version, "num_bytes": num_bytes} |
| 153 | + |
43 | 154 |
|
44 | 155 | uproot.classes[
|
45 | 156 | "ROOT::Experimental::RNTuple"
|
|
0 commit comments