Author: [log in to unmask] Date: Fri May 22 12:22:19 2015 New Revision: 3009 Log: modify cluster driver to duplicate output collections Added: java/trunk/users/src/main/java/org/hps/users/holly/ClusterDriver.java Added: java/trunk/users/src/main/java/org/hps/users/holly/ClusterDriver.java ============================================================================= --- java/trunk/users/src/main/java/org/hps/users/holly/ClusterDriver.java (added) +++ java/trunk/users/src/main/java/org/hps/users/holly/ClusterDriver.java Fri May 22 12:22:19 2015 @@ -0,0 +1,349 @@ +package org.hps.users.holly; + +import java.util.ArrayList; +import java.util.List; + +import org.hps.recon.ecal.cluster.ClusterUtilities; +import org.hps.recon.ecal.cluster.Clusterer; +import org.hps.recon.ecal.cluster.ClustererFactory; +import org.hps.recon.ecal.cluster.NumericalCuts; +import org.lcsim.event.CalorimeterHit; +import org.lcsim.event.Cluster; +import org.lcsim.event.EventHeader; +import org.lcsim.geometry.Detector; +import org.lcsim.geometry.Subdetector; +import org.lcsim.geometry.subdetector.HPSEcal3; +import org.lcsim.geometry.subdetector.HPSEcal3.NeighborMap; +import org.lcsim.lcio.LCIOConstants; +import org.lcsim.util.Driver; + +/** + * <p> + * This is a {@link org.lcsim.util.Driver} that creates ECAL {@link org.lcsim.event.Cluster} + * collections using the {@link Clusterer} interface. + * <p> + * A specific clustering engine can be created with the {@link #setClustererName(String)} method + * which will use a factory to create it by name. The cuts of the {@link Clusterer} + * can be set generically with the {@link #setCuts(double[])} method. + * + * @see Clusterer + * @see NumericalCuts + * @see org.lcsim.event.Cluster + * @see org.lcsim.util.Driver + * + * @author Jeremy McCormick <[log in to unmask]> + * @author Holly Szumila <[log in to unmask]> + */ +public class ClusterDriver extends Driver { + + protected String ecalName = "Ecal"; + protected HPSEcal3 ecal; + protected NeighborMap neighborMap; + protected String outputClusterCollectionName = "EcalClusters"; + protected String inputHitCollectionName = "EcalCalHits"; + protected Clusterer clusterer; + protected double[] cuts; + protected String correctionClusterCollectionName = "CorrEcalClusters"; + + protected boolean createEmptyClusterCollection = true; + protected boolean raiseErrorNoHitCollection = false; + protected boolean skipNoClusterEvents = false; + protected boolean writeClusterCollection = true; + protected boolean storeHits = true; + protected boolean sortHits = false; + protected boolean validateClusters = false; + protected boolean copyClusterCollection = false; + + /** + * No argument constructor. + */ + public ClusterDriver() { + } + + /** + * Set the name of the ECAL in the detector framework. + * This is kind of dangerous, so set this argument at your own peril! + * @param ecalName The name of the ECAL. + */ + public void setEcalName(String ecalName) { + this.ecalName = ecalName; + } + + /** + * Set the name of the output Cluster collection. + * @param outputClusterCollectionName The name of the output Cluster collection. + */ + public void setOutputClusterCollectionName(String outputClusterCollectionName) { + this.outputClusterCollectionName = outputClusterCollectionName; + getLogger().config("outputClusterCollectionName = " + this.outputClusterCollectionName); + } + + /** + * Set the name of the copied output Cluster collection to have energy and position + * information later overwritten when cluster is matched to track. + * @param correctionClusterCollectionName + */ + public void setCorrectionClusterCollectionName(String correctionClusterCollectionName){ + this.correctionClusterCollectionName = correctionClusterCollectionName; + getLogger().config("correctionClusterCollectionName = " + this.correctionClusterCollectionName); + } + + /** + * Set the name of the input CalorimeterHit collection to use for clustering. + * @param inputHitcollectionName The name of the input hit collection. + */ + public void setInputHitCollectionName(String inputHitCollectionName) { + this.inputHitCollectionName = inputHitCollectionName; + getLogger().config("inputClusterCollectionName = " + this.inputHitCollectionName); + } + + /** + * True to raise a <code>NextEventException</code> if no Clusters are created by the Clusterer. + * @param skipNoClusterEvents True to skip events with no clusters. + */ + public void setSkipNoClusterEvents(boolean skipNoClusterEvents) { + this.skipNoClusterEvents = skipNoClusterEvents; + getLogger().config("skipNoClusterEvents = " + this.skipNoClusterEvents); + } + + /** + * True to write the Cluster collection to the output LCIO file. + * @param writeClusterCollection True to write the Cluster to the event; false to mark as transient. + */ + public void setWriteClusterCollection(boolean writeClusterCollection) { + this.writeClusterCollection = writeClusterCollection; + getLogger().config("writeClusterCollection = " + this.writeClusterCollection); + } + + /** + * True to raise an exception if the input hit collection is not found in the event. + * @param raiseErrorNoHitCollection True to raise an exception if hit collection is not in event. + */ + public void setRaiseErrorNoHitCollection(boolean raiseErrorNoHitCollection) { + this.raiseErrorNoHitCollection = raiseErrorNoHitCollection; + } + + /** + * True to store hit references into the output clusters. + * This will set <code>LCIOConstants.CLBIT_HITS</code> on the collection flags. + * @param storeHits True to store hits. + */ + public void setStoreHits(boolean storeHits) { + this.storeHits = storeHits; + } + + /** + * True to sort the clusters' hits before writing to event. + * @param sortHits True to sort hits. + */ + public void setSortHits(boolean sortHits) { + this.sortHits = sortHits; + } + + /** + * True to copy cluster collection for a second time to the event. + * This is meant to allow for overwriting of cluster energy and positions + * with corrections when the cluster is matched to a track and pid. + * @param copyClusterCollection + */ + public void setCopyClusterCollection(boolean copyClusterCollection) { + this.copyClusterCollection = copyClusterCollection; + } + + /** + * Set the Clusterer by name. + * This will use a factory method which first tries to use some hard-coded names from + * the cluster package. As a last resort, it will interpret the name as a canonical + * class name and try to instantiate it using the Class API. + * @param The name or canonical class name of the Clusterer. + */ + public void setClustererName(String name) { + clusterer = ClustererFactory.create(name); + getLogger().config("Clusterer was set to " + this.clusterer.getClass().getSimpleName()); + } + + /** + * Set the Clusterer which implements the clustering algorithm. + * @param clusterer The Clusterer. + */ + public void setClusterer(Clusterer clusterer) { + this.clusterer = clusterer; + getLogger().config("Clusterer was set to " + this.clusterer.getClass().getSimpleName()); + } + + /** + * Set whether an empty collection should be created if there are no clusters made by the Clusterer. + * @param createEmptyClusterCollection True to write an empty collection to the event. + */ + public void setCreateEmptyClusterCollection(boolean createEmptyClusterCollection) { + this.createEmptyClusterCollection = createEmptyClusterCollection; + } + + /** + * Set the numerical cuts of the Clusterer. + * @param cuts The numerical cuts. + */ + public void setCuts(double[] cuts) { + this.cuts = cuts; + } + + /** + * Set whether to validate the output. + * @param validateClusters True to validate output. + */ + public void setValidateClusters(boolean validateClusters) { + this.validateClusters = validateClusters; + } + + /** + * Setup conditions specific configuration. + */ + public void detectorChanged(Detector detector) { + Subdetector subdetector = detector.getSubdetector("Ecal"); + if (subdetector == null) { + throw new RuntimeException("There is no subdetector called " + ecalName + " in the detector."); + } + if (!(subdetector instanceof HPSEcal3)) { + throw new RuntimeException("Ther subdetector " + ecalName + " does not have the right type."); + } + ecal = (HPSEcal3) subdetector; + neighborMap = ecal.getNeighborMap(); + } + + /** + * Perform start of job initialization. + */ + public void startOfData() { + if (this.clusterer == null) { + throw new RuntimeException("The clusterer was never initialized."); + } + if (this.cuts != null) { + this.clusterer.getCuts().setValues(cuts); + } + StringBuffer sb = new StringBuffer(); + sb.append("Clusterer has the following cuts ..."); + sb.append('\n'); + for (int cutIndex = 0; cutIndex < clusterer.getCuts().getValues().length; cutIndex++) { + sb.append(this.clusterer.getCuts().getNames()[cutIndex] + " = " + this.clusterer.getCuts().getValue(cutIndex)); + sb.append('\n'); + } + getLogger().config(sb.toString()); + this.clusterer.initialize(); + } + + /** + * This method implements the default clustering procedure based on input parameters. + */ + public void process(EventHeader event) { + + if (event.hasCollection(CalorimeterHit.class, inputHitCollectionName)) { + List<CalorimeterHit> hits = event.get(CalorimeterHit.class, inputHitCollectionName); + getLogger().fine("input hit collection " + inputHitCollectionName + " has " + hits.size() + " hits"); + + // Cluster the hits, copying the list from the event in case the clustering algorithm modifies it. + List<Cluster> clusters = clusterer.createClusters(event, new ArrayList<CalorimeterHit>(hits)); + + if (clusters == null) { + throw new RuntimeException("The clusterer returned a null list from its createClusters method."); + } + if (clusters.isEmpty() && this.skipNoClusterEvents) { + getLogger().finer("skipping event with no clusters"); + throw new NextEventException(); + } + if (event.hasCollection(Cluster.class, this.outputClusterCollectionName)) { + this.getLogger().severe("There is already a cluster collection called " + this.outputClusterCollectionName); + throw new RuntimeException("Cluster collection already exists in event."); + } + int flags = 0; + if (this.storeHits) { + flags = 1 << LCIOConstants.CLBIT_HITS; + } + if (!clusters.isEmpty() || this.createEmptyClusterCollection) { + if (sortHits) { + // Sort the hits. + ClusterUtilities.sortReconClusterHits(clusters); + } + getLogger().finer("writing " + clusters.size() + " clusters to collection " + outputClusterCollectionName); + event.put(outputClusterCollectionName, clusters, Cluster.class, flags); + + if (copyClusterCollection + && event.hasCollection(Cluster.class, outputClusterCollectionName)){ + List<Cluster> clusterCopy = event.get(Cluster.class, outputClusterCollectionName); + event.put(correctionClusterCollectionName,clusterCopy,Cluster.class,flags); + } + + if (!this.writeClusterCollection) { + getLogger().finer("Collection is set to transient and will not be persisted."); + event.getMetaData(clusters).setTransient(true); + } + + if (validateClusters) { + // Perform basic validation checks. + this.validateClusters(event); + } + } + } else { + getLogger().info("The input hit collection " + this.inputHitCollectionName + " is missing from the event."); + if (this.raiseErrorNoHitCollection) { + throw new RuntimeException("The expected input hit collection is missing from the event."); + } + } + } + + /** + * Get a {@link Clusterer} using type inference for the concrete type. + * @return The Clusterer object. + */ + @SuppressWarnings("unchecked") + <ClustererType extends Clusterer> ClustererType getClusterer() { + // Return the Clusterer and cast it to the type provided by the caller. + return (ClustererType) clusterer; + } + + /** + * Perform basic validation of the cluster output collection, including checking + * that the cluster collection was created, clusters are not null, + * none of the clustered hits are null, and each hit exists in the input + * hit collection. + * @param event The LCSim event. + */ + void validateClusters(EventHeader event) { + if (!event.hasCollection(Cluster.class, outputClusterCollectionName)) { + throw new RuntimeException("Cluster collection " + outputClusterCollectionName + " is missing."); + } + List<Cluster> clusters = event.get(Cluster.class, outputClusterCollectionName); + List<CalorimeterHit> inputHitCollection = event.get(CalorimeterHit.class, inputHitCollectionName); + for (int clusterIndex = 0; clusterIndex < clusters.size(); clusterIndex++) { + getLogger().finest("checking cluster " + clusterIndex); + Cluster cluster = clusters.get(clusterIndex); + if (clusters.get(clusterIndex) == null) { + throw new RuntimeException("The Cluster at index " + clusterIndex + " is null."); + } + List<CalorimeterHit> clusterHits = cluster.getCalorimeterHits(); + getLogger().finest("cluster has " + clusterHits.size() + " hits"); + for (int hitIndex = 0; hitIndex < clusterHits.size(); hitIndex++) { + getLogger().finest("checking cluster hit " + hitIndex); + CalorimeterHit clusterHit = clusterHits.get(hitIndex); + if (clusterHit == null) { + throw new RuntimeException("The CalorimeterHit at index " + hitIndex + " in the cluster is null."); + } + if (!inputHitCollection.contains(clusterHit)) { + getLogger().severe("The CalorimeterHit at index " + hitIndex + " with ID " + clusterHit.getIdentifier().toHexString() + " is missing from the input hit collection."); + printHitIDs(inputHitCollection); + throw new RuntimeException("The CalorimeterHit at index " + hitIndex + " in the cluster is missing from the input hit collection."); + } + } + } + } + + void printHitIDs(List<CalorimeterHit> hits) { + StringBuffer buffer = new StringBuffer(); + buffer.append("hit IDs"); + buffer.append('\n'); + for (CalorimeterHit hit : hits) { + buffer.append(hit.getIdentifier().toHexString()); + buffer.append('\n'); + } + getLogger().finest(buffer.toString()); + } +}