h5tab 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- h5tab-1.0.0.dist-info/METADATA +7 -0
- h5tab-1.0.0.dist-info/RECORD +6 -0
- h5tab-1.0.0.dist-info/WHEEL +5 -0
- h5tab-1.0.0.dist-info/entry_points.txt +2 -0
- h5tab-1.0.0.dist-info/top_level.txt +1 -0
- h5tab.py +724 -0
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
h5tab.py,sha256=_Iq2Q-eLMQkiXeMO14DX_GhAu5FxkEokLnJH9nddIkA,25342
|
|
2
|
+
h5tab-1.0.0.dist-info/METADATA,sha256=R5gsgqkKQCvkxqNHJfeROABR5iceQh-MU6Doj0gYNRo,176
|
|
3
|
+
h5tab-1.0.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
4
|
+
h5tab-1.0.0.dist-info/entry_points.txt,sha256=P_xuXd_3HyBKplnz2NRv3Gvpdg7W84fbTjQ02g6DZb4,37
|
|
5
|
+
h5tab-1.0.0.dist-info/top_level.txt,sha256=TxwH-qTPgWP4Rn_SWhuXOUaBTHyfGafquJK0sBOM3C8,6
|
|
6
|
+
h5tab-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
h5tab
|
h5tab.py
ADDED
|
@@ -0,0 +1,724 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
# TODO:
|
|
5
|
+
#
|
|
6
|
+
# More sensible way to determine no. of rows
|
|
7
|
+
# Add attribute/info icons
|
|
8
|
+
#
|
|
9
|
+
|
|
10
|
+
# Avoid warnings if h5py has MPI support
|
|
11
|
+
try:
|
|
12
|
+
import mpi4py
|
|
13
|
+
except ImportError:
|
|
14
|
+
pass
|
|
15
|
+
else:
|
|
16
|
+
mpi4py.rc.initialize = False
|
|
17
|
+
|
|
18
|
+
import h5py
|
|
19
|
+
|
|
20
|
+
import os.path
|
|
21
|
+
import sys
|
|
22
|
+
|
|
23
|
+
from tkinter import *
|
|
24
|
+
from idlelib.tree import TreeItem, TreeNode
|
|
25
|
+
|
|
26
|
+
import tkinter.font as tkFont
|
|
27
|
+
|
|
28
|
+
ITEM_TOP = 0
|
|
29
|
+
ITEM_GROUP = 1
|
|
30
|
+
ITEM_DATASET = 2
|
|
31
|
+
ITEM_ATTR = 3
|
|
32
|
+
ITEM_DESCR = 4
|
|
33
|
+
|
|
34
|
+
icon_images = None
|
|
35
|
+
|
|
36
|
+
# Nodes which are highlighted
|
|
37
|
+
selected_nodes = {}
|
|
38
|
+
|
|
39
|
+
# Format for displaying values
|
|
40
|
+
float_fmt = "%14.6e"
|
|
41
|
+
int_fmt = "%d"
|
|
42
|
+
|
|
43
|
+
# Minimum column width
|
|
44
|
+
min_width = 16
|
|
45
|
+
|
|
46
|
+
# Maximum elements per row
|
|
47
|
+
max_elements = 6
|
|
48
|
+
|
|
49
|
+
# Maximum column width
|
|
50
|
+
max_width = max_elements * min_width
|
|
51
|
+
|
|
52
|
+
def get_shape(data):
|
|
53
|
+
"""Return shape of array or [1] if its a scalar"""
|
|
54
|
+
if len(data.shape) > 0:
|
|
55
|
+
return data.shape
|
|
56
|
+
else:
|
|
57
|
+
return [1]
|
|
58
|
+
|
|
59
|
+
def get_format(dtype):
|
|
60
|
+
# Check for variable length data types
|
|
61
|
+
if h5py.check_string_dtype(dtype) is not None:
|
|
62
|
+
return "%s"
|
|
63
|
+
elif h5py.check_vlen_dtype(dtype) is not None:
|
|
64
|
+
dtype = h5py.check_vlen_dtype(dtype)
|
|
65
|
+
# Choose format string depending on base type
|
|
66
|
+
if dtype.kind == "i" or dtype.kind == "u":
|
|
67
|
+
fmt = int_fmt
|
|
68
|
+
elif dtype.kind == "f":
|
|
69
|
+
fmt = float_fmt
|
|
70
|
+
elif dtype.kind == "S" or dtype.kind == "U" or dtype.kind == "a":
|
|
71
|
+
fmt = "%s"
|
|
72
|
+
else:
|
|
73
|
+
fmt = None
|
|
74
|
+
return fmt
|
|
75
|
+
|
|
76
|
+
def get_width(dtype):
|
|
77
|
+
if h5py.check_string_dtype(dtype) is not None or h5py.check_vlen_dtype(dtype) is not None:
|
|
78
|
+
return max_width
|
|
79
|
+
elif dtype.kind == "S" or dtype.kind == "U" or dtype.kind == "a":
|
|
80
|
+
return min(max_width, max((dtype.itemsize, min_width)))
|
|
81
|
+
else:
|
|
82
|
+
return min_width
|
|
83
|
+
|
|
84
|
+
def is_too_big(shape):
|
|
85
|
+
if len(shape) < 2:
|
|
86
|
+
# 1D arrays are always ok
|
|
87
|
+
return False
|
|
88
|
+
elif len(shape) == 2:
|
|
89
|
+
# 2D is ok if second dim is small
|
|
90
|
+
return (shape[1] > max_elements)
|
|
91
|
+
else:
|
|
92
|
+
# Multidimensional array - check no. of elements
|
|
93
|
+
i = shape[1]
|
|
94
|
+
for j in shape[2:]:
|
|
95
|
+
i *= j
|
|
96
|
+
return (i > max_elements)
|
|
97
|
+
|
|
98
|
+
def argsort(seq):
|
|
99
|
+
return sorted(range(len(seq)), key=seq.__getitem__)
|
|
100
|
+
|
|
101
|
+
def get_icons():
|
|
102
|
+
# idlelib doesn't have appropriate icons for datasets/attributes
|
|
103
|
+
# so encode some here (data strings are base64 encoded .gif files)
|
|
104
|
+
dataset = PhotoImage(data=r'R0lGODlhEAANAPIEADAwMAD//8PDw9zc3P///6usr'+
|
|
105
|
+
r'QAAAAAAACH5BAEAAAUALAAAAAAQAA0AAAMqOLDcrSHKORe9'+
|
|
106
|
+
r'0uktuv/fIhBkaZKCCK6deL5ECrCsC5syvaq6BygbzSABADs=')
|
|
107
|
+
node = PhotoImage(data=r'R0lGODlhCwALAPECAAAAAH9/f////6usrSH5BAEAAA'+
|
|
108
|
+
r'MALAAAAAALAAsAAAIanI8Wy6wCowhPQjqCvdVivX3b1Elf0y'+
|
|
109
|
+
r'TqUAAAOw==')
|
|
110
|
+
return {"data":dataset, "attr":node}
|
|
111
|
+
|
|
112
|
+
def value_to_text(value, fmt):
|
|
113
|
+
"""Write out a multidimensional array as text"""
|
|
114
|
+
if isinstance(value, str):
|
|
115
|
+
return value
|
|
116
|
+
elif value.dtype.kind == "O" and value.shape == ():
|
|
117
|
+
# Value is a single object reference
|
|
118
|
+
return value_to_text(value[()], fmt)
|
|
119
|
+
elif len(value.shape) > 0:
|
|
120
|
+
# Value is an array, possibly of objects
|
|
121
|
+
s = []
|
|
122
|
+
for i in range(value.shape[0]):
|
|
123
|
+
s.append(value_to_text(value[i,...], fmt))
|
|
124
|
+
return "[" + (",".join(s)) + "]"
|
|
125
|
+
else:
|
|
126
|
+
# Value is a non-object scalar
|
|
127
|
+
if fmt is None:
|
|
128
|
+
return str(value)
|
|
129
|
+
else:
|
|
130
|
+
return fmt % value
|
|
131
|
+
|
|
132
|
+
def type_string(dtype):
|
|
133
|
+
"""Return a description of a HDF5 data type"""
|
|
134
|
+
if h5py.check_string_dtype(dtype) is not None:
|
|
135
|
+
return "vlen string"
|
|
136
|
+
elif h5py.check_vlen_dtype(dtype) is not None:
|
|
137
|
+
return "vlen "+str(h5py.check_vlen_dtype(dtype))
|
|
138
|
+
else:
|
|
139
|
+
return str(dtype)
|
|
140
|
+
|
|
141
|
+
class MyTreeNode(TreeNode):
|
|
142
|
+
def __init__(self, canvas, parent, item):
|
|
143
|
+
TreeNode.__init__(self, canvas, parent, item)
|
|
144
|
+
self.item = item
|
|
145
|
+
def select(self, event=None, value=None):
|
|
146
|
+
if self.item.item_type == ITEM_DATASET:
|
|
147
|
+
if value is None:
|
|
148
|
+
self.selected = not(self.selected)
|
|
149
|
+
else:
|
|
150
|
+
self.selected = value
|
|
151
|
+
self.canvas.delete(self.image_id)
|
|
152
|
+
self.drawicon()
|
|
153
|
+
self.drawtext()
|
|
154
|
+
global selected_nodes
|
|
155
|
+
my_id = (self.item.file.filename, self.item.path, self.item.field)
|
|
156
|
+
if self.selected:
|
|
157
|
+
selected_nodes[my_id] = self.item
|
|
158
|
+
else:
|
|
159
|
+
if my_id in selected_nodes:
|
|
160
|
+
del selected_nodes[my_id]
|
|
161
|
+
|
|
162
|
+
def geticonimage(self, name):
|
|
163
|
+
global icon_images
|
|
164
|
+
if icon_images is None:
|
|
165
|
+
icon_images = get_icons()
|
|
166
|
+
if name in icon_images:
|
|
167
|
+
return icon_images[name]
|
|
168
|
+
else:
|
|
169
|
+
return TreeNode.geticonimage(self, name)
|
|
170
|
+
|
|
171
|
+
class TopItem(TreeItem):
|
|
172
|
+
"""Top level tree widget item with one sub-item for each file"""
|
|
173
|
+
def __init__(self):
|
|
174
|
+
self.nodes = []
|
|
175
|
+
self.item_type = ITEM_TOP
|
|
176
|
+
def GetText(self):
|
|
177
|
+
return "HDF5 files"
|
|
178
|
+
def IsExpandable(self):
|
|
179
|
+
return True
|
|
180
|
+
def GetSubList(self):
|
|
181
|
+
return self.nodes
|
|
182
|
+
def add_child(self, child):
|
|
183
|
+
self.nodes.append(child)
|
|
184
|
+
|
|
185
|
+
class AttrItem(TreeItem):
|
|
186
|
+
"""Tree widget item which represents a HDF5 attribute"""
|
|
187
|
+
def __init__(self, name, value):
|
|
188
|
+
self.name = name
|
|
189
|
+
self.value = value_to_text(value, None)
|
|
190
|
+
self.item_type = ITEM_ATTR
|
|
191
|
+
def GetText(self):
|
|
192
|
+
return self.name + " = " + self.value
|
|
193
|
+
def IsExpandable(self):
|
|
194
|
+
return False
|
|
195
|
+
def GetSubList(self):
|
|
196
|
+
return []
|
|
197
|
+
def GetIconName(self):
|
|
198
|
+
return "attr"
|
|
199
|
+
|
|
200
|
+
class DescrItem(TreeItem):
|
|
201
|
+
"""Tree widget item which represents a description of a dataset"""
|
|
202
|
+
def __init__(self, id, dtype, field):
|
|
203
|
+
self.item_type = ITEM_DESCR
|
|
204
|
+
self.text = "shape="+str(id.shape)+", dtype="+type_string(dtype)
|
|
205
|
+
if field is None:
|
|
206
|
+
# Don't show layout and chunks for dataset sub-fields
|
|
207
|
+
plist = id.get_create_plist()
|
|
208
|
+
if plist.get_layout() == h5py.h5d.CHUNKED:
|
|
209
|
+
chunk = plist.get_chunk()
|
|
210
|
+
self.text += ", chunks="+str(chunk)
|
|
211
|
+
for i in range(plist.get_nfilters()):
|
|
212
|
+
(code, flags, value, name) = plist.get_filter(i)
|
|
213
|
+
self.text += ", "+str(name.decode())
|
|
214
|
+
def GetText(self):
|
|
215
|
+
return self.text
|
|
216
|
+
def IsExpandable(self):
|
|
217
|
+
return False
|
|
218
|
+
def GetSubList(self):
|
|
219
|
+
return []
|
|
220
|
+
def GetIconName(self):
|
|
221
|
+
return "attr"
|
|
222
|
+
|
|
223
|
+
#
|
|
224
|
+
# Use low level interface instead of instantiating h5py objects for every
|
|
225
|
+
# tree item in here to avoid performance issue with datasets with
|
|
226
|
+
# many chunks if using h5py < 2.0.
|
|
227
|
+
#
|
|
228
|
+
class HDF5Item(TreeItem):
|
|
229
|
+
"""Tree widget item which represents a HDF5 file, group or dataset"""
|
|
230
|
+
def __init__(self, id, name="", file=None, is_file=False, parent_name="", field=None):
|
|
231
|
+
self.id = id
|
|
232
|
+
self.file = file
|
|
233
|
+
self.is_file = is_file
|
|
234
|
+
self.name = name
|
|
235
|
+
if field is None:
|
|
236
|
+
self.path = (parent_name+"/"+name).lstrip("/")
|
|
237
|
+
else:
|
|
238
|
+
self.path = parent_name
|
|
239
|
+
self.field = field if field is not None else ()
|
|
240
|
+
if isinstance(self.id, h5py.h5d.DatasetID):
|
|
241
|
+
self.item_type = ITEM_DATASET
|
|
242
|
+
else:
|
|
243
|
+
self.item_type = ITEM_GROUP
|
|
244
|
+
def GetText(self):
|
|
245
|
+
if self.is_file:
|
|
246
|
+
return self.file.filename
|
|
247
|
+
else:
|
|
248
|
+
return self.name
|
|
249
|
+
def IsExpandable(self):
|
|
250
|
+
return True
|
|
251
|
+
def GetSubList(self):
|
|
252
|
+
sublist = []
|
|
253
|
+
if isinstance(self.id, h5py.h5d.DatasetID):
|
|
254
|
+
# This is a dataset or a sub-field of a dataset. Find its data type.
|
|
255
|
+
dtype = self.id.get_type().dtype
|
|
256
|
+
if self.field is not None:
|
|
257
|
+
for f in self.field:
|
|
258
|
+
dtype = dtype[f]
|
|
259
|
+
# Add its description to the tree.
|
|
260
|
+
sublist.append(DescrItem(self.id, dtype, field=self.field))
|
|
261
|
+
# Check for compound type and add sub fields
|
|
262
|
+
if dtype.fields is not None:
|
|
263
|
+
for field in dtype.fields:
|
|
264
|
+
sublist.append(HDF5Item(self.id, field, self.file,
|
|
265
|
+
parent_name=self.path, field=self.field+(field,)))
|
|
266
|
+
if isinstance(self.id, h5py.h5g.GroupID):
|
|
267
|
+
# This is a group. Add its members to the tree.
|
|
268
|
+
n = self.id.get_num_objs()
|
|
269
|
+
for i in range(n):
|
|
270
|
+
name = self.id.get_objname_by_idx(i)
|
|
271
|
+
ot = self.id.get_objtype_by_idx(i)
|
|
272
|
+
if ot == h5py.h5g.DATASET:
|
|
273
|
+
subid = h5py.h5d.open(self.id, name)
|
|
274
|
+
sublist.append(HDF5Item(subid, name.decode(), self.file,
|
|
275
|
+
parent_name=self.path))
|
|
276
|
+
elif ot == h5py.h5g.GROUP:
|
|
277
|
+
subid = h5py.h5g.open(self.id, name)
|
|
278
|
+
sublist.append(HDF5Item(subid, name.decode(), self.file,
|
|
279
|
+
parent_name=self.path))
|
|
280
|
+
else:
|
|
281
|
+
# Unsupported object type
|
|
282
|
+
pass
|
|
283
|
+
# Get number of attributes on this object
|
|
284
|
+
try:
|
|
285
|
+
nr_attrs = h5py.h5a.get_num_attrs(self.id)
|
|
286
|
+
except ValueError:
|
|
287
|
+
# Workaround for old HDF5 versions: need to open root group
|
|
288
|
+
if self.is_file:
|
|
289
|
+
nr_attrs = h5py.h5a.get_num_attrs(self.id["/"])
|
|
290
|
+
else:
|
|
291
|
+
raise
|
|
292
|
+
# Add attributes of this object to the tree
|
|
293
|
+
if nr_attrs > 0:
|
|
294
|
+
for name in self.get_data().attrs.keys():
|
|
295
|
+
try:
|
|
296
|
+
value = self.get_data().attrs[name]
|
|
297
|
+
except IOError as e:
|
|
298
|
+
pass
|
|
299
|
+
else:
|
|
300
|
+
sublist.append(AttrItem(name, value))
|
|
301
|
+
|
|
302
|
+
return sublist
|
|
303
|
+
def GetIconName(self):
|
|
304
|
+
if self.item_type == ITEM_DATASET:
|
|
305
|
+
return "data"
|
|
306
|
+
else:
|
|
307
|
+
return "folder"
|
|
308
|
+
def get_data(self):
|
|
309
|
+
if len(self.path) > 0:
|
|
310
|
+
return self.file[self.path]
|
|
311
|
+
else:
|
|
312
|
+
return self.file
|
|
313
|
+
|
|
314
|
+
class AutoScrollbar(Scrollbar):
|
|
315
|
+
def set(self, lo, hi):
|
|
316
|
+
if float(lo) <= 0.0 and float(hi) >= 1.0:
|
|
317
|
+
self.tk.call("grid", "remove", self)
|
|
318
|
+
else:
|
|
319
|
+
self.grid()
|
|
320
|
+
Scrollbar.set(self, lo, hi)
|
|
321
|
+
|
|
322
|
+
class DataWindow():
|
|
323
|
+
def __init__(self, data):
|
|
324
|
+
|
|
325
|
+
global windows_open
|
|
326
|
+
windows_open += 1
|
|
327
|
+
|
|
328
|
+
self.ids = data.keys()
|
|
329
|
+
self.filenames = [id[0] for id in self.ids]
|
|
330
|
+
self.names = [id[1] for id in self.ids]
|
|
331
|
+
self.fields = [id[2] for id in self.ids]
|
|
332
|
+
self.datasets = [data[k].get_data() for k in self.ids]
|
|
333
|
+
self.nrows_tot = max([get_shape(arr)[0] for arr in self.datasets])
|
|
334
|
+
|
|
335
|
+
# Sort by file name
|
|
336
|
+
#idx = argsort(self.filenames)
|
|
337
|
+
sort_key = [fn + n for (fn,n) in zip(self.filenames, self.names)]
|
|
338
|
+
idx = argsort(sort_key)
|
|
339
|
+
self.names = [self.names[i] for i in idx]
|
|
340
|
+
self.filenames = [self.filenames[i] for i in idx]
|
|
341
|
+
self.datasets = [self.datasets[i] for i in idx]
|
|
342
|
+
self.fields = [self.fields[i] for i in idx]
|
|
343
|
+
|
|
344
|
+
# Figure out which columns need to show file names
|
|
345
|
+
self.nspan = [1 for _ in range(len(self.names))]
|
|
346
|
+
for i in range(len(self.names)-1):
|
|
347
|
+
for j in range(i+1,len(self.names)):
|
|
348
|
+
if self.filenames[i] == self.filenames[j]:
|
|
349
|
+
self.nspan[i] += 1
|
|
350
|
+
for i in range(1, len(self.names)):
|
|
351
|
+
if self.filenames[i] == self.filenames[i-1]:
|
|
352
|
+
self.nspan[i] = 0
|
|
353
|
+
|
|
354
|
+
# Make top level window
|
|
355
|
+
self.toplevel = Toplevel()
|
|
356
|
+
self.toplevel.title("HDF5 data table")
|
|
357
|
+
self.toplevel.grid_rowconfigure(0,weight=1)
|
|
358
|
+
self.toplevel.grid_columnconfigure(0,weight=1)
|
|
359
|
+
|
|
360
|
+
# Add label, entry and button for jumping to row
|
|
361
|
+
lframe = Frame(self.toplevel)
|
|
362
|
+
lframe.grid(row=2, column=0, sticky=E+W)
|
|
363
|
+
lframe.columnconfigure(0, weight=0)
|
|
364
|
+
lframe.columnconfigure(1, weight=0)
|
|
365
|
+
lframe.columnconfigure(2, weight=1)
|
|
366
|
+
self.label = Label(lframe, text="Find index: ")
|
|
367
|
+
self.label.grid(row=2, column=0)
|
|
368
|
+
self.entry = Entry(lframe)
|
|
369
|
+
self.entry.grid(row=2, column=1)
|
|
370
|
+
self.entry.bind("<Return>", self.on_return)
|
|
371
|
+
|
|
372
|
+
# Add row count
|
|
373
|
+
self.rowlabel = Label(lframe, text="Total rows: "+str(self.nrows_tot))
|
|
374
|
+
self.rowlabel.grid(row=2, column=2, sticky=E)
|
|
375
|
+
|
|
376
|
+
# Make vertical scroll bar
|
|
377
|
+
self.vsbar = AutoScrollbar(self.toplevel, orient=VERTICAL,
|
|
378
|
+
command=self.scrollbar_moved)
|
|
379
|
+
self.vsbar.grid(row=0, column=1, sticky=N+S)
|
|
380
|
+
|
|
381
|
+
# Make horizontal scroll bar
|
|
382
|
+
hsbar = AutoScrollbar(self.toplevel, orient=HORIZONTAL)
|
|
383
|
+
hsbar.grid(row=1, column=0, sticky=E+W)
|
|
384
|
+
|
|
385
|
+
# Put a canvas in the window
|
|
386
|
+
self.canvas = Canvas(self.toplevel, xscrollcommand=hsbar.set)
|
|
387
|
+
self.canvas.grid(row=0, column=0, sticky=N+S+E+W)
|
|
388
|
+
hsbar.config(command=self.canvas.xview)
|
|
389
|
+
|
|
390
|
+
# Put a frame in the canvas
|
|
391
|
+
self.frame = Frame(self.canvas, bd=3, relief=SUNKEN)
|
|
392
|
+
self.frame.rowconfigure(2,weight=1)
|
|
393
|
+
|
|
394
|
+
# Calculate column widths
|
|
395
|
+
self.widths = []
|
|
396
|
+
for (name,dset,fname,fields) in zip(self.names,self.datasets,self.filenames,self.fields):
|
|
397
|
+
dtype = dset.dtype
|
|
398
|
+
for f in fields:
|
|
399
|
+
dtype = dtype[f]
|
|
400
|
+
width = get_width(dtype)
|
|
401
|
+
if len(dset.shape) > 1:
|
|
402
|
+
width += 3
|
|
403
|
+
for s in dset.shape[1:]:
|
|
404
|
+
width *= s
|
|
405
|
+
if is_too_big(dset.shape):
|
|
406
|
+
width = min_width
|
|
407
|
+
width = min((max_width, max(width, len(name))))
|
|
408
|
+
self.widths.append(width)
|
|
409
|
+
|
|
410
|
+
# Calculate file name widths
|
|
411
|
+
self.fname_widths = []
|
|
412
|
+
icol = 0
|
|
413
|
+
for (width, nspan) in zip(self.widths, self.nspan):
|
|
414
|
+
w = 0
|
|
415
|
+
for i in range(nspan):
|
|
416
|
+
w += self.widths[icol+i]
|
|
417
|
+
self.fname_widths.append(w)
|
|
418
|
+
icol += 1
|
|
419
|
+
|
|
420
|
+
# Add text widgets with column headings and data columns
|
|
421
|
+
font = tkFont.Font(family="courier", size=-16)
|
|
422
|
+
self.title = []
|
|
423
|
+
self.column = []
|
|
424
|
+
self.fname = []
|
|
425
|
+
icol = 1
|
|
426
|
+
self.index = Text(self.frame, font=font, wrap=NONE,
|
|
427
|
+
width=12, bg="light cyan")
|
|
428
|
+
self.index.grid(row=2, column=0, sticky=W+N+S)
|
|
429
|
+
for (name,dset,fname,width,fw,fields) in zip(self.names,self.datasets,
|
|
430
|
+
self.filenames, self.widths,
|
|
431
|
+
self.fname_widths, self.fields):
|
|
432
|
+
if len(fields) == 0:
|
|
433
|
+
# Basic type - just display the dataset name
|
|
434
|
+
display_name = name
|
|
435
|
+
else:
|
|
436
|
+
# Compound type - need to display the field name too
|
|
437
|
+
display_name = "|".join((name,)+fields)
|
|
438
|
+
|
|
439
|
+
self.title.append(Text(self.frame, font=font, wrap=NONE,
|
|
440
|
+
width=width, height=1, bg="light yellow"))
|
|
441
|
+
self.title[-1].insert("end", display_name+" ")
|
|
442
|
+
self.title[-1].grid(row=1, column=icol,sticky=N+W)
|
|
443
|
+
self.title[-1].config(state=DISABLED)
|
|
444
|
+
self.column.append(Text(self.frame, font=font, wrap=NONE,
|
|
445
|
+
width=width, bg="white", setgrid=1))
|
|
446
|
+
self.column[-1].grid(row=2, column=icol, sticky=W+N+S)
|
|
447
|
+
self.column[-1].config(state=DISABLED)
|
|
448
|
+
|
|
449
|
+
# Add file name spanning columns from same file
|
|
450
|
+
if self.nspan[icol-1] > 0:
|
|
451
|
+
self.fname.append(Text(self.frame, font=font, wrap=NONE,
|
|
452
|
+
height=1, width=fw))
|
|
453
|
+
self.fname[-1].grid(row=0, column=icol, sticky=E+W,
|
|
454
|
+
columnspan=self.nspan[icol-1])
|
|
455
|
+
self.fname[-1].insert("end", fname)
|
|
456
|
+
self.fname[-1].config(state=DISABLED)
|
|
457
|
+
|
|
458
|
+
icol += 1
|
|
459
|
+
|
|
460
|
+
self.win = self.canvas.create_window(0, 0, anchor=NW,
|
|
461
|
+
window=self.frame)
|
|
462
|
+
self.frame.update_idletasks()
|
|
463
|
+
self.canvas.config(scrollregion=self.canvas.bbox("all"))
|
|
464
|
+
|
|
465
|
+
# Resize window if size of canvas changes
|
|
466
|
+
self.canvas.bind( '<Configure>', self.on_resize)
|
|
467
|
+
|
|
468
|
+
# Scrolling with keys/mouse wheel
|
|
469
|
+
# TODO - see if there's a way to get events passed to the parent
|
|
470
|
+
# frame instead of binding everything!
|
|
471
|
+
self.bind_scrolling(self.canvas)
|
|
472
|
+
self.bind_scrolling(self.frame)
|
|
473
|
+
self.bind_scrolling(self.index)
|
|
474
|
+
self.bind_scrolling(self.vsbar)
|
|
475
|
+
self.bind_scrolling(self.entry)
|
|
476
|
+
self.bind_scrolling(self.label)
|
|
477
|
+
for c in self.column:
|
|
478
|
+
self.bind_scrolling(c)
|
|
479
|
+
for t in self.title:
|
|
480
|
+
self.bind_scrolling(t)
|
|
481
|
+
for f in self.fname:
|
|
482
|
+
self.bind_scrolling(f)
|
|
483
|
+
|
|
484
|
+
# Store height of one row of text
|
|
485
|
+
for i in range(1000):
|
|
486
|
+
self.index.insert("end", str(i)+"\n")
|
|
487
|
+
height = self.index.winfo_height()
|
|
488
|
+
nrows = int(self.index.index("@0,%d" % height).split(".")[0])
|
|
489
|
+
self.row_height = int(height//nrows)
|
|
490
|
+
|
|
491
|
+
# Set up initial content
|
|
492
|
+
self.update_nrows()
|
|
493
|
+
self.move_to_row(0)
|
|
494
|
+
self.frame.focus_set()
|
|
495
|
+
|
|
496
|
+
self.toplevel.protocol("WM_DELETE_WINDOW", self.destroy)
|
|
497
|
+
|
|
498
|
+
def bind_scrolling(self, widget):
|
|
499
|
+
widget.bind("<Prior>", self.pageup)
|
|
500
|
+
widget.bind("<Next>", self.pagedown)
|
|
501
|
+
widget.bind("<Button-4>", self.pageup)
|
|
502
|
+
widget.bind("<Button-5>", self.pagedown)
|
|
503
|
+
|
|
504
|
+
def on_resize(self, event):
|
|
505
|
+
# Resize the data window
|
|
506
|
+
self.canvas.itemconfig(self.win, height=event.height)
|
|
507
|
+
self.canvas.update_idletasks()
|
|
508
|
+
self.update_nrows()
|
|
509
|
+
# Resize text widgets
|
|
510
|
+
#self.index.configure(height=self.nrows_display)
|
|
511
|
+
#for c in self.column:
|
|
512
|
+
# c.configure(height=self.nrows_display)
|
|
513
|
+
# Redraw values
|
|
514
|
+
self.update_scroll_bar()
|
|
515
|
+
self.update_data()
|
|
516
|
+
|
|
517
|
+
def update_data(self):
|
|
518
|
+
# Redraw array index
|
|
519
|
+
self.index.config(state=NORMAL)
|
|
520
|
+
self.index.delete(1.0, END)
|
|
521
|
+
self.ilast = min(self.ifirst + self.nrows_display, self.nrows_tot)
|
|
522
|
+
for i in range(self.ifirst, self.ilast):
|
|
523
|
+
if i < self.ilast -1:
|
|
524
|
+
self.index.insert("end", str(i)+"\n")
|
|
525
|
+
else:
|
|
526
|
+
self.index.insert("end", str(i))
|
|
527
|
+
self.index.config(state=DISABLED)
|
|
528
|
+
# Redraw columns
|
|
529
|
+
for col, dat, fields in zip(self.column, self.datasets, self.fields):
|
|
530
|
+
# Find data type of this column
|
|
531
|
+
dtype = dat.dtype
|
|
532
|
+
for f in fields:
|
|
533
|
+
dtype = dtype[f]
|
|
534
|
+
# Clear this column
|
|
535
|
+
col.config(state=NORMAL)
|
|
536
|
+
col.delete(1.0, END)
|
|
537
|
+
# Check the data is something we can sensibly display
|
|
538
|
+
if is_too_big(dat.shape):
|
|
539
|
+
col.insert("end", "Unable to display!")
|
|
540
|
+
else:
|
|
541
|
+
# Check at least one value is to be displayed
|
|
542
|
+
if len(dat.shape) == 0:
|
|
543
|
+
# Its a scalar, so only visible if scrolled up to the top
|
|
544
|
+
if self.ifirst == 0:
|
|
545
|
+
# Decide format to use
|
|
546
|
+
fmt = get_format(dat.dtype)
|
|
547
|
+
col.insert("end", value_to_text(dat[...], fmt)+"\n")
|
|
548
|
+
else:
|
|
549
|
+
# Its an array
|
|
550
|
+
if self.ifirst < dat.shape[0] and self.ilast > self.ifirst:
|
|
551
|
+
ilast = min(self.ilast, dat.shape[0])
|
|
552
|
+
# Read the data
|
|
553
|
+
d = dat[self.ifirst:ilast, ...]
|
|
554
|
+
for f in fields:
|
|
555
|
+
d = d[f]
|
|
556
|
+
# Decide format to use
|
|
557
|
+
fmt = get_format(d.dtype)
|
|
558
|
+
# Add values to display
|
|
559
|
+
for i in range(d.shape[0]):
|
|
560
|
+
if i < d.shape[0] - 1:
|
|
561
|
+
col.insert("end", value_to_text(d[i,...],fmt)+
|
|
562
|
+
"\n")
|
|
563
|
+
else:
|
|
564
|
+
col.insert("end", value_to_text(d[i,...],fmt))
|
|
565
|
+
|
|
566
|
+
col.config(state=DISABLED)
|
|
567
|
+
|
|
568
|
+
|
|
569
|
+
def update_scroll_bar(self):
|
|
570
|
+
lo = float(self.ifirst) / float(self.nrows_tot)
|
|
571
|
+
hi = float(self.ilast) / float(self.nrows_tot)
|
|
572
|
+
self.vsbar.set(lo, hi)
|
|
573
|
+
|
|
574
|
+
def scrollbar_moved(self, action, *args):
|
|
575
|
+
if action == "moveto":
|
|
576
|
+
offset = float(args[0])
|
|
577
|
+
i = int(offset * self.nrows_tot)
|
|
578
|
+
elif action == "scroll":
|
|
579
|
+
step = int(args[0])
|
|
580
|
+
what = args[1]
|
|
581
|
+
if what == "units":
|
|
582
|
+
i = self.ifirst + step
|
|
583
|
+
else:
|
|
584
|
+
i = self.ifirst + step * self.nrows_display
|
|
585
|
+
self.move_to_row(i)
|
|
586
|
+
|
|
587
|
+
def on_return(self, event):
|
|
588
|
+
try:
|
|
589
|
+
i = int(self.entry.get())
|
|
590
|
+
except ValueError:
|
|
591
|
+
pass
|
|
592
|
+
else:
|
|
593
|
+
self.move_to_row(i)
|
|
594
|
+
|
|
595
|
+
def move_to_row(self, i):
|
|
596
|
+
self.ifirst = i
|
|
597
|
+
if self.ifirst >= self.nrows_tot - self.nrows_display:
|
|
598
|
+
self.ifirst = self.nrows_tot - self.nrows_display
|
|
599
|
+
if self.ifirst < 0:
|
|
600
|
+
self.ifirst = 0
|
|
601
|
+
self.ilast = min((self.ifirst + self.nrows_display,
|
|
602
|
+
self.nrows_tot))
|
|
603
|
+
self.update_scroll_bar()
|
|
604
|
+
self.update_data()
|
|
605
|
+
|
|
606
|
+
def pageup(self, event):
|
|
607
|
+
self.move_to_row(self.ifirst - self.nrows_display)
|
|
608
|
+
return "break"
|
|
609
|
+
|
|
610
|
+
def pagedown(self, event):
|
|
611
|
+
self.move_to_row(self.ifirst + self.nrows_display)
|
|
612
|
+
return "break"
|
|
613
|
+
|
|
614
|
+
def destroy(self, *args):
|
|
615
|
+
global windows_open
|
|
616
|
+
global root
|
|
617
|
+
self.toplevel.destroy()
|
|
618
|
+
windows_open -= 1
|
|
619
|
+
if windows_open == 0:
|
|
620
|
+
root.destroy()
|
|
621
|
+
|
|
622
|
+
def update_nrows(self):
|
|
623
|
+
n = ((self.index.winfo_height()-0.5*self.row_height)/
|
|
624
|
+
self.row_height)
|
|
625
|
+
n = max((0, n))
|
|
626
|
+
self.nrows_display = int(n)
|
|
627
|
+
|
|
628
|
+
class BrowserWindow():
|
|
629
|
+
def __init__(self, filenames):
|
|
630
|
+
|
|
631
|
+
global windows_open
|
|
632
|
+
windows_open += 1
|
|
633
|
+
|
|
634
|
+
nloaded = 0
|
|
635
|
+
|
|
636
|
+
self.windows = []
|
|
637
|
+
|
|
638
|
+
self.toplevel = Toplevel()
|
|
639
|
+
self.toplevel.title("HDF5 Browser")
|
|
640
|
+
self.toplevel.config(bg="white")
|
|
641
|
+
|
|
642
|
+
canvas = Canvas(self.toplevel)
|
|
643
|
+
canvas.config(bg='white')
|
|
644
|
+
canvas.grid(sticky=N+S+E+W)
|
|
645
|
+
top_item = TopItem()
|
|
646
|
+
top_node = MyTreeNode(canvas, None, top_item)
|
|
647
|
+
|
|
648
|
+
vsbar = AutoScrollbar(self.toplevel)
|
|
649
|
+
vsbar.config(command=canvas.yview)
|
|
650
|
+
canvas.config(yscrollcommand=vsbar.set)
|
|
651
|
+
vsbar.grid(row=0, column=1, sticky=N+S)
|
|
652
|
+
|
|
653
|
+
hsbar = AutoScrollbar(self.toplevel, orient=HORIZONTAL)
|
|
654
|
+
hsbar.config(command=canvas.xview)
|
|
655
|
+
canvas.config(xscrollcommand=hsbar.set)
|
|
656
|
+
hsbar.grid(row=1, column=0, sticky=E+W)
|
|
657
|
+
canvas.bind('<4>', lambda event : canvas.yview('scroll', -1, 'units'))
|
|
658
|
+
canvas.bind('<5>', lambda event : canvas.yview('scroll', 1, 'units'))
|
|
659
|
+
|
|
660
|
+
self.toplevel.grid_rowconfigure(0, weight=1)
|
|
661
|
+
self.toplevel.grid_columnconfigure(0, weight=1)
|
|
662
|
+
|
|
663
|
+
for fname in sys.argv[1:]:
|
|
664
|
+
try:
|
|
665
|
+
f = h5py.File(fname, "r")
|
|
666
|
+
except IOError:
|
|
667
|
+
print("Unable to read file: ", fname)
|
|
668
|
+
continue
|
|
669
|
+
top_item.add_child(HDF5Item(f.id, file=f, is_file=True))
|
|
670
|
+
nloaded += 1
|
|
671
|
+
|
|
672
|
+
if nloaded == 0:
|
|
673
|
+
raise IOError("No files loaded!")
|
|
674
|
+
|
|
675
|
+
bframe = Frame(self.toplevel, relief="raised", borderwidth=2)
|
|
676
|
+
bframe.grid(row=2, column=0, sticky=N+S+E+W)
|
|
677
|
+
b1 = Button(bframe, text="Open selected datasets as table",
|
|
678
|
+
command=self.open_datasets)
|
|
679
|
+
b1.grid(row=0, column=0)
|
|
680
|
+
|
|
681
|
+
b2 = Button(bframe, text="Deselect all",
|
|
682
|
+
command=self.deselect_all)
|
|
683
|
+
b2.grid(row=0, column=1)
|
|
684
|
+
|
|
685
|
+
self.canvas = canvas
|
|
686
|
+
self.top_node = top_node
|
|
687
|
+
self.top_item = top_item
|
|
688
|
+
|
|
689
|
+
top_node.update()
|
|
690
|
+
top_node.expand()
|
|
691
|
+
|
|
692
|
+
self.toplevel.protocol("WM_DELETE_WINDOW", self.destroy)
|
|
693
|
+
|
|
694
|
+
def open_datasets(self):
|
|
695
|
+
if len(selected_nodes.keys()) > 0:
|
|
696
|
+
self.windows.append(DataWindow(selected_nodes))
|
|
697
|
+
|
|
698
|
+
def deselect_all(self):
|
|
699
|
+
global selected_nodes
|
|
700
|
+
self.top_node.deselecttree()
|
|
701
|
+
selected_nodes = {}
|
|
702
|
+
|
|
703
|
+
def destroy(self, *args):
|
|
704
|
+
global windows_open
|
|
705
|
+
global root
|
|
706
|
+
self.toplevel.destroy()
|
|
707
|
+
windows_open -= 1
|
|
708
|
+
if windows_open == 0:
|
|
709
|
+
root.destroy()
|
|
710
|
+
|
|
711
|
+
def main():
|
|
712
|
+
|
|
713
|
+
global root
|
|
714
|
+
global windows_open
|
|
715
|
+
|
|
716
|
+
root = Tk()
|
|
717
|
+
root.withdraw()
|
|
718
|
+
|
|
719
|
+
windows_open = 0
|
|
720
|
+
|
|
721
|
+
filenames = sys.argv[1:]
|
|
722
|
+
mainwin = BrowserWindow(filenames)
|
|
723
|
+
|
|
724
|
+
root.mainloop()
|