Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions core/opengate_core/opengate_core.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,8 @@ void init_GateHitsCollectionActor(py::module &);

void init_GateHitsAdderActor(py::module &);

void init_GateDigitizerDeadTimeActor(py::module &);

void init_GateDigitizerPileupActor(py::module &);

void init_GateDigitizerReadoutActor(py::module &m);
Expand Down Expand Up @@ -765,6 +767,7 @@ PYBIND11_MODULE(opengate_core, m) {
init_GateDigiAttributeManager(m);
init_GateVDigiAttribute(m);
init_GateHitsAdderActor(m);
init_GateDigitizerDeadTimeActor(m);
init_GateDigitizerPileupActor(m);
init_GateDigitizerReadoutActor(m);
init_GateDigitizerBlurringActor(m);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/* --------------------------------------------------
Copyright (C): OpenGATE Collaboration
This software is distributed under the terms
of the GNU Lesser General Public Licence (LGPL)
See LICENSE.md for further details
-------------------------------------------------- */

#include "GateDigitizerDeadTimeActor.h"
#include "../GateHelpersDict.h"
#include "GateHelpersDigitizer.h"
#include <memory>

GateDigitizerDeadTimeActor::GateDigitizerDeadTimeActor(py::dict &user_info)
: GateVDigitizerWithOutputActor(user_info, true) {
fActions.insert("EndOfEventAction");
fActions.insert("EndOfRunAction");
}

GateDigitizerDeadTimeActor::~GateDigitizerDeadTimeActor() = default;

void GateDigitizerDeadTimeActor::InitializeUserInfo(py::dict &user_info) {

GateVDigitizerWithOutputActor::InitializeUserInfo(user_info);
if (py::len(user_info) > 0 && user_info.contains("dead_time")) {
fDeadTime = DictGetDouble(user_info, "dead_time"); // nanoseconds
}
if (py::len(user_info) > 0 && user_info.contains("policy")) {
const auto policy_str = DictGetStr(user_info, "policy");
if (policy_str == "NonParalyzable") {
fPolicy = DeadTimePolicy::NonParalyzable;
} else if (policy_str == "Paralyzable") {
fPolicy = DeadTimePolicy::Paralyzable;
} else {
Fatal("Unknown dead time policy '" + policy_str + "'");
}
}

if (py::len(user_info) > 0 && user_info.contains("sorting_time")) {
fSortingTime = DictGetDouble(user_info, "sorting_time"); // nanoseconds
}
fGroupVolumeDepth = -1;
fInputDigiCollectionName = DictGetStr(user_info, "input_digi_collection");
}

void GateDigitizerDeadTimeActor::BeginOfRunActionMasterThread(int run_id) {

fTimeSorter = std::make_unique<GateTimeSorter>(fOutputDigiCollectionName);
fTimeSorter->Init(fInputDigiCollection);
fTimeSorter->SetSortingWindow(fSortingTime);
fTimeSorter->SetMaxSize(fClearEveryNEvents);

auto &outputIter = fTimeSorter->OutputIterator();
outputIter.TrackAttribute("GlobalTime", &fTimeSorterOutputTime);
outputIter.TrackAttribute("PreStepUniqueVolumeID", &fTimeSorterOutputVolID);

fillerOut = std::make_unique<GateDigiAttributesFiller>(
fTimeSorter->OutputCollection(), fOutputDigiCollection,
fTimeSorter->OutputCollection()->GetDigiAttributeNames());

fVolumeEndOfDeadTimeInterval.clear();
}

void GateDigitizerDeadTimeActor::SetGroupVolumeDepth(const int depth) {
fGroupVolumeDepth = depth;
}

void GateDigitizerDeadTimeActor::DigitInitialize(
const std::vector<std::string> &attributes_not_in_filler) {

auto a = attributes_not_in_filler;
GateVDigitizerWithOutputActor::DigitInitialize(a);

fOutputDigiCollection->RootInitializeTupleForWorker();
}

void GateDigitizerDeadTimeActor::EndOfEventAction(const G4Event *) {

fTimeSorter->OnEndOfEventAction([this]() { ProcessTimeSortedDigis(); });
}

void GateDigitizerDeadTimeActor::EndOfRunAction(const G4Run *) {

fTimeSorter->OnEndOfRunAction(
[this]() { fOutputDigiCollection->FillToRootIfNeeded(true); },
[this]() { ProcessTimeSortedDigis(); });
}

void GateDigitizerDeadTimeActor::ProcessTimeSortedDigis() {

auto &iter = fTimeSorter->OutputIterator();
iter.GoToBegin();
while (!iter.IsAtEnd()) {

const auto volHash =
fTimeSorterOutputVolID->get()->GetIdUpToDepthAsHash(fGroupVolumeDepth);
std::optional<double> endTime{};
auto it = fVolumeEndOfDeadTimeInterval.find(volHash);
if (it != fVolumeEndOfDeadTimeInterval.end()) {
endTime = it->second;
}

const auto currentTime = *fTimeSorterOutputTime;
if (!endTime.has_value() || currentTime > *endTime) {
// Digi goes to the output.
fillerOut->Fill(iter.fIndex);
// Update end of dead time interval.
fVolumeEndOfDeadTimeInterval[volHash] = currentTime + fDeadTime;
} else {
// Digi is dropped because it arrived during a dead time interval.
if (fPolicy == DeadTimePolicy::NonParalyzable) {
// End of dead time interval does not change.
} else if (fPolicy == DeadTimePolicy::Paralyzable) {
// End of dead time interval is updated.
fVolumeEndOfDeadTimeInterval[volHash] = currentTime + fDeadTime;
} else {
Fatal("Unknown dead time policy");
}
}

iter++;
}
fTimeSorter->MarkOutputAsProcessed();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/* --------------------------------------------------
Copyright (C): OpenGATE Collaboration
This software is distributed under the terms
of the GNU Lesser General Public Licence (LGPL)
See LICENSE.md for further details
-------------------------------------------------- */

#ifndef GateDigitizerDeadTimeActor_h
#define GateDigitizerDeadTimeActor_h

#include "GateDigiCollection.h"
#include "GateTimeSorter.h"
#include "GateVDigitizerWithOutputActor.h"
#include <G4Cache.hh>
#include <G4Navigator.hh>
#include <map>
#include <memory>
#include <pybind11/stl.h>
#include <queue>

namespace py = pybind11;

/*
* Digitizer module for dead time.
*/

class GateDigitizerDeadTimeActor : public GateVDigitizerWithOutputActor {

public:
explicit GateDigitizerDeadTimeActor(py::dict &user_info);

~GateDigitizerDeadTimeActor() override;

void InitializeUserInfo(py::dict &user_info) override;

void BeginOfRunActionMasterThread(int run_id) override;

void EndOfEventAction(const G4Event *event) override;

void EndOfRunAction(const G4Run *run) override;

void SetGroupVolumeDepth(int depth);

protected:
enum class DeadTimePolicy {
NonParalyzable,
Paralyzable,
};

void DigitInitialize(
const std::vector<std::string> &attributes_not_in_filler) override;

// User parameters
double fDeadTime;
DeadTimePolicy fPolicy;
double fSortingTime;
int fGroupVolumeDepth;

std::unique_ptr<GateTimeSorter> fTimeSorter;
std::map<uint64_t, double> fVolumeEndOfDeadTimeInterval;

// Tracking pointers used by GateTimeSorter output iterator.
GateUniqueVolumeID::Pointer *fTimeSorterOutputVolID{};
double *fTimeSorterOutputTime{};

std::unique_ptr<GateDigiAttributesFiller> fillerOut;

void ProcessTimeSortedDigis();
};

#endif // GateDigitizerDeadTimeActor_h
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/* --------------------------------------------------
Copyright (C): OpenGATE Collaboration
This software is distributed under the terms
of the GNU Lesser General Public Licence (LGPL)
See LICENSE.md for further details
-------------------------------------------------- */

#include <pybind11/pybind11.h>
#include <pybind11/stl.h>

namespace py = pybind11;

#include "GateDigitizerDeadTimeActor.h"

void init_GateDigitizerDeadTimeActor(py::module &m) {

py::class_<GateDigitizerDeadTimeActor,
std::unique_ptr<GateDigitizerDeadTimeActor, py::nodelete>,
GateVDigitizerWithOutputActor>(m, "GateDigitizerDeadTimeActor")
.def(py::init<py::dict &>())
.def("SetGroupVolumeDepth",
&GateDigitizerDeadTimeActor::SetGroupVolumeDepth);
}
43 changes: 43 additions & 0 deletions docs/source/user_guide/user_guide_reference_actors.rst
Original file line number Diff line number Diff line change
Expand Up @@ -878,6 +878,49 @@ Reference
.. autoclass:: opengate.actors.digitizers.DigitizerEfficiencyActor


DigitizerDeadTimeActor
----------------------

Description
~~~~~~~~~~~

Dead time is the period following the detection of a single during which a detector cannot register a new event.
The :class:`~.opengate.actors.digitizers.DigitizerDeadTimeActor` simulates this effect by dropping singles that arrive within the dead time interval
after a previously accepted single, on a per-volume basis (set by `group_volume`).

Two policies are available, controlled by the `policy` parameter:

* **NonParalyzable** (default): the dead time interval is fixed in duration, starting from each accepted single.
Singles arriving during that interval are discarded, but do not extend it. This models, for example,
a detector that keeps accepting events after the dead time has elapsed regardless of what happened during the interval.
* **Paralyzable**: every single that arrives during the current dead time interval extends it by `dead_time` from the time of that single.
If the rate is very high, the detector can become fully paralyzed because each new single keeps resetting the interval.

The actor internally time-sorts its input digis using a buffer window controlled by `sorting_time` before applying the dead time logic.
This is necessary to correctly handle events from different threads or runs that may arrive out of order.

.. code-block:: python

ns = gate.g4_units.ns

dt = sim.add_actor("DigitizerDeadTimeActor", "Singles_after_deadtime")
dt.attached_to = crystal.name
dt.authorize_repeated_volumes = True
dt.input_digi_collection = "Singles"
dt.group_volume = crystal.name
dt.dead_time = 1.0 * ns
dt.policy = "NonParalyzable" # or "Paralyzable"
dt.output_filename = "singles.root"

Refer to `test100 <https://github.com/OpenGATE/opengate/blob/master/opengate/tests/src/actors/test100_deadtime_actor.py>`_ for an example.
The multi-threaded variant is in `test100_deadtime_actor_mt <https://github.com/OpenGATE/opengate/blob/master/opengate/tests/src/actors/test100_deadtime_actor_mt.py>`_.

Reference
~~~~~~~~~

.. autoclass:: opengate.actors.digitizers.DigitizerDeadTimeActor


DigitizerPileupActor
--------------------

Expand Down
88 changes: 88 additions & 0 deletions opengate/actors/digitizers.py
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,93 @@ def EndSimulationAction(self):
g4.GateDigitizerBlurringActor.EndSimulationAction(self)


class DigitizerDeadTimeActor(DigitizerWithRootOutput, g4.GateDigitizerDeadTimeActor):
"""
Dititizer module for simulating dead time
(drops singles that occur briefly after a previous one, in the same volume).
"""

user_info_defaults = {
"input_digi_collection": (
"Singles",
{
"doc": "Digi collection to be used as input.",
},
),
"dead_time": (
0,
{
"doc": "Time interval after one digi during which following digis are dropped",
},
),
"policy": (
"NonParalyzable",
{
"doc": "Policy controlling whether the dead time interval is extended when new digis occur. "
+ "NonParalyzable: the dead time interval is fixed and does not change when new digis occur during that interval. "
+ "Paralyzable: when a new digi arrives during the current dead time interval, the interval is extended to last until dead_time after the new digi. ",
"allowed_values": (
"NonParalyzable",
"Paralyzable",
),
},
),
"group_volume": (
None,
{
"doc": "Name of the volume in which the dead time effect takes place",
},
),
"clear_every": (
1e5,
{
"doc": "The memory consumed by the actor is minimized after having processed the specified amount of digis",
},
),
"sorting_time": (
1e3,
{
"doc": "Time interval during which digis are buffered for time-sorting",
},
),
"skip_attributes": (
[],
{
"doc": "Attributes to be omitted from the output.",
},
),
}

def __init__(self, *args, **kwargs):
DigitizerBase.__init__(self, *args, **kwargs)
self.__initcpp__()

def __initcpp__(self):
g4.GateDigitizerDeadTimeActor.__init__(self, self.user_info)
self.AddActions({"StartSimulationAction", "EndSimulationAction"})

def initialize(self):
DigitizerBase.initialize(self)
self.InitializeUserInfo(self.user_info)
self.InitializeCpp()

def set_group_by_depth(self):
depth = -1
if self.user_info.group_volume is not None:
depth = self.simulation.volume_manager.get_volume(
self.user_info.group_volume
).volume_depth_in_tree
self.SetGroupVolumeDepth(depth)

def StartSimulationAction(self):
DigitizerBase.StartSimulationAction(self)
self.set_group_by_depth()
g4.GateDigitizerDeadTimeActor.StartSimulationAction(self)

def EndSimulationAction(self):
g4.GateDigitizerDeadTimeActor.EndSimulationAction(self)


class DigitizerPileupActor(DigitizerWithRootOutput, g4.GateDigitizerPileupActor):
"""
Dititizer module for simulating pile-up
Expand Down Expand Up @@ -1581,6 +1668,7 @@ def volume_name(self, value):
process_cls(DigitizerWithRootOutput)
process_cls(DigitizerAdderActor)
process_cls(DigitizerBlurringActor)
process_cls(DigitizerDeadTimeActor)
process_cls(DigitizerPileupActor)
process_cls(DigitizerSpatialBlurringActor)
process_cls(DigitizerEfficiencyActor)
Expand Down
Loading
Loading