Print

Print


Commit in lcsim/src/org/lcsim/util on MAIN
OverlayDriver.java+695added 1.1
Driver to overlay background events and model a bunch train.

lcsim/src/org/lcsim/util
OverlayDriver.java added at 1.1
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;
+	}
+	
+}
CVSspam 0.2.8