Working with Hits Collections in Geant4

This guide explains how to use the hits collection system in the Geant4 Geometry Editor and how to extend it with custom hits collections in your Geant4 simulation.

1. Using Active Volumes in the Geometry Editor

Marking Volumes as Active

  1. Select a volume in the geometry tree

  2. In the properties panel, check the “Active Volume (for hit collection)” checkbox

  3. By default, the volume will be added to the “MyHitsCollection”

Active Volume Toggle

Hits Collection Selection

For each active volume, you can select which hits collection it belongs to:

  1. Mark a volume as active by checking the “Active Volume” checkbox

  2. Use the “Hits Collection” dropdown to select the collection

  3. By default, “MyHitsCollection” is selected

JSON Export Format

When you export your geometry, active volumes and hits collections will be included in the JSON:

{
  "world": { /* world properties */ },
  "volumes": [
    {
      "name": "Detector1",
      "type": "box",
      /* other properties */
      "isActive": true,
      "hitsCollectionName": "MyHitsCollection"
    }
  ],
  "hitsCollections": [
    {
      "name": "MyHitsCollection",
      "description": "Default hits collection for energy deposits",
      "associatedVolumes": ["Detector1", "Detector2"]
    }
  ]
}

2. Understanding the Default Implementation

The simulation comes with a default MyHitsCollection implementation that demonstrates how to:

  • Define a hit class

  • Create a sensitive detector

  • Process hits and store them in a collection

Key Files

  • MyHit.hh/cc: Defines what information is stored for each hit

  • MySensitiveDetector.hh/cc: Processes physics steps and creates hits

  • GeometryParser.cc: Assigns sensitive detectors to active volumes

3. Creating Your Own Hits Collection

To create a new hits collection (e.g., “ScintillatorHits”):

Step 1: Create a new hit class

// ScintillatorHit.hh
#ifndef ScintillatorHit_h
#define ScintillatorHit_h 1

#include "G4VHit.hh"
#include "G4THitsCollection.hh"
#include "G4ThreeVector.hh"
#include "G4Threading.hh"

class ScintillatorHit : public G4VHit {
public:
    ScintillatorHit();
    virtual ~ScintillatorHit();
    
    // Add your hit properties and methods here
    void SetEnergy(G4double e) { fEnergy = e; }
    void SetPosition(G4ThreeVector pos) { fPosition = pos; }
    void SetScintillationPhotons(G4int n) { fScintPhotons = n; }
    
    G4double GetEnergy() const { return fEnergy; }
    G4ThreeVector GetPosition() const { return fPosition; }
    G4int GetScintillationPhotons() const { return fScintPhotons; }
    
private:
    G4double fEnergy;
    G4ThreeVector fPosition;
    G4int fScintPhotons;  // Scintillator-specific property
};

typedef G4THitsCollection<ScintillatorHit> ScintillatorHitsCollection;

#endif

Step 2: Create a new sensitive detector

// ScintillatorSD.hh
#ifndef ScintillatorSD_h
#define ScintillatorSD_h 1

#include "G4VSensitiveDetector.hh"
#include "ScintillatorHit.hh"

class ScintillatorSD : public G4VSensitiveDetector {
public:
    ScintillatorSD(const G4String& name);
    virtual ~ScintillatorSD();
    
    virtual void Initialize(G4HCofThisEvent* hitCollection);
    virtual G4bool ProcessHits(G4Step* step, G4TouchableHistory* history);
    virtual void EndOfEvent(G4HCofThisEvent* hitCollection);
    
private:
    ScintillatorHitsCollection* fHitsCollection;
    G4int fHitsCollectionID;
};

#endif

Step 3: Modify GeometryParser to support your new collection

Add to the SetupSensitiveDetectors method:

void GeometryParser::SetupSensitiveDetectors() {
    // Existing code...
    
    // Create and register the default sensitive detector
    MySensitiveDetector* mySD = new MySensitiveDetector("MySD");
    sdManager->AddNewDetector(mySD);
    
    // Create and register your new sensitive detector
    ScintillatorSD* scintSD = new ScintillatorSD("ScintillatorSD");
    sdManager->AddNewDetector(scintSD);
    
    // Iterate through all volumes in the JSON configuration
    for (const auto& volConfig : geometryConfig["volumes"]) {
        // Check if this volume is marked as active
        if (volConfig.contains("isActive") && volConfig["isActive"].get<bool>()) {
            // Get the hits collection name
            std::string hitsCollName = "MyHitsCollection"; // Default
            if (volConfig.contains("hitsCollectionName")) {
                hitsCollName = volConfig["hitsCollectionName"].get<std::string>();
            }
            
            // Get the logical volume
            std::string volName = volConfig["name"].get<std::string>();
            G4LogicalVolume* logicalVol = logicalVolumeMap[volName + "_logical"];
            
            if (logicalVol) {
                // Assign the appropriate sensitive detector based on the collection name
                if (hitsCollName == "MyHitsCollection") {
                    logicalVol->SetSensitiveDetector(mySD);
                }
                else if (hitsCollName == "ScintillatorHits") {
                    logicalVol->SetSensitiveDetector(scintSD);
                }
            }
        }
    }
}

4. Accessing Hits in the Analysis

To access your hits in the analysis code:

void AnalysisManager::ProcessEvent(const G4Event* event) {
    // Get hits collections IDs
    G4HCofThisEvent* hce = event->GetHCofThisEvent();
    if (!hce) return;
    
    // Get the collection ID (do this once and cache it)
    static G4int myHitsID = -1;
    if (myHitsID < 0) {
        myHitsID = G4SDManager::GetSDMpointer()->GetCollectionID("MyHitsCollection");
    }
    
    // Get the hits collection
    MyHitsCollection* hc = static_cast<MyHitsCollection*>(hce->GetHC(myHitsID));
    
    if (hc) {
        // Process hits
        G4int nHits = hc->entries();
        for (G4int i = 0; i < nHits; i++) {
            MyHit* hit = (*hc)[i];
            
            // Access hit data
            G4double energy = hit->GetEnergy();
            G4ThreeVector position = hit->GetPosition();
            G4String volumeName = hit->GetVolumeName();
            
            // Do something with the hit data...
            G4cout << "Hit in " << volumeName 
                   << " with energy " << energy/keV << " keV"
                   << " at position " << position/mm << " mm" << G4endl;
        }
    }
}

5. Handling Multiple Instances of Volumes

When you have multiple copies of the same volume type (e.g., an array of PMTs), all copies will use the same sensitive detector. The copy number is used to distinguish between different instances:

G4bool MySensitiveDetector::ProcessHits(G4Step* step, G4TouchableHistory*) {
    // Get the copy number
    G4int copyNo = step->GetPreStepPoint()->GetTouchableHandle()->GetCopyNumber();
    
    // Create a new hit
    MyHit* hit = new MyHit();
    hit->SetCopyNumber(copyNo);  // Store the copy number
    
    // Other hit properties...
    
    // Add hit to collection
    fHitsCollection->insert(hit);
    
    return true;
}

This allows you to identify which specific instance of a volume was hit, even if they all share the same logical volume and sensitive detector.

6. Best Practices

  1. Keep hit classes focused: Only store the information you actually need

  2. Use appropriate units: Always apply Geant4 units when setting/getting values

  3. Handle threading properly: Use G4ThreadLocal for allocators

  4. Document your hits collections: Add clear comments about what each collection is for

  5. Use meaningful names: Name your collections based on their purpose or detector type

7. Troubleshooting

No Hits Being Recorded

  • Check that volumes are marked as “active” in the geometry editor

  • Verify the JSON export includes the isActive and hitsCollectionName properties

  • Ensure the sensitive detector is properly registered with G4SDManager

  • Check that the logical volume has the sensitive detector assigned

Accessing Wrong Hits Collection

  • Verify collection names match exactly between JSON and C++ code

  • Check the collection ID retrieval using G4SDManager

  • Print collection names and IDs for debugging