Skip to content

Commit a3d71f4

Browse files
committed
clustering detector hits by time as well
1 parent ada16e1 commit a3d71f4

File tree

2 files changed

+99
-30
lines changed

2 files changed

+99
-30
lines changed

src/ChargeClustering/ChargeClustering.jl

Lines changed: 82 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,69 +2,121 @@
22

33
# breaking changes in Clustering v0.14 -> v0.15
44
@inline _get_clusters(clusters::Clustering.DbscanResult) = clusters.clusters
5-
@inline _get_clusters(clusters::Vector{Clustering.DbscanCluster}) = clusters
5+
6+
@inline function finalize_group!(time_groups, current_group, new_index)
7+
push!(time_groups, current_group)
8+
return [new_index]
9+
end
610

711
function cluster_detector_hits(
812
detno::AbstractVector{<:Integer},
913
edep::AbstractVector{TT},
10-
pos::AbstractVector{<:CartesianPoint{PT}},
11-
cluster_radius::RealQuantity
12-
) where {TT<:RealQuantity, PT <: RealQuantity}
14+
pos::AbstractVector{CartesianPoint{PT}},
15+
thit::AbstractVector{TTT},
16+
cluster_radius::RealQuantity,
17+
cluster_time::RealQuantity
18+
) where {TT<:RealQuantity, PT <: RealQuantity, TTT <: RealQuantity}
1319

14-
pos = isa(first(pos), CartesianPoint) ? [SVector(p.x, p.y, p.z) for p in pos] : pos #converting CartesianPoint to SVector
20+
pos = [SVector(p.x, p.y, p.z) for p in pos] # converting CartesianPoint to SVector
1521

1622
Table = TypedTables.Table
17-
unsorted = Table(detno = detno, edep = edep, pos = pos)
23+
unsorted = Table(detno = detno, edep = edep, pos = pos, thit = thit)
1824
sorting_idxs = sortperm(unsorted.detno)
1925
sorted = unsorted[sorting_idxs]
2026
grouped = Table(consgroupedview(sorted.detno, TypedTables.columns(sorted)))
2127

2228
r_detno = similar(detno, 0)
2329
r_edep = similar(edep, 0)
2430
r_pos = similar(pos, 0)
31+
r_thit = similar(thit, 0)
2532

2633
posunit = unit(PT)
2734
ustripped_cradius = ustrip(posunit, cluster_radius)
28-
35+
36+
thitunit = unit(TTT)
37+
ustripped_ctime = ustrip(thitunit, cluster_time)
38+
2939
for d_hits_nt in grouped
3040
d_hits = Table(d_hits_nt)
3141
d_detno = first(d_hits.detno)
3242
@assert all(isequal(d_detno), d_hits.detno)
33-
if length(d_hits) > 3
34-
clusters = Clustering.dbscan(ustrip.(flatview(d_hits.pos)), ustripped_cradius, leafsize = 20, min_neighbors = 1, min_cluster_size = 1)
35-
for c in _get_clusters(clusters)
36-
idxs = vcat(c.boundary_indices, c.core_indices)
37-
@assert length(idxs) == c.size
38-
c_hits = view(d_hits, idxs)
39-
40-
push!(r_detno, d_detno)
41-
esum_u = sum(c_hits.edep)
42-
push!(r_edep, esum_u)
43-
esum = ustrip(esum_u)
44-
if esum 0
45-
push!(r_pos, mean(c_hits.pos))
46-
else
47-
weights = ustrip.(c_hits.edep) .* inv(esum)
48-
push!(r_pos, sum(c_hits.pos .* weights))
43+
44+
# sort hits by time
45+
t_sort_idx = sortperm(d_hits.thit)
46+
d_hits = d_hits[t_sort_idx]
47+
t_vals = ustrip.(d_hits.thit)
48+
49+
time_groups = Vector{Vector{Int}}()
50+
current_group = [1]
51+
start_time = t_vals[1]
52+
for i in 2:length(t_vals)
53+
if t_vals[i] - start_time ustripped_ctime
54+
push!(current_group, i)
55+
else
56+
current_group = finalize_group!(time_groups, current_group, i)
57+
start_time = t_vals[i]
58+
end
59+
end
60+
current_group = finalize_group!(time_groups, current_group, nothing)
61+
62+
63+
# spatial clustering within each temporal cluster
64+
for tg in time_groups
65+
t_hits = view(d_hits, tg)
66+
if length(t_hits) > 3
67+
clusters = Clustering.dbscan(ustrip.(flatview(t_hits.pos)), ustripped_cradius;
68+
leafsize = 20, min_neighbors = 1, min_cluster_size = 1)
69+
for c in _get_clusters(clusters)
70+
idxs = vcat(c.boundary_indices, c.core_indices)
71+
@assert length(idxs) == c.size
72+
c_hits = view(t_hits, idxs)
73+
74+
push!(r_detno, d_detno)
75+
esum_u = sum(c_hits.edep)
76+
push!(r_edep, esum_u)
77+
esum = ustrip(esum_u)
78+
if esum 0
79+
push!(r_pos, mean(c_hits.pos))
80+
push!(r_thit, mean(c_hits.thit))
81+
else
82+
weights = ustrip.(c_hits.edep) .* inv(esum)
83+
push!(r_pos, sum(c_hits.pos .* weights))
84+
push!(r_thit, sum(c_hits.thit .* weights))
85+
end
4986
end
87+
else
88+
append!(r_detno, t_hits.detno)
89+
append!(r_edep, t_hits.edep)
90+
append!(r_pos, t_hits.pos)
91+
append!(r_thit, t_hits.thit)
5092
end
51-
else
52-
append!(r_detno, d_hits.detno)
53-
append!(r_edep, d_hits.edep)
54-
append!(r_pos, d_hits.pos)
5593
end
5694
end
5795

58-
(detno = r_detno, edep = r_edep, pos = r_pos)
96+
r_pos = [CartesianPoint(p[1], p[2], p[3]) for p in r_pos] #converting SVector back to CartesianPoint
97+
98+
(detno = r_detno, edep = r_edep, pos = r_pos, thit = r_thit)
5999
end
60100

61101

62-
function cluster_detector_hits(table::TypedTables.Table, cluster_radius::RealQuantity)
102+
function cluster_detector_hits(
103+
table::TypedTables.Table,
104+
cluster_radius::PT,
105+
cluster_time::TTT
106+
) where {PT <: RealQuantity, TTT <: RealQuantity}
63107
@assert :pos in TypedTables.columnnames(table) "Table has no column `pos`"
108+
@assert :thit in TypedTables.columnnames(table) "Table has no column `thit`"
64109
@assert :edep in TypedTables.columnnames(table) "Table has no column `edep`"
65110
@assert :detno in TypedTables.columnnames(table) "Table has no column `detno`"
66111
clustered_nt = map(
67-
evt -> cluster_detector_hits(evt.detno, evt.edep, evt.pos, cluster_radius),
112+
evt -> cluster_detector_hits(
113+
evt.detno,
114+
evt.edep,
115+
[p isa CartesianPoint ? p : CartesianPoint(p...) for p in evt.pos],
116+
evt.thit,
117+
cluster_radius,
118+
cluster_time
119+
),
68120
table
69121
)
70122
TypedTables.Table(merge(

test/test_geant4.jl

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,23 @@ end
6666
@test evts isa Table
6767
@test length(evts) == 100
6868

69+
# Cluster events by time and radius
70+
clustered_evts = @test_nowarn SolidStateDetectors.cluster_detector_hits(evts, 10u"µm", 100u"s")
71+
@test length(clustered_evts) == length(evts)
72+
@test length(flatview(clustered_evts.pos)) length(flatview(evts.pos))
73+
74+
@test all(isa.(flatview(clustered_evts.pos), CartesianPoint{<:Real})) #checking type consistency for pos
75+
@test isapprox(sum(flatview(clustered_evts.edep)), sum(flatview(evts.edep)); rtol=1e-6) #checking energy conservation after clustering
76+
77+
#sanity check that all values in the table are not NaN
78+
pos = flatview(clustered_evts.pos)
79+
@test all(!isnan.(ustrip.([p.x for p in pos]))) &&
80+
all(!isnan.(ustrip.([p.y for p in pos]))) &&
81+
all(!isnan.(ustrip.([p.z for p in pos])))
82+
83+
@test all(!isnan.(ustrip.(flatview(clustered_evts.thit))))
84+
@test all(!isnan.(ustrip.(flatview(clustered_evts.edep))))
85+
6986
# Generate waveforms
7087
simulate!(sim, refinement_limits = [0.2,0.1,0.05,0.03,0.02])
7188
wf = simulate_waveforms(evts, sim, Δt = 1u"ns", max_nsteps = 2000)

0 commit comments

Comments
 (0)