reboost 0.8.4__py3-none-any.whl → 0.9.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.
- reboost/_version.py +2 -2
- reboost/hpge/psd.py +168 -104
- reboost/hpge/utils.py +99 -13
- reboost/optmap/convolve.py +164 -17
- reboost/spms/__init__.py +10 -1
- reboost/spms/pe.py +80 -1
- reboost/utils.py +2 -2
- {reboost-0.8.4.dist-info → reboost-0.9.0.dist-info}/METADATA +1 -1
- {reboost-0.8.4.dist-info → reboost-0.9.0.dist-info}/RECORD +13 -13
- {reboost-0.8.4.dist-info → reboost-0.9.0.dist-info}/WHEEL +0 -0
- {reboost-0.8.4.dist-info → reboost-0.9.0.dist-info}/entry_points.txt +0 -0
- {reboost-0.8.4.dist-info → reboost-0.9.0.dist-info}/licenses/LICENSE +0 -0
- {reboost-0.8.4.dist-info → reboost-0.9.0.dist-info}/top_level.txt +0 -0
reboost/_version.py
CHANGED
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '0.
|
|
32
|
-
__version_tuple__ = version_tuple = (0,
|
|
31
|
+
__version__ = version = '0.9.0'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 9, 0)
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
reboost/hpge/psd.py
CHANGED
|
@@ -13,7 +13,7 @@ from numpy.typing import ArrayLike, NDArray
|
|
|
13
13
|
|
|
14
14
|
from .. import units
|
|
15
15
|
from ..units import ureg as u
|
|
16
|
-
from .utils import
|
|
16
|
+
from .utils import HPGePulseShapeLibrary, HPGeRZField
|
|
17
17
|
|
|
18
18
|
log = logging.getLogger(__name__)
|
|
19
19
|
|
|
@@ -83,7 +83,7 @@ def drift_time(
|
|
|
83
83
|
xloc: ArrayLike,
|
|
84
84
|
yloc: ArrayLike,
|
|
85
85
|
zloc: ArrayLike,
|
|
86
|
-
dt_map:
|
|
86
|
+
dt_map: HPGeRZField,
|
|
87
87
|
coord_offset: pint.Quantity | pyg4ometry.gdml.Position = (0, 0, 0) * u.m,
|
|
88
88
|
) -> VectorOfVectors:
|
|
89
89
|
"""Calculates drift times for each step (cluster) in an HPGe detector.
|
|
@@ -454,33 +454,7 @@ def _get_waveform_value_surface(
|
|
|
454
454
|
start: float,
|
|
455
455
|
dt: float,
|
|
456
456
|
) -> tuple[float, float]:
|
|
457
|
-
"""Get the value of the waveform at a certain index.
|
|
458
|
-
|
|
459
|
-
Parameters
|
|
460
|
-
----------
|
|
461
|
-
idx
|
|
462
|
-
the index of the time array to find the waveform at.
|
|
463
|
-
edep
|
|
464
|
-
Array of energies for each step
|
|
465
|
-
drift_time
|
|
466
|
-
Array of drift times for each step
|
|
467
|
-
template
|
|
468
|
-
array of the template for the current waveforms
|
|
469
|
-
templates_surface
|
|
470
|
-
The current templates from the surface.
|
|
471
|
-
activeness_surface
|
|
472
|
-
The total collected charge for each surface point.
|
|
473
|
-
dist_step_in_um
|
|
474
|
-
The binning in distance for the surface pulse library.
|
|
475
|
-
start
|
|
476
|
-
first time value of the template
|
|
477
|
-
dt
|
|
478
|
-
timestep (in ns) for the template.
|
|
479
|
-
|
|
480
|
-
Returns
|
|
481
|
-
-------
|
|
482
|
-
Value of the current waveform and the energy.
|
|
483
|
-
"""
|
|
457
|
+
"""Get the value of the waveform at a certain index."""
|
|
484
458
|
n = len(bulk_template)
|
|
485
459
|
out = 0
|
|
486
460
|
etmp = 0
|
|
@@ -513,6 +487,7 @@ def _get_waveform_value_surface(
|
|
|
513
487
|
else:
|
|
514
488
|
out += E * _interpolate_pulse_model(bulk_template, time, start, start + dt * n, dt, mu)
|
|
515
489
|
etmp += E
|
|
490
|
+
|
|
516
491
|
return out, etmp
|
|
517
492
|
|
|
518
493
|
|
|
@@ -525,40 +500,78 @@ def _get_waveform_value(
|
|
|
525
500
|
start: float,
|
|
526
501
|
dt: float,
|
|
527
502
|
) -> float:
|
|
528
|
-
"""Get the value of the waveform at a certain index.
|
|
503
|
+
"""Get the value of the waveform at a certain index."""
|
|
504
|
+
out = 0
|
|
505
|
+
time = start + dt * idx
|
|
529
506
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
idx
|
|
533
|
-
the index of the time array to find the waveform at.
|
|
534
|
-
edep
|
|
535
|
-
Array of energies for each step
|
|
536
|
-
drift_time
|
|
537
|
-
Array of drift times for each step
|
|
538
|
-
template
|
|
539
|
-
array of the template for the current waveforms
|
|
540
|
-
start
|
|
541
|
-
first time value of the template
|
|
542
|
-
dt
|
|
543
|
-
timestep (in ns) for the template.
|
|
507
|
+
for i in range(len(edep)):
|
|
508
|
+
n = len(template)
|
|
544
509
|
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
510
|
+
E = edep[i]
|
|
511
|
+
mu = drift_time[i]
|
|
512
|
+
|
|
513
|
+
out += E * _interpolate_pulse_model(template, time, start, start + dt * n, dt, mu)
|
|
514
|
+
|
|
515
|
+
return out
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
@numba.njit(cache=True)
|
|
519
|
+
def _get_waveform_value_pulse_shape_library(
|
|
520
|
+
idx: int,
|
|
521
|
+
edep: ak.Array,
|
|
522
|
+
drift_time: ak.Array,
|
|
523
|
+
r: ak.Array,
|
|
524
|
+
z: ak.Array,
|
|
525
|
+
pulse_shape_library: tuple[np.array, np.array, np.array],
|
|
526
|
+
start: float,
|
|
527
|
+
dt: float,
|
|
528
|
+
) -> float:
|
|
529
|
+
"""Get the value of the waveform at a certain index, using the pulse shape library."""
|
|
550
530
|
out = 0
|
|
551
531
|
time = start + dt * idx
|
|
552
532
|
|
|
553
533
|
for i in range(len(edep)):
|
|
534
|
+
ri, zi = _get_template_idx(r[i], z[i], pulse_shape_library[1], pulse_shape_library[2])
|
|
535
|
+
|
|
536
|
+
n = len(pulse_shape_library[0][ri][zi])
|
|
537
|
+
|
|
554
538
|
E = edep[i]
|
|
555
539
|
mu = drift_time[i]
|
|
556
540
|
|
|
557
|
-
out += E * _interpolate_pulse_model(
|
|
541
|
+
out += E * _interpolate_pulse_model(
|
|
542
|
+
pulse_shape_library[0][ri][zi], time, start, start + dt * n, dt, mu
|
|
543
|
+
)
|
|
558
544
|
|
|
559
545
|
return out
|
|
560
546
|
|
|
561
547
|
|
|
548
|
+
@numba.njit(cache=True)
|
|
549
|
+
def _get_template_idx(
|
|
550
|
+
r: float,
|
|
551
|
+
z: float,
|
|
552
|
+
r_grid: np.array,
|
|
553
|
+
z_grid: np.array,
|
|
554
|
+
) -> tuple[int, int]:
|
|
555
|
+
"""Extract the closest template to a given (r,z) point with uniform grid, apart from the first and last point."""
|
|
556
|
+
if r < r_grid[1]:
|
|
557
|
+
ri = 0
|
|
558
|
+
elif r > r_grid[-2]:
|
|
559
|
+
ri = len(r_grid) - 1
|
|
560
|
+
else:
|
|
561
|
+
dr = r_grid[2] - r_grid[1]
|
|
562
|
+
ri = int((r - r_grid[1]) / dr) + 1
|
|
563
|
+
|
|
564
|
+
if z < z_grid[1]:
|
|
565
|
+
zi = 0
|
|
566
|
+
elif z > z_grid[-2]:
|
|
567
|
+
zi = len(z_grid) - 1
|
|
568
|
+
else:
|
|
569
|
+
dz = z_grid[2] - z_grid[1]
|
|
570
|
+
zi = int((z - z_grid[1]) / dz) + 1
|
|
571
|
+
|
|
572
|
+
return ri, zi
|
|
573
|
+
|
|
574
|
+
|
|
562
575
|
def get_current_template(
|
|
563
576
|
low: float = -1000, high: float = 4000, step: float = 1, mean_aoe: float = 1, **kwargs
|
|
564
577
|
) -> tuple[NDArray, NDArray]:
|
|
@@ -598,7 +611,10 @@ def _get_waveform_maximum_impl(
|
|
|
598
611
|
t: ArrayLike,
|
|
599
612
|
e: ArrayLike,
|
|
600
613
|
dist: ArrayLike,
|
|
614
|
+
r: ArrayLike,
|
|
615
|
+
z: ArrayLike,
|
|
601
616
|
template: ArrayLike,
|
|
617
|
+
pulse_shape_library: tuple[np.array, np.array, np.array],
|
|
602
618
|
templates_surface: ArrayLike,
|
|
603
619
|
activeness_surface: ArrayLike,
|
|
604
620
|
tmin: float,
|
|
@@ -609,18 +625,9 @@ def _get_waveform_maximum_impl(
|
|
|
609
625
|
time_step: int,
|
|
610
626
|
surface_step_in_um: float,
|
|
611
627
|
include_surface_effects: bool,
|
|
628
|
+
use_library: bool,
|
|
612
629
|
):
|
|
613
|
-
"""Basic implementation to get the maximum of the waveform.
|
|
614
|
-
|
|
615
|
-
Parameters
|
|
616
|
-
----------
|
|
617
|
-
t
|
|
618
|
-
drift time for each step.
|
|
619
|
-
e
|
|
620
|
-
energy for each step.
|
|
621
|
-
dist
|
|
622
|
-
distance to surface for each step.
|
|
623
|
-
"""
|
|
630
|
+
"""Basic implementation to get the maximum of the waveform."""
|
|
624
631
|
max_a = 0
|
|
625
632
|
max_t = 0
|
|
626
633
|
energy = np.sum(e)
|
|
@@ -634,8 +641,12 @@ def _get_waveform_maximum_impl(
|
|
|
634
641
|
if time < tmin or (time > (tmax + time_step)):
|
|
635
642
|
continue
|
|
636
643
|
|
|
637
|
-
if not has_surface_hit:
|
|
644
|
+
if not has_surface_hit and (not use_library):
|
|
638
645
|
val_tmp = _get_waveform_value(j, e, t, template, start=start, dt=1.0)
|
|
646
|
+
elif use_library:
|
|
647
|
+
val_tmp = _get_waveform_value_pulse_shape_library(
|
|
648
|
+
j, e, t, r, z, pulse_shape_library, start=start, dt=1.0
|
|
649
|
+
)
|
|
639
650
|
else:
|
|
640
651
|
val_tmp, energy = _get_waveform_value_surface(
|
|
641
652
|
j,
|
|
@@ -663,9 +674,13 @@ def _estimate_current_impl(
|
|
|
663
674
|
edep: ak.Array,
|
|
664
675
|
dt: ak.Array,
|
|
665
676
|
dist_to_nplus: ak.Array,
|
|
677
|
+
r: ak.Array,
|
|
678
|
+
z: ak.Array,
|
|
666
679
|
template: np.array,
|
|
680
|
+
pulse_shape_library: tuple[np.array, np.array, np.array],
|
|
667
681
|
times: np.array,
|
|
668
682
|
include_surface_effects: bool,
|
|
683
|
+
use_library: bool,
|
|
669
684
|
fccd: float,
|
|
670
685
|
templates_surface: np.array,
|
|
671
686
|
activeness_surface: np.array,
|
|
@@ -674,26 +689,13 @@ def _estimate_current_impl(
|
|
|
674
689
|
"""Estimate the maximum current that would be measured in the HPGe detector.
|
|
675
690
|
|
|
676
691
|
This is based on extracting a waveform with :func:`get_current_waveform` and finding the maxima of it.
|
|
677
|
-
|
|
678
|
-
Parameters
|
|
679
|
-
----------
|
|
680
|
-
edep
|
|
681
|
-
Array of energies for each step.
|
|
682
|
-
dt
|
|
683
|
-
Array of drift times for each step.
|
|
684
|
-
dist_to_nplus
|
|
685
|
-
Array of distance to nplus contact for each step (can be `None`, in which case no surface effects are included.)
|
|
686
|
-
template
|
|
687
|
-
array of the bulk pulse template
|
|
688
|
-
times
|
|
689
|
-
time-stamps for the bulk pulse template
|
|
690
692
|
"""
|
|
691
693
|
A = np.zeros(len(dt))
|
|
692
694
|
maximum_t = np.zeros(len(dt))
|
|
693
695
|
energy = np.zeros(len(dt))
|
|
694
696
|
|
|
695
697
|
time_step = 1
|
|
696
|
-
n = len(
|
|
698
|
+
n = len(times)
|
|
697
699
|
start = times[0]
|
|
698
700
|
|
|
699
701
|
if include_surface_effects:
|
|
@@ -707,6 +709,9 @@ def _estimate_current_impl(
|
|
|
707
709
|
for i in range(len(dt)):
|
|
708
710
|
t = np.asarray(dt[i])
|
|
709
711
|
e = np.asarray(edep[i])
|
|
712
|
+
r_tmp = np.asarray(r[i])
|
|
713
|
+
z_tmp = np.asarray(z[i])
|
|
714
|
+
|
|
710
715
|
dist = np.asarray(dist_to_nplus[i])
|
|
711
716
|
|
|
712
717
|
# get the expected maximum
|
|
@@ -720,7 +725,6 @@ def _estimate_current_impl(
|
|
|
720
725
|
for j, d in enumerate(dist):
|
|
721
726
|
dtmp = int(d / surface_step_in_um)
|
|
722
727
|
|
|
723
|
-
# Use branchless selection
|
|
724
728
|
use_offset = dtmp <= ncols
|
|
725
729
|
offset_val = offsets[dtmp] if use_offset else 0.0
|
|
726
730
|
time_tmp = t[j] + offset_val * use_offset
|
|
@@ -737,9 +741,12 @@ def _estimate_current_impl(
|
|
|
737
741
|
t,
|
|
738
742
|
e,
|
|
739
743
|
dist,
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
744
|
+
r=r_tmp,
|
|
745
|
+
z=z_tmp,
|
|
746
|
+
template=template,
|
|
747
|
+
pulse_shape_library=pulse_shape_library,
|
|
748
|
+
templates_surface=templates_surface,
|
|
749
|
+
activeness_surface=activeness_surface,
|
|
743
750
|
tmin=tmin,
|
|
744
751
|
tmax=tmax,
|
|
745
752
|
start=start,
|
|
@@ -748,17 +755,73 @@ def _estimate_current_impl(
|
|
|
748
755
|
time_step=time_step,
|
|
749
756
|
surface_step_in_um=surface_step_in_um,
|
|
750
757
|
include_surface_effects=include_surface_effects,
|
|
758
|
+
use_library=use_library,
|
|
751
759
|
)
|
|
752
760
|
|
|
753
761
|
return A, maximum_t, energy
|
|
754
762
|
|
|
755
763
|
|
|
764
|
+
def prepare_surface_inputs(
|
|
765
|
+
dist_to_nplus: ak.Array,
|
|
766
|
+
edep: ak.Array,
|
|
767
|
+
templates_surface: ArrayLike,
|
|
768
|
+
activeness_surface,
|
|
769
|
+
template: ArrayLike,
|
|
770
|
+
) -> tuple:
|
|
771
|
+
"""Prepare the inputs needed for surface sims."""
|
|
772
|
+
include_surface_effects = False
|
|
773
|
+
|
|
774
|
+
# prepare surface templates
|
|
775
|
+
if templates_surface is not None:
|
|
776
|
+
if dist_to_nplus is None:
|
|
777
|
+
msg = "Surface effects requested but distance not provided"
|
|
778
|
+
raise ValueError(msg)
|
|
779
|
+
|
|
780
|
+
include_surface_effects = True
|
|
781
|
+
else:
|
|
782
|
+
# convert types to keep numba happy
|
|
783
|
+
templates_surface = np.zeros((1, len(template)))
|
|
784
|
+
dist_to_nplus = ak.full_like(edep, np.nan)
|
|
785
|
+
|
|
786
|
+
# convert types for numba
|
|
787
|
+
if activeness_surface is None:
|
|
788
|
+
activeness_surface = np.zeros(len(template))
|
|
789
|
+
|
|
790
|
+
return include_surface_effects, dist_to_nplus, templates_surface, activeness_surface
|
|
791
|
+
|
|
792
|
+
|
|
793
|
+
def prepare_pulse_shape_library(
|
|
794
|
+
template: ArrayLike | HPGePulseShapeLibrary,
|
|
795
|
+
times: ArrayLike,
|
|
796
|
+
edep: ak.Array,
|
|
797
|
+
r: ak.Array,
|
|
798
|
+
z: ak.Array,
|
|
799
|
+
):
|
|
800
|
+
"""Prepare the inputs for the full pulse shape library."""
|
|
801
|
+
use_library = False
|
|
802
|
+
if isinstance(template, HPGePulseShapeLibrary):
|
|
803
|
+
# convert to a form we can use
|
|
804
|
+
times = template.t
|
|
805
|
+
pulse_shape_library = (template.waveforms, template.r, template.z)
|
|
806
|
+
template = np.zeros_like(template.waveforms[0][0])
|
|
807
|
+
use_library = True
|
|
808
|
+
|
|
809
|
+
else:
|
|
810
|
+
pulse_shape_library = (np.zeros((1, 1, len(template))), np.zeros(1), np.zeros(1))
|
|
811
|
+
r = ak.full_like(edep, np.nan)
|
|
812
|
+
z = ak.full_like(edep, np.nan)
|
|
813
|
+
|
|
814
|
+
return use_library, pulse_shape_library, template, times, r, z
|
|
815
|
+
|
|
816
|
+
|
|
756
817
|
def maximum_current(
|
|
757
818
|
edep: ArrayLike,
|
|
758
819
|
drift_time: ArrayLike,
|
|
759
820
|
dist_to_nplus: ArrayLike | None = None,
|
|
821
|
+
r: ArrayLike | None = None,
|
|
822
|
+
z: ArrayLike | None = None,
|
|
760
823
|
*,
|
|
761
|
-
template: np.array,
|
|
824
|
+
template: np.array | HPGePulseShapeLibrary,
|
|
762
825
|
times: np.array,
|
|
763
826
|
fccd_in_um: float = 0,
|
|
764
827
|
templates_surface: ArrayLike | None = None,
|
|
@@ -776,16 +839,22 @@ def maximum_current(
|
|
|
776
839
|
Array of drift times for each step.
|
|
777
840
|
dist_to_nplus
|
|
778
841
|
Distance to n-plus electrode, only needed if surface heuristics are enabled.
|
|
842
|
+
r
|
|
843
|
+
Radial coordinate (only needed if a full PSS library is used)
|
|
844
|
+
z
|
|
845
|
+
z coordinate (only needed if a full PSS library is used).
|
|
779
846
|
template
|
|
780
|
-
array of the bulk pulse template
|
|
847
|
+
array of the bulk pulse template, can also be a :class:`HPGePulseShapeLibrary`.
|
|
781
848
|
times
|
|
782
849
|
time-stamps for the bulk pulse template
|
|
783
|
-
|
|
850
|
+
fccd_in_um
|
|
784
851
|
Value of the full-charge-collection depth, if `None` no surface corrections are performed.
|
|
785
|
-
|
|
852
|
+
templates_surface
|
|
786
853
|
2D array (distance, time) of the rate of charge arriving at the p-n junction. Each row
|
|
787
854
|
should be an array of length 10000 giving the charge arriving at the p-n junction for each timestep
|
|
788
855
|
(in ns). This is produced by :func:`.hpge.surface.get_surface_response` or other libraries.
|
|
856
|
+
activeness_surface
|
|
857
|
+
An array of the activeness at each surface point.
|
|
789
858
|
surface_step_in_um
|
|
790
859
|
Distance step for the surface library.
|
|
791
860
|
return_mode
|
|
@@ -796,40 +865,35 @@ def maximum_current(
|
|
|
796
865
|
An Array of the maximum current/ time / energy for each hit.
|
|
797
866
|
"""
|
|
798
867
|
# extract LGDO data and units
|
|
799
|
-
|
|
800
868
|
drift_time, _ = units.unwrap_lgdo(drift_time)
|
|
801
869
|
edep, _ = units.unwrap_lgdo(edep)
|
|
802
870
|
dist_to_nplus, _ = units.unwrap_lgdo(dist_to_nplus)
|
|
871
|
+
r, _ = units.unwrap_lgdo(r)
|
|
872
|
+
z, _ = units.unwrap_lgdo(z)
|
|
803
873
|
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
msg = "Surface effects requested but distance not provided"
|
|
809
|
-
raise ValueError(msg)
|
|
810
|
-
|
|
811
|
-
include_surface_effects = True
|
|
812
|
-
else:
|
|
813
|
-
# convert types to keep numba happy
|
|
814
|
-
templates_surface = np.zeros((1, len(template)))
|
|
815
|
-
dist_to_nplus = ak.full_like(edep, np.nan)
|
|
816
|
-
|
|
817
|
-
# convert types for numba
|
|
818
|
-
if activeness_surface is None:
|
|
819
|
-
activeness_surface = np.zeros(len(template))
|
|
874
|
+
# prepare inputs for surface sims
|
|
875
|
+
include_surface_effects, dist_to_nplus, templates_surface, activeness_surface = (
|
|
876
|
+
prepare_surface_inputs(dist_to_nplus, edep, templates_surface, activeness_surface, template)
|
|
877
|
+
)
|
|
820
878
|
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
879
|
+
# and for the full PS library
|
|
880
|
+
use_library, pulse_shape_library, template, times, r, z = prepare_pulse_shape_library(
|
|
881
|
+
template, times, edep, r, z
|
|
882
|
+
)
|
|
824
883
|
|
|
884
|
+
# and now compute the current
|
|
825
885
|
curr, time, energy = _estimate_current_impl(
|
|
826
886
|
ak.values_astype(ak.Array(edep), np.float64),
|
|
827
887
|
ak.values_astype(ak.Array(drift_time), np.float64),
|
|
828
888
|
ak.values_astype(ak.Array(dist_to_nplus), np.float64),
|
|
889
|
+
r=ak.values_astype(ak.Array(r), np.float64),
|
|
890
|
+
z=ak.values_astype(ak.Array(z), np.float64),
|
|
829
891
|
template=template,
|
|
892
|
+
pulse_shape_library=pulse_shape_library,
|
|
830
893
|
times=times,
|
|
831
894
|
fccd=fccd_in_um,
|
|
832
895
|
include_surface_effects=include_surface_effects,
|
|
896
|
+
use_library=use_library,
|
|
833
897
|
templates_surface=templates_surface,
|
|
834
898
|
activeness_surface=activeness_surface,
|
|
835
899
|
surface_step_in_um=surface_step_in_um,
|
reboost/hpge/utils.py
CHANGED
|
@@ -11,23 +11,105 @@ from lgdo import lh5
|
|
|
11
11
|
from scipy.interpolate import RegularGridInterpolator
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
class
|
|
15
|
-
"""A
|
|
14
|
+
class HPGePulseShapeLibrary(NamedTuple):
|
|
15
|
+
"""A set of templates defined in the cylindrical-like (r, z) HPGe plane."""
|
|
16
|
+
|
|
17
|
+
waveforms: np.array
|
|
18
|
+
"Field, function of the coordinates (r, z)."
|
|
19
|
+
r_units: pint.Unit
|
|
20
|
+
"Physical units of the coordinate `r`."
|
|
21
|
+
z_units: pint.Unit
|
|
22
|
+
"Physical units of the coordinate `z`."
|
|
23
|
+
t_units: pint.Unit
|
|
24
|
+
"Physical units of the times."
|
|
25
|
+
r: np.array
|
|
26
|
+
"One dimensional arrays specifying the radial coordinates"
|
|
27
|
+
z: np.array
|
|
28
|
+
"One dimensional arrays specifying the z coordinates"
|
|
29
|
+
t: np.array
|
|
30
|
+
"Times used to define the waveforms"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def get_hpge_pulse_shape_library(
|
|
34
|
+
filename: str, obj: str, field: str, out_of_bounds_val: int | float = np.nan
|
|
35
|
+
) -> HPGePulseShapeLibrary:
|
|
36
|
+
"""Create the pulse shape library, holding simulated waveforms.
|
|
37
|
+
|
|
38
|
+
Reads from disk the following data structure: ::
|
|
39
|
+
|
|
40
|
+
FILENAME/
|
|
41
|
+
└── OBJ · struct{r,z,dt,t0,FIELD}
|
|
42
|
+
├── r · array<1>{real} ── {'units': 'UNITS'}
|
|
43
|
+
├── z · array<1>{real} ── {'units': 'UNITS'}
|
|
44
|
+
├── dt · real ── {'units': 'UNITS'}
|
|
45
|
+
├── t0 · real ── {'units': 'UNITS'}
|
|
46
|
+
└── FIELD · array<3>{real} ── {'units': 'UNITS'}
|
|
47
|
+
|
|
48
|
+
The conventions follow those used for :func:`get_hpge_rz_field`.
|
|
49
|
+
For the FIELD the first and second dimensions are `r` and `z`, respectively, with the last
|
|
50
|
+
dimension representing the waveform. dt and t0 define the timestamps for the waveforms.
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
Parameters
|
|
54
|
+
----------
|
|
55
|
+
filename
|
|
56
|
+
name of the LH5 file containing the gridded scalar field.
|
|
57
|
+
obj
|
|
58
|
+
name of the HDF5 dataset where the data is saved.
|
|
59
|
+
field
|
|
60
|
+
name of the HDF5 dataset holding the waveforms.
|
|
61
|
+
out_of_bounds_val
|
|
62
|
+
value to use to replace NaNs in the field values.
|
|
63
|
+
"""
|
|
64
|
+
data = lh5.read(obj, filename)
|
|
65
|
+
|
|
66
|
+
if not isinstance(data, lgdo.Struct):
|
|
67
|
+
msg = f"{obj} in {filename} is not an LGDO Struct"
|
|
68
|
+
raise ValueError(msg)
|
|
69
|
+
|
|
70
|
+
t0 = data["t0"].value
|
|
71
|
+
dt = data["dt"].value
|
|
72
|
+
|
|
73
|
+
t0_u = data["t0"].attrs["units"]
|
|
74
|
+
dt_u = data["dt"].attrs["units"]
|
|
75
|
+
|
|
76
|
+
if t0_u != dt_u:
|
|
77
|
+
msg = "t0 and dt must have the same units"
|
|
78
|
+
raise ValueError(msg)
|
|
79
|
+
|
|
80
|
+
tu = t0_u
|
|
81
|
+
|
|
82
|
+
data = AttrsDict(
|
|
83
|
+
{
|
|
84
|
+
k: np.nan_to_num(data[k].view_as("np", with_units=True), nan=out_of_bounds_val)
|
|
85
|
+
for k in ("r", "z", field)
|
|
86
|
+
}
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
times = t0 + np.arange(np.shape(data[field].m)[2]) * dt
|
|
90
|
+
|
|
91
|
+
return HPGePulseShapeLibrary(data[field].m, data.r.u, data.z.u, tu, data.r.m, data.z.m, times)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class HPGeRZField(NamedTuple):
|
|
95
|
+
"""A field defined in the cylindrical-like (r, z) HPGe plane."""
|
|
16
96
|
|
|
17
97
|
φ: Callable
|
|
18
|
-
"
|
|
98
|
+
"Field, function of the coordinates (r, z)."
|
|
19
99
|
r_units: pint.Unit
|
|
20
100
|
"Physical units of the coordinate `r`."
|
|
21
101
|
z_units: pint.Unit
|
|
22
102
|
"Physical units of the coordinate `z`."
|
|
23
103
|
φ_units: pint.Unit
|
|
24
104
|
"Physical units of the field."
|
|
105
|
+
ndim: int
|
|
106
|
+
"Number of dimensions for the field"
|
|
25
107
|
|
|
26
108
|
|
|
27
|
-
def
|
|
109
|
+
def get_hpge_rz_field(
|
|
28
110
|
filename: str, obj: str, field: str, out_of_bounds_val: int | float = np.nan, **kwargs
|
|
29
|
-
) ->
|
|
30
|
-
"""Create an interpolator for a gridded
|
|
111
|
+
) -> HPGeRZField:
|
|
112
|
+
"""Create an interpolator for a gridded HPGe field defined on `(r, z)`.
|
|
31
113
|
|
|
32
114
|
Reads from disk the following data structure: ::
|
|
33
115
|
|
|
@@ -35,7 +117,7 @@ def get_hpge_scalar_rz_field(
|
|
|
35
117
|
└── OBJ · struct{r,z,FIELD}
|
|
36
118
|
├── r · array<1>{real} ── {'units': 'UNITS'}
|
|
37
119
|
├── z · array<1>{real} ── {'units': 'UNITS'}
|
|
38
|
-
└── FIELD · array<2>{real} ── {'units': 'UNITS'}
|
|
120
|
+
└── FIELD · array<N+2>{real} ── {'units': 'UNITS'}
|
|
39
121
|
|
|
40
122
|
where ``FILENAME``, ``OBJ`` and ``FIELD`` are provided as
|
|
41
123
|
arguments to this function. `obj` is a :class:`~lgdo.types.struct.Struct`,
|
|
@@ -43,9 +125,11 @@ def get_hpge_scalar_rz_field(
|
|
|
43
125
|
coordinates of the rectangular grid — not the coordinates of each single
|
|
44
126
|
grid point. In this coordinate system, the center of the p+ contact surface
|
|
45
127
|
is at `(0, 0)`, with the p+ contact facing downwards. `field` is instead a
|
|
46
|
-
two-dimensional array specifying the field value at each grid point. The
|
|
47
|
-
first and second dimensions are `r` and `z`, respectively
|
|
48
|
-
|
|
128
|
+
ndim plus two-dimensional array specifying the field value at each grid point. The
|
|
129
|
+
first and second dimensions are `r` and `z`, respectively, with the latter dimensions
|
|
130
|
+
representing the dimensions of the output field.
|
|
131
|
+
|
|
132
|
+
NaN values are interpreted as points outside the detector profile in the `(r, z)` plane.
|
|
49
133
|
|
|
50
134
|
Before returning a :class:`HPGeScalarRZField`, the gridded field is fed to
|
|
51
135
|
:class:`scipy.interpolate.RegularGridInterpolator`.
|
|
@@ -73,7 +157,9 @@ def get_hpge_scalar_rz_field(
|
|
|
73
157
|
for k in ("r", "z", field)
|
|
74
158
|
}
|
|
75
159
|
)
|
|
160
|
+
ndim = data[field].m.ndim - 2
|
|
161
|
+
interpolator = RegularGridInterpolator(
|
|
162
|
+
(data.r.m, data.z.m), data[field].m, **(kwargs | {"fill_value": out_of_bounds_val})
|
|
163
|
+
)
|
|
76
164
|
|
|
77
|
-
interpolator
|
|
78
|
-
|
|
79
|
-
return HPGeScalarRZField(interpolator, data.r.u, data.z.u, data[field].u)
|
|
165
|
+
return HPGeRZField(interpolator, data.r.u, data.z.u, data[field].u, ndim)
|
reboost/optmap/convolve.py
CHANGED
|
@@ -156,15 +156,70 @@ def iterate_stepwise_depositions_scintillate(
|
|
|
156
156
|
raise ValueError(msg)
|
|
157
157
|
|
|
158
158
|
rng = np.random.default_rng() if rng is None else rng
|
|
159
|
-
|
|
159
|
+
counts = ak.num(edep_hits.edep)
|
|
160
|
+
output_array = _iterate_stepwise_depositions_scintillate(
|
|
161
|
+
edep_hits, rng, scint_mat_params, mode, ak.sum(counts)
|
|
162
|
+
)
|
|
160
163
|
|
|
161
|
-
|
|
162
|
-
builder = ak.ArrayBuilder()
|
|
163
|
-
for r in output_list:
|
|
164
|
-
with builder.list():
|
|
165
|
-
builder.extend(r)
|
|
164
|
+
return ak.unflatten(output_array, counts)
|
|
166
165
|
|
|
167
|
-
|
|
166
|
+
|
|
167
|
+
def iterate_stepwise_depositions_numdet(
|
|
168
|
+
edep_hits: ak.Array,
|
|
169
|
+
optmap: OptmapForConvolve,
|
|
170
|
+
det: str,
|
|
171
|
+
map_scaling: float = 1,
|
|
172
|
+
map_scaling_sigma: float = 0,
|
|
173
|
+
rng: np.random.Generator | None = None,
|
|
174
|
+
):
|
|
175
|
+
if edep_hits.xloc.ndim == 1:
|
|
176
|
+
msg = "the pe processors only support already reshaped output"
|
|
177
|
+
raise ValueError(msg)
|
|
178
|
+
|
|
179
|
+
rng = np.random.default_rng() if rng is None else rng
|
|
180
|
+
counts = ak.num(edep_hits.num_scint_ph)
|
|
181
|
+
output_array, res = _iterate_stepwise_depositions_numdet(
|
|
182
|
+
edep_hits,
|
|
183
|
+
rng,
|
|
184
|
+
np.where(optmap.dets == det)[0][0],
|
|
185
|
+
map_scaling,
|
|
186
|
+
map_scaling_sigma,
|
|
187
|
+
optmap.edges,
|
|
188
|
+
optmap.weights,
|
|
189
|
+
ak.sum(counts),
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
if res["det_no_stats"] > 0:
|
|
193
|
+
log.warning(
|
|
194
|
+
"had edep out in voxels without stats: %d",
|
|
195
|
+
res["det_no_stats"],
|
|
196
|
+
)
|
|
197
|
+
if res["oob"] > 0:
|
|
198
|
+
log.warning(
|
|
199
|
+
"had edep out of map bounds: %d (%.2f%%)",
|
|
200
|
+
res["oob"],
|
|
201
|
+
(res["oob"] / (res["ib"] + res["oob"])) * 100,
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
return ak.unflatten(output_array, counts)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def iterate_stepwise_depositions_times(
|
|
208
|
+
edep_hits: ak.Array,
|
|
209
|
+
scint_mat_params: sc.ComputedScintParams,
|
|
210
|
+
rng: np.random.Generator | None = None,
|
|
211
|
+
):
|
|
212
|
+
if edep_hits.particle.ndim == 1:
|
|
213
|
+
msg = "the pe processors only support already reshaped output"
|
|
214
|
+
raise ValueError(msg)
|
|
215
|
+
|
|
216
|
+
rng = np.random.default_rng() if rng is None else rng
|
|
217
|
+
counts = ak.sum(edep_hits.num_det_ph, axis=1)
|
|
218
|
+
output_array = _iterate_stepwise_depositions_times(
|
|
219
|
+
edep_hits, rng, scint_mat_params, ak.sum(counts)
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
return ak.unflatten(output_array, counts)
|
|
168
223
|
|
|
169
224
|
|
|
170
225
|
_pdg_func = numba_pdgid_funcs()
|
|
@@ -270,17 +325,15 @@ def _iterate_stepwise_depositions_pois(
|
|
|
270
325
|
# - cache=True does not work with outer prange, i.e. loading the cached file fails (numba bug?)
|
|
271
326
|
@njit(parallel=False, nogil=True, cache=True)
|
|
272
327
|
def _iterate_stepwise_depositions_scintillate(
|
|
273
|
-
edep_hits, rng, scint_mat_params: sc.ComputedScintParams, mode: str
|
|
328
|
+
edep_hits, rng, scint_mat_params: sc.ComputedScintParams, mode: str, output_length: int
|
|
274
329
|
):
|
|
275
330
|
pdgid_map = {}
|
|
276
|
-
|
|
331
|
+
output = np.empty(shape=output_length, dtype=np.int64)
|
|
277
332
|
|
|
333
|
+
output_index = 0
|
|
278
334
|
for rowid in range(len(edep_hits)): # iterate hits
|
|
279
335
|
hit = edep_hits[rowid]
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
# iterate steps inside the hit
|
|
283
|
-
for si in range(len(hit.particle)):
|
|
336
|
+
for si in range(len(hit.particle)): # iterate steps inside the hit
|
|
284
337
|
# get the particle information.
|
|
285
338
|
particle = hit.particle[si]
|
|
286
339
|
if particle not in pdgid_map:
|
|
@@ -295,12 +348,106 @@ def _iterate_stepwise_depositions_scintillate(
|
|
|
295
348
|
rng,
|
|
296
349
|
emission_term_model=("poisson" if mode == "no-fano" else "normal_fano"),
|
|
297
350
|
)
|
|
298
|
-
|
|
351
|
+
output[output_index] = num_phot
|
|
352
|
+
output_index += 1
|
|
299
353
|
|
|
300
|
-
|
|
301
|
-
|
|
354
|
+
assert output_index == output_length
|
|
355
|
+
return output
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
# - run with NUMBA_FULL_TRACEBACKS=1 NUMBA_BOUNDSCHECK=1 for testing/checking
|
|
359
|
+
# - cache=True does not work with outer prange, i.e. loading the cached file fails (numba bug?)
|
|
360
|
+
@njit(parallel=False, nogil=True, cache=True)
|
|
361
|
+
def _iterate_stepwise_depositions_numdet(
|
|
362
|
+
edep_hits,
|
|
363
|
+
rng,
|
|
364
|
+
detidx: int,
|
|
365
|
+
map_scaling: float,
|
|
366
|
+
map_scaling_sigma: float,
|
|
367
|
+
optmap_edges,
|
|
368
|
+
optmap_weights,
|
|
369
|
+
output_length: int,
|
|
370
|
+
):
|
|
371
|
+
oob = ib = det_no_stats = 0
|
|
372
|
+
output = np.empty(shape=output_length, dtype=np.int64)
|
|
373
|
+
|
|
374
|
+
output_index = 0
|
|
375
|
+
for rowid in range(len(edep_hits)): # iterate hits
|
|
376
|
+
hit = edep_hits[rowid]
|
|
377
|
+
|
|
378
|
+
map_scaling_evt = map_scaling
|
|
379
|
+
if map_scaling_sigma > 0:
|
|
380
|
+
map_scaling_evt = rng.normal(loc=map_scaling, scale=map_scaling_sigma)
|
|
381
|
+
|
|
382
|
+
# iterate steps inside the hit
|
|
383
|
+
for si in range(len(hit.xloc)):
|
|
384
|
+
loc = np.array([hit.xloc[si], hit.yloc[si], hit.zloc[si]], dtype=np.float64)
|
|
385
|
+
# coordinates -> bins of the optical map.
|
|
386
|
+
bins = np.empty(3, dtype=np.int64)
|
|
387
|
+
for j in range(3):
|
|
388
|
+
edges = optmap_edges[j].astype(np.float64)
|
|
389
|
+
start = edges[0]
|
|
390
|
+
width = edges[1] - edges[0]
|
|
391
|
+
nbins = edges.shape[0] - 1
|
|
392
|
+
bins[j] = int((loc[j] - start) / width)
|
|
393
|
+
|
|
394
|
+
if bins[j] < 0 or bins[j] >= nbins:
|
|
395
|
+
bins[j] = -1 # normalize all out-of-bounds bins just to one end.
|
|
396
|
+
|
|
397
|
+
if bins[0] == -1 or bins[1] == -1 or bins[2] == -1:
|
|
398
|
+
detp = 0.0 # out-of-bounds of optmap
|
|
399
|
+
oob += 1
|
|
400
|
+
else:
|
|
401
|
+
# get probabilities from map.
|
|
402
|
+
detp = optmap_weights[detidx, bins[0], bins[1], bins[2]] * map_scaling_evt
|
|
403
|
+
if detp < 0:
|
|
404
|
+
det_no_stats += 1
|
|
405
|
+
ib += 1
|
|
406
|
+
|
|
407
|
+
pois_cnt = 0 if detp <= 0.0 else rng.poisson(lam=hit.num_scint_ph[si] * detp)
|
|
408
|
+
output[output_index] = pois_cnt
|
|
409
|
+
output_index += 1
|
|
410
|
+
|
|
411
|
+
assert output_index == output_length
|
|
412
|
+
return output, {"oob": oob, "ib": ib, "det_no_stats": det_no_stats}
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
# - run with NUMBA_FULL_TRACEBACKS=1 NUMBA_BOUNDSCHECK=1 for testing/checking
|
|
416
|
+
# - cache=True does not work with outer prange, i.e. loading the cached file fails (numba bug?)
|
|
417
|
+
# - the output dictionary is not threadsafe, so parallel=True is not working with it.
|
|
418
|
+
@njit(parallel=False, nogil=True, cache=True)
|
|
419
|
+
def _iterate_stepwise_depositions_times(
|
|
420
|
+
edep_hits, rng, scint_mat_params: sc.ComputedScintParams, output_length: int
|
|
421
|
+
):
|
|
422
|
+
pdgid_map = {}
|
|
423
|
+
output = np.empty(shape=output_length, dtype=np.float64)
|
|
424
|
+
|
|
425
|
+
output_index = 0
|
|
426
|
+
for rowid in range(len(edep_hits)): # iterate hits
|
|
427
|
+
hit = edep_hits[rowid]
|
|
428
|
+
|
|
429
|
+
assert len(hit.particle) == len(hit.num_det_ph)
|
|
430
|
+
# iterate steps inside the hit
|
|
431
|
+
for si in range(len(hit.particle)):
|
|
432
|
+
pois_cnt = hit.num_det_ph[si]
|
|
433
|
+
if pois_cnt <= 0:
|
|
434
|
+
continue
|
|
435
|
+
|
|
436
|
+
# get the particle information.
|
|
437
|
+
particle = hit.particle[si]
|
|
438
|
+
if particle not in pdgid_map:
|
|
439
|
+
pdgid_map[particle] = (_pdgid_to_particle(particle), _pdg_func.charge(particle))
|
|
440
|
+
part, _charge = pdgid_map[particle]
|
|
441
|
+
|
|
442
|
+
# get time spectrum.
|
|
443
|
+
# note: we assume "immediate" propagation after scintillation.
|
|
444
|
+
scint_times = sc.scintillate_times(scint_mat_params, part, pois_cnt, rng) + hit.time[si]
|
|
445
|
+
assert len(scint_times) == pois_cnt
|
|
446
|
+
output[output_index : output_index + len(scint_times)] = scint_times
|
|
447
|
+
output_index += len(scint_times)
|
|
302
448
|
|
|
303
|
-
|
|
449
|
+
assert output_index == output_length
|
|
450
|
+
return output
|
|
304
451
|
|
|
305
452
|
|
|
306
453
|
def _get_scint_params(material: str):
|
reboost/spms/__init__.py
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from .pe import
|
|
3
|
+
from .pe import (
|
|
4
|
+
detected_photoelectrons,
|
|
5
|
+
emitted_scintillation_photons,
|
|
6
|
+
load_optmap,
|
|
7
|
+
load_optmap_all,
|
|
8
|
+
number_of_detected_photoelectrons,
|
|
9
|
+
photoelectron_times,
|
|
10
|
+
)
|
|
4
11
|
|
|
5
12
|
__all__ = [
|
|
6
13
|
"detected_photoelectrons",
|
|
7
14
|
"emitted_scintillation_photons",
|
|
8
15
|
"load_optmap",
|
|
9
16
|
"load_optmap_all",
|
|
17
|
+
"number_of_detected_photoelectrons",
|
|
18
|
+
"photoelectron_times",
|
|
10
19
|
]
|
reboost/spms/pe.py
CHANGED
|
@@ -109,7 +109,12 @@ def detected_photoelectrons(
|
|
|
109
109
|
map_scaling: float = 1,
|
|
110
110
|
map_scaling_sigma: float = 0,
|
|
111
111
|
) -> VectorOfVectors:
|
|
112
|
-
"""Derive the number of detected photoelectrons (p.e.) from scintillator hits using an optical map.
|
|
112
|
+
"""Derive the number and arrival times of detected photoelectrons (p.e.) from scintillator hits using an optical map.
|
|
113
|
+
|
|
114
|
+
.. deprecated :: 0.8.5
|
|
115
|
+
Use the other, more fine-granular and split processors
|
|
116
|
+
:func:`number_of_detected_photoelectrons` and :func:`photoelectron_times` to
|
|
117
|
+
replace this legacy processor.
|
|
113
118
|
|
|
114
119
|
Parameters
|
|
115
120
|
----------
|
|
@@ -181,3 +186,77 @@ def emitted_scintillation_photons(
|
|
|
181
186
|
scint_mat_params = convolve._get_scint_params(material)
|
|
182
187
|
ph = convolve.iterate_stepwise_depositions_scintillate(hits, scint_mat_params)
|
|
183
188
|
return VectorOfVectors(ph)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def number_of_detected_photoelectrons(
|
|
192
|
+
xloc: ak.Array,
|
|
193
|
+
yloc: ak.Array,
|
|
194
|
+
zloc: ak.Array,
|
|
195
|
+
num_scint_ph: ak.Array,
|
|
196
|
+
optmap: convolve.OptmapForConvolve,
|
|
197
|
+
spm_detector: str,
|
|
198
|
+
map_scaling: float = 1,
|
|
199
|
+
map_scaling_sigma: float = 0,
|
|
200
|
+
) -> VectorOfVectors:
|
|
201
|
+
"""Derive the number of detected photoelectrons (p.e.) from scintillator hits using an optical map.
|
|
202
|
+
|
|
203
|
+
Parameters
|
|
204
|
+
----------
|
|
205
|
+
xloc
|
|
206
|
+
array of x coordinate position of scintillation events.
|
|
207
|
+
yloc
|
|
208
|
+
array of y coordinate position of scintillation events.
|
|
209
|
+
zloc
|
|
210
|
+
array of z coordinate position of scintillation events.
|
|
211
|
+
num_scint_ph
|
|
212
|
+
array of emitted scintillation photons, as generated by
|
|
213
|
+
:func:`emitted_scintillation_photons`.
|
|
214
|
+
detp
|
|
215
|
+
"""
|
|
216
|
+
hits = ak.Array(
|
|
217
|
+
{
|
|
218
|
+
"xloc": units_conv_ak(xloc, "m"),
|
|
219
|
+
"yloc": units_conv_ak(yloc, "m"),
|
|
220
|
+
"zloc": units_conv_ak(zloc, "m"),
|
|
221
|
+
"num_scint_ph": units_conv_ak(num_scint_ph, "dimensionless"),
|
|
222
|
+
}
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
ph = convolve.iterate_stepwise_depositions_numdet(
|
|
226
|
+
hits, optmap, spm_detector, map_scaling, map_scaling_sigma
|
|
227
|
+
)
|
|
228
|
+
return VectorOfVectors(ph)
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def photoelectron_times(
|
|
232
|
+
num_det_ph: ak.Array,
|
|
233
|
+
particle: ak.Array,
|
|
234
|
+
time: ak.Array,
|
|
235
|
+
material: str,
|
|
236
|
+
) -> VectorOfVectors:
|
|
237
|
+
"""Derive the arrival times of scintillation photons.
|
|
238
|
+
|
|
239
|
+
Parameters
|
|
240
|
+
----------
|
|
241
|
+
num_det_ph
|
|
242
|
+
array of detected scintillation photons, as generated by
|
|
243
|
+
:func:`emitted_scintillation_photons`.
|
|
244
|
+
particle
|
|
245
|
+
array of particle PDG IDs of scintillation events.
|
|
246
|
+
time
|
|
247
|
+
array of timestamps of scintillation events.
|
|
248
|
+
material
|
|
249
|
+
scintillating material name.
|
|
250
|
+
"""
|
|
251
|
+
hits = ak.Array(
|
|
252
|
+
{
|
|
253
|
+
"num_det_ph": units_conv_ak(num_det_ph, "dimensionless"),
|
|
254
|
+
"particle": units_conv_ak(particle, "dimensionless"),
|
|
255
|
+
"time": units_conv_ak(time, "ns"),
|
|
256
|
+
}
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
scint_mat_params = convolve._get_scint_params(material)
|
|
260
|
+
pe = convolve.iterate_stepwise_depositions_times(hits, scint_mat_params)
|
|
261
|
+
|
|
262
|
+
return VectorOfVectors(pe, attrs={"units": "ns"})
|
reboost/utils.py
CHANGED
|
@@ -445,7 +445,7 @@ def write_lh5(
|
|
|
445
445
|
time_dict.update_field("write", start_time)
|
|
446
446
|
|
|
447
447
|
|
|
448
|
-
def get_remage_detector_uids(h5file: str | Path) -> dict:
|
|
448
|
+
def get_remage_detector_uids(h5file: str | Path, *, lh5_table: str = "stp") -> dict:
|
|
449
449
|
"""Get mapping of detector names to UIDs from a remage output file.
|
|
450
450
|
|
|
451
451
|
The remage LH5 output files contain a link structure that lets the user
|
|
@@ -483,7 +483,7 @@ def get_remage_detector_uids(h5file: str | Path) -> dict:
|
|
|
483
483
|
|
|
484
484
|
out = {}
|
|
485
485
|
with h5py.File(h5file, "r") as f:
|
|
486
|
-
g = f["/
|
|
486
|
+
g = f[f"/{lh5_table}/__by_uid__"]
|
|
487
487
|
# loop over links
|
|
488
488
|
for key in g:
|
|
489
489
|
# is this a link?
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: reboost
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.0
|
|
4
4
|
Summary: New LEGEND Monte-Carlo simulation post-processing
|
|
5
5
|
Author-email: Manuel Huber <info@manuelhu.de>, Toby Dixon <toby.dixon.23@ucl.ac.uk>, Luigi Pertoldi <gipert@pm.me>
|
|
6
6
|
Maintainer: The LEGEND Collaboration
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
reboost/__init__.py,sha256=VZz9uo7i2jgAx8Zi15SptLZnE_qcnGuNWwqkD3rYHFA,278
|
|
2
2
|
reboost/__main__.py,sha256=42koSxY2st4mMIRSAnKz06nP5HppMPxBVFf2jaHljGs,95
|
|
3
|
-
reboost/_version.py,sha256=
|
|
3
|
+
reboost/_version.py,sha256=TvxBYkx8Rz_Q1S3JFp831BRT8Wo0Yxt6TJMtgZKenTo,704
|
|
4
4
|
reboost/build_evt.py,sha256=VXIfK_pfe_Cgym6gI8dESwONZi-v_4fll0Pn09vePQY,3767
|
|
5
5
|
reboost/build_glm.py,sha256=IerSLQfe51ZO7CQP2kmfPnOIVaDtcfw3byOM02Vaz6o,9472
|
|
6
6
|
reboost/build_hit.py,sha256=N_nxvH69SvILVNmyvVfhQwQdD_PDW8tlsqj2ciO5nKE,17409
|
|
@@ -10,21 +10,21 @@ reboost/iterator.py,sha256=qlEqRv5qOh8eIs-dyVOLYTvH-ZpQDx9fLckpcAdtWjs,6975
|
|
|
10
10
|
reboost/log_utils.py,sha256=VqS_9OC5NeNU3jcowVOBB0NJ6ssYvNWnirEY-JVduEA,766
|
|
11
11
|
reboost/profile.py,sha256=EOTmjmS8Rm_nYgBWNh6Rntl2XDsxdyed7yEdWtsZEeg,2598
|
|
12
12
|
reboost/units.py,sha256=LUwl6swLQoG09Rt9wcDdu6DTrwDsy-C751BNGzX4sz8,3651
|
|
13
|
-
reboost/utils.py,sha256
|
|
13
|
+
reboost/utils.py,sha256=vl-_BUOeXcazNs4zN-9k-OVEptdf3FtCeej2QZhClKc,14599
|
|
14
14
|
reboost/daq/__init__.py,sha256=rNPhxx1Yawt3tENYhmOYSum9_TdV57ZU5kjxlWFAGuo,107
|
|
15
15
|
reboost/daq/core.py,sha256=Rs6Q-17fzEod2iX_2WqEmnqKnNRFoWTYURl3wYhFihU,9915
|
|
16
16
|
reboost/daq/utils.py,sha256=KcH6zvlInmD2YiF6V--DSYBTYudJw3G-hp2JGOcES2o,1042
|
|
17
17
|
reboost/hpge/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
18
|
-
reboost/hpge/psd.py,sha256=
|
|
18
|
+
reboost/hpge/psd.py,sha256=P7dUJQPvxW6vndJ79r0j7ANZvSuV_IuauERhvWD74j0,26989
|
|
19
19
|
reboost/hpge/surface.py,sha256=feH-kxRRp3HkikRRJ-LCu6zvVONEOYVd3THx12emGTM,8494
|
|
20
|
-
reboost/hpge/utils.py,sha256=
|
|
20
|
+
reboost/hpge/utils.py,sha256=0hHu5S1lDOiMMVPgfHY03R5ggyeWX2OwONHVZeFmcpU,5652
|
|
21
21
|
reboost/math/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
22
22
|
reboost/math/functions.py,sha256=OymiYTcA0NXxxm-MBDw5kqyNwHoLCmuv4J48AwnSrbU,5633
|
|
23
23
|
reboost/math/stats.py,sha256=Rq4Wdzv-3aoSK7EsPZCuOEHfnOz3w0moIzCEHbC07xw,3173
|
|
24
24
|
reboost/optmap/__init__.py,sha256=imvuyld-GLw8qdwqW-lXCg2feptcTyQo3wIzPvDHwmY,93
|
|
25
25
|
reboost/optmap/__main__.py,sha256=DfzkXQ7labOe53hd7jH5pAbTW491jjQYSMLyl72L4Rk,111
|
|
26
26
|
reboost/optmap/cli.py,sha256=_7WBlx55eRyW_wWB-ELbFaWXin2d3xsh6Q5bFoNJaHE,8694
|
|
27
|
-
reboost/optmap/convolve.py,sha256=
|
|
27
|
+
reboost/optmap/convolve.py,sha256=e3vTBurYSM4UmicIdDif6_cI04pV8khHEG5n7M_DWNg,16079
|
|
28
28
|
reboost/optmap/create.py,sha256=GmGd0-F0eWmw7ywH8pT1lKiMb60QXCq9al8Ka_ySD1Q,14382
|
|
29
29
|
reboost/optmap/evt.py,sha256=nZcB3aOPtMhySu00J23KlOTijeI5Oazde-vfM5A71e8,5664
|
|
30
30
|
reboost/optmap/mapview.py,sha256=fswwXolA6au8u8gljBKy8PSXC2W7Cy_GwOV86-duYG8,6880
|
|
@@ -34,11 +34,11 @@ reboost/shape/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
34
34
|
reboost/shape/cluster.py,sha256=nwR1Dnf00SDICGPqpXeM1Q7_DwTtO9uP3wmuML45c3g,8195
|
|
35
35
|
reboost/shape/group.py,sha256=gOCYgir2gZqmW1JXtbNRPlQqP0gmUcbe7RVb9CbY1pU,5540
|
|
36
36
|
reboost/shape/reduction.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
37
|
-
reboost/spms/__init__.py,sha256=
|
|
38
|
-
reboost/spms/pe.py,sha256=
|
|
39
|
-
reboost-0.
|
|
40
|
-
reboost-0.
|
|
41
|
-
reboost-0.
|
|
42
|
-
reboost-0.
|
|
43
|
-
reboost-0.
|
|
44
|
-
reboost-0.
|
|
37
|
+
reboost/spms/__init__.py,sha256=8I6WT8i_kUPqEDnSD0aCf6A26cjKjQQZSNrvwZ3o-Ac,415
|
|
38
|
+
reboost/spms/pe.py,sha256=LwqrK1HOZWzGcNZnntaqI6r4rnDww4KW9Mao4xLFbDE,8226
|
|
39
|
+
reboost-0.9.0.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
40
|
+
reboost-0.9.0.dist-info/METADATA,sha256=wrdKu5HVcTaoKIz6mp72uJ9ehRzyF0e1xCWUe63g7DM,3877
|
|
41
|
+
reboost-0.9.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
42
|
+
reboost-0.9.0.dist-info/entry_points.txt,sha256=DxhD6BidSWNot9BrejHJjQ7RRLmrMaBIl52T75oWTwM,93
|
|
43
|
+
reboost-0.9.0.dist-info/top_level.txt,sha256=q-IBsDepaY_AbzbRmQoW8EZrITXRVawVnNrB-_zyXZs,8
|
|
44
|
+
reboost-0.9.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|