legend-pydataobj 1.10.2__tar.gz → 1.11.0__tar.gz

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.
Files changed (92) hide show
  1. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/PKG-INFO +1 -1
  2. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/src/legend_pydataobj.egg-info/PKG-INFO +1 -1
  3. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/src/lgdo/_version.py +2 -2
  4. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/src/lgdo/cli.py +30 -2
  5. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/src/lgdo/lh5/_serializers/read/composite.py +12 -28
  6. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/src/lgdo/lh5/_serializers/read/utils.py +64 -14
  7. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/src/lgdo/lh5/_serializers/read/vector_of_vectors.py +1 -1
  8. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/tests/test_cli.py +18 -0
  9. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/LICENSE +0 -0
  10. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/README.md +0 -0
  11. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/pyproject.toml +0 -0
  12. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/setup.cfg +0 -0
  13. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/src/legend_pydataobj.egg-info/SOURCES.txt +0 -0
  14. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/src/legend_pydataobj.egg-info/dependency_links.txt +0 -0
  15. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/src/legend_pydataobj.egg-info/entry_points.txt +0 -0
  16. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/src/legend_pydataobj.egg-info/not-zip-safe +0 -0
  17. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/src/legend_pydataobj.egg-info/requires.txt +0 -0
  18. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/src/legend_pydataobj.egg-info/top_level.txt +0 -0
  19. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/src/lgdo/__init__.py +0 -0
  20. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/src/lgdo/compression/__init__.py +0 -0
  21. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/src/lgdo/compression/base.py +0 -0
  22. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/src/lgdo/compression/generic.py +0 -0
  23. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/src/lgdo/compression/radware.py +0 -0
  24. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/src/lgdo/compression/utils.py +0 -0
  25. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/src/lgdo/compression/varlen.py +0 -0
  26. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/src/lgdo/lgdo_utils.py +0 -0
  27. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/src/lgdo/lh5/__init__.py +0 -0
  28. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/src/lgdo/lh5/_serializers/__init__.py +0 -0
  29. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/src/lgdo/lh5/_serializers/read/__init__.py +0 -0
  30. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/src/lgdo/lh5/_serializers/read/array.py +0 -0
  31. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/src/lgdo/lh5/_serializers/read/encoded.py +0 -0
  32. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/src/lgdo/lh5/_serializers/read/ndarray.py +0 -0
  33. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/src/lgdo/lh5/_serializers/read/scalar.py +0 -0
  34. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/src/lgdo/lh5/_serializers/write/__init__.py +0 -0
  35. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/src/lgdo/lh5/_serializers/write/array.py +0 -0
  36. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/src/lgdo/lh5/_serializers/write/composite.py +0 -0
  37. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/src/lgdo/lh5/_serializers/write/scalar.py +0 -0
  38. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/src/lgdo/lh5/_serializers/write/vector_of_vectors.py +0 -0
  39. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/src/lgdo/lh5/core.py +0 -0
  40. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/src/lgdo/lh5/datatype.py +0 -0
  41. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/src/lgdo/lh5/exceptions.py +0 -0
  42. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/src/lgdo/lh5/iterator.py +0 -0
  43. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/src/lgdo/lh5/store.py +0 -0
  44. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/src/lgdo/lh5/tools.py +0 -0
  45. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/src/lgdo/lh5/utils.py +0 -0
  46. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/src/lgdo/lh5_store.py +0 -0
  47. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/src/lgdo/logging.py +0 -0
  48. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/src/lgdo/types/__init__.py +0 -0
  49. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/src/lgdo/types/array.py +0 -0
  50. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/src/lgdo/types/arrayofequalsizedarrays.py +0 -0
  51. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/src/lgdo/types/encoded.py +0 -0
  52. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/src/lgdo/types/fixedsizearray.py +0 -0
  53. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/src/lgdo/types/histogram.py +0 -0
  54. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/src/lgdo/types/lgdo.py +0 -0
  55. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/src/lgdo/types/scalar.py +0 -0
  56. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/src/lgdo/types/struct.py +0 -0
  57. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/src/lgdo/types/table.py +0 -0
  58. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/src/lgdo/types/vectorofvectors.py +0 -0
  59. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/src/lgdo/types/vovutils.py +0 -0
  60. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/src/lgdo/types/waveformtable.py +0 -0
  61. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/src/lgdo/units.py +0 -0
  62. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/src/lgdo/utils.py +0 -0
  63. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/tests/compression/conftest.py +0 -0
  64. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/tests/compression/sigcompress/LDQTA_r117_20200110T105115Z_cal_geds_raw-0.dat +0 -0
  65. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/tests/compression/sigcompress/special-wf-clipped.dat +0 -0
  66. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/tests/compression/test_compression.py +0 -0
  67. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/tests/compression/test_radware_sigcompress.py +0 -0
  68. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/tests/compression/test_str2wfcodec.py +0 -0
  69. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/tests/compression/test_uleb128_zigzag_diff.py +0 -0
  70. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/tests/conftest.py +0 -0
  71. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/tests/lh5/conftest.py +0 -0
  72. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/tests/lh5/test_core.py +0 -0
  73. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/tests/lh5/test_lh5_datatype.py +0 -0
  74. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/tests/lh5/test_lh5_iterator.py +0 -0
  75. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/tests/lh5/test_lh5_store.py +0 -0
  76. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/tests/lh5/test_lh5_tools.py +0 -0
  77. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/tests/lh5/test_lh5_utils.py +0 -0
  78. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/tests/lh5/test_lh5_write.py +0 -0
  79. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/tests/test_lgdo_utils.py +0 -0
  80. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/tests/types/test_array.py +0 -0
  81. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/tests/types/test_arrayofequalsizedarrays.py +0 -0
  82. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/tests/types/test_encoded.py +0 -0
  83. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/tests/types/test_fixedsizearray.py +0 -0
  84. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/tests/types/test_histogram.py +0 -0
  85. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/tests/types/test_representations.py +0 -0
  86. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/tests/types/test_scalar.py +0 -0
  87. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/tests/types/test_struct.py +0 -0
  88. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/tests/types/test_table.py +0 -0
  89. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/tests/types/test_table_eval.py +0 -0
  90. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/tests/types/test_vectorofvectors.py +0 -0
  91. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/tests/types/test_vovutils.py +0 -0
  92. {legend_pydataobj-1.10.2 → legend_pydataobj-1.11.0}/tests/types/test_waveformtable.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: legend_pydataobj
3
- Version: 1.10.2
3
+ Version: 1.11.0
4
4
  Summary: LEGEND Python Data Objects
5
5
  Author: The LEGEND Collaboration
6
6
  Maintainer: The LEGEND Collaboration
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: legend_pydataobj
3
- Version: 1.10.2
3
+ Version: 1.11.0
4
4
  Summary: LEGEND Python Data Objects
5
5
  Author: The LEGEND Collaboration
6
6
  Maintainer: The LEGEND Collaboration
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '1.10.2'
16
- __version_tuple__ = version_tuple = (1, 10, 2)
15
+ __version__ = version = '1.11.0'
16
+ __version_tuple__ = version_tuple = (1, 11, 0)
@@ -7,7 +7,7 @@ import fnmatch
7
7
  import logging
8
8
  import sys
9
9
 
10
- from . import Array, Table, VectorOfVectors, __version__, lh5
10
+ from . import Array, Scalar, Struct, Table, VectorOfVectors, __version__, lh5
11
11
  from . import logging as lgdogging # eheheh
12
12
 
13
13
  log = logging.getLogger(__name__)
@@ -212,6 +212,7 @@ Exclude the /data/table1/col1 Table column:
212
212
  store = lh5.LH5Store()
213
213
  h5f0 = store.gimme_file(file0)
214
214
  lgdos = {}
215
+ lgdo_structs = {}
215
216
  # loop over object list in the first file
216
217
  for name in obj_list:
217
218
  # now loop over groups starting from root
@@ -222,7 +223,7 @@ Exclude the /data/table1/col1 Table column:
222
223
  if current in lgdos:
223
224
  break
224
225
 
225
- # not even an LGDO!
226
+ # not even an LGDO (i.e. a plain HDF5 group)!
226
227
  if "datatype" not in h5f0[current].attrs:
227
228
  continue
228
229
 
@@ -232,14 +233,30 @@ Exclude the /data/table1/col1 Table column:
232
233
  # read all!
233
234
  obj, _ = store.read(current, h5f0)
234
235
  lgdos[current] = obj
236
+ elif isinstance(obj, Struct):
237
+ # structs might be used in a "group-like" fashion (i.e. they might only
238
+ # contain array-like objects).
239
+ # note: handle after handling tables, as tables also satisfy this check.
240
+ lgdo_structs[current] = obj.attrs["datatype"]
241
+ continue
242
+ elif isinstance(obj, Scalar):
243
+ msg = f"cannot concat scalar field {current}"
244
+ log.warning(msg)
235
245
 
236
246
  break
237
247
 
238
248
  msg = f"first-level, array-like objects: {lgdos.keys()}"
239
249
  log.debug(msg)
250
+ msg = f"nested structs: {lgdo_structs.keys()}"
251
+ log.debug(msg)
240
252
 
241
253
  h5f0.close()
242
254
 
255
+ if lgdos == {}:
256
+ msg = "did not find any field to concatenate, exit"
257
+ log.error(msg)
258
+ return
259
+
243
260
  # 2. remove (nested) table fields based on obj_list
244
261
 
245
262
  def _inplace_table_filter(name, table, obj_list):
@@ -298,3 +315,14 @@ Exclude the /data/table1/col1 Table column:
298
315
  _inplace_table_filter(name, obj, obj_list)
299
316
 
300
317
  store.write(obj, name, args.output, wo_mode="append")
318
+
319
+ # 5. reset datatypes of the "group-like" structs
320
+
321
+ if lgdo_structs != {}:
322
+ output_file = store.gimme_file(args.output, mode="a")
323
+ for struct, struct_dtype in lgdo_structs.items():
324
+ msg = f"reset datatype of struct {struct} to {struct_dtype}"
325
+ log.debug(msg)
326
+
327
+ output_file[struct].attrs["datatype"] = struct_dtype
328
+ output_file.close()
@@ -3,7 +3,6 @@ from __future__ import annotations
3
3
  import bisect
4
4
  import logging
5
5
  import sys
6
- from collections import defaultdict
7
6
 
8
7
  import h5py
9
8
  import numpy as np
@@ -72,19 +71,8 @@ def _h5_read_lgdo(
72
71
  obj_buf=obj_buf,
73
72
  )
74
73
 
75
- # check field_mask and make it a default dict
76
- if field_mask is None:
77
- field_mask = defaultdict(lambda: True)
78
- elif isinstance(field_mask, dict):
79
- default = True
80
- if len(field_mask) > 0:
81
- default = not field_mask[next(iter(field_mask.keys()))]
82
- field_mask = defaultdict(lambda: default, field_mask)
83
- elif isinstance(field_mask, (list, tuple, set)):
84
- field_mask = defaultdict(bool, {field: True for field in field_mask})
85
- elif not isinstance(field_mask, defaultdict):
86
- msg = "bad field_mask type"
87
- raise ValueError(msg, type(field_mask).__name__)
74
+ # Convert whatever we input into a defaultdict
75
+ field_mask = utils.build_field_mask(field_mask)
88
76
 
89
77
  if lgdotype is Struct:
90
78
  return _h5_read_struct(
@@ -246,18 +234,16 @@ def _h5_read_struct(
246
234
 
247
235
  # determine fields to be read out
248
236
  all_fields = dtypeutils.get_struct_fields(attrs["datatype"])
249
- selected_fields = (
250
- [field for field in all_fields if field_mask[field]]
251
- if field_mask is not None
252
- else all_fields
253
- )
237
+ selected_fields = utils.eval_field_mask(field_mask, all_fields)
254
238
 
255
239
  # modify datatype in attrs if a field_mask was used
256
- attrs["datatype"] = "struct{" + ",".join(selected_fields) + "}"
240
+ attrs["datatype"] = (
241
+ "struct{" + ",".join(field for field, _ in selected_fields) + "}"
242
+ )
257
243
 
258
244
  # loop over fields and read
259
245
  obj_dict = {}
260
- for field in selected_fields:
246
+ for field, submask in selected_fields:
261
247
  # support for integer keys
262
248
  field_key = int(field) if attrs.get("int_keys") else str(field)
263
249
  h5o = h5py.h5o.open(h5g, field.encode("utf-8"))
@@ -269,6 +255,7 @@ def _h5_read_struct(
269
255
  n_rows=n_rows,
270
256
  idx=idx,
271
257
  use_h5idx=use_h5idx,
258
+ field_mask=submask,
272
259
  decompress=decompress,
273
260
  )
274
261
  h5o.close()
@@ -297,19 +284,15 @@ def _h5_read_table(
297
284
 
298
285
  # determine fields to be read out
299
286
  all_fields = dtypeutils.get_struct_fields(attrs["datatype"])
300
- selected_fields = (
301
- [field for field in all_fields if field_mask[field]]
302
- if field_mask is not None
303
- else all_fields
304
- )
287
+ selected_fields = utils.eval_field_mask(field_mask, all_fields)
305
288
 
306
289
  # modify datatype in attrs if a field_mask was used
307
- attrs["datatype"] = "table{" + ",".join(selected_fields) + "}"
290
+ attrs["datatype"] = "table{" + ",".join(field for field, _ in selected_fields) + "}"
308
291
 
309
292
  # read out each of the fields
310
293
  col_dict = {}
311
294
  rows_read = []
312
- for field in selected_fields:
295
+ for field, submask in selected_fields:
313
296
  fld_buf = None
314
297
  if obj_buf is not None:
315
298
  if not isinstance(obj_buf, Table) or field not in obj_buf:
@@ -329,6 +312,7 @@ def _h5_read_table(
329
312
  use_h5idx=use_h5idx,
330
313
  obj_buf=fld_buf,
331
314
  obj_buf_start=obj_buf_start,
315
+ field_mask=submask,
332
316
  decompress=decompress,
333
317
  )
334
318
  h5o.close()
@@ -1,6 +1,8 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import logging
4
+ from collections import defaultdict
5
+ from collections.abc import Collection, Mapping
4
6
 
5
7
  import h5py
6
8
  import numpy as np
@@ -22,6 +24,56 @@ def check_obj_buf_attrs(attrs, new_attrs, fname, oname):
22
24
  raise LH5DecodeError(msg, fname, oname)
23
25
 
24
26
 
27
+ def build_field_mask(field_mask: Mapping[str, bool] | Collection[str]) -> defaultdict:
28
+ # check field_mask and make it a default dict
29
+ if field_mask is None:
30
+ return defaultdict(lambda: True)
31
+ if isinstance(field_mask, dict):
32
+ default = True
33
+ if len(field_mask) > 0:
34
+ default = not field_mask[next(iter(field_mask.keys()))]
35
+ return defaultdict(lambda: default, field_mask)
36
+ if isinstance(field_mask, (list, tuple, set)):
37
+ return defaultdict(bool, {field: True for field in field_mask})
38
+ if isinstance(field_mask, defaultdict):
39
+ return field_mask
40
+ msg = "bad field_mask type"
41
+ raise ValueError(msg, type(field_mask).__name__)
42
+
43
+
44
+ def eval_field_mask(
45
+ field_mask: defaultdict, all_fields: list[str]
46
+ ) -> list[tuple(str, defaultdict)]:
47
+ """Get list of fields that need to be loaded along with a sub-field-mask
48
+ in case we have a nested Table"""
49
+
50
+ if field_mask is None:
51
+ return all_fields
52
+
53
+ this_field_mask = defaultdict(field_mask.default_factory)
54
+ sub_field_masks = {}
55
+
56
+ for key, val in field_mask.items():
57
+ field = key.strip("/")
58
+ pos = field.find("/")
59
+ if pos < 0:
60
+ this_field_mask[field] = val
61
+ else:
62
+ sub_field = field[pos + 1 :]
63
+ field = field[:pos]
64
+ this_field_mask[field] = True
65
+ sub_mask = sub_field_masks.setdefault(
66
+ field, defaultdict(field_mask.default_factory)
67
+ )
68
+ sub_mask[sub_field] = val
69
+
70
+ return [
71
+ (field, sub_field_masks.get(field))
72
+ for field in all_fields
73
+ if this_field_mask[field]
74
+ ]
75
+
76
+
25
77
  def read_attrs(h5o, fname, oname):
26
78
  """Read all attributes for an hdf5 dataset or group using low level API
27
79
  and return them as a dict. Assume all are strings or scalar types."""
@@ -114,6 +166,7 @@ def read_size_in_bytes(h5o, fname, oname, field_mask=None):
114
166
  h5a.read(type_attr)
115
167
  type_attr = type_attr.item().decode()
116
168
  lgdotype = datatype.datatype(type_attr)
169
+ field_mask = build_field_mask(field_mask)
117
170
 
118
171
  # scalars are dim-0 datasets
119
172
  if lgdotype in (
@@ -124,24 +177,21 @@ def read_size_in_bytes(h5o, fname, oname, field_mask=None):
124
177
  ):
125
178
  return int(np.prod(h5o.shape) * h5o.dtype.itemsize)
126
179
 
127
- # structs don't have rows
128
- if lgdotype in (types.Struct, types.Histogram, types.Histogram.Axis):
129
- size = 0
130
- for key in h5o:
131
- obj = h5py.h5o.open(h5o, key)
132
- size += read_size_in_bytes(obj, fname, oname, field_mask)
133
- obj.close()
134
- return size
135
-
136
180
  # tables should have elements with all the same length
137
- if lgdotype in (types.Table, types.WaveformTable):
181
+ if lgdotype in (
182
+ types.Struct,
183
+ types.Histogram,
184
+ types.Histogram.Axis,
185
+ types.Table,
186
+ types.WaveformTable,
187
+ ):
138
188
  # read out each of the fields
139
189
  size = 0
140
- if not field_mask:
141
- field_mask = datatype.get_struct_fields(type_attr)
142
- for field in field_mask:
190
+ all_fields = datatype.get_struct_fields(type_attr)
191
+ selected_fields = eval_field_mask(field_mask, all_fields)
192
+ for field, submask in selected_fields:
143
193
  obj = h5py.h5o.open(h5o, field.encode())
144
- size += read_size_in_bytes(obj, fname, field)
194
+ size += read_size_in_bytes(obj, fname, field, submask)
145
195
  obj.close()
146
196
  return size
147
197
 
@@ -156,7 +156,7 @@ def _h5_read_vector_of_vectors(
156
156
  # grow fd_buf if necessary to hold the data
157
157
  fdb_size = fd_buf_start + fd_n_rows
158
158
  if len(fd_buf) < fdb_size:
159
- fd_buf.nda.resize(fdb_size, refcheck=False)
159
+ fd_buf.resize(fdb_size)
160
160
 
161
161
  # now read
162
162
  h5o = h5py.h5o.open(h5g, b"flattened_data")
@@ -149,3 +149,21 @@ def test_lh5concat(lgnd_test_data, tmptestdir):
149
149
  assert tbl.packet_id[i] == tbl2.packet_id[i - 10]
150
150
  assert np.array_equal(tbl.tracelist[i], tbl2.tracelist[i - 10])
151
151
  assert np.array_equal(tbl.waveform.values[i], tbl2.waveform.values[i - 10])
152
+
153
+ # test concatenating arrays in structs.
154
+ infile1 = f"{tmptestdir}/concat_test_struct_0.lh5"
155
+ tb1 = types.Table(col_dict={"col": types.Array(np.zeros(4))})
156
+ struct1 = types.Struct({"x": tb1})
157
+ store.write(struct1, "stp", infile1, wo_mode="overwrite_file")
158
+
159
+ infile2 = f"{tmptestdir}/concat_test_struct_1.lh5"
160
+ tb2 = types.Table(col_dict={"col": types.Array(np.ones(7))})
161
+ struct2 = types.Struct({"x": tb2})
162
+ store.write(struct2, "stp", infile2, wo_mode="overwrite_file")
163
+
164
+ outfile = f"{tmptestdir}/concat_test_struct_out.lh5"
165
+ cli.lh5concat(["--output", outfile, "--", infile1, infile2])
166
+
167
+ out_stp = store.read("stp", outfile)[0]
168
+ assert out_stp.attrs["datatype"] == "struct{x}"
169
+ assert np.all(out_stp.x["col"].nda == np.array([0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1]))