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.
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/PKG-INFO +2 -1
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/pyproject.toml +1 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/legend_pydataobj.egg-info/PKG-INFO +2 -1
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/legend_pydataobj.egg-info/SOURCES.txt +2 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/legend_pydataobj.egg-info/requires.txt +1 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/__init__.py +4 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/_version.py +2 -2
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/lh5/_serializers/__init__.py +2 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/lh5/_serializers/read/composite.py +62 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/lh5/_serializers/write/composite.py +8 -1
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/lh5/datatype.py +1 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/lh5_store.py +1 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/types/__init__.py +2 -0
- legend_pydataobj-1.9.0/src/lgdo/types/histogram.py +404 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/types/table.py +1 -1
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/tests/conftest.py +1 -1
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/tests/lh5/test_lh5_datatype.py +1 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/tests/lh5/test_lh5_write.py +123 -0
- legend_pydataobj-1.9.0/tests/types/test_histogram.py +292 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/LICENSE +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/README.md +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/setup.cfg +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/legend_pydataobj.egg-info/dependency_links.txt +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/legend_pydataobj.egg-info/entry_points.txt +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/legend_pydataobj.egg-info/not-zip-safe +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/legend_pydataobj.egg-info/top_level.txt +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/cli.py +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/compression/__init__.py +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/compression/base.py +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/compression/generic.py +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/compression/radware.py +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/compression/utils.py +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/compression/varlen.py +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/lgdo_utils.py +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/lh5/__init__.py +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/lh5/_serializers/read/__init__.py +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/lh5/_serializers/read/array.py +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/lh5/_serializers/read/encoded.py +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/lh5/_serializers/read/ndarray.py +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/lh5/_serializers/read/scalar.py +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/lh5/_serializers/read/utils.py +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/lh5/_serializers/read/vector_of_vectors.py +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/lh5/_serializers/write/__init__.py +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/lh5/_serializers/write/array.py +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/lh5/_serializers/write/scalar.py +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/lh5/_serializers/write/vector_of_vectors.py +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/lh5/core.py +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/lh5/exceptions.py +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/lh5/iterator.py +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/lh5/store.py +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/lh5/tools.py +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/lh5/utils.py +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/logging.py +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/types/array.py +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/types/arrayofequalsizedarrays.py +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/types/encoded.py +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/types/fixedsizearray.py +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/types/lgdo.py +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/types/scalar.py +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/types/struct.py +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/types/vectorofvectors.py +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/types/vovutils.py +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/types/waveformtable.py +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/units.py +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/utils.py +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/tests/compression/conftest.py +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/tests/compression/sigcompress/LDQTA_r117_20200110T105115Z_cal_geds_raw-0.dat +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/tests/compression/sigcompress/special-wf-clipped.dat +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/tests/compression/test_compression.py +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/tests/compression/test_radware_sigcompress.py +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/tests/compression/test_str2wfcodec.py +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/tests/compression/test_uleb128_zigzag_diff.py +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/tests/lh5/conftest.py +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/tests/lh5/test_core.py +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/tests/lh5/test_lh5_iterator.py +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/tests/lh5/test_lh5_store.py +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/tests/lh5/test_lh5_tools.py +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/tests/lh5/test_lh5_utils.py +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/tests/test_cli.py +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/tests/test_lgdo_utils.py +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/tests/types/test_array.py +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/tests/types/test_arrayofequalsizedarrays.py +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/tests/types/test_encoded.py +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/tests/types/test_fixedsizearray.py +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/tests/types/test_representations.py +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/tests/types/test_scalar.py +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/tests/types/test_struct.py +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/tests/types/test_table.py +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/tests/types/test_table_eval.py +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/tests/types/test_vectorofvectors.py +0 -0
- {legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/tests/types/test_vovutils.py +0 -0
- {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.
|
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
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: legend_pydataobj
|
3
|
-
Version: 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
|
@@ -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",
|
@@ -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",
|
{legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/lh5/_serializers/read/composite.py
RENAMED
@@ -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
|
{legend_pydataobj-1.8.1 → legend_pydataobj-1.9.0}/src/lgdo/lh5/_serializers/write/composite.py
RENAMED
@@ -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
|
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+>\{.+\}$"),
|
@@ -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
|
171
|
+
"""Alias for :meth:`Struct.remove_field` using table terminology 'column'."""
|
172
172
|
super().remove_field(name, delete)
|
173
173
|
|
174
174
|
def join(
|