legend-pydataobj 1.8.1__tar.gz → 1.9.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.8.1 → legend_pydataobj-1.9.0}/PKG-INFO +2 -1
  2. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/pyproject.toml +1 -0
  3. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/legend_pydataobj.egg-info/PKG-INFO +2 -1
  4. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/legend_pydataobj.egg-info/SOURCES.txt +2 -0
  5. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/legend_pydataobj.egg-info/requires.txt +1 -0
  6. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/__init__.py +4 -0
  7. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/_version.py +2 -2
  8. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/lh5/_serializers/__init__.py +2 -0
  9. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/lh5/_serializers/read/composite.py +62 -0
  10. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/lh5/_serializers/write/composite.py +8 -1
  11. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/lh5/datatype.py +1 -0
  12. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/lh5_store.py +1 -0
  13. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/types/__init__.py +2 -0
  14. legend_pydataobj-1.9.0/src/lgdo/types/histogram.py +404 -0
  15. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/types/table.py +1 -1
  16. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/tests/conftest.py +1 -1
  17. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/tests/lh5/test_lh5_datatype.py +1 -0
  18. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/tests/lh5/test_lh5_write.py +123 -0
  19. legend_pydataobj-1.9.0/tests/types/test_histogram.py +292 -0
  20. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/LICENSE +0 -0
  21. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/README.md +0 -0
  22. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/setup.cfg +0 -0
  23. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/legend_pydataobj.egg-info/dependency_links.txt +0 -0
  24. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/legend_pydataobj.egg-info/entry_points.txt +0 -0
  25. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/legend_pydataobj.egg-info/not-zip-safe +0 -0
  26. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/legend_pydataobj.egg-info/top_level.txt +0 -0
  27. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/cli.py +0 -0
  28. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/compression/__init__.py +0 -0
  29. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/compression/base.py +0 -0
  30. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/compression/generic.py +0 -0
  31. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/compression/radware.py +0 -0
  32. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/compression/utils.py +0 -0
  33. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/compression/varlen.py +0 -0
  34. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/lgdo_utils.py +0 -0
  35. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/lh5/__init__.py +0 -0
  36. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/lh5/_serializers/read/__init__.py +0 -0
  37. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/lh5/_serializers/read/array.py +0 -0
  38. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/lh5/_serializers/read/encoded.py +0 -0
  39. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/lh5/_serializers/read/ndarray.py +0 -0
  40. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/lh5/_serializers/read/scalar.py +0 -0
  41. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/lh5/_serializers/read/utils.py +0 -0
  42. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/lh5/_serializers/read/vector_of_vectors.py +0 -0
  43. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/lh5/_serializers/write/__init__.py +0 -0
  44. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/lh5/_serializers/write/array.py +0 -0
  45. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/lh5/_serializers/write/scalar.py +0 -0
  46. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/lh5/_serializers/write/vector_of_vectors.py +0 -0
  47. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/lh5/core.py +0 -0
  48. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/lh5/exceptions.py +0 -0
  49. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/lh5/iterator.py +0 -0
  50. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/lh5/store.py +0 -0
  51. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/lh5/tools.py +0 -0
  52. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/lh5/utils.py +0 -0
  53. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/logging.py +0 -0
  54. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/types/array.py +0 -0
  55. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/types/arrayofequalsizedarrays.py +0 -0
  56. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/types/encoded.py +0 -0
  57. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/types/fixedsizearray.py +0 -0
  58. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/types/lgdo.py +0 -0
  59. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/types/scalar.py +0 -0
  60. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/types/struct.py +0 -0
  61. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/types/vectorofvectors.py +0 -0
  62. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/types/vovutils.py +0 -0
  63. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/types/waveformtable.py +0 -0
  64. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/units.py +0 -0
  65. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/utils.py +0 -0
  66. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/tests/compression/conftest.py +0 -0
  67. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/tests/compression/sigcompress/LDQTA_r117_20200110T105115Z_cal_geds_raw-0.dat +0 -0
  68. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/tests/compression/sigcompress/special-wf-clipped.dat +0 -0
  69. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/tests/compression/test_compression.py +0 -0
  70. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/tests/compression/test_radware_sigcompress.py +0 -0
  71. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/tests/compression/test_str2wfcodec.py +0 -0
  72. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/tests/compression/test_uleb128_zigzag_diff.py +0 -0
  73. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/tests/lh5/conftest.py +0 -0
  74. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/tests/lh5/test_core.py +0 -0
  75. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/tests/lh5/test_lh5_iterator.py +0 -0
  76. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/tests/lh5/test_lh5_store.py +0 -0
  77. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/tests/lh5/test_lh5_tools.py +0 -0
  78. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/tests/lh5/test_lh5_utils.py +0 -0
  79. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/tests/test_cli.py +0 -0
  80. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/tests/test_lgdo_utils.py +0 -0
  81. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/tests/types/test_array.py +0 -0
  82. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/tests/types/test_arrayofequalsizedarrays.py +0 -0
  83. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/tests/types/test_encoded.py +0 -0
  84. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/tests/types/test_fixedsizearray.py +0 -0
  85. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/tests/types/test_representations.py +0 -0
  86. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/tests/types/test_scalar.py +0 -0
  87. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/tests/types/test_struct.py +0 -0
  88. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/tests/types/test_table.py +0 -0
  89. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/tests/types/test_table_eval.py +0 -0
  90. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/tests/types/test_vectorofvectors.py +0 -0
  91. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/tests/types/test_vovutils.py +0 -0
  92. {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.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.8.1
3
+ Version: 1.9.0
4
4
  Summary: LEGEND Python Data Objects
5
5
  Author: The LEGEND Collaboration
6
6
  Maintainer: The LEGEND Collaboration
@@ -702,6 +702,7 @@ Requires-Dist: awkward-pandas
702
702
  Requires-Dist: colorlog
703
703
  Requires-Dist: h5py>=3.2
704
704
  Requires-Dist: hdf5plugin
705
+ Requires-Dist: hist
705
706
  Requires-Dist: numba!=0.53.*,!=0.54.*
706
707
  Requires-Dist: numexpr
707
708
  Requires-Dist: numpy>=1.21
@@ -36,6 +36,7 @@ dependencies = [
36
36
  "colorlog",
37
37
  "h5py>=3.2",
38
38
  "hdf5plugin",
39
+ "hist",
39
40
  "numba!=0.53.*,!=0.54.*",
40
41
  "numexpr",
41
42
  "numpy>=1.21",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: legend_pydataobj
3
- Version: 1.8.1
3
+ Version: 1.9.0
4
4
  Summary: LEGEND Python Data Objects
5
5
  Author: The LEGEND Collaboration
6
6
  Maintainer: The LEGEND Collaboration
@@ -702,6 +702,7 @@ Requires-Dist: awkward-pandas
702
702
  Requires-Dist: colorlog
703
703
  Requires-Dist: h5py>=3.2
704
704
  Requires-Dist: hdf5plugin
705
+ Requires-Dist: hist
705
706
  Requires-Dist: numba!=0.53.*,!=0.54.*
706
707
  Requires-Dist: numexpr
707
708
  Requires-Dist: numpy>=1.21
@@ -49,6 +49,7 @@ src/lgdo/types/array.py
49
49
  src/lgdo/types/arrayofequalsizedarrays.py
50
50
  src/lgdo/types/encoded.py
51
51
  src/lgdo/types/fixedsizearray.py
52
+ src/lgdo/types/histogram.py
52
53
  src/lgdo/types/lgdo.py
53
54
  src/lgdo/types/scalar.py
54
55
  src/lgdo/types/struct.py
@@ -78,6 +79,7 @@ tests/types/test_array.py
78
79
  tests/types/test_arrayofequalsizedarrays.py
79
80
  tests/types/test_encoded.py
80
81
  tests/types/test_fixedsizearray.py
82
+ tests/types/test_histogram.py
81
83
  tests/types/test_representations.py
82
84
  tests/types/test_scalar.py
83
85
  tests/types/test_struct.py
@@ -3,6 +3,7 @@ awkward-pandas
3
3
  colorlog
4
4
  h5py>=3.2
5
5
  hdf5plugin
6
+ hist
6
7
  numba!=0.53.*,!=0.54.*
7
8
  numexpr
8
9
  numpy>=1.21
@@ -33,6 +33,8 @@ basic data object classes are:
33
33
  :class:`dict`
34
34
  * :class:`.Table`: a :class:`.Struct` whose elements ("columns") are all array
35
35
  types with the same length (number of rows)
36
+ * :class:`.Histogram`: holds an array of histogrammed data, and the associated
37
+ binning of arbitrary dimensionality.
36
38
 
37
39
  Currently the primary on-disk format for LGDO object is LEGEND HDF5 (LH5) files. IO
38
40
  is done via the class :class:`.lh5_store.LH5Store`. LH5 files can also be
@@ -50,6 +52,7 @@ from .types import (
50
52
  ArrayOfEncodedEqualSizedArrays,
51
53
  ArrayOfEqualSizedArrays,
52
54
  FixedSizeArray,
55
+ Histogram,
53
56
  Scalar,
54
57
  Struct,
55
58
  Table,
@@ -63,6 +66,7 @@ __all__ = [
63
66
  "ArrayOfEqualSizedArrays",
64
67
  "ArrayOfEncodedEqualSizedArrays",
65
68
  "FixedSizeArray",
69
+ "Histogram",
66
70
  "LGDO",
67
71
  "Scalar",
68
72
  "Struct",
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '1.8.1'
16
- __version_tuple__ = version_tuple = (1, 8, 1)
15
+ __version__ = version = '1.9.0'
16
+ __version_tuple__ = version_tuple = (1, 9, 0)
@@ -7,6 +7,7 @@ from .read.array import (
7
7
  _h5_read_ndarray,
8
8
  )
9
9
  from .read.composite import (
10
+ _h5_read_histogram,
10
11
  _h5_read_lgdo,
11
12
  _h5_read_struct,
12
13
  _h5_read_table,
@@ -32,6 +33,7 @@ __all__ = [
32
33
  "_h5_read_array_of_equalsized_arrays",
33
34
  "_h5_read_struct",
34
35
  "_h5_read_table",
36
+ "_h5_read_histogram",
35
37
  "_h5_read_scalar",
36
38
  "_h5_read_array_of_encoded_equalsized_arrays",
37
39
  "_h5_read_vector_of_encoded_vectors",
@@ -13,6 +13,7 @@ from ....types import (
13
13
  ArrayOfEncodedEqualSizedArrays,
14
14
  ArrayOfEqualSizedArrays,
15
15
  FixedSizeArray,
16
+ Histogram,
16
17
  Scalar,
17
18
  Struct,
18
19
  Table,
@@ -168,6 +169,19 @@ def _h5_read_lgdo(
168
169
  decompress=decompress,
169
170
  )
170
171
 
172
+ if lgdotype is Histogram:
173
+ return _h5_read_histogram(
174
+ h5o,
175
+ start_row=start_row,
176
+ n_rows=n_rows,
177
+ idx=idx,
178
+ use_h5idx=use_h5idx,
179
+ field_mask=field_mask,
180
+ obj_buf=obj_buf,
181
+ obj_buf_start=obj_buf_start,
182
+ decompress=decompress,
183
+ )
184
+
171
185
  if lgdotype is ArrayOfEncodedEqualSizedArrays:
172
186
  return _h5_read_array_of_encoded_equalsized_arrays(
173
187
  h5o,
@@ -385,3 +399,51 @@ def _h5_read_table(
385
399
  utils.check_obj_buf_attrs(obj_buf.attrs, attrs, h5g)
386
400
 
387
401
  return obj_buf, n_rows_read
402
+
403
+
404
+ def _h5_read_histogram(
405
+ h5g,
406
+ start_row=0,
407
+ n_rows=sys.maxsize,
408
+ idx=None,
409
+ use_h5idx=False,
410
+ field_mask=None,
411
+ obj_buf=None,
412
+ obj_buf_start=0,
413
+ decompress=True,
414
+ ):
415
+ if obj_buf is not None or obj_buf_start != 0:
416
+ msg = "reading a histogram into an existing object buffer is not supported"
417
+ raise LH5DecodeError(msg, h5g)
418
+
419
+ struct, n_rows_read = _h5_read_struct(
420
+ h5g,
421
+ start_row,
422
+ n_rows,
423
+ idx,
424
+ use_h5idx,
425
+ field_mask,
426
+ decompress,
427
+ )
428
+ binning = []
429
+ for _, a in struct.binning.items():
430
+ be = a.binedges
431
+ if isinstance(be, Struct):
432
+ b = (None, be.first.value, be.last.value, be.step.value, a.closedleft.value)
433
+ elif isinstance(be, Array):
434
+ b = (be, None, None, None, a.closedleft.value)
435
+ else:
436
+ msg = "unexpected binning of histogram"
437
+ raise LH5DecodeError(msg, h5g)
438
+ ax = Histogram.Axis(*b)
439
+ # copy attrs to "clone" the "whole" struct.
440
+ ax.attrs = a.getattrs(datatype=True)
441
+ ax["binedges"].attrs = be.getattrs(datatype=True)
442
+ binning.append(ax)
443
+
444
+ isdensity = struct.isdensity.value
445
+ weights = struct.weights
446
+ attrs = struct.getattrs(datatype=True)
447
+ histogram = Histogram(weights, binning, isdensity, attrs=attrs)
448
+
449
+ return histogram, n_rows_read
@@ -63,8 +63,15 @@ def _h5_write_lgdo(
63
63
  msg = f"can't overwrite '{name}' in wo_mode 'write_safe'"
64
64
  raise LH5EncodeError(msg, lh5_file, group, name)
65
65
 
66
- # struct or table or waveform table
66
+ # struct, table, waveform table or histogram.
67
67
  if isinstance(obj, types.Struct):
68
+ if isinstance(obj, types.Histogram) and wo_mode not in ["w", "o", "of"]:
69
+ msg = f"can't append-write histogram in wo_mode '{wo_mode}'"
70
+ raise LH5EncodeError(msg, lh5_file, group, name)
71
+ if isinstance(obj, types.Histogram) and write_start != 0:
72
+ msg = f"can't write histogram in wo_mode '{wo_mode}' with write_start != 0"
73
+ raise LH5EncodeError(msg, lh5_file, group, name)
74
+
68
75
  return _h5_write_struct(
69
76
  obj,
70
77
  name,
@@ -14,6 +14,7 @@ _lgdo_datatype_map: dict[str, lgdo.LGDO] = OrderedDict(
14
14
  lgdo.ArrayOfEncodedEqualSizedArrays,
15
15
  r"^array_of_encoded_equalsized_arrays<1,1>\{.+\}$",
16
16
  ),
17
+ (lgdo.Histogram, r"^struct\{binning,weights,isdensity\}$"),
17
18
  (lgdo.Struct, r"^struct\{.*\}$"),
18
19
  (lgdo.Table, r"^table\{.*\}$"),
19
20
  (lgdo.FixedSizeArray, r"^fixedsize_array<\d+>\{.+\}$"),
@@ -20,6 +20,7 @@ from .types import (
20
20
  ArrayOfEncodedEqualSizedArrays, # noqa: F401
21
21
  ArrayOfEqualSizedArrays, # noqa: F401
22
22
  FixedSizeArray, # noqa: F401
23
+ Histogram, # noqa: F401
23
24
  Scalar,
24
25
  Struct,
25
26
  Table, # noqa: F401
@@ -6,6 +6,7 @@ from .array import Array
6
6
  from .arrayofequalsizedarrays import ArrayOfEqualSizedArrays
7
7
  from .encoded import ArrayOfEncodedEqualSizedArrays, VectorOfEncodedVectors
8
8
  from .fixedsizearray import FixedSizeArray
9
+ from .histogram import Histogram
9
10
  from .lgdo import LGDO
10
11
  from .scalar import Scalar
11
12
  from .struct import Struct
@@ -18,6 +19,7 @@ __all__ = [
18
19
  "ArrayOfEqualSizedArrays",
19
20
  "ArrayOfEncodedEqualSizedArrays",
20
21
  "FixedSizeArray",
22
+ "Histogram",
21
23
  "LGDO",
22
24
  "Scalar",
23
25
  "Struct",
@@ -0,0 +1,404 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Iterable
4
+ from typing import Any
5
+
6
+ import hist
7
+ import numpy as np
8
+ from numpy.typing import NDArray
9
+
10
+ from .array import Array
11
+ from .lgdo import LGDO
12
+ from .scalar import Scalar
13
+ from .struct import Struct
14
+
15
+
16
+ class Histogram(Struct):
17
+ class Axis(Struct):
18
+ def __init__(
19
+ self,
20
+ edges: NDArray | Array | None,
21
+ first: float | None,
22
+ last: float | None,
23
+ step: float | None,
24
+ closedleft: bool = True,
25
+ binedge_attrs: dict[str, Any] | None = None,
26
+ ) -> None:
27
+ """
28
+ A special struct to group axis parameters for use in a :class:`Histogram`.
29
+
30
+ Depending on the parameters, an axis either can have
31
+
32
+ * a binning described by a range object, if ``first``, ``last`` and ``step``
33
+ are passed, or
34
+ * a variable binning described by the ``edges`` array.
35
+
36
+ Parameters
37
+ ----------
38
+ edges
39
+ an array of edges that describe the binning of this axis.
40
+ first
41
+ left edge of the leftmost bin
42
+ last
43
+ right edge of the rightmost bin
44
+ step
45
+ step size (width of each bin)
46
+ closedleft
47
+ if True, the bin intervals are left-closed :math:`[a,b)`;
48
+ if False, intervals are right-closed :math:`(a,b]`.
49
+ binedge_attrs
50
+ attributes that will be added to the ``binedges`` LGDO that
51
+ is part of the axis struct.
52
+ """
53
+ if edges is not None and (
54
+ first is not None or last is not None or step is not None
55
+ ):
56
+ msg = "can only construct Axis either from edges or from range"
57
+ raise ValueError(msg)
58
+ if edges is None and (first is None or last is None or step is None):
59
+ msg = "did not pass all range parameters"
60
+ raise ValueError(msg)
61
+
62
+ if edges is None:
63
+ edges = Struct(
64
+ {
65
+ "first": Scalar(first),
66
+ "last": Scalar(last),
67
+ "step": Scalar(step),
68
+ },
69
+ binedge_attrs,
70
+ )
71
+ else:
72
+ if not isinstance(edges, Array):
73
+ edges = Array(edges, attrs=binedge_attrs)
74
+ elif binedge_attrs is not None:
75
+ msg = "passed both binedge as Array LGDO instance and binedge_attrs"
76
+ raise ValueError(msg)
77
+
78
+ if len(edges.nda.shape) != 1:
79
+ msg = "must pass an array<1>{real} as edges vector"
80
+ raise ValueError(msg)
81
+
82
+ super().__init__({"binedges": edges, "closedleft": Scalar(closedleft)})
83
+
84
+ @classmethod
85
+ def from_edges(
86
+ cls,
87
+ edges: NDArray | Iterable[float],
88
+ binedge_attrs: dict[str, Any] | None = None,
89
+ ) -> Histogram.Axis:
90
+ """Create a new axis with variable binning described by ``edges``."""
91
+ edges = np.array(edges)
92
+ return cls(edges, None, None, None, True, binedge_attrs)
93
+
94
+ @classmethod
95
+ def from_range_edges(
96
+ cls,
97
+ edges: NDArray | Iterable[float],
98
+ binedge_attrs: dict[str, Any] | None = None,
99
+ ) -> Histogram.Axis:
100
+ """Create a new axis from the binning described by ``edges``, but try to convert it to
101
+ a evenly-spaced range object first.
102
+
103
+ .. warning ::
104
+
105
+ This function might return a wrong binning, especially in the case of very small
106
+ magnitudes of the spacing. See the documentation of :func:`numpy.isclose` for
107
+ details. Use this function only with caution, if you know the binning's order of
108
+ magniutude.
109
+ """
110
+ edges = np.array(edges)
111
+ edge_diff = np.diff(edges)
112
+ if np.any(~np.isclose(edge_diff, edge_diff[0])):
113
+ return cls(edges, None, None, None, True, binedge_attrs)
114
+ return cls(None, edges[0], edges[-1], edge_diff[0], True, binedge_attrs)
115
+
116
+ @property
117
+ def is_range(self) -> bool:
118
+ return isinstance(self["binedges"], Struct)
119
+
120
+ @property
121
+ def first(self) -> float:
122
+ if not self.is_range:
123
+ msg = "Axis is not a range"
124
+ raise TypeError(msg)
125
+ return self["binedges"]["first"].value
126
+
127
+ @property
128
+ def last(self) -> float:
129
+ if not self.is_range:
130
+ msg = "Axis is not a range"
131
+ raise TypeError(msg)
132
+ return self["binedges"]["last"].value
133
+
134
+ @property
135
+ def step(self) -> float:
136
+ if not self.is_range:
137
+ msg = "Axis is not a range"
138
+ raise TypeError(msg)
139
+ return self["binedges"]["step"].value
140
+
141
+ @property
142
+ def closedleft(self) -> bool:
143
+ return self["closedleft"].value
144
+
145
+ @property
146
+ def nbins(self) -> int:
147
+ """Return the number of bins, both for variable and range binning."""
148
+ if self.is_range:
149
+ bins = (self.last - self.first) / self.step
150
+ bins_int = int(np.rint(bins))
151
+ assert np.isclose(bins, bins_int)
152
+ return bins_int
153
+ return len(self["binedges"].nda) - 1
154
+
155
+ @property
156
+ def edges(self) -> NDArray:
157
+ """Return all binedges, both for variable and range binning."""
158
+ if self.is_range:
159
+ return np.linspace(self.first, self.last, self.nbins + 1)
160
+ return self["binedges"].nda
161
+
162
+ def __str__(self) -> str:
163
+ thr_orig = np.get_printoptions()["threshold"]
164
+ np.set_printoptions(threshold=8)
165
+
166
+ if self.is_range:
167
+ string = f"first={self.first}, last={self.last}, step={self.step}"
168
+ else:
169
+ string = f"edges={self.edges}"
170
+ string += f", closedleft={self.closedleft}"
171
+
172
+ attrs = self.get_binedgeattrs()
173
+ if attrs:
174
+ string += f" with attrs={attrs}"
175
+
176
+ np.set_printoptions(threshold=thr_orig)
177
+ return string
178
+
179
+ def get_binedgeattrs(self, datatype: bool = False) -> dict:
180
+ """Return a copy of the LGDO attributes dictionary of the binedges
181
+
182
+ Parameters
183
+ ----------
184
+ datatype
185
+ if ``False``, remove ``datatype`` attribute from the output
186
+ dictionary.
187
+ """
188
+ return self["binedges"].getattrs(datatype)
189
+
190
+ def __init__(
191
+ self,
192
+ weights: hist.Hist | NDArray | Array,
193
+ binning: None
194
+ | Iterable[Histogram.Axis]
195
+ | Iterable[NDArray]
196
+ | Iterable[tuple[float, float, float]] = None,
197
+ isdensity: bool = False,
198
+ attrs: dict[str, Any] | None = None,
199
+ binedge_attrs: dict[str, Any] | None = None,
200
+ ) -> None:
201
+ """A special struct to contain histogrammed data.
202
+
203
+ Parameters
204
+ ----------
205
+ weights
206
+ An :class:`numpy.ndarray` to be used for this object's internal
207
+ array, or a :class:`hist.Hist` object, whose data view is used
208
+ for this object's internal array.
209
+ Note: the array/histogram view is used directly, not copied
210
+ binning
211
+ * has to by None if a :class:`hist.Hist` has been passed as ``weights``
212
+ * can be a list of pre-initialized :class:`Histogram.Axis`
213
+ * can be a list of tuples, each representing a range, ``(first, last, step)``
214
+ * can be a list of numpy arrays, as returned by :func:`numpy.histogramdd`.
215
+ isdensity
216
+ If True, all bin contents represent a density (amount per volume), and not
217
+ an absolute amount.
218
+ binedge_attrs
219
+ attributes that will be added to the all ``binedges`` of all axes.
220
+ This does not work if :class:`Histogram.Axis` instances are directly passed
221
+ as binning.
222
+ attrs
223
+ a set of user attributes to be carried along with this LGDO.
224
+ """
225
+ if isinstance(weights, hist.Hist):
226
+ if binning is not None:
227
+ msg = "not allowed to pass custom binning if constructing from hist.Hist instance"
228
+ raise ValueError(msg)
229
+ if isdensity:
230
+ msg = "not allowed to pass isdensity=True if constructing from hist.Hist instance"
231
+ raise ValueError(msg)
232
+
233
+ if weights.sum(flow=True) != weights.sum(flow=False):
234
+ msg = "flow bins of hist.Hist cannot be represented"
235
+ raise ValueError(msg)
236
+ weights_view = weights.view(flow=False)
237
+ if type(weights_view) is not np.ndarray:
238
+ msg = "only simple numpy-backed storages can be used in a hist.Hist"
239
+ raise ValueError(msg)
240
+ w = Array(weights_view)
241
+
242
+ b = []
243
+ for ax in weights.axes:
244
+ if not isinstance(ax, (hist.axis.Regular, hist.axis.Variable)):
245
+ msg = "only regular or variable axes of hist.Hist can be converted"
246
+ raise ValueError(msg)
247
+ if isinstance(ax, hist.axis.Regular):
248
+ step = (ax.edges[-1] - ax.edges[0]) / ax.size
249
+ bax = Histogram.Axis(
250
+ None, ax.edges[0], ax.edges[-1], step, True, binedge_attrs
251
+ )
252
+ b.append(bax)
253
+ else:
254
+ b.append(Histogram.Axis.from_edges(ax.edges, binedge_attrs))
255
+ else:
256
+ if binning is None:
257
+ msg = "need to also pass binning if passing histogram as array"
258
+ raise ValueError(msg)
259
+ w = weights if isinstance(weights, Array) else Array(weights)
260
+
261
+ if all(isinstance(ax, Histogram.Axis) for ax in binning):
262
+ if binedge_attrs is not None:
263
+ msg = "passed both binedges as Axis instances and binedge_attrs"
264
+ raise ValueError(msg)
265
+ b = binning
266
+ elif all(isinstance(ax, np.ndarray) for ax in binning):
267
+ b = [Histogram.Axis.from_edges(ax, binedge_attrs) for ax in binning]
268
+ elif all(isinstance(ax, tuple) for ax in binning):
269
+ b = [Histogram.Axis(None, *ax, True, binedge_attrs) for ax in binning]
270
+ else:
271
+ msg = "invalid binning object passed"
272
+ raise ValueError(msg)
273
+
274
+ if len(binning) != len(w.nda.shape):
275
+ msg = "binning and weight dimensions do not match"
276
+ raise ValueError(msg)
277
+ for i, ax in enumerate(b):
278
+ if ax.nbins != w.nda.shape[i]:
279
+ msg = f"bin count does not match weight count along axis {i}"
280
+ raise ValueError(msg)
281
+
282
+ b = Struct({f"axis_{i}": a for i, a in enumerate(b)})
283
+
284
+ super().__init__(
285
+ {"binning": b, "weights": w, "isdensity": Scalar(isdensity)},
286
+ attrs,
287
+ )
288
+
289
+ @property
290
+ def isdensity(self) -> bool:
291
+ return self["isdensity"].value
292
+
293
+ @property
294
+ def weights(self) -> Array:
295
+ return self["weights"]
296
+
297
+ @property
298
+ def binning(self) -> tuple[Histogram.Axis, ...]:
299
+ bins = sorted(self["binning"].items())
300
+ assert all(isinstance(v, Histogram.Axis) for k, v in bins)
301
+ return tuple(v for _, v in bins)
302
+
303
+ def __setitem__(self, name: str, obj: LGDO) -> None:
304
+ # do not allow for new attributes on this
305
+ msg = "histogram fields cannot be mutated"
306
+ raise TypeError(msg)
307
+
308
+ def __getattr__(self, name: str) -> None:
309
+ # do not allow for new attributes on this
310
+ msg = "histogram fields cannot be mutated"
311
+ raise TypeError(msg)
312
+
313
+ def add_field(self, name: str | int, obj: LGDO) -> None: # noqa: ARG002
314
+ """
315
+ .. error ::
316
+
317
+ Not applicable: A histogram cannot be used as a struct
318
+ """
319
+ msg = "histogram fields cannot be mutated"
320
+ raise TypeError(msg)
321
+
322
+ def remove_field(self, name: str | int, delete: bool = False) -> None: # noqa: ARG002
323
+ """
324
+ .. error ::
325
+
326
+ Not applicable: A histogram cannot be used as a struct
327
+ """
328
+ msg = "histogram fields cannot be mutated"
329
+ raise TypeError(msg)
330
+
331
+ def __str__(self) -> str:
332
+ string = "{\n"
333
+ for k, v in enumerate(self.binning):
334
+ string += f" 'axis_{k}': {v},\n"
335
+ string += "}"
336
+
337
+ attrs = self.getattrs()
338
+ if attrs:
339
+ string += f" with attrs={attrs}"
340
+
341
+ return string
342
+
343
+ def view_as(
344
+ self,
345
+ library: str,
346
+ ) -> tuple[NDArray] | hist.Hist:
347
+ r"""View the histogram data as a third-party format data structure.
348
+
349
+ This is typically a zero-copy or nearly zero-copy operation.
350
+
351
+ Supported third-party formats are:
352
+
353
+ - ``np``: returns a tuple of binning and an :class:`np.ndarray`, similar
354
+ to the return value of :func:`numpy.histogramdd`.
355
+ - ``hist``: returns an :class:`hist.Hist` that holds **a copy** of this
356
+ histogram's data.
357
+
358
+ Warning
359
+ -------
360
+ Viewing as ``hist`` will perform a copy of the stored histogram data.
361
+
362
+ Parameters
363
+ ----------
364
+ library
365
+ format of the returned data view.
366
+
367
+ See Also
368
+ --------
369
+ .LGDO.view_as
370
+ """
371
+ if library == "hist":
372
+ if self.isdensity:
373
+ msg = "hist.Hist cannot represent density histograms"
374
+ raise ValueError(msg)
375
+
376
+ hist_axes = []
377
+ for a in self.binning:
378
+ if not a.closedleft:
379
+ msg = "hist.Hist cannot represent right-closed intervals"
380
+ raise ValueError(msg)
381
+ if a.is_range:
382
+ hist_ax = hist.axis.Regular(
383
+ bins=a.nbins,
384
+ start=a.first,
385
+ stop=a.last,
386
+ underflow=False,
387
+ overflow=False,
388
+ )
389
+ else:
390
+ hist_ax = hist.axis.Variable(
391
+ a.edges,
392
+ underflow=False,
393
+ overflow=False,
394
+ )
395
+ hist_axes.append(hist_ax)
396
+
397
+ return hist.Hist(*hist_axes, data=self.weights.view_as("np"))
398
+
399
+ if library == "np":
400
+ edges = tuple([a.edges for a in self.binning])
401
+ return self.weights.view_as("np"), edges
402
+
403
+ msg = f"{library!r} is not a supported third-party format."
404
+ raise TypeError(msg)
@@ -168,7 +168,7 @@ class Table(Struct):
168
168
  self.add_field(name, obj, use_obj_size=use_obj_size)
169
169
 
170
170
  def remove_column(self, name: str, delete: bool = False) -> None:
171
- """Alias for :meth:`.remove_field` using table terminology 'column'."""
171
+ """Alias for :meth:`Struct.remove_field` using table terminology 'column'."""
172
172
  super().remove_field(name, delete)
173
173
 
174
174
  def join(
@@ -26,5 +26,5 @@ def pytest_sessionfinish(session, exitstatus): # noqa: ARG001
26
26
  @pytest.fixture(scope="session")
27
27
  def lgnd_test_data():
28
28
  ldata = LegendTestData()
29
- ldata.checkout("8f55832")
29
+ ldata.checkout("82ad6c2")
30
30
  return ldata
@@ -33,6 +33,7 @@ def test_datatype2lgdo():
33
33
  assert d("struct{a,b,c,d}") == types.Struct
34
34
  assert d("struct{}") == types.Struct
35
35
  assert d("table{a,b,c,d}") == types.Table
36
+ assert d("struct{binning,weights,isdensity}") == types.Histogram
36
37
 
37
38
 
38
39
  def test_utils():