lcsim/src/org/lcsim/util
diff -N OverlayDriver.java
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ OverlayDriver.java 15 Feb 2011 23:21:42 -0000 1.1
@@ -0,0 +1,695 @@
+package org.lcsim.util;
+
+import hep.physics.particle.properties.ParticlePropertyManager;
+import hep.physics.particle.properties.ParticleType;
+import hep.physics.vec.SpacePoint;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.distribution.DistributionFactory;
+import org.apache.commons.math.distribution.PoissonDistribution;
+import org.freehep.record.source.EndOfSourceException;
+import org.lcsim.event.EventHeader;
+import org.lcsim.event.GenericObject;
+import org.lcsim.event.HitWithPosition;
+import org.lcsim.event.MCParticle;
+import org.lcsim.event.SimCalorimeterHit;
+import org.lcsim.event.SimTrackerHit;
+import org.lcsim.event.EventHeader.LCMetaData;
+import org.lcsim.event.base.BaseMCParticle;
+import org.lcsim.event.base.BaseSimCalorimeterHit;
+import org.lcsim.event.base.BaseSimTrackerHit;
+import org.lcsim.geometry.Detector;
+import org.lcsim.util.Driver;
+import org.lcsim.util.lcio.LCIOConstants;
+import org.lcsim.util.lcio.LCIOUtil;
+import org.lcsim.util.loop.LCIOEventSource;
+
+/**
+ * Driver to overlay one or more events from another slcio source over the current event.
+ * A bunch train can be modeled by setting the number of bunch crossings and the time between
+ * those bunch crossings. The number of events overlayed per bunch crossing is drawn from a
+ * Poisson distribution and its mpv is set by the weight. Time windows can be set for each
+ * collection to model a realistic readout. They control which hits to keep and are applied
+ * relative to the time of the original event plus a time of flight correction. A separate
+ * collection of McParticles is created only for the particles from the overlayed events.
+ *
+ * @version 1.0 (Feb 15, 2011)
+ * @author <a href="mailto:[log in to unmask]">Christian Grefe</a>
+ */
+public class OverlayDriver extends Driver {
+
+ protected boolean debug = false;
+ protected double c = 299.792458; // speed of light in mm/ns
+ protected SpacePoint interactionPoint = new SpacePoint(); // assuming 0 0 0 as IP
+ protected DistributionFactory df;
+ protected double tofCaloOffset = -0.25; // tolerance for keeping calo hits: tof is calculated to center of cell, but interaction can happen earlier
+ protected int bunchCrossings;
+ protected double bunchSpacing;
+ protected boolean randomSignal;
+ protected boolean fullCaloProcessing;
+ protected boolean shuffleBackground;
+ protected boolean signalAtZero;
+ protected double signalTime;
+ protected int signalBunchCrossing;
+ protected String mcOverlayName;
+ protected LCIOEventSource overlayEvents;
+ protected double overlayWeight;
+ protected List<Integer> overlayList;
+ protected PoissonDistribution backgroundDistribution;
+ protected Map<String, Double> readoutTimes;
+ protected Map<String, Map<Long,SimCalorimeterHit>> caloHitMap;
+
+ // -------------------- Constructors --------------------
+ /**
+ * Default constructor
+ */
+ public OverlayDriver() {
+ df = DistributionFactory.newInstance();
+ bunchCrossings = 1;
+ bunchSpacing = 1.;
+ randomSignal = true;
+ fullCaloProcessing = false;
+ signalAtZero = true;
+ signalBunchCrossing = -1;
+ mcOverlayName = "MCParticles_overlay";
+ overlayWeight = 0;
+ overlayList = new ArrayList<Integer>();
+ readoutTimes = new HashMap<String, Double>();
+ caloHitMap = new HashMap<String, Map<Long,SimCalorimeterHit>>();
+ shuffleBackground = true;
+ }
+
+ // -------------------- Steering Parameters --------------------
+ /**
+ * Sets the number of bunch crossings in a train. This is the maximum number
+ * of bunch crossings overlayed, independent of readout times.
+ * @param n the number of bunch crossings in a bunch train, default is 1
+ */
+ public void setBunchCrossings(int n) {
+ if (n > 1) {
+ this.bunchCrossings = n;
+ } else {
+ this.bunchCrossings = 1;
+ }
+ }
+
+ /**
+ * Sets the time between two bunch crossings.
+ * @param t the time between two bunch crossings in ns, default is 1 ns
+ */
+ public void setBunchSpacing(double t) {
+ if (t > 0.) {
+ this.bunchSpacing = t;
+ } else {
+ this.bunchSpacing = 0.;
+ }
+ }
+
+ /**
+ * Sets the bunch crossing of the signal event. In case of a negative value
+ * the bunch crossing will be selected randomly. In case of a value higher
+ * than the total amount of bunch crossings in a train it will be placed in
+ * the last bunch crossing. Default is a random bunch crossing.
+ * crossing.
+ * @param bunchCrossing the bunch crossing of the signal event
+ */
+ public void setSignalBunchCrossing(int bunchCrossing) {
+ if (bunchCrossing < 0) {
+ this.randomSignal = true;
+ } else {
+ this.randomSignal = false;
+ this.signalBunchCrossing = bunchCrossing;
+ }
+ }
+
+ /**
+ * Sets a name as an identifier for the overlayed events.
+ * The name is used in an LCRelation to identify McParticles from the overlay.
+ * @param name identifier for the overlay events
+ */
+ public void setOverlayName(String name) {
+ mcOverlayName = "MCParticles_"+name;
+ }
+
+ /**
+ * Sets the number of overlay event used per bunch crossing.
+ * The actual number per event is drawn from a poisson distribution with the
+ * weight being the most probable value of the distribution.
+ * A weight of 0 will instead add one overlay event per bunch crossing.
+ * @param weight the most probable number of overlay events added per bunch crossing
+ */
+ public void setOverlayWeight(double weight) {
+ overlayWeight = weight;
+ }
+
+ public void setOverlayFiles(String[] fileList) {
+ List<File> files = new ArrayList<File>();
+ for (String fileName : fileList) {
+ files.add(new File(fileName));
+ }
+ try {
+ LCIOEventSource lcio = new LCIOEventSource("overlay", files);
+ overlayEvents = lcio;
+ } catch (IOException e) {
+ System.err.println(e.getMessage());
+ }
+ }
+
+ /**
+ * Sets the readout time window for an LCCollection. The list of strings has to have
+ * an exact length of two with the following pattern:
+ * "CollectionName time", where the time is given in ns.
+ * @param collection a string of the form "CollectionName time"
+ */
+ public void setReadoutTime(String[] collection) {
+ if (collection.length != 2) {
+ throw new RuntimeException("Needs exactly two strings");
+ }
+ readoutTimes.put(collection[0], Double.valueOf(collection[1]));
+ }
+
+ /**
+ * Removes all readout times that were set.
+ */
+ public void clearReadoutTimes() {
+ readoutTimes.clear();
+ }
+
+ /**
+ * Selects if calorimeter hits are treated in full detail or simplified.
+ * The simple mode just takes the time of the first contribution in a SimCalorimeterHit
+ * in order to check if it falls into a relevant time window (keeping all contributions).
+ * The full mode checks all contributions and only keeps those inside the time window.
+ * @param fullCaloProcessing use full detailed mode (default false)
+ */
+ public void setFullCaloProcessing(boolean fullCaloProcessing) {
+ this.fullCaloProcessing = fullCaloProcessing;
+ }
+
+ /**
+ * Selects if the overlay events are randomly overlayed on the event instead of
+ * in a serial way
+ * @param shuffleOverlay shuffle the overlay events (default true)
+ */
+ public void setShuffleOverlay(boolean shuffleOverlay) {
+ this.shuffleBackground = shuffleOverlay;
+ }
+
+ /**
+ * Selects if the time of the signal event is placed at time 0, so that all other
+ * bunch crossings are shifted accordingly. Otherwise the first bunch crossing is
+ * at time 0 and the signal event at the time of its bunch crossing.
+ * @param signalAtZero set time of signal to 0 (default true)
+ */
+ public void setSignalAtZero(boolean signalAtZero) {
+ this.signalAtZero = signalAtZero;
+ }
+
+ /**
+ * Switches on additional messages
+ * @param debug
+ */
+ public void setDebug(boolean debug) {
+ this.debug = debug;
+ }
+
+ // -------------------- Driver Interface --------------------
+ @Override
+ protected void startOfData() {
+ backgroundDistribution = df.createPoissonDistribution(overlayWeight);
+ }
+
+ @Override
+ protected void detectorChanged(Detector detector) {
+ // nothing to do
+ }
+
+ @Override
+ protected void process(EventHeader event) {
+
+ // reset list of hit calorimeter cells
+ caloHitMap.clear();
+
+ // shift the signal event in time according to its BX
+ if (randomSignal) {
+ signalBunchCrossing = this.getRandom().nextInt(bunchCrossings);
+ } else if (signalBunchCrossing >= bunchCrossings) {
+ signalBunchCrossing = bunchCrossings -1;
+ }
+ double signalTime = 0;
+ if (!signalAtZero) signalTime = signalBunchCrossing * bunchSpacing;
+ if (debug) System.out.println("Move signal event to BX: "+signalBunchCrossing);
+ this.moveEventToTime(event, signalTime);
+
+ // building a list of all bunch crossings in this train
+ overlayList.clear();
+ for (int bX = 0; bX != bunchCrossings; bX++) {
+ int nBackgroundEvts = 1;
+ if (overlayWeight != 0.) {
+ try {
+ // need to add one to the number because it is an integer distribution with a lower limit
+ nBackgroundEvts = backgroundDistribution.inverseCumulativeProbability(this.getRandom().nextDouble()) + 1;
+ } catch (MathException e) {
+ System.err.println("Error getting poisson distribution: "+e.getMessage());
+ }
+ }
+ // add this bX one time for every background event to happen during this bX
+ for (int i = 0; i < nBackgroundEvts; i++) {
+ overlayList.add(bX);
+ }
+ }
+ // shuffle the list
+ if (shuffleBackground) Collections.shuffle(overlayList, this.getRandom());
+
+ System.out.println("signal event mc particles: "+event.getMCParticles().size());
+ for (int bX : overlayList) {
+ if (debug) System.out.println("Overlaying BX "+bX);
+
+ double overlayTime = (bX - signalBunchCrossing) * bunchSpacing;
+ if (!signalAtZero) overlayTime = bX * bunchSpacing;
+
+ EventHeader overlayEvent = this.getNextEvent(overlayEvents);
+ if (debug) System.out.println("Memory free: "+100*Runtime.getRuntime().freeMemory()/Runtime.getRuntime().totalMemory()+(" %"));
+ if (overlayEvent != null) {
+ if (event.getDetector().equals(overlayEvent.getDetector())) {
+ this.mergeEvents(event, overlayEvent, overlayTime);
+ } else {
+ if (debug) System.err.println("Unable to merge events simulated in different detectors");
+ }
+ } else {
+ if (debug) System.err.println("Error reading from overlay event list");
+ }
+ }
+ if (debug) {
+ System.out.println("Mc particles after merging: "+event.getMCParticles().size());
+ System.out.println("Mc particles from background: "+event.get(MCParticle.class, mcOverlayName).size());
+ }
+ }
+
+ @Override
+ protected void suspend() {
+ // nothing to do
+ }
+
+ @Override
+ protected void endOfData() {
+ // nothing to do
+ }
+
+ // -------------------- Protected Methods --------------------
+ /**
+ * Goes to the next event in the LCIOEventSource and returns it.
+ * If the end of the source is reached, the source is rewound and
+ * the first event will be returned. If any other error occurs,
+ * i.e. the source does not exist, null is returned instead.
+ * @param lcio The LCIO source
+ * @return The next event in the LCIO file
+ */
+ protected EventHeader getNextEvent(LCIOEventSource lcio) {
+ EventHeader event = null;
+ try {
+ lcio.next();
+ event = (EventHeader) lcio.getCurrentRecord();
+ } catch (EndOfSourceException e) {
+ try {
+ lcio.rewind();
+ lcio.next();
+ event = (EventHeader) lcio.getCurrentRecord();
+ } catch (Exception e2) {
+ System.err.println(e2.getMessage());
+ }
+ } catch (Exception e) {
+ System.err.println(e.getMessage());
+ }
+ return event;
+ }
+
+ /**
+ * Calculates the time of flight from the interaction point to
+ * the position of the given hit along a straight line.
+ * @param hit
+ */
+ protected double getLosTof(HitWithPosition hit) {
+ return SpacePoint.distance(new SpacePoint(hit.getPositionVec()), interactionPoint)/c;
+ }
+
+ /**
+ * Adds a collection to an event using the meta data information from the
+ * given collection and the entries from the given list.
+ * @param collection the collection to take the meta data from
+ * @param entries the list of entries to put into the event
+ * @param event the event to put the collection
+ */
+ protected void putCollection(LCMetaData collection, List entries, EventHeader event) {
+ String[] readout = collection.getStringParameters().get("READOUT_NAME");
+ if (readout != null) {
+ event.put(collection.getName(), entries, collection.getType(), collection.getFlags(), readout[0]);
+ } else {
+ event.put(collection.getName(), entries, collection.getType(), collection.getFlags());
+ }
+ if (debug) System.out.println("Put collection: "+collection.getName());
+ }
+
+ /**
+ * Shifts an event in time. Moves all entries in all collections
+ * in the event by the given offset in time.
+ * @param event the event to move in time
+ * @param time the time shift applied to all entries in all collections
+ */
+ protected void moveEventToTime(EventHeader event, double time) {
+ // need to copy list of collections to avoid concurrent modification
+ List<LCMetaData> collections = new ArrayList<LCMetaData>(event.getMetaData());
+ for (LCMetaData collection : collections) {
+ List movedCollection = this.moveCollectionToTime(collection, time);
+ if (movedCollection != null) {
+ // replace the original collection
+ event.remove(collection.getName());
+ this.putCollection(collection, movedCollection, event);
+ }
+ }
+ }
+
+ /**
+ * Shifts a collection in time. Moves all entries in the collection
+ * by the given offset in time. If a readout time is set for the
+ * given collection, all entries outside of that window will be removed.
+ * @param collection the collection to move in time
+ * @param time the time shift applied to all entries in the collection
+ * @return returns the list of moved entries
+ */
+ protected List moveCollectionToTime(LCMetaData collection, double time ) {
+ EventHeader event = collection.getEvent();
+ String collectionName = collection.getName();
+ Class collectionType = collection.getType();
+ int flags = collection.getFlags();
+ if (debug) System.out.println("Moving collection: "+collectionName+" of type "+collectionType);
+
+ double timeWindow = 0;
+ if (readoutTimes.get(collectionName) != null) {
+ timeWindow = readoutTimes.get(collectionName);
+ }
+
+ // negative time window means ignore hits and return empty list
+ if (timeWindow < 0) return new ArrayList<Object>();
+
+ List movedCollection;
+ if (collectionType.isAssignableFrom(MCParticle.class)) {
+ // MCParticles
+ movedCollection = new ArrayList<MCParticle>();
+
+ // need a mep to store relations between old and new mc particles to resolve parent daughter relations
+ Map<MCParticle,BaseMCParticle> oldToNewMcMap = new HashMap<MCParticle, BaseMCParticle>();
+
+ for (MCParticle mcP : event.get(MCParticle.class, collectionName)) {
+ ParticleType mcpType = ParticlePropertyManager.getParticlePropertyProvider().get(mcP.getPDGID());
+ BaseMCParticle movedMcP = new BaseMCParticle(mcP.getOrigin(), mcP.asFourVector(),
+ mcpType, mcP.getGeneratorStatus(), mcP.getProductionTime() + time);
+ movedMcP.setSimulatorStatus(mcP.getSimulatorStatus());
+ movedMcP.setMass(mcP.getMass());
+ movedMcP.setEndPoint(mcP.getEndPoint());
+ oldToNewMcMap.put(mcP, movedMcP);
+ movedCollection.add(movedMcP);
+ }
+ for (MCParticle oldMc : oldToNewMcMap.keySet()) {
+ for (MCParticle daughter : oldMc.getDaughters()) {
+ oldToNewMcMap.get(oldMc).addDaughter(oldToNewMcMap.get(daughter));
+ }
+ }
+ } else if (collectionType.isAssignableFrom(SimTrackerHit.class)) {
+ // SimTrackerHits
+ movedCollection = new ArrayList<SimTrackerHit>();
+ for (SimTrackerHit hit : event.get(SimTrackerHit.class, collectionName)) {
+ // check if hit falls into relevant readout time window
+ double hitTime = hit.getTime() + time;
+ double tofCorr = this.getLosTof(hit);
+ if (timeWindow > 0) {
+ if (hitTime < signalTime + tofCorr || hitTime > signalTime + tofCorr + timeWindow) continue;
+ }
+ SimTrackerHit movedHit = new BaseSimTrackerHit(hit.getPosition(),
+ hit.getdEdx(), hit.getMomentum(), hit.getPathLength(), hit.getTime()+time,
+ hit.getCellID(), hit.getMCParticle(), hit.getMetaData(), hit.getDetectorElement());
+ movedCollection.add(movedHit);
+ }
+ } else if (collectionType.isAssignableFrom(SimCalorimeterHit.class)) {
+ // SimCalorimeterHits
+ movedCollection = new ArrayList<SimCalorimeterHit>();
+ // check if hit contains PDGIDs
+ boolean hasPDG = LCIOUtil.bitTest(flags,LCIOConstants.CHBIT_PDG);
+ List<SimCalorimeterHit> hits = event.get(SimCalorimeterHit.class, collectionName);
+ int nSimCaloHits = hits.size();
+ int nHitsMoved = 0;
+ for (SimCalorimeterHit hit : event.get(SimCalorimeterHit.class, collectionName)) {
+ // check if earliest energy deposit is later than relevant time window
+ double tofCorr = this.getLosTof(hit);
+ BaseSimCalorimeterHit movedHit = null;
+ if (fullCaloProcessing) {
+ if (hit.getTime() > signalTime + tofCorr + timeWindow) continue;
+ nHitsMoved++;
+ // create arrays to hold contributions from different mc particles
+ List<Object> mcList = new ArrayList<Object>();
+ List<Float> eneList = new ArrayList<Float>();
+ List<Float> timeList = new ArrayList<Float>();
+ List<Integer> pdgList = new ArrayList<Integer>();
+ double rawEnergy = 0.;
+ for (int i = 0; i != hit.getMCParticleCount(); i++) {
+ float hitTime = (float) (hit.getContributedTime(i) + time);
+ if (hitTime < signalTime + tofCorr + tofCaloOffset || hitTime > signalTime + tofCorr + timeWindow) continue;
+ float hitEnergy = (float) hit.getContributedEnergy(i);
+ mcList.add(hit.getMCParticle(i));
+ eneList.add(hitEnergy);
+ timeList.add(hitTime);
+ if (hasPDG) pdgList.add(hit.getPDG(i));
+ rawEnergy += hitEnergy;
+ }
+ int hitEntries = mcList.size();
+ if (hitEntries == 0) continue;
+ Object[] mcArr = mcList.toArray();
+ float[] eneArr = new float[hitEntries];
+ float[] timeArr = new float[hitEntries];
+ int[] pdgArr = null;
+ if (hasPDG) pdgArr = new int[hitEntries];
+ for (int i = 0; i != hitEntries; i++) {
+ mcArr[i] = mcList.get(i);
+ eneArr[i] = eneList.get(i);
+ timeArr[i] = timeList.get(i);
+ if (hasPDG) pdgArr[i] = pdgList.get(i);
+ }
+ // need to set time to 0 so it is recalculated from the timeList
+ movedHit = new BaseSimCalorimeterHit(hit.getCellID(),
+ rawEnergy, 0., mcArr, eneArr, timeArr, pdgArr);
+ movedHit.setMetaData(collection);
+ // set detector elements for collections which actually implement this method
+ try {
+ movedHit.setDetectorElement(hit.getDetectorElement());
+ } catch (Exception e) {
+ // nothing to do
+ }
+ } else {
+ double hitTime = hit.getTime() + time;
+ if (hitTime < signalTime + tofCorr + tofCaloOffset || hitTime > signalTime + tofCorr + timeWindow) continue;
+ movedHit = (BaseSimCalorimeterHit) hit;
+ movedHit.shiftTime(time);
+ }
+ movedCollection.add(movedHit);
+ if (debug && nHitsMoved%100 == 0) System.out.print("Moved "+nHitsMoved+" / "+nSimCaloHits+" hits\r");
+ }
+ if (debug) System.out.print("\n");
+ } else if (collectionType.isAssignableFrom(GenericObject.class)) {
+ // nothing to do for GenericObjects
+ return event.get(GenericObject.class, collectionName);
+ } else {
+ if (debug) System.err.println("Unable to move collection: "+collectionName+" of type "+collectionType);
+ return null;
+ }
+ if (debug) System.out.println("Moved collection: "+collectionName+" of type "+collectionType+" to "+time+"ns");
+ return movedCollection;
+ }
+
+ /**
+ * Merges all collections from the given events and applies a time offset
+ * to all entries in all collections of the overlay event.
+ * @param event the event where everything is merged into
+ * @param overlayEvent the event overlayed
+ * @param overlayTime the time offset for the overlay event
+ */
+ protected void mergeEvents(EventHeader event, EventHeader overlayEvent, double overlayTime) {
+ // create a collection for the background mc particles
+ if (!event.hasCollection(MCParticle.class, mcOverlayName)) {
+ int flags = event.getMetaData(event.getMCParticles()).getFlags();
+ flags = LCIOUtil.bitSet(flags, LCIOConstants.BITSubset, true);
+ event.put(mcOverlayName, new ArrayList<MCParticle>(), MCParticle.class, flags);
+ }
+
+ // need to copy list of collections to avoid concurrent modification
+ List<LCMetaData> overlayCollections = new ArrayList<LCMetaData>(overlayEvent.getMetaData());
+ for (LCMetaData overlayCollection : overlayCollections) {
+ String overlayCollectionName = overlayCollection.getName();
+ if (event.hasItem(overlayCollectionName)) {
+ this.mergeCollections(event.getMetaData((List)event.get(overlayCollectionName)), overlayCollection, overlayTime);
+ } else {
+ // event does not contain corresponding collection from overlayEvent, just put it there
+ this.putCollection(overlayCollection, (List)overlayEvent.get(overlayCollectionName), event);
+ }
+ }
+ }
+
+ /**
+ * Adds an mc particle to the list of mc particles as well as the overlay mc particles.
+ * Also adds the chain of parents.
+ * @param event
+ * @param particle
+ */
+ protected void addOverlayMcParticle(EventHeader event, MCParticle particle) {
+ List<MCParticle> overlayMcParticles = event.get(MCParticle.class, mcOverlayName);
+ List<MCParticle> mcParticles = event.getMCParticles();
+ if (!overlayMcParticles.contains(particle)) {
+ overlayMcParticles.add(particle);
+ mcParticles.add(particle);
+ List<MCParticle> parents = particle.getParents();
+ for (MCParticle parent : parents) {
+ this.addOverlayMcParticle(event, parent);
+ }
+ }
+ }
+
+ /**
+ * Merges two collections and applies a time offset to all entries in
+ * the overlay collection.
+ * @param collection the collection where the overlay collection is merged into
+ * @param overlayCollection the collection overlayed
+ * @param overlayTime the time offset for the overlay collection
+ * @return returns <c>false</c> if unable to merge collections, otherwise <c>true</c>
+ */
+ protected boolean mergeCollections(LCMetaData collection, LCMetaData overlayCollection, double overlayTime) {
+ String collectionName = collection.getName();
+ Class collectionType = collection.getType();
+ Class overlayCollectionType = overlayCollection.getType();
+ if (!collectionType.equals(overlayCollectionType)) {
+ if (debug) System.err.println("Can not merge collections: "+collectionName
+ +" of type "+collectionType+" and "+overlayCollectionType);
+ return false;
+ }
+
+ // move the overlay hits in time, signal should have been moved already
+ List overlayEntries = this.moveCollectionToTime(overlayCollection, overlayTime);
+ //List overlayEntries = overlayCollection.getEvent().get(overlayCollectionType, overlayCollection.getName());
+ // Check if there are actually entries to overlay
+ if (overlayEntries.size() == 0) return true;
+ EventHeader event = collection.getEvent();
+
+ if (collectionType.isAssignableFrom(MCParticle.class)) {
+ // Nothing to do. Only add mc particles that are connected to something kept in the event.
+ // This is done in the other steps below.
+ if (!collectionName.equals(event.MC_PARTICLES) ) {
+ event.get(MCParticle.class, collectionName).addAll(overlayEntries);
+ }
+
+ } else if (collectionType.isAssignableFrom(SimTrackerHit.class)) {
+ // SimTrackerHits: just append all hits from overlayEvents
+ event.get(SimTrackerHit.class, collectionName).addAll(overlayEntries);
+
+ // add contributing mc particles to lists
+ for (SimTrackerHit hit : (List<SimTrackerHit>)overlayEntries) {
+ this.addOverlayMcParticle(event, hit.getMCParticle());
+ }
+
+ } else if (collectionType.isAssignableFrom(SimCalorimeterHit.class)) {
+ // SimCalorimeterHits: need to merge hits in cells which are hit in both events
+ // check if map has already been filled
+ Map<Long,SimCalorimeterHit> hitMap;
+ List<SimCalorimeterHit> hits = event.get(SimCalorimeterHit.class, collectionName);
+ if (!caloHitMap.containsKey(collectionName)) {
+ // build map of cells which are hit in signalEvent
+ hitMap = new HashMap<Long, SimCalorimeterHit>();
+ for (SimCalorimeterHit hit : hits) {
+ hitMap.put(hit.getCellID(), hit);
+ }
+ caloHitMap.put(collectionName, hitMap);
+ } else {
+ hitMap = caloHitMap.get(collectionName);
+ }
+
+ boolean hasPDG = LCIOUtil.bitTest(collection.getFlags(),LCIOConstants.CHBIT_PDG);
+ // loop over the hits from the overlay event
+ for (SimCalorimeterHit overlayHit : (List<SimCalorimeterHit>)overlayEntries) {
+ long cellID = overlayHit.getCellID();
+
+ // add contributing mc particles to lists
+ for (int i = 0; i != overlayHit.getMCParticleCount(); i++) {
+ this.addOverlayMcParticle(event, overlayHit.getMCParticle(i));
+ }
+
+ if (hitMap.containsKey(cellID)) {
+ SimCalorimeterHit hit = hitMap.get(overlayHit.getCellID());
+ int nHitMcP = hit.getMCParticleCount();
+ int nOverlayMcP = overlayHit.getMCParticleCount();
+ int nMcP = nHitMcP + nOverlayMcP;
+ // arrays of mc particle contributions to the hit
+ Object[] mcpList = new Object[nMcP];
+ float[] eneList = new float[nMcP];
+ float[] timeList = new float[nMcP];
+ int[] pdgList = null;
+ if (hasPDG) pdgList = new int[nMcP];
+ double rawEnergy = 0.;
+ // fill arrays with values from hit
+ for (int i = 0; i != nHitMcP; i++) {
+ mcpList[i] = hit.getMCParticle(i);
+ eneList[i] = (float)hit.getContributedEnergy(i);
+ timeList[i] = (float)hit.getContributedTime(i);
+ if (hasPDG) pdgList[i] = hit.getPDG(i);
+ rawEnergy += eneList[i];
+ }
+ // add values of overlay hit
+ for (int i = 0; i != nOverlayMcP; i++) {
+ int j = nHitMcP + i;
+ mcpList[j] = overlayHit.getMCParticle(i);
+ eneList[j] = (float)overlayHit.getContributedEnergy(i);
+ timeList[j] = (float)overlayHit.getContributedTime(i);
+ if (hasPDG) pdgList[j] = overlayHit.getPDG(i);
+ rawEnergy += eneList[j];
+ }
+ // need to set time to 0 so it is recalculated from the timeList
+ SimCalorimeterHit mergedHit = new BaseSimCalorimeterHit(hit.getCellID(),
+ rawEnergy, 0., mcpList, eneList, timeList, pdgList);
+ mergedHit.setMetaData(collection);
+ // set detector elements for collections which actually implement this method
+ try {
+ mergedHit.setDetectorElement(hit.getDetectorElement());
+ } catch (Exception e) {
+ // nothing to do
+ }
+ // replace old hit with merged hit
+ hits.remove(hit);
+ hits.add(mergedHit);
+ hitMap.put(cellID, mergedHit);
+ } else {
+ hits.add(overlayHit);
+ hitMap.put(cellID, overlayHit);
+ }
+
+ }
+ } else if (collectionType.isAssignableFrom(GenericObject.class)) {
+ // need to implement all kinds of possible GenericObjects separately
+ if (collectionName.equals("MCParticleEndPointEnergy")) {
+ // TODO decide what to do with this collection in the overlay events
+ // TODO would need to resolve the position of kept mc particles and keep the same position here
+ //event.get(GenericObject.class, collectionName).addAll(overlayEntries);
+ //event.remove("MCParticleEndPointEnergy");
+ } else {
+ if (debug) System.err.println("Can not merge collection "+collectionName
+ +" of type "+collectionType+". Unhandled type.");
+ return false;
+ }
+ }
+ return true;
+ }
+
+}