Print

Print


Commit in lcsim/src/org/lcsim/recon/cluster/mst on MAIN
MSTPhotonFinder.java+170added 1.1
MSTPhotonFinderDriver.java+101added 1.1
TransverseDistanceMetric.java+78added 1.1
PhotonShapeDecision.java+89added 1.1
+438
4 added files
Photon-finder

lcsim/src/org/lcsim/recon/cluster/mst
MSTPhotonFinder.java added at 1.1
diff -N MSTPhotonFinder.java
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ MSTPhotonFinder.java	11 Aug 2006 23:31:25 -0000	1.1
@@ -0,0 +1,170 @@
+package org.lcsim.recon.cluster.mst;
+
+import java.util.*; 
+
+import org.lcsim.event.Cluster;
+import org.lcsim.event.CalorimeterHit;
+import org.lcsim.recon.cluster.util.Clusterer;
+import org.lcsim.util.decision.*;
+import org.lcsim.util.hitmap.HitMap;
+import org.lcsim.recon.cluster.util.ClusterSizeDecision;
+import org.lcsim.recon.cluster.util.ClusterFirstLayerDecision;
+import org.lcsim.recon.cluster.util.ClusterLayerSeparationDecision;
+import org.lcsim.recon.cluster.util.BasicCluster;
+
+/**
+ * Class to reconstruct and select photon clusters in the ECAL.
+ * Conforms to the Clusterer interface.
+ *
+ * @version $Id: MSTPhotonFinder.java,v 1.1 2006/08/11 23:31:25 mcharles Exp $
+ */
+
+public class MSTPhotonFinder implements Clusterer 
+{
+    /** Simple constructor */
+    public MSTPhotonFinder() {
+	m_shapeDec.setMinimum(null,null,null);
+	m_shapeDec.setMaximum(null,null,null);
+    }
+
+    // Config stuff
+    protected List<Cluster> m_MIPVetoList = null;
+    protected double m_coreThreshold = 7.5; // default (mm)
+    protected double m_fragThreshold = 75.0; // default (mm)
+    protected int    m_layerProximityThreshold = 2; // default
+    protected int    m_coreSizeMinimum = 10;
+    protected int    m_fragSizeMaximum = 6;
+    protected int    m_coreFirstLayerRange = 6;
+    protected DecisionMakerSingle<Cluster> m_inputDec = null;
+    protected PhotonShapeDecision m_shapeDec = new PhotonShapeDecision();
+
+    /** Set minimum 3D distance between hits in photon core */
+    public void setCoreThreshold(double newThreshold) { m_coreThreshold = newThreshold; }
+
+    /** Set minimum transverse distance between photon core center and fragment center */
+    public void setFragmentThreshold(double newThreshold) { m_fragThreshold = newThreshold; }
+
+    /** 
+     * For a fragment to be added, require that there is at
+     * least one fragment hit within must be within [layerThreshold]
+     * layers of a core hit. Default is 2.
+     */
+    public void setLayerProximityThreshold(int layerThreshold) { m_layerProximityThreshold = layerThreshold; }
+
+    /**
+     * Set input hit decision (e.g. energy threshold) if desired.
+     * As per MSTClusterDriver interface, the hits are wrapped into
+     * one-hit clusters and a DecisionMakerSingle<Cluster> is applied.
+     * This applies to both cores and fragments.
+     */
+    public void setInputHitDecision(DecisionMakerSingle<Cluster> dec) { m_inputDec = dec; }
+
+    /** Cores must have at least [min] hits. */
+    public void setCoreSizeMinimum(int min) { m_coreSizeMinimum = min; }
+
+    /** Fragments must have at most [min] hits. */
+    public void setFragmentSizeMaximum(int max) { m_fragSizeMaximum = max; }
+
+    /** Cores must have a hit in the first [nLayers] layers. */
+    public void setCoreFirstLayerRange(int nLayers) { m_coreFirstLayerRange = nLayers; }
+
+    /** Ron's Clusterer interface */
+    public List<Cluster> createClusters(Map<Long,CalorimeterHit> map) {
+	List<CalorimeterHit> list = new Vector<CalorimeterHit>();
+	list.addAll(map.values());
+	return createClusters(list);
+    }
+    
+    /** 
+     * Find photon candidate clusters, checking for consistency
+     * with photon shape. The caller supplies a list of hits in
+     * the ECAL from which the MIPs have already been removed.
+     *   
+     */
+    public List<Cluster> createClusters(List<CalorimeterHit> hits) 
+    {
+	// First step: Find clusters with MST
+	// ----------------------------------
+
+	// Values are hard-coded for now
+	MSTClusterDriver mstClusterer = new MSTClusterDriver();
+	mstClusterer.setThreshold(m_coreThreshold);
+	mstClusterer.registerMetrics(new GeometricalDistance());
+	if (m_inputDec != null) {
+	    mstClusterer.setInputDecision(m_inputDec);
+	}
+	List<Cluster> photonClusters = mstClusterer.createClusters(hits);
+	
+	// Second step: Separate photon cores from fragments
+	// -------------------------------------------------
+
+	// Set up the decision-maker to identify valid photon cores
+	AndDecisionMakerSingle<Cluster> coreInputDecision = new AndDecisionMakerSingle<Cluster>();
+	coreInputDecision.addDecisionMaker(new ClusterSizeDecision(m_coreSizeMinimum)); // Require >= n hits
+	coreInputDecision.addDecisionMaker(new ClusterFirstLayerDecision(m_coreFirstLayerRange)); // Require first hit in first n layers
+	if (m_MIPVetoList != null) {
+	    // NOT YET IMPLEMENTED
+	    //List<Cluster> mipList = event.get(Cluster.class, m_MIPVetoList);
+	    //coreInputDecision.addDecisionMaker(new ProximityDecision(mipList, 0.75)); // Require isolation w.r.t. MIPs
+	}
+	coreInputDecision.addDecisionMaker(m_shapeDec); // Require photon shape
+
+	// Set up the decision-maker to identify fragments.
+	// We should require that they are
+	//   (a) inconsistent with being a core
+	//   (b) consistent with being a fragment
+	AndDecisionMakerSingle<Cluster> fragInputDecision = new AndDecisionMakerSingle<Cluster>();
+	fragInputDecision.addDecisionMaker(new NotDecisionMakerSingle<Cluster> (coreInputDecision)); // Inconsistent with being a core
+	fragInputDecision.addDecisionMaker(new NotDecisionMakerSingle<Cluster> (new ClusterSizeDecision(m_fragSizeMaximum+1))); // Require < n+1 hits
+
+	// OK, now split into cores and non-cores
+	ListFilter findCores = new ListFilter(coreInputDecision);
+	ListFilter findFrags = new ListFilter(fragInputDecision);
+	List<Cluster> cores = findCores.filterList(photonClusters);
+	List<Cluster> frags = findFrags.filterList(photonClusters);
+	if (m_debug) { System.out.println("DEBUG: Found "+cores.size()+" cores and "+frags.size()+" fragments."); }
+
+	// Third step: Find fragments surrounding each photon
+	// --------------------------------------------------
+
+	DecisionMakerPair<Cluster,Cluster> pairDecision = new ClusterLayerSeparationDecision(m_layerProximityThreshold);
+	Metrics metric = new TransverseDistanceMetric();
+
+	Map<Cluster,Cluster> closestCore = new HashMap<Cluster,Cluster>();
+	
+	// For each fragment, find the best-match core
+	for (Cluster frag : frags) {
+	    // Loop over cores to find the best match
+	    double dMin = -1;
+	    Cluster bestCore = null;
+	    for (Cluster core : cores) {
+		if (pairDecision.valid(core, frag)) {
+		    double d = metric.getDistance(core, frag);
+		    if (bestCore==null || d < dMin) {
+			// This is the best match so far
+			bestCore = core;
+			dMin = d;
+		    }
+		}
+	    }
+	    if (bestCore != null && dMin < m_fragThreshold) {
+		// Found a valid match
+		closestCore.put(frag,bestCore);
+	    }
+	}
+
+	// Fourth step: Add nearby fragments to core
+	// -----------------------------------------
+	for (Cluster frag : closestCore.keySet()) {
+	    // Cores were produced by MST earlier, so should be BasicCluster
+	    Cluster matchedCore = closestCore.get(frag);
+	    BasicCluster basicMatchedCore = (BasicCluster) (matchedCore);
+	    basicMatchedCore.addCluster(frag);
+	}
+
+	return cores;
+    }
+
+    protected boolean m_debug = false;
+    public void setDebug(boolean debug) { m_debug = debug; }
+}

lcsim/src/org/lcsim/recon/cluster/mst
MSTPhotonFinderDriver.java added at 1.1
diff -N MSTPhotonFinderDriver.java
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ MSTPhotonFinderDriver.java	11 Aug 2006 23:31:25 -0000	1.1
@@ -0,0 +1,101 @@
+package org.lcsim.recon.cluster.mst;
+
+import java.util.*;
+
+import org.lcsim.event.EventHeader;
+import org.lcsim.event.Cluster;
+import org.lcsim.event.CalorimeterHit;
+import org.lcsim.util.hitmap.HitMap;
+import org.lcsim.util.Driver;
+import org.lcsim.util.decision.DecisionMakerSingle;
+
+/**
+ * Driver wrapper around the MSTPhotonFinder class.
+ *
+ * @version $Id: MSTPhotonFinderDriver.java,v 1.1 2006/08/11 23:31:25 mcharles Exp $
+ */
+
+public class MSTPhotonFinderDriver extends Driver
+{
+    /** Simple constructor. */
+    public MSTPhotonFinderDriver() {
+	m_finder = new MSTPhotonFinder();
+    }
+
+    /** Processs one event, looking for photon candidates. */
+    public void process(EventHeader event) {
+	super.process(event);
+	HitMap hits = (HitMap) (event.get(m_inputHitMapName));
+	List<Cluster> photons = m_finder.createClusters(hits);
+	event.put(m_outputClusterListName, photons);
+	// Build output HitMap
+	HitMap outputHitMap = new HitMap(hits);
+	// Remove photon hits
+	for (Cluster clus : photons) {
+	    for (CalorimeterHit hit : clus.getCalorimeterHits()) {
+		Long cellID = new Long(hit.getCellID());
+		outputHitMap.remove(cellID);
+	    }
+	}
+	if (m_debug) { System.out.println("DEBUG: In this event, found "+photons.size()+" photons and wrote them out as '"+m_outputClusterListName+"'."); }
+	event.put(m_outputHitMapName, outputHitMap);
+    }
+
+    /** Read this HitMap in from the event and look for photons in those hits. */
+    public void setInputHitMap(String name) { m_inputHitMapName = name; }
+    /** Write out unused hits to the event under this name. */
+    public void setOutputHitMap(String name) { m_outputHitMapName = name; }    
+    /** Write out photon clusters to the event under this name. */
+    public void setOutputClusterList(String name) { m_outputClusterListName = name ; }
+
+    protected MSTPhotonFinder m_finder;
+    protected String m_outputHitMapName;
+    protected String m_inputHitMapName;
+    protected String m_outputClusterListName;
+
+    // Config stuff
+
+    /** Set minimum 3D distance between hits in photon core */
+    public void setCoreThreshold(double newThreshold) { 
+	m_finder.setCoreThreshold(newThreshold);
+    }
+    /** Set minimum transverse distance between photon core center and fragment center */
+    public void setFragmentThreshold(double newThreshold) { 
+	m_finder.setFragmentThreshold(newThreshold); 
+    }
+    /** 
+     * For a fragment to be added, require that there is at
+     * least one fragment hit within must be within [layerThreshold]
+     * layers of a core hit. Default is 2.
+     */
+    public void setLayerProximityThreshold(int layerThreshold) { 
+	m_finder.setLayerProximityThreshold(layerThreshold);
+    }
+    /**
+     * Set input hit decision (e.g. energy threshold) if desired.
+     * As per MSTClusterDriver interface, the hits are wrapped into
+     * one-hit clusters and a DecisionMakerSingle<Cluster> is applied.
+     * This applies to both cores and fragments.
+     */
+    public void setInputHitDecision(DecisionMakerSingle<Cluster> dec) { 
+	m_finder.setInputHitDecision(dec);
+    }
+    /** Cores must have at least [min] hits. */
+    public void setCoreSizeMinimum(int min) {
+	m_finder.setCoreSizeMinimum(min);
+    }
+    /** Fragments must have at most [min] hits. */
+    public void setFragmentSizeMaximum(int max) { 
+	m_finder.setFragmentSizeMaximum(max);
+    }
+    /** Cores must have a hit in the first [nLayers] layers. */
+    public void setCoreFirstLayerRange(int nLayers) { 
+	m_finder.setCoreFirstLayerRange(nLayers);
+    }
+
+    protected boolean m_debug = false;
+    public void setDebug(boolean debug) { 
+	m_debug = debug; 
+	m_finder.setDebug(debug);
+    }
+}

lcsim/src/org/lcsim/recon/cluster/mst
TransverseDistanceMetric.java added at 1.1
diff -N TransverseDistanceMetric.java
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ TransverseDistanceMetric.java	11 Aug 2006 23:31:25 -0000	1.1
@@ -0,0 +1,78 @@
+package org.lcsim.recon.cluster.mst; 
+
+import java.io.*; 
+import java.util.*; 
+import hep.physics.vec.*;
+
+import org.lcsim.event.Cluster;
+import org.lcsim.recon.cluster.util.BasicCluster;
+import org.lcsim.recon.cluster.util.TensorClusterPropertyCalculator;
+
+
+/**
+  * A distance metric that computes the transverse distance
+  * between a large cluster with well-defined direction
+  * (e.g. a MIP or a photon) and a fragment.
+  *
+  * The first cluster is the large one.
+  *   Let its primary eigenvector be d1.
+  *   Let its center-of-energy be at p1.
+  * The second cluster is the fragment.
+  *   Let its center-of-energy be at p2.
+  *
+  * Let the angle between (d1) and (p2-p1) be theta:
+  *    (d1).(p2-p1) = |d1| |p2-p1| cos(theta)
+  * [Note that there is an ambiguity between (theta) and (pi-theta).]
+  * Then the metric M is given by
+  *   M^2 = |p2-p1|^2 sin^2(theta)
+  *       = |p2-p1|^2 [1-cos^2(theta)]
+  *       = |p2-p1|^2 - |p2-p1|^2 cos^2(theta)
+  *       = |p2-p1|^2 - [(d1).(p2-p1)/|d1|]^2
+  */
+public class TransverseDistanceMetric implements Metrics {
+    /**
+     * Simple constructor 
+     */
+    public TransverseDistanceMetric(){}
+
+    /**
+     * Compute the transverse distance from the large cluster to the
+     * other, as described above.
+     */
+    public double getDistance (Cluster cluster1, Cluster cluster2)
+    {
+	// Find the centers and direction:
+	Hep3Vector p1 = new BasicHep3Vector(cluster1.getPosition());
+        Hep3Vector p2 = new BasicHep3Vector(cluster2.getPosition());
+	Hep3Vector d1 = findDirection(cluster1);
+
+	// Vector arithmetic:
+	Hep3Vector p2_minus_p1 = VecOp.sub(p2,p1);
+	double p2_minus_p1_squared = VecOp.dot(p2_minus_p1, p2_minus_p1);
+	double d1_dot_p2_minus_p1 = VecOp.dot(d1, p2_minus_p1);
+	double d1_squared = VecOp.dot(d1,d1);
+	
+	// Compute the metric with the formula above:
+	double m_squared = p2_minus_p1_squared - d1_dot_p2_minus_p1*d1_dot_p2_minus_p1/d1_squared ;
+	return Math.sqrt(m_squared);
+    }
+
+    private Hep3Vector findDirection(Cluster clus) 
+    {
+	if (clus.getCalorimeterHits().size()<4) {
+	    throw new AssertionError("Too few hits to calculate direction of cluster: there were "+clus.getCalorimeterHits().size()+", but I needed 4.");
+	}
+	
+        BasicCluster copy = new BasicCluster();
+        copy.addCluster(clus);
+        TensorClusterPropertyCalculator calc = new TensorClusterPropertyCalculator();
+        copy.setPropertyCalculator(calc);
+        copy.calculateProperties();
+        double[][]axes = calc.getPrincipleAxis();
+        if (axes == null) {
+            throw new AssertionError("Principal axes not calculated");
+        }
+        Hep3Vector direction = new BasicHep3Vector(axes[0][0], axes[0][1], axes[0][2]);
+        return direction;
+    }
+}

lcsim/src/org/lcsim/recon/cluster/mst
PhotonShapeDecision.java added at 1.1
diff -N PhotonShapeDecision.java
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ PhotonShapeDecision.java	11 Aug 2006 23:31:25 -0000	1.1
@@ -0,0 +1,89 @@
+package org.lcsim.recon.cluster.mst;
+
+import java.util.*;
+import hep.aida.*;
+import org.lcsim.event.Cluster;
+import org.lcsim.recon.cluster.util.BasicCluster;
+import org.lcsim.recon.cluster.util.TensorClusterPropertyCalculator;
+import org.lcsim.util.decision.DecisionMakerSingle;
+import org.lcsim.event.CalorimeterHit;
+
+/**
+ * Decision-maker class which attempts to identify photons based
+ * on their normalized eigenvalues. Default is no cuts.
+ */
+
+public class PhotonShapeDecision implements DecisionMakerSingle<Cluster>
+{
+    /** Simple constructor. */
+    public PhotonShapeDecision() {
+    }
+
+    /** Main interface. */
+    public boolean valid(Cluster clus) {
+	calculateValues(clus);
+	for (int i=0; i<3; i++){
+	    if (m_max[i] != null) {
+		if (m_value[i] > m_max[i].doubleValue()) {
+		    return false;
+		}
+	    }
+	    if (m_min[i] != null) {
+		if (m_value[i] < m_min[i].doubleValue()) {
+		    return false;
+		}
+	    }
+	}
+	return true;
+    }
+
+    /** Set maximum values for cuts on normalized eigenvectors. */
+    public void setMaximum(double max0, double max1, double max2) {
+	m_max[0] = new Double(max0);
+	m_max[1] = new Double(max1);
+	m_max[2] = new Double(max2);
+    }
+    /** Set maximum values for cuts on normalized eigenvectors. A null value means no cut. */
+    public void setMaximum(Double max0, Double max1, Double max2) {
+	m_max[0] = max0;
+	m_max[1] = max1;
+	m_max[2] = max2;
+    }
+
+    /** Set minimum values for cuts on normalized eigenvectors. */
+    public void setMinimum(double min0, double min1, double min2) {
+	m_min[0] = new Double(min0);
+	m_min[1] = new Double(min1);
+	m_min[2] = new Double(min2);
+    }
+    /** Set minimum values for cuts on normalized eigenvectors. A null value means no cut. */
+    public void setMinimum(Double min0, Double min1, Double min2) {
+	m_min[0] = min0;
+	m_min[1] = min1;
+	m_min[2] = min2;
+    }
+
+    /** Internal utility routine. */
+    protected void calculateValues(Cluster clus) {
+	if (clus.getCalorimeterHits().size() < 4) {
+	    // Too few hits to calculate the tensor
+	    throw new AssertionError(this.getClass().getName()+".calculateValues() called on a cluster with only "+clus.getCalorimeterHits().size()+" hits; need at least 4.");
+	}
+	BasicCluster copy = new BasicCluster();
+	copy.addCluster(clus);
+	TensorClusterPropertyCalculator calc = new TensorClusterPropertyCalculator();
+	copy.setPropertyCalculator(calc);
+	copy.calculateProperties();
+	double[] nev = calc.getNormalizedEigenvalues();
+	if (nev == null) {
+	    throw new AssertionError("Normalized eigenvalues not calculated");
+	}
+	m_value[0] = nev[0];
+	m_value[1] = (nev[2]+nev[1])*0.5;
+	m_value[2] = (nev[2]-nev[1])*0.5;
+    }
+
+    protected Double[] m_max = {null,null,null};
+    protected Double[] m_min = {null,null,null};
+    protected double[] m_value = {0,0,0};
+}
CVSspam 0.2.8