changeset 2:e16016635b2a

Uploaded
author timpalpant
date Mon, 13 Feb 2012 22:12:06 -0500
parents a54db233ee3d
children 4b610dc8f6ba
files META-INF/MANIFEST.MF README.rdoc build.properties build.xml galaxy-conf/BaseAlignCounts.xml galaxy-conf/galaxyToolRunner.sh galaxyToolConf.xml gui/edu/unc/genomics/AssemblyManager.java gui/edu/unc/genomics/AssemblyManagerDialog.java gui/edu/unc/genomics/AssemblyTableModel.java gui/edu/unc/genomics/ButtonLabel.java gui/edu/unc/genomics/Job.java gui/edu/unc/genomics/JobConfigPanel.java gui/edu/unc/genomics/JobException.java gui/edu/unc/genomics/JobQueue.java gui/edu/unc/genomics/JobQueueCellRenderer.java gui/edu/unc/genomics/JobQueueManager.java gui/edu/unc/genomics/ResourceManager.java gui/edu/unc/genomics/SubmittedJob.java gui/edu/unc/genomics/ThreadFilter.java gui/edu/unc/genomics/ToolRunner.java gui/edu/unc/genomics/ToolRunnerFrame.java gui/edu/unc/genomics/ToolsTree.java gui/edu/unc/genomics/ToolsTreeModel.java gui/edu/unc/genomics/ToolsTreeNode.java gui/javax/swing/layout/SpringUtilities.java launch4j.xml lib/BigWig.jar lib/commons-lang3-3.1.jar lib/commons-math-2.2.jar lib/jarbundler-2.2.0.jar lib/java-genomics-io.jar lib/jcommander-1.20.jar lib/jtransforms-2.4.jar lib/launch4j.jar lib/log4j-1.2.15.jar lib/macify-1.4.jar lib/sam-1.56.jar lib/swing-layout.jar lib/xstream.jar log4j.properties resources/assemblies/ce2.len resources/assemblies/ce3.len resources/assemblies/ce4.len resources/assemblies/ce5.len resources/assemblies/ce6.len resources/assemblies/ce7.len resources/assemblies/ce8.len resources/assemblies/ce9.len resources/assemblies/dm1.len resources/assemblies/dm2.len resources/assemblies/dm3.len resources/assemblies/hg15.len resources/assemblies/hg16.len resources/assemblies/hg17.len resources/assemblies/hg18.len resources/assemblies/hg19.len resources/assemblies/hg19Haps.len resources/assemblies/hg19Patch2.len resources/assemblies/klac.len resources/assemblies/kwal.len resources/assemblies/sacCer1.len resources/assemblies/sacCer2.len resources/assemblies/sacCer3.len resources/images/add_icon.png resources/images/add_icon_dark.png resources/images/arrow-circle.png resources/images/beachball.png resources/images/bug.png resources/images/delete_icon_dark.png resources/images/delete_icon_grey.png resources/images/eye_icon.png resources/images/eye_icon_dark.png resources/images/eye_icon_grey.png resources/images/folder.png resources/images/folder_page.png resources/images/icon_error_sml.gif resources/images/icon_info_sml.gif resources/images/icon_success_sml.gif resources/images/icon_warning_sml.gif resources/images/information-white.png resources/images/mag_glass.png resources/images/osx-spinner.gif resources/images/pencil_icon.png resources/images/pencil_icon_dark.png resources/images/pencil_icon_grey.png resources/images/sticky-note-text.png resources/toolConf.xml src/edu/unc/config/GalaxyConfig.java src/edu/unc/genomics/AssemblyConverter.java src/edu/unc/genomics/AssemblyFactory.java src/edu/unc/genomics/CommandLineTool.java src/edu/unc/genomics/CommandLineToolException.java src/edu/unc/genomics/IntervalFileConverter.java src/edu/unc/genomics/IntervalFileFactory.java src/edu/unc/genomics/PathConverter.java src/edu/unc/genomics/PathFactory.java src/edu/unc/genomics/PositiveIntegerValidator.java src/edu/unc/genomics/ReadablePathValidator.java src/edu/unc/genomics/WigFileConverter.java src/edu/unc/genomics/WigFileFactory.java src/edu/unc/genomics/converters/IntervalToWig.java src/edu/unc/genomics/converters/RomanNumeralize.java src/edu/unc/genomics/ngs/Autocorrelation.java src/edu/unc/genomics/ngs/BaseAlignCounts.java src/edu/unc/genomics/ngs/FindAbsoluteMaxima.java src/edu/unc/genomics/ngs/IntervalLengthDistribution.java src/edu/unc/genomics/ngs/IntervalStats.java src/edu/unc/genomics/ngs/PowerSpectrum.java src/edu/unc/genomics/ngs/RollingReadLength.java src/edu/unc/genomics/nucleosomes/FindBoundaryNucleosomes.java src/edu/unc/genomics/nucleosomes/GreedyCaller.java src/edu/unc/genomics/nucleosomes/MapDyads.java src/edu/unc/genomics/nucleosomes/NRLCalculator.java src/edu/unc/genomics/nucleosomes/NucleosomeCall.java src/edu/unc/genomics/nucleosomes/NucleosomeCallsFile.java src/edu/unc/genomics/nucleosomes/Phasogram.java src/edu/unc/genomics/visualization/IntervalAverager.java src/edu/unc/genomics/visualization/KMeans.java src/edu/unc/genomics/visualization/KMeansRow.java src/edu/unc/genomics/visualization/MatrixAligner.java src/edu/unc/genomics/visualization/StripMatrix.java src/edu/unc/genomics/wigmath/Add.java src/edu/unc/genomics/wigmath/Average.java src/edu/unc/genomics/wigmath/Divide.java src/edu/unc/genomics/wigmath/GaussianSmooth.java src/edu/unc/genomics/wigmath/LogTransform.java src/edu/unc/genomics/wigmath/MovingAverageSmooth.java src/edu/unc/genomics/wigmath/Multiply.java src/edu/unc/genomics/wigmath/Scale.java src/edu/unc/genomics/wigmath/Subtract.java src/edu/unc/genomics/wigmath/WigMathTool.java src/edu/unc/genomics/wigmath/WigSummary.java src/edu/unc/genomics/wigmath/ZScore.java src/edu/unc/utils/RomanNumeral.java src/edu/unc/utils/SortUtils.java stubFile.sh toolRunner.sh
diffstat 137 files changed, 6882 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/META-INF/MANIFEST.MF	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,9 @@
+Manifest-Version: 1.0
+Main-Class: edu.unc.genomics.GenomicsToolkit
+Bundle-ManifestVersion: 2
+Bundle-Name: java-genomics-toolkit
+Bundle-SymbolicName: java-genomics-toolkit
+Bundle-Version: 1.0.0
+Bundle-RequiredExecutionEnvironment: JavaSE-1.7
+Bundle-Description: This project provides tools for common genomic data processing.
+Bundle-DocURL: http://github.com/timpalpant/java-genomics-toolkit
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/README.rdoc	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,43 @@
+= Java Genomics Toolkit
+
+This is a collection of applications for genomics data processing, primarily high-throughput next-generation sequencing. There is a particular focus on processing data in Wiggle format, since many other tools already cover SAM, BAM, FastQ, etc. However, Wiggle/BigWig formats provide a compact way to store numerical data resulting from ChIP-seq and MNase-seq experiments. Common computations provided in this toolkit include adding, subtracting, dividing, multiplying, log-transforming, averaging, Z-scoring, and Gaussian smoothing Wig files.
+
+Tools may be run from the command-line, a simple Swing GUI, or from Galaxy (http://getgalaxy.org).
+
+== Loading the Tools into Galaxy
+
+TODO
+
+== Using the ToolRunner GUI
+
+TODO
+
+== Command-Line Usage
+
+Applications can be run on the command-line, and the toolRunner.sh script is provided for convenience. Calling any script without arguments will display the help, as well as the missing mandatory arguments:
+
+  $ > ./toolRunner.sh wigmath.AddWig
+  $ Usage: <main class> [options] Input files
+  $   Options:
+  $   * -o, --output   Output file
+
+Mandatory arguments are denoted with a (*).
+
+Other tools require more input:
+
+  $ > ./toolRunner.sh ngs.Autocorrelation
+  $ Usage: <main class> [options]
+  $   Options:
+  $   * -i, --input    Input file
+  $   * -l, --loci     Genomic loci (Bed format)
+  $   -m, --max        Autocorrelation limit (bp)
+  $                    Default: 200
+  $   * -o, --output   Output file
+
+=== Log transform a Wig file with base 2
+
+  $ > ./toolRunner.sh wigmath.LogTransform --input input.wig --base 2 --output output.log2.wig
+
+== Java Genomics IO
+
+Those wishing to write their own scripts may be interested in https://github.com/timpalpant/java-genomics-io, the toolkit upon which these applications are built.
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/build.xml	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,112 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<project name="java-genomics-toolkit" default="jar" basedir=".">
+  <description>
+    build the toolkit
+  </description>
+  
+  <!-- set global properties for this build -->
+  <property name="version" value="1.1.0"/>
+  <property name="buildnumber" value="1"/>
+  <property name="copyright" value="Copyright &#xa9; 2011 Timothy Palpant"/>
+  
+  <!-- directory variables -->
+  <property name="src" location="src"/>
+  <property name="test" location="test"/>
+  <property name="gui" location="gui"/>
+  <property name="build" location="build"/>
+  <property name="dist" location="dist"/>
+  <property name="lib" location="lib"/>
+
+  <!-- compile all Java code -->
+  <target name="compile" description="compile the scripts">
+    <!-- Create the build directory structure used by compile -->
+    <mkdir dir="${build}"/>
+    
+    <!-- Compile the java code from ${src} into ${build} -->
+    <javac srcdir="${src}" destdir="${build}" source="1.7" target="1.7">
+	  <classpath>
+	    <path id="lib.path.ref">
+	      <fileset dir="lib" includes="*.jar"/>
+	    </path>
+	  </classpath>
+    </javac>
+    
+    <!-- Compile the java code from ${gui} into ${build} -->
+    <javac srcdir="${gui}" destdir="${build}" source="1.7" target="1.7">
+      <classpath>
+  	    <path id="lib.path.ref">
+  	      <fileset dir="lib" includes="*.jar"/>
+  	    </path>
+  	  </classpath>
+	</javac>
+  </target>
+
+  <!-- package all Java code into a JAR file -->
+  <target name="jar" depends="compile" description="generate the jarfile">
+    <!-- Create the distribution directory -->
+    <mkdir dir="${dist}"/>
+
+    <!-- Put everything in ${build} into the jar file -->
+    <jar jarfile="${dist}/${ant.project.name}.jar" manifest="META-INF/MANIFEST.MF">
+      <fileset dir="${build}" />
+    </jar>
+  </target>
+
+  <!-- Package the jar file into a Mac OS X application with jarbundler -->
+  <target name="package-osx" depends="jar" description="Build the application for OS X">
+    <taskdef name="jarbundler"
+             classpath="${lib}/jarbundler-2.2.0.jar" 
+             classname="net.sourceforge.jarbundler.JarBundler"/>
+
+    <jarbundler dir="${dist}" verbose="false" showPlist="false"
+                name="Genomics Toolkit"
+                mainclass="edu.unc.genomics.ToolRunner"
+      			jvmversion="1.7+"
+      			stubfile="stubFile.sh"
+                version="${version}"
+                infostring="${ant.project.name}, ${copyright}"
+                build="${buildnumber}"
+                bundleid="edu.unc.genomics.GenomicsToolkit">
+      
+        <jarfilelist dir="${dist}" files="${ant.project.name}.jar"/>
+        <jarfileset dir="${lib}">
+          <include name="*.jar" />
+          <exclude name="launch4j.jar" />
+          <exclude name="xstream.jar" />
+          <exclude name="jarbundler-2.2.0.jar" />
+        </jarfileset>
+
+	    <!-- Adjust the look, feel and behavior -->
+	    <javaproperty name="apple.laf.useScreenMenuBar" value="true"/>
+	    <javaproperty name="apple.awt.brushMetal" value="true"/>
+	    <javaproperty name="apple.awt.showGrowBox" value="false"/>
+      	<javaproperty name="apple.awt.textantialiasing" value="true"/>
+        <javaproperty name="apple.awt.antialiasing" value="true"/>
+	
+	    <!-- Associate document types with this application -->
+	    <documenttype name="Assembly files"
+	      			  extensions="len" 
+	                  role="Viewer" />
+	
+	    <!-- Include resource files -->                
+	    <resourcefilelist dir="." files="README.rdoc"/>
+        <resourcefilelist dir="." files="toolConf.xml"/>
+	    <resourcefileset dir="." includes="resources/assemblies/*.len"/>
+        <javafilelist dir="." files="log4j.properties"/>
+    </jarbundler>
+  </target>
+  
+  <!-- Package the jar file into a Windows application with launch4j -->
+  <target name="package-win" depends="jar" description="Build the application for Windows">
+    <taskdef name="launch4j"
+        classname="net.sf.launch4j.ant.Launch4jTask"
+        classpath="${lib}/launch4j.jar:${lib}/xstream.jar" />
+    
+    <launch4j configFile="launch4j.xml" />
+  </target>
+
+  <target name="clean" description="clean up" >
+    <delete dir="${build}"/>
+    <delete dir="${dist}"/>
+  </target>
+</project>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/galaxy-conf/BaseAlignCounts.xml	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,21 @@
+<tool id="BaseAlignCounts" name="Map coverage" version="1.0.0">
+  <description>of sequencing reads</description>
+  <command>galaxyToolRunner.sh -i $input -g $dbkey -x $X -p 4 -o $output</command>
+  <inputs>
+    <param name="input" type="data" format="bam,sam,bed,bedgraph" label="Sequencing reads" />
+    <param name="X" type="integer" value="0" label="In silico extension (leave 0 for read length)" />
+  </inputs>
+  <outputs>
+    <data name="output" format="wig" />
+  </outputs>
+  
+  <help>
+    .. class:: warningmark
+    
+    This tool requires sequencing reads in SAM/BAM/Bed/BedGraph format.
+    
+    .. class:: warningmark
+    
+    This tool was contributed by Timothy Palpant.
+  </help>
+</tool>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/galaxy-conf/galaxyToolRunner.sh	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,14 @@
+#!/usr/bin/env bash
+
+if [ $# -eq 0 ]
+then
+  echo "USAGE: galaxyToolRunner.sh APPNAME [ARGS]";
+  exit;
+fi
+
+if [ "$1" = "list" ]
+then
+  find src/edu/unc/genomics/**/*.java -exec basename -s .java {} \;
+fi
+
+java -Dlog4j.configuration=log4j.properties -cp .:../build:../lib/* edu.unc.genomics."$@"
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/galaxyToolConf.xml	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,41 @@
+<!-- Add to galaxy/tool_conf.xml within the <toolbox></toolbox> section -->
+  <label name="Java Genomics Toolkit" />
+  <section name="Converters" id="java-genomics-toolkit-converters">
+  	<tool file="java-genomics-toolkit/galaxy-conf/IntervalToWig.xml" />
+    <tool file="java-genomics-toolkit/galaxy-conf/RomanNumeralize.xml" />
+  </section>
+  <section name="Nucleosomes" id="java-genomics-toolkit-nucleosomes">
+    <tool file="java-genomics-toolkit/galaxy-conf/FindBoundaryNucleosomes.xml" />
+    <tool file="java-genomics-toolkit/galaxy-conf/GreedyCaller.xml" />
+    <tool file="java-genomics-toolkit/galaxy-conf/MapDyads.xml" />
+    <tool file="java-genomics-toolkit/galaxy-conf/NRLCalculator.xml" />
+    <tool file="java-genomics-toolkit/galaxy-conf/Phasogram.xml" />
+  </section>
+  <section name="NGS" id="java-genomics-toolkit-ngs">
+    <tool file="java-genomics-toolkit/other-tools/Autocorrelation.xml" />
+    <tool file="java-genomics-toolkit/galaxy-conf/BaseAlignCounts.xml" />
+    <tool file="java-genomics-toolkit/galaxy-conf/FindAbsoluteMaxima.xml" />
+    <tool file="java-genomics-toolkit/galaxy-conf/IntervalLengthDistribution.xml" />
+    <tool file="java-genomics-toolkit/galaxy-conf/IntervalStats.xml" />
+    <tool file="java-genomics-toolkit/galaxy-conf/PowerSpectrum.xml" />
+    <tool file="java-genomics-toolkit/galaxy-conf/RollingReadLength.xml" />
+  </section>
+  <section name="WigMath" id="java-genomics-toolkit-wigmath">
+    <tool file="java-genomics-toolkit/galaxy-conf/Add.xml" />
+    <tool file="java-genomics-toolkit/galaxy-conf/Average.xml" />
+    <tool file="java-genomics-toolkit/galaxy-conf/Divide.xml" />
+    <tool file="java-genomics-toolkit/galaxy-conf/GaussianSmooth.xml" />
+    <tool file="java-genomics-toolkit/galaxy-conf/LogTransform.xml" />
+    <tool file="java-genomics-toolkit/galaxy-conf/MovingAverageSmooth.xml" />
+    <tool file="java-genomics-toolkit/galaxy-conf/Multiply.xml" />
+    <tool file="java-genomics-toolkit/galaxy-conf/Scale.xml" />
+    <tool file="java-genomics-toolkit/galaxy-conf/Subtract.xml" />
+    <tool file="java-genomics-toolkit/galaxy-conf/WigSummary.xml" />
+    <tool file="java-genomics-toolkit/galaxy-conf/ZScore.xml" />
+  </section>
+  <section name="Visualization" id="java-genomics-toolkit-visualization">
+    <tool file="java-genomics-toolkit/galaxy-conf/IntervalAverager.xml" />
+    <tool file="java-genomics-toolkit/galaxy-conf/KMeans.xml" />
+    <tool file="java-genomics-toolkit/galaxy-conf/MatrixAligner.xml" />
+    <tool file="java-genomics-toolkit/galaxy-conf/StripMatrix.xml" />
+  </section>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gui/edu/unc/genomics/AssemblyManager.java	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,83 @@
+package edu.unc.genomics;
+
+import java.io.IOException;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.zip.DataFormatException;
+
+import org.apache.log4j.Logger;
+
+/**
+ * Suite of static methods for managing the built-in assemblies in the resources dir
+ * as well as keeping track of the last used assembly
+ * 
+ * @author timpalpant
+ *
+ */
+public class AssemblyManager {
+	
+	private static final Logger log = Logger.getLogger(AssemblyManager.class);
+	
+	/**
+	 * The last used Assembly
+	 */
+	private static Assembly lastUsed;
+	
+	/**
+	 * Returns all available assemblies in the resources directory
+	 * @return the assemblies available in the resources directory
+	 */
+	public static List<Assembly> getAvailableAssemblies() {
+		List<Assembly> assemblies = new ArrayList<>();
+		
+		try (DirectoryStream<Path> stream = Files.newDirectoryStream(ResourceManager.getAssembliesDirectory(), "*.{len}")) {
+      for (Path entry : stream) {
+      	log.debug("Loading assembly: " + entry);
+				try {
+					Assembly a = new Assembly(entry);
+					assemblies.add(a);
+				} catch (IOException | DataFormatException e1) { 
+					log.warn("Error loading assembly: " + entry);
+				}
+      }
+		} catch (IOException e) {
+			log.error("Error listing assemblies");
+			e.printStackTrace();
+		}
+		
+		return assemblies;
+	}
+	
+	public static void deleteAssembly(Assembly a) throws IOException {
+		Files.deleteIfExists(a.getPath());
+	}
+	
+	public static Assembly loadCustomAssembly(Path assemblyFile) throws IOException, DataFormatException {
+		log.debug("Loading custom assembly from file: " + assemblyFile);
+		Assembly a = new Assembly(assemblyFile);
+		
+		// TODO: Warn if this assembly is already loaded
+		
+		// Copy the assembly file into the built-in assemblies directory
+		Files.copy(assemblyFile, ResourceManager.getAssembliesDirectory().resolve(assemblyFile.getFileName()));
+		return a;
+	}
+
+	/**
+	 * @return the lastUsed
+	 */
+	public static Assembly getLastUsed() {
+		return lastUsed;
+	}
+
+	/**
+	 * @param lastUsed the lastUsed to set
+	 */
+	public static void setLastUsed(Assembly lastUsed) {
+		AssemblyManager.lastUsed = lastUsed;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gui/edu/unc/genomics/AssemblyManagerDialog.java	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,135 @@
+package edu.unc.genomics;
+
+import java.awt.BorderLayout;
+import java.awt.FlowLayout;
+
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JFileChooser;
+import javax.swing.JFrame;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+import javax.swing.border.EmptyBorder;
+
+import org.apache.log4j.Logger;
+
+import java.awt.event.ActionListener;
+import java.awt.event.ActionEvent;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.zip.DataFormatException;
+
+/**
+ * View for AssemblyManager
+ * 
+ * @author timpalpant
+ *
+ */
+public class AssemblyManagerDialog extends JDialog {
+	
+	public static final int DEFAULT_WIDTH = 400;
+	public static final int DEFAULT_HEIGHT = 500;
+	
+	private static final long serialVersionUID = -1461628562713621064L;
+	private static final Logger log = Logger.getLogger(AssemblyManagerDialog.class);
+
+	private final JPanel contentPanel = new JPanel();
+	private final JFileChooser fcCustomAssembly = new JFileChooser();
+	private final JTable assembliesTable = new JTable();
+	
+	private AssemblyTableModel model;
+
+	/**
+	 * Create the dialog.
+	 */
+	public AssemblyManagerDialog(JFrame parent) {
+		super(parent, "Assembly Manager", true);
+		
+		setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
+		int centeredX = parent.getX() + (parent.getWidth()-getWidth()) / 2;
+		int centeredY = parent.getY() + (parent.getHeight()-getHeight()) / 2;
+		setLocation(centeredX, centeredY);
+		
+		getContentPane().setLayout(new BorderLayout());
+		contentPanel.setBorder(new EmptyBorder(5, 5, 5, 5));
+		getContentPane().add(contentPanel, BorderLayout.CENTER);
+		contentPanel.setLayout(new BorderLayout(0, 0));
+
+		// Initialize the assemblies list
+		model = new AssemblyTableModel(AssemblyManager.getAvailableAssemblies());
+		assembliesTable.setModel(model);
+		assembliesTable.setRowSelectionAllowed(true);
+		JScrollPane scrollPane = new JScrollPane(assembliesTable);
+		assembliesTable.setFillsViewportHeight(true);
+		contentPanel.add(scrollPane);
+
+		JPanel buttonPane = new JPanel();
+		buttonPane.setLayout(new FlowLayout(FlowLayout.RIGHT));
+		getContentPane().add(buttonPane, BorderLayout.SOUTH);
+		
+		JButton removeAssemblyButton = new JButton("Remove");
+		removeAssemblyButton.setActionCommand("RemoveAssembly");
+		removeAssemblyButton.addActionListener(new ActionListener() {
+			public void actionPerformed(ActionEvent e) {
+				removeCustomAssembly();
+			}
+		});
+		buttonPane.add(removeAssemblyButton);
+		
+		JButton loadAssemblyButton = new JButton("Load Custom Assembly");
+		loadAssemblyButton.setActionCommand("LoadAssembly");
+		loadAssemblyButton.addActionListener(new ActionListener() {
+			public void actionPerformed(ActionEvent e) {
+				loadCustomAssembly();
+			}
+		});
+		buttonPane.add(loadAssemblyButton);
+		
+		JButton doneButton = new JButton("Done");
+		doneButton.setActionCommand("Done");
+		doneButton.addActionListener(new ActionListener() {
+			public void actionPerformed(ActionEvent e) {
+				closeDialog();
+			}
+		});
+		buttonPane.add(doneButton);
+		getRootPane().setDefaultButton(doneButton);
+	}
+	
+	private void removeCustomAssembly() {
+		for (int row : assembliesTable.getSelectedRows()) {
+			try {
+				Assembly a = model.getRow(row);
+				AssemblyManager.deleteAssembly(a);
+				model.removeRow(row);
+			} catch (IOException e) {
+				log.error("Error deleting Assembly");
+				e.printStackTrace();
+				JOptionPane.showMessageDialog(this, "Error deleting assembly", "Assembly Manager Error", JOptionPane.ERROR_MESSAGE);
+			}
+		}
+	}
+	
+	private void loadCustomAssembly() {
+		int returnVal = fcCustomAssembly.showOpenDialog(this);
+		if (returnVal == JFileChooser.APPROVE_OPTION) {
+			Path assemblyFile = fcCustomAssembly.getSelectedFile().toPath();
+			try {
+				Assembly a = AssemblyManager.loadCustomAssembly(assemblyFile);
+				// Add it to the assemblies list model
+				model.addAssembly(a);
+			} catch (IOException | DataFormatException e) {
+				log.error("Error loading custom assembly: " + assemblyFile);
+				e.printStackTrace();
+				JOptionPane.showMessageDialog(this, "Error loading custom assembly", "Assembly Manager Error", JOptionPane.ERROR_MESSAGE);
+			}
+		}
+	}
+	
+	private void closeDialog() {
+		this.dispose();
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gui/edu/unc/genomics/AssemblyTableModel.java	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,97 @@
+package edu.unc.genomics;
+
+import java.util.List;
+
+import javax.swing.table.AbstractTableModel;
+
+/**
+ * Model for the AssemblyManagerDialog table view
+ * 
+ * @author timpalpant
+ *
+ */
+public class AssemblyTableModel extends AbstractTableModel {
+	
+	private static final long serialVersionUID = 8225453782461913732L;
+	
+	private static final String[] COLUMN_NAMES = { "Name", "# Contigs" };
+	
+	private final List<Assembly> assemblies;
+	
+	public AssemblyTableModel(List<Assembly> assemblies) {
+		this.assemblies = assemblies;
+	}
+	
+	/* (non-Javadoc)
+	 * @see javax.swing.table.AbstractTableModel#getColumnName(int)
+	 */
+	@Override
+	public String getColumnName(int col) {
+    return COLUMN_NAMES[col];
+	}
+	
+	/* (non-Javadoc)
+	 * @see javax.swing.table.AbstractTableModel#isCellEditable(int, int)
+	 */
+	@Override
+	public boolean isCellEditable(int row, int col) { 
+		return false; 
+	}
+
+	/* (non-Javadoc)
+	 * @see javax.swing.table.TableModel#getRowCount()
+	 */
+	@Override
+	public int getRowCount() {
+		return assemblies.size();
+	}
+
+	/* (non-Javadoc)
+	 * @see javax.swing.table.TableModel#getColumnCount()
+	 */
+	@Override
+	public int getColumnCount() {
+		return 2;
+	}
+
+	/* (non-Javadoc)
+	 * @see javax.swing.table.TableModel#getValueAt(int, int)
+	 */
+	@Override
+	public Object getValueAt(int rowIndex, int columnIndex) {
+		Assembly a = assemblies.get(rowIndex);
+		if (columnIndex == 0) {
+			return a.toString();
+		} else if (columnIndex == 1) {
+			return a.chromosomes().size();
+		} else {
+			return null;
+		}
+	}
+	
+	public Assembly getRow(int rowIndex) {
+		return assemblies.get(rowIndex);
+	}
+	
+	public boolean containsAssembly(Assembly a) {
+		String aName = a.toString();
+		for (Assembly assembly : assemblies) {
+			if (assembly.toString().equalsIgnoreCase(aName)) {
+				return true;
+			}
+		}
+		
+		return false;
+	}
+	
+	public void addAssembly(Assembly a) {
+		assemblies.add(a);
+		fireTableRowsInserted(assemblies.size()-1, assemblies.size()-1);
+	}
+	
+	public void removeRow(int rowIndex) {
+		assemblies.remove(rowIndex);
+		fireTableRowsDeleted(rowIndex, rowIndex);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gui/edu/unc/genomics/ButtonLabel.java	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,50 @@
+package edu.unc.genomics;
+
+import javax.swing.Action;
+import javax.swing.BorderFactory;
+import javax.swing.Icon;
+import javax.swing.JButton;
+
+/**
+ * Act like a button, look like a label
+ * 
+ * @author timpalpant
+ *
+ */
+public class ButtonLabel extends JButton {
+
+	private static final long serialVersionUID = -4449260534784095223L;
+
+	public ButtonLabel() {
+		init();
+	}
+
+	public ButtonLabel(Icon icon) {
+		super(icon);
+		init();
+	}
+
+	public ButtonLabel(String text) {
+		super(text);
+		init();
+	}
+
+	public ButtonLabel(Action a) {
+		super(a);
+		init();
+	}
+
+	public ButtonLabel(String text, Icon icon) {
+		super(text, icon);
+		init();
+	}
+	
+	private void init() {
+		setBorder(BorderFactory.createEmptyBorder());
+		setBorderPainted(false);  
+		setContentAreaFilled(false);  
+		setFocusPainted(false);  
+		setOpaque(false);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gui/edu/unc/genomics/Job.java	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,207 @@
+package edu.unc.genomics;
+
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.log4j.Logger;
+import org.apache.log4j.PatternLayout;
+import org.apache.log4j.WriterAppender;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.ParameterDescription;
+
+/**
+ * A Job represents an instance of a tool configured with specific arguments
+ * Attempting to run a Job with invalid arguments will throw a RuntimeException
+ * If tool execution fails, a RuntimeException will also be thrown
+ * UncheckedExceptions should be managed by setting the Job's UncheckedExceptionManager
+ * or providing a ThreadGroup / default UncheckedExceptionManager
+ * 
+ * @author timpalpant
+ *
+ */
+public class Job implements Iterable<ParameterDescription>, Runnable {
+	
+	private static final Logger log = Logger.getLogger(Job.class);
+	
+	private final Class<? extends CommandLineTool> tool;
+	private final CommandLineTool app;
+	private final List<ParameterDescription> parameters;
+	private final String usageText;
+	private boolean isRunning = false;
+	private StringWriter writer = new StringWriter();
+	
+	/**
+	 * Arguments for running this Job
+	 */
+	private Map<ParameterDescription,String> args = new HashMap<>();
+	
+	/**
+	 * Creates a new Job model for the specified tool
+	 * @param tool
+	 * @throws IllegalAccessException 
+	 * @throws InstantiationException 
+	 */
+	public Job(final Class<? extends CommandLineTool> tool) throws InstantiationException, IllegalAccessException {
+		this.tool = tool;
+		
+		// Attempt to instantiate the tool and extract parameter information
+		app = tool.newInstance();
+		JCommander jc = new JCommander(app);
+		jc.setProgramName(tool.getSimpleName());
+		parameters = jc.getParameters();
+		StringBuilder sbuilder = new StringBuilder();
+		jc.usage(sbuilder);
+		usageText = sbuilder.toString();
+		
+		// Set default arguments
+		for (ParameterDescription param : parameters) {
+			if (param.getDefault() != null) {
+				setArgument(param, String.valueOf(param.getDefault()));
+			}
+		}
+	}
+	
+	/**
+	 * Copy-constructor
+	 * @param job
+	 */
+	public Job(final Job job) {
+		this.tool = job.tool;
+		this.app = job.app;
+		this.parameters = job.parameters;
+		this.args = job.args;
+		this.usageText = job.usageText;
+	}
+	
+	@Override
+	public void run() {
+		// Load the arguments for running the tool
+		String[] args;
+		try {
+			args = getArguments();
+		} catch (JobException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+			throw new CommandLineToolException("Job arguments are not valid");
+		}
+		
+		// Attempt to instantiate and run the tool
+		WriterAppender appender = new WriterAppender(new PatternLayout(), writer);
+		appender.addFilter(new ThreadFilter(Thread.currentThread().getName()));
+		Logger.getRootLogger().addAppender(appender);
+		isRunning = true;
+		app.toolRunnerMain(args);
+		isRunning = false;
+		Logger.getRootLogger().removeAppender(appender);
+	}
+	
+	/**
+	 * Render the arguments for running an instance of this Job
+	 * @return
+	 * @throws JobException 
+	 */
+	public String[] getArguments() throws JobException {
+		if (!validateArguments()) {
+			throw new JobException("Job Arguments are not valid");
+		}
+		
+		List<String> cmdArgs = new ArrayList<>();
+		for (ParameterDescription p : args.keySet()) {
+			cmdArgs.add(p.getLongestName());
+			cmdArgs.add(args.get(p));
+		}
+		
+		String[] ret = new String[cmdArgs.size()];
+		return cmdArgs.toArray(ret);
+	}
+	
+	public String getArgument(final ParameterDescription p) {
+		return args.get(p);
+	}
+	
+	/**
+	 * Set a value for the given parameter
+	 * @param p
+	 * @param value
+	 */
+	public void setArgument(final ParameterDescription p, final String value) {
+		if (value.length() == 0) {
+			args.remove(p);
+		} else {
+			args.put(p, value);
+		}
+	}
+	
+	/**
+	 * Remove all set arguments for this Job
+	 */
+	public void resetArguments() {
+		args.clear();
+	}
+	
+	/**
+	 * Is this parameter set?
+	 * @param p
+	 * @return
+	 */
+	public boolean isSet(final ParameterDescription p) {
+		return args.containsKey(p);
+	}
+	
+	/**
+	 * Validate that this job has all of its parameters set
+	 * and that they are all valid
+	 * @return
+	 */
+	public boolean validateArguments() {
+		// TODO: Better validation based on parameter type
+		boolean hasAllRequiredParams = true;
+		for (ParameterDescription param : parameters) {
+			if (param.getParameter().required() && !isSet(param)) {
+				log.debug("Job is missing required argument: " + param.getLongestName());
+				hasAllRequiredParams = false;
+			}
+		}
+		
+		return hasAllRequiredParams;
+	}
+	
+	public int numParameters() {
+		return parameters.size();
+	}
+
+	@Override
+	public Iterator<ParameterDescription> iterator() {
+		return parameters.iterator();
+	}
+	
+	public String getName() {
+		return tool.getSimpleName();
+	}
+	
+	public boolean isRunning() {
+		return isRunning;
+	}
+
+	/**
+	 * @return the usageText
+	 */
+	public String getUsageText() {
+		return usageText;
+	}
+
+	@Override
+	public String toString() {
+		return getName();
+	}
+	
+	public String getOutput() {
+		return writer.toString();
+	}
+	
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gui/edu/unc/genomics/JobConfigPanel.java	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,246 @@
+package edu.unc.genomics;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.FileDialog;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.lang.reflect.Field;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.swing.BorderFactory;
+import javax.swing.BoxLayout;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JComponent;
+import javax.swing.JFileChooser;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+import javax.swing.SpringLayout;
+import javax.swing.SwingUtilities;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import javax.swing.layout.SpringUtilities;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.Document;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.log4j.Logger;
+
+import com.beust.jcommander.ParameterDescription;
+
+import edu.unc.genomics.io.IntervalFile;
+import edu.unc.genomics.io.WigFile;
+
+/**
+ * View for configuring the parameters of a Job
+ * Implements databinding between a Job object and the various Swing components
+ * for configuring each parameter
+ * 
+ * @author timpalpant
+ *
+ */
+public class JobConfigPanel extends JPanel {
+
+	private static final long serialVersionUID = 3336295203155728629L;
+	private static final Logger log = Logger.getLogger(JobConfigPanel.class);
+	
+	private static final ImageIcon fileIcon = new ImageIcon(ResourceManager.getImagesDirectory().resolve("folder_page.png").toString());
+	
+	/**
+	 * Maps parameters in the Job to GUI components (forward data-binding)
+	 */
+	private Map<ParameterDescription, JComponent> guiMap = new HashMap<>();
+	
+	/**
+	 * Maps GUI components to parameters in the Job (reverse data-binding)
+	 */
+	private Map<Component, ParameterDescription> jobMap = new HashMap<>();
+	
+	/**
+	 * The model for the Job that this panel allows you to configure
+	 */
+	private Job job;
+
+	/**
+	 * Initialize a new ConfigurationPanel with no Job
+	 */
+	public JobConfigPanel() { 
+		this(null);
+	}
+	
+	/**
+	 * Initialize a new ConfigurationPanel for the given Job
+	 * @param job
+	 */
+	public JobConfigPanel(final Job job) {
+		setJob(job);
+		setLayout(new SpringLayout());
+	}
+	
+	/**
+	 * Return the Job that this ConfigurationPanel is editing
+	 * @return
+	 */
+	public Job getJob() {
+		return job;
+	}
+	
+	/**
+	 * Set the job for this Configuration panel and re-render
+	 * @param job
+	 */
+	public void setJob(final Job job) {
+		this.job = job;
+		renderJob();
+	}
+	
+	/**
+	 * Highlights fields on the Panel that are not set correctly
+	 */
+	public void highlightInvalidArguments() {
+		for (ParameterDescription param : job) {
+			JComponent guiComponent = guiMap.get(param);
+			if (param.getParameter().required() && !job.isSet(param)) {
+				guiComponent.setBorder(BorderFactory.createLineBorder(Color.RED));
+			} else {
+				guiComponent.setBorder(BorderFactory.createEmptyBorder());
+			}
+		}
+	}
+	
+	/**
+	 * Render the parameters from the Job into GUI components
+	 * and set up one-way data binding to map changes to the GUI fields
+	 * back into the Job object's parameters
+	 */
+	private void renderJob() {
+		removeAll();
+		guiMap.clear();
+		jobMap.clear();
+		if (job == null) {
+			validate();
+			repaint();
+			return;
+		}
+		
+		// Iterate through the parameters in the Job
+		// and render them appropriately based on their type
+		for (ParameterDescription paramDescription : job) {
+			// Add the parameter name to the configuration panel
+			String name = paramDescription.getLongestName();
+			while (name.startsWith("-")) {
+				name = name.substring(1);
+			}
+			name = StringUtils.capitalize(name);
+			JLabel label = new JLabel(name);
+			label.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 5));
+			add(label);
+			
+			// Add a panel for configuring the parameter
+			JPanel fieldPanel = new JPanel();
+			fieldPanel.setLayout(new BoxLayout(fieldPanel, BoxLayout.LINE_AXIS));
+			add(fieldPanel);
+			Field field = paramDescription.getField();
+			Class<?> type = field.getType();
+			if (type.equals(Assembly.class)) {
+				List<Assembly> availableAssemblies = AssemblyManager.getAvailableAssemblies();
+				Assembly[] assemblies = new Assembly[availableAssemblies.size()];
+				assemblies = availableAssemblies.toArray(assemblies);
+				final JComboBox<Assembly> cbAssemblyChooser = new JComboBox<Assembly>(assemblies);
+				cbAssemblyChooser.setPreferredSize(new Dimension(0, 25));
+				cbAssemblyChooser.setMaximumSize(new Dimension(Integer.MAX_VALUE, cbAssemblyChooser.getPreferredSize().height));
+				cbAssemblyChooser.setSelectedItem(AssemblyManager.getLastUsed());
+				cbAssemblyChooser.addActionListener(new ActionListener() {
+					public void actionPerformed(ActionEvent e) {
+						log.debug("Auto-databinding changed assembly into Job argument");
+						Assembly selectedAssembly = (Assembly) cbAssemblyChooser.getSelectedItem();
+						AssemblyManager.setLastUsed(selectedAssembly);
+						ParameterDescription param = jobMap.get(cbAssemblyChooser);
+						job.setArgument(param, selectedAssembly.toString());
+					}
+				});
+				fieldPanel.add(cbAssemblyChooser);
+				guiMap.put(paramDescription, cbAssemblyChooser);
+				jobMap.put(cbAssemblyChooser, paramDescription);
+			} else {
+				final JTextField textField = new JTextField();
+				// Set to default parameter, if it exists
+				if (job.isSet(paramDescription)) {
+					textField.setText(job.getArgument(paramDescription));
+				}
+				textField.setPreferredSize(new Dimension(0, 20));
+				textField.setMaximumSize(new Dimension(Integer.MAX_VALUE, textField.getPreferredSize().height));
+				textField.getDocument().addDocumentListener(new DocumentListener() {
+					public void changedUpdate(DocumentEvent e) {
+						pushTextToModel(e);
+					}
+					
+					public void removeUpdate(DocumentEvent e) {
+						pushTextToModel(e);
+					}
+					
+					public void insertUpdate(DocumentEvent e) {
+						pushTextToModel(e);
+					}
+					
+					private void pushTextToModel(DocumentEvent e) {
+						log.debug("Auto-databinding changed text into Job argument");
+						Document doc = (Document) e.getDocument();
+						ParameterDescription param = jobMap.get(textField);
+						try {
+							String text = doc.getText(0, doc.getLength());
+							job.setArgument(param, text);
+						} catch (BadLocationException e1) {
+							log.error("Error pushing changed text into Job model");
+							e1.printStackTrace();
+						}
+					}
+				});
+				fieldPanel.add(textField);
+				guiMap.put(paramDescription, textField);
+				jobMap.put(textField, paramDescription);
+				
+				// For input/output files, add a file chooser button
+				if (type.equals(Path.class) || type.equals(WigFile.class) || type.equals(IntervalFile.class)) {
+					// TODO Replace with file icon
+					JButton btnChooseFile = new JButton(fileIcon);
+					btnChooseFile.addActionListener(new ActionListener() {
+						public void actionPerformed(ActionEvent e) {
+							// AWT FileDialog uses native components, but seems to hang
+							//Component c = (Component) e.getSource();
+			        //JFrame frame = (JFrame) SwingUtilities.getRoot(c);
+							//FileDialog fd = new FileDialog(frame, "Choose File");
+							//fd.setVisible(true);
+							//if (fd.getFile() != null) {
+							//	textField.setText(fd.getDirectory()+fd.getFile());
+							//}
+							
+							// Swing JFileChooser
+							JFileChooser fc = new JFileChooser();
+							int retValue = fc.showDialog(getParent(), "OK");
+							if (retValue == JFileChooser.APPROVE_OPTION) {
+								textField.setText(fc.getSelectedFile().toString());
+							}
+						}
+					});
+					fieldPanel.add(btnChooseFile);
+				}
+			}
+		}
+		
+		// Lay out the panel
+		SpringUtilities.makeCompactGrid(this, job.numParameters(), 2, 5, 5, 5, 5);
+		
+		validate();
+		repaint();
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gui/edu/unc/genomics/JobException.java	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,57 @@
+package edu.unc.genomics;
+
+/**
+ * Exception thrown if attempting to perform an invalid action with a Job
+ * 
+ * @author timpalpant
+ *
+ */
+public class JobException extends Exception {
+
+	private static final long serialVersionUID = -831504993593959450L;
+
+	/**
+	 * 
+	 */
+	public JobException() {
+		// TODO Auto-generated constructor stub
+	}
+
+	/**
+	 * @param message
+	 */
+	public JobException(String message) {
+		super(message);
+		// TODO Auto-generated constructor stub
+	}
+
+	/**
+	 * @param cause
+	 */
+	public JobException(Throwable cause) {
+		super(cause);
+		// TODO Auto-generated constructor stub
+	}
+
+	/**
+	 * @param message
+	 * @param cause
+	 */
+	public JobException(String message, Throwable cause) {
+		super(message, cause);
+		// TODO Auto-generated constructor stub
+	}
+
+	/**
+	 * @param message
+	 * @param cause
+	 * @param enableSuppression
+	 * @param writableStackTrace
+	 */
+	public JobException(String message, Throwable cause,
+			boolean enableSuppression, boolean writableStackTrace) {
+		super(message, cause, enableSuppression, writableStackTrace);
+		// TODO Auto-generated constructor stub
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gui/edu/unc/genomics/JobQueue.java	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,77 @@
+package edu.unc.genomics;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.swing.ListModel;
+import javax.swing.event.ListDataEvent;
+import javax.swing.event.ListDataListener;
+
+import org.apache.log4j.Logger;
+
+/**
+ * Model for the queue of SubmittedJobs
+ * Should be managed through the JobQueueManager controller
+ * 
+ * @author timpalpant
+ *
+ */
+public class JobQueue implements ListModel<SubmittedJob>, Iterable<SubmittedJob> {
+	
+	private static final Logger log = Logger.getLogger(JobQueue.class);
+
+	private final List<SubmittedJob> submittedJobs = new ArrayList<>();
+	private final List<ListDataListener> dataListeners = new ArrayList<>();
+	
+	public void add(SubmittedJob job) {
+		int N = submittedJobs.size();
+		submittedJobs.add(job);
+		ListDataEvent e = new ListDataEvent(this, ListDataEvent.INTERVAL_ADDED, N, N);
+		for (ListDataListener l : dataListeners) {
+			l.intervalAdded(e);
+		}
+	}
+	
+	public void remove(SubmittedJob job) {
+		submittedJobs.remove(job);
+		int N = submittedJobs.size();
+		ListDataEvent e = new ListDataEvent(this, ListDataEvent.INTERVAL_REMOVED, N, N);
+		for (ListDataListener l : dataListeners) {
+			l.intervalAdded(e);
+		}
+	}
+	
+	public void update(SubmittedJob job) {
+		int index = submittedJobs.indexOf(job);
+		ListDataEvent e = new ListDataEvent(this, ListDataEvent.CONTENTS_CHANGED, index, index);
+		for (ListDataListener l : dataListeners) {
+			l.intervalAdded(e);
+		}
+	}
+	
+	@Override
+	public int getSize() {
+		return submittedJobs.size();
+	}
+
+	@Override
+	public SubmittedJob getElementAt(int index) {
+		return submittedJobs.get(index);
+	}
+
+	@Override
+	public void addListDataListener(ListDataListener l) {
+		dataListeners.add(l);		
+	}
+
+	@Override
+	public void removeListDataListener(ListDataListener l) {
+		dataListeners.remove(l);
+	}
+
+	@Override
+	public Iterator<SubmittedJob> iterator() {
+		return submittedJobs.iterator();
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gui/edu/unc/genomics/JobQueueCellRenderer.java	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,125 @@
+package edu.unc.genomics;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.Font;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.BorderFactory;
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.JProgressBar;
+import javax.swing.ListCellRenderer;
+import javax.swing.SwingConstants;
+import javax.swing.border.EtchedBorder;
+
+/**
+ * @author timpalpant
+ *
+ */
+public class JobQueueCellRenderer extends JPanel implements ListCellRenderer<SubmittedJob> {
+
+	private static final long serialVersionUID = 4270263302075586018L;
+	
+	private static final ImageIcon inProgressIcon = new ImageIcon(ResourceManager.getImagesDirectory().resolve("beachball.png").toString());
+	private static final ImageIcon rerunIcon = new ImageIcon(ResourceManager.getImagesDirectory().resolve("arrow-circle.png").toString());
+	private static final ImageIcon infoIcon = new ImageIcon(ResourceManager.getImagesDirectory().resolve("information-white.png").toString());
+	private static final ImageIcon logIcon = new ImageIcon(ResourceManager.getImagesDirectory().resolve("sticky-note-text.png").toString());
+	private static final ImageIcon showFileIcon = new ImageIcon(ResourceManager.getImagesDirectory().resolve("eye_icon.png").toString());
+	
+	private static final ImageIcon successIcon = new ImageIcon(ResourceManager.getImagesDirectory().resolve("icon_success_sml.gif").toString());
+	private static final ImageIcon errorIcon = new ImageIcon(ResourceManager.getImagesDirectory().resolve("icon_error_sml.gif").toString());
+	private static final ImageIcon warningIcon = new ImageIcon(ResourceManager.getImagesDirectory().resolve("icon_warning_sml.gif").toString());
+
+	private JLabel statusIconLabel = new JLabel(inProgressIcon);
+	private JLabel nameLabel = new JLabel();
+	
+	public JobQueueCellRenderer() {
+		FlowLayout flowLayout = new FlowLayout(FlowLayout.LEADING, 5, 2);
+		setPreferredSize(new Dimension(190, 48));
+		setLayout(flowLayout);
+		setBorder(BorderFactory.createLineBorder(Color.GRAY, 1, true));
+		
+		JPanel statusPanel = new JPanel();
+		statusPanel.setBorder(BorderFactory.createEmptyBorder(0, 3, 16, 0));
+		statusPanel.setLayout(new BoxLayout(statusPanel, BoxLayout.PAGE_AXIS));
+		statusPanel.add(statusIconLabel);
+		add(statusPanel);
+		
+		JPanel mainPanel = new JPanel();
+		mainPanel.setAlignmentX(LEFT_ALIGNMENT);
+		mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.PAGE_AXIS));
+		
+		JPanel namePanel = new JPanel();
+		namePanel.setLayout(flowLayout);
+		nameLabel.setPreferredSize(new Dimension(145, 16));
+		nameLabel.setHorizontalTextPosition(SwingConstants.LEFT);
+		nameLabel.setFont(nameLabel.getFont().deriveFont(Font.BOLD));
+		namePanel.add(nameLabel);
+		mainPanel.add(namePanel);
+		
+		JPanel buttonsPanel = new JPanel();
+		buttonsPanel.setLayout(flowLayout);
+		mainPanel.add(buttonsPanel);
+		
+		ButtonLabel rerunLabel = new ButtonLabel(rerunIcon);
+		rerunLabel.addActionListener(new ActionListener() {
+			public void actionPerformed(ActionEvent e) {
+				//addJobToQueue();
+			}
+		});
+		buttonsPanel.add(rerunLabel);
+		ButtonLabel infoLabel = new ButtonLabel(infoIcon);
+		infoLabel.addActionListener(new ActionListener() {
+			public void actionPerformed(ActionEvent e) {
+				//addJobToQueue();
+			}
+		});
+		buttonsPanel.add(infoLabel);
+		ButtonLabel logLabel = new ButtonLabel(logIcon);
+		logLabel.addActionListener(new ActionListener() {
+			public void actionPerformed(ActionEvent e) {
+				//addJobToQueue();
+			}
+		});
+		buttonsPanel.add(logLabel);
+		ButtonLabel showFileLabel = new ButtonLabel(showFileIcon);
+		showFileLabel.addActionListener(new ActionListener() {
+			public void actionPerformed(ActionEvent e) {
+				//addJobToQueue();
+			}
+		});
+		buttonsPanel.add(showFileLabel);
+		
+		add(mainPanel);
+	}
+
+	@Override
+	public Component getListCellRendererComponent(
+			JList<? extends SubmittedJob> list, SubmittedJob value, int index,
+			boolean isSelected, boolean cellHasFocus) {
+		
+		nameLabel.setText(value.toString());
+		if (value.isRunning()) {
+			statusIconLabel.setIcon(inProgressIcon);
+			nameLabel.setForeground(Color.BLACK);
+		} else if (value.succeeded()) {
+			statusIconLabel.setIcon(successIcon);
+			nameLabel.setForeground(Color.BLACK);
+		} else {
+			statusIconLabel.setIcon(errorIcon);
+			nameLabel.setForeground(Color.RED);
+		}
+		
+		return this;
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gui/edu/unc/genomics/JobQueueManager.java	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,105 @@
+package edu.unc.genomics;
+
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+import org.apache.log4j.Logger;
+
+/**
+ * Controller for scheduling and running jobs
+ * Wrapper for ExcecutorService, although the implementation could change
+ * 
+ * @author timpalpant
+ *
+ */
+public class JobQueueManager {
+		
+	private static final Logger log = Logger.getLogger(JobQueueManager.class);
+	
+	private final JobQueue queue;
+	private final ExecutorService exec;
+	private final Thread monitor;
+	
+	public JobQueueManager(JobQueue queue) {
+		this.queue = queue;
+		
+		int numProcessors = Runtime.getRuntime().availableProcessors();
+		log.debug("Initializing thread pool with "+numProcessors+" processors");
+		exec = Executors.newFixedThreadPool(numProcessors);
+		
+		monitor = new Thread(new JobMonitor());
+		monitor.start();
+	}
+	
+	public List<Runnable> shutdownNow() {
+		return exec.shutdownNow();
+	}
+	
+	/**
+	 * Add a Job to the queue
+	 * @param job
+	 * @throws JobException 
+	 */
+	public SubmittedJob submitJob(Job job) throws JobException {
+		// Refuse to add the Job to the queue if its arguments are not valid
+		if (!job.validateArguments()) {
+			throw new JobException("Job arguments are not valid");
+		}
+		
+		// Submit the job for execution into the thread pool
+		Future<?> future = exec.submit(job);
+		SubmittedJob submittedJob = new SubmittedJob(job, future);
+		log.info("Submitted job " + submittedJob.getId());
+		
+		// Add the SubmittedJob to the JobQueue
+		queue.add(submittedJob);
+		return submittedJob;
+	}
+	
+	/**
+	 * Are any jobs running? (not done)
+	 * @return
+	 */
+	public boolean isRunning() {
+		for (SubmittedJob job : queue) {
+			if (!job.isDone()) {
+				return true;
+			}
+		}
+		
+		return false;
+	}
+
+
+	/**
+	 * Background process for polling the status of submitted jobs
+	 * @author timpalpant
+	 *
+	 */
+	public class JobMonitor implements Runnable {
+
+		public static final int JOB_POLL_INTERVAL = 1_000;
+		
+		public void run() {
+			try {
+				while (true) {
+					// Check Job statuses every 1s
+					Thread.sleep(JOB_POLL_INTERVAL);
+					
+					for (SubmittedJob job : queue) {
+						if (job.isDone()) {
+							queue.update(job);
+						}
+					}
+				}
+			} catch (InterruptedException e) {
+				log.fatal("JobMonitor crashed");
+				e.printStackTrace();
+				throw new RuntimeException("JobMonitor crashed");
+			}
+		}
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gui/edu/unc/genomics/ResourceManager.java	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,33 @@
+package edu.unc.genomics;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+/**
+ * Find resources based on platform/package
+ * @author timpalpant
+ *
+ */
+public class ResourceManager {
+	
+	private static final Path ASSEMBLIES_DIR = Paths.get("assemblies");
+	private static final Path IMAGES_DIR = Paths.get("images");
+	
+	public static Path getResourceDirectory() {
+		Path osx = Paths.get("../Resources");
+		if (Files.exists(osx)) {
+			return osx;
+		}
+		
+		return Paths.get("resources");
+	}
+	
+	public static Path getAssembliesDirectory() {
+		return getResourceDirectory().resolve(ASSEMBLIES_DIR);
+	}
+	
+	public static Path getImagesDirectory() {
+		return getResourceDirectory().resolve(IMAGES_DIR);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gui/edu/unc/genomics/SubmittedJob.java	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,103 @@
+package edu.unc.genomics;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+
+/**
+ * Represents a job that was submitted for processing
+ * 
+ * @author timpalpant
+ *
+ */
+public class SubmittedJob {
+	private static int numJobs = 0;
+	
+	private final Future<?> future;
+	private final int id;
+	private final Job job;
+	
+	public SubmittedJob(Job job, Future<?> future) {
+		this.id = ++numJobs;
+		this.job = job;
+		this.future = future;
+	}
+	
+	/**
+	 * @return the id
+	 */
+	public int getId() {
+		return id;
+	}
+
+	/**
+	 * @return the job
+	 */
+	public Job getJob() {
+		return job;
+	}
+	
+	/**
+	 * If the job is currently running
+	 * @return
+	 */
+	public boolean isRunning() {
+		return job.isRunning() && !isDone();
+	}
+	
+	/**
+	 * If the job is done running
+	 * (it may have failed or succeeded)
+	 * @return
+	 */
+	public boolean isDone() {
+		return future.isDone();
+	}
+	
+	/**
+	 * If this job completed without any Exceptions
+	 * @return
+	 */
+	public boolean succeeded() {
+		return (future.isDone() && !failed());
+	}
+	
+	/**
+	 * If this job completed with Exceptions
+	 * @return
+	 */
+	public boolean failed() {
+		if (future.isDone()) {
+			try {
+				future.get();
+				return false;
+			} catch (InterruptedException | ExecutionException e) {
+				return true;
+			}
+		}
+		
+		return false;
+	}
+	
+	/**
+	 * Return an Exception that occured, or null if there were none
+	 * or the job is not yet done
+	 * @return
+	 */
+	public Exception getException() {
+		if (future.isDone()) {
+			try {
+				future.get();
+				return null;
+			} catch (InterruptedException | ExecutionException e) {
+				return e;
+			}
+		}
+		
+		return null;
+	}
+	
+	@Override
+	public String toString() {
+		return "Job "+id+": "+job.getName();
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gui/edu/unc/genomics/ThreadFilter.java	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,35 @@
+package edu.unc.genomics;
+
+import org.apache.log4j.spi.Filter;
+import org.apache.log4j.spi.LoggingEvent;
+
+/**
+ * A Log4j filter that filters for messages from a single thread
+ * @author timpalpant
+ *
+ */
+public class ThreadFilter extends Filter {
+	
+	private final String threadName;
+	
+	public ThreadFilter(String threadName) {
+		this.threadName = threadName;
+	}
+
+	@Override
+	public int decide(LoggingEvent e) {
+		if(e.getThreadName().equalsIgnoreCase(threadName)) {
+			return Filter.DENY;
+		}
+		
+		return Filter.NEUTRAL;
+	}
+
+	/**
+	 * @return the threadName
+	 */
+	public String getThreadName() {
+		return threadName;
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gui/edu/unc/genomics/ToolRunner.java	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,38 @@
+package edu.unc.genomics;
+
+import java.awt.EventQueue;
+
+import javax.swing.JFrame;
+
+import org.apache.log4j.Logger;
+
+/**
+ * The main application for running the genomics toolkit gui
+ * Could do resource checking, etc. prior to startup
+ * 
+ * @author timpalpant
+ * 
+ */
+public class ToolRunner {
+	
+	private static final Logger log = Logger.getLogger(ToolRunner.class);
+
+	private JFrame frmToolRunner = new ToolRunnerFrame();
+
+	/**
+	 * Launch the application.
+	 */
+	public static void main(String[] args) {
+		EventQueue.invokeLater(new Runnable() {
+			@Override
+			public void run() {
+				try {
+					ToolRunner window = new ToolRunner();
+					window.frmToolRunner.setVisible(true);
+				} catch (Exception e) {
+					e.printStackTrace();
+				}
+			}
+		});
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gui/edu/unc/genomics/ToolRunnerFrame.java	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,328 @@
+package edu.unc.genomics;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.Font;
+
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+
+import javax.swing.BorderFactory;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JMenuBar;
+import javax.swing.JMenu;
+import javax.swing.JMenuItem;
+import javax.swing.JButton;
+import javax.swing.JOptionPane;
+import javax.swing.JPopupMenu;
+import javax.swing.JScrollPane;
+import javax.swing.SwingConstants;
+
+import javax.swing.JProgressBar;
+import javax.swing.JSplitPane;
+import javax.swing.BoxLayout;
+import javax.swing.JTabbedPane;
+import javax.swing.JTextPane;
+
+import javax.swing.event.TreeSelectionListener;
+import javax.swing.event.TreeSelectionEvent;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.apache.log4j.Logger;
+import org.simplericity.macify.eawt.Application;
+import org.simplericity.macify.eawt.ApplicationEvent;
+import org.simplericity.macify.eawt.ApplicationListener;
+import org.simplericity.macify.eawt.DefaultApplication;
+import org.xml.sax.SAXException;
+
+import com.beust.jcommander.JCommander;
+
+import java.awt.event.ActionListener;
+import java.awt.event.ActionEvent;
+import java.io.IOException;
+
+/**
+ * The main ToolRunner window
+ * and controller for creating, running, and managing Jobs
+ * 
+ * @author timpalpant
+ *
+ */
+public class ToolRunnerFrame extends JFrame implements ApplicationListener {
+	
+	private static final long serialVersionUID = 6454774196137357898L;
+	private static final Logger log = Logger.getLogger(ToolRunnerFrame.class);
+	
+	private final Application application = new DefaultApplication();
+	
+	private final JPanel contentPane = new JPanel();
+	private final JSplitPane splitPane = new JSplitPane();
+	private final JPanel mainPane = new JPanel();
+	private final JProgressBar progressBar = new JProgressBar();
+	private final JTabbedPane tabbedPane = new JTabbedPane(SwingConstants.TOP);
+	private final JobConfigPanel configurationPanel = new JobConfigPanel();
+	private final JTextPane helpTextPanel = new JTextPane();
+	private final ToolsTree toolsTree = new ToolsTree();
+		
+	private final JobQueue queue = new JobQueue();
+	private final JobQueueManager queueManager = new JobQueueManager(queue);
+	private final JList<SubmittedJob> queueList = new JList<>(queue);
+
+	/**
+	 * Create the frame.
+	 */
+	public ToolRunnerFrame() {
+    //application.addPreferencesMenuItem();
+    //application.setEnabledPreferencesMenu(true);
+    application.addApplicationListener(this);
+		
+    // set OS X-specific properties
+    if (application.isMac()) {
+    	setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
+    	//toolsTree.putClientProperty("Quaqua.Tree.style", "sourceList");
+    } else {
+    	setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+    }
+		setTitle("Genomics Toolkit Tool Runner");
+		setBounds(100, 100, 1000, 600);
+		
+		contentPane.setBorder(BorderFactory.createEmptyBorder());
+		contentPane.setLayout(new BorderLayout(0, 0));
+		setContentPane(contentPane);
+		
+		initializeChildren();
+		initializeMenuBar();
+	}
+	
+	private void initializeChildren() {
+		splitPane.setBorder(BorderFactory.createEmptyBorder());
+		contentPane.add(splitPane, BorderLayout.CENTER);
+		
+		initializeQueuePanel();
+		initializeToolsTree();
+		
+		mainPane.setLayout(new BorderLayout(0, 0));
+		mainPane.setBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY));
+		mainPane.add(tabbedPane, BorderLayout.CENTER);
+		
+		JPanel runPanel = new JPanel();
+		runPanel.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 0));
+		runPanel.setLayout(new BoxLayout(runPanel, BoxLayout.X_AXIS));
+		runPanel.add(progressBar);
+		JButton btnRun = new JButton("Run");
+		btnRun.addActionListener(new ActionListener() {
+			public void actionPerformed(ActionEvent e) {
+				addJobToQueue();
+			}
+		});
+		runPanel.add(btnRun);
+		mainPane.add(runPanel, BorderLayout.SOUTH);
+		splitPane.setRightComponent(mainPane);
+		
+		initializeConfigurationPanel();
+		initializeHelpPanel();
+	}
+	
+	private void initializeMenuBar() {
+		JMenuBar menuBar = new JMenuBar();
+		setJMenuBar(menuBar);
+
+		JMenu mnFileMenu = new JMenu("File");
+		menuBar.add(mnFileMenu);
+		
+		JMenuItem mntmAssemblyManager = new JMenuItem("Assembly manager");
+		mntmAssemblyManager.addActionListener(new ActionListener() {
+			public void actionPerformed(ActionEvent e) {
+				JMenuItem menuItem = (JMenuItem) e.getSource();  
+        JPopupMenu popupMenu = (JPopupMenu) menuItem.getParent();  
+        Component invoker = popupMenu.getInvoker(); //this is the JMenu (in my code)  
+        JComponent invokerAsJComponent = (JComponent) invoker;  
+        Container topLevel = invokerAsJComponent.getTopLevelAncestor();
+				AssemblyManagerDialog dialog = new AssemblyManagerDialog((JFrame) topLevel);
+				//dialog.getRootPane().putClientProperty("Window.style", "small");
+				dialog.setVisible(true);
+			}
+		});
+		mnFileMenu.add(mntmAssemblyManager);
+		
+		JMenu mnHelpMenu = new JMenu("Help");
+		menuBar.add(mnHelpMenu);
+		
+		JMenuItem mntmHelpContents = new JMenuItem("Help Contents");
+		mnHelpMenu.add(mntmHelpContents);
+		
+		if (!application.isMac()) {
+			JMenuItem mntmAbout = new JMenuItem("About");
+			mnHelpMenu.add(mntmAbout);
+			mnHelpMenu.addActionListener(new ActionListener() {
+				public void actionPerformed(ActionEvent e) {
+					handleAbout(null);
+				}
+			});
+		}
+	}
+	
+	private void initializeQueuePanel() {
+		JPanel queuePanel = new JPanel();
+		queuePanel.setLayout(new BoxLayout(queuePanel, BoxLayout.PAGE_AXIS));
+		queuePanel.setBorder(BorderFactory.createEmptyBorder());
+		contentPane.add(queuePanel, BorderLayout.EAST);
+		
+		JLabel queueLabel = new JLabel("Job Queue");
+		queueLabel.setBorder(BorderFactory.createEmptyBorder(5, 0, 5, 0));
+		queueLabel.setAlignmentX(JLabel.CENTER_ALIGNMENT);
+		queuePanel.add(queueLabel);
+
+		queueList.setBackground(contentPane.getBackground());
+		queueList.setCellRenderer(new JobQueueCellRenderer());
+		JScrollPane queueListScrollPane = new JScrollPane(queueList);
+		queueListScrollPane.setBorder(BorderFactory.createEmptyBorder());
+		queueListScrollPane.setBackground(contentPane.getBackground());
+		queueListScrollPane.setPreferredSize(new Dimension(200, Integer.MAX_VALUE));
+		queuePanel.add(queueListScrollPane);
+	}
+	
+	private void initializeConfigurationPanel() {		
+		JScrollPane configScrollPane = new JScrollPane(configurationPanel);
+		configScrollPane.setBorder(BorderFactory.createEmptyBorder());
+		tabbedPane.addTab("Tool Configuration", null, configScrollPane, "Configure tool");
+	}
+	
+	private void initializeHelpPanel() {
+		JPanel helpPanel = new JPanel();
+		tabbedPane.addTab("Help", null, helpPanel, null);
+		helpPanel.setLayout(new BorderLayout(0, 0));
+		
+		helpTextPanel.setEditable(false);
+		helpTextPanel.setBackground(tabbedPane.getBackground());
+		Font mono = new Font("Monospaced", helpTextPanel.getFont().getStyle(), helpTextPanel.getFont().getSize());
+		helpTextPanel.setFont(mono);
+		JScrollPane helpScrollPane = new JScrollPane(helpTextPanel);
+		helpScrollPane.setBorder(BorderFactory.createEmptyBorder());
+		helpPanel.add(helpScrollPane);
+	}
+		
+	private void initializeToolsTree() {
+		try {
+			ToolsTreeModel model = ToolsTreeModel.loadDefaultConfig();
+			toolsTree.setModel(model);
+		} catch (ParserConfigurationException | SAXException | IOException e1) {
+			log.error("Error loading tool configuration file");
+			e1.printStackTrace();
+			System.exit(-1);
+		} catch (ClassNotFoundException e) {
+			log.error("Error loading tool: " + e.getMessage());
+			e.printStackTrace();
+			System.exit(-1);
+		}
+
+		toolsTree.addTreeSelectionListener(new TreeSelectionListener() {
+			public void valueChanged(TreeSelectionEvent e) {
+				changeTool();
+			}
+		});
+		
+		JScrollPane toolsTreeScrollPane = new JScrollPane(toolsTree);
+		toolsTreeScrollPane.setBorder(BorderFactory.createMatteBorder(0, 0, 0, 1, Color.LIGHT_GRAY));
+		splitPane.setLeftComponent(toolsTreeScrollPane);
+	}
+
+	/**
+	 * Change the configuration panel to the currently selected tool
+	 * to configure a new Job
+	 */
+	private void changeTool() {
+		// Returns the last path element of the selection.
+    Object node = toolsTree.getLastSelectedPathComponent();
+    // Nothing is selected
+    if (node == null) {   
+    	return;
+    }
+
+    if (node instanceof ToolsTreeNode) {
+    	ToolsTreeNode toolNode = (ToolsTreeNode) node;
+			try {
+	      Class<? extends CommandLineTool> tool = toolNode.getClazz();
+	      
+	      // Set up the configuration panel to configure this tool
+				Job job = new Job(tool);
+	      configurationPanel.setJob(job);
+	      // Set the help text to the usage
+	      helpTextPanel.setText(job.getUsageText());
+			} catch (InstantiationException | IllegalAccessException e) {
+				log.error("Error initializing Job");
+				e.printStackTrace();
+				JOptionPane.showMessageDialog(this, "Error initializing job", "Job Initialization Error", JOptionPane.ERROR_MESSAGE);
+			}
+
+    }
+	}
+	
+	private void addJobToQueue() {
+		Job currentJob = configurationPanel.getJob();
+		if (currentJob == null) return;
+		
+		// Validate the required parameters
+		log.info("Validating parameters for tool");
+		if (!currentJob.validateArguments()) {
+			configurationPanel.highlightInvalidArguments();
+			return;
+		}
+		
+		// Add the job to the queue
+		try {
+			queueManager.submitJob(currentJob);
+			configurationPanel.setJob(null);
+		} catch (JobException e) {
+			log.error("Error adding Job to queue");
+			e.printStackTrace();
+			JOptionPane.showMessageDialog(this, "Error adding job to queue", "Job Queue Error", JOptionPane.ERROR_MESSAGE);
+		}
+	}
+	
+	public void handleAbout(ApplicationEvent event) {
+		JOptionPane.showMessageDialog(this, "Java Genomics Toolkit v1.0");
+		if (event != null) {
+			event.setHandled(true);
+		}
+	}
+
+	public void handleOpenApplication(ApplicationEvent event) {
+		// Application was opened
+	}
+
+	public void handleOpenFile(ApplicationEvent event) {
+		//JOptionPane.showMessageDialog(frmToolRunner, "OS X told us to open " + event.getFilename());
+	}
+
+	public void handlePreferences(ApplicationEvent event) {
+		//JOptionPane.showMessageDialog(frmToolRunner, "No preferences available");
+	}
+
+	public void handlePrintFile(ApplicationEvent event) {
+		//JOptionPane.showMessageDialog(frmToolRunner, "OS X told us to print " + event.getFilename());
+	}
+
+	public void handleQuit(ApplicationEvent event) {
+		boolean confirm = true;
+		if (queueManager.isRunning()) {
+			int result = JOptionPane.showConfirmDialog(this, "Jobs are currently running. Are you sure you want to quit?", "Confirm Quit", JOptionPane.OK_CANCEL_OPTION);
+			confirm = (result == JOptionPane.OK_OPTION);
+		}
+		
+		if (confirm) {
+			dispose();
+			System.exit(0);
+		}
+	}
+
+	public void handleReOpenApplication(ApplicationEvent event) {
+		//JOptionPane.showMessageDialog(frmToolRunner, "OS X told the application was reopened");
+		setVisible(true);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gui/edu/unc/genomics/ToolsTree.java	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,38 @@
+package edu.unc.genomics;
+
+import java.awt.Dimension;
+
+import javax.swing.BorderFactory;
+import javax.swing.JTree;
+import javax.swing.tree.TreeSelectionModel;
+
+/**
+ * Tree view of the available tools
+ * 
+ * @author timpalpant
+ *
+ */
+public class ToolsTree extends JTree {
+
+	private static final long serialVersionUID = -2591915754191263660L;
+
+	public ToolsTree() {
+		super();
+		initialize();
+	}
+	
+	public ToolsTree(ToolsTreeModel model) {
+		super(model);
+		initialize();
+	}
+	
+	private void initialize() {
+		getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
+		
+		setBorder(BorderFactory.createEmptyBorder());
+		setRootVisible(false);
+		setShowsRootHandles(true);
+		setPreferredSize(new Dimension(200, 0));
+	}
+	
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gui/edu/unc/genomics/ToolsTreeModel.java	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,82 @@
+package edu.unc.genomics;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.DefaultTreeModel;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.apache.log4j.Logger;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+/**
+ * Model for the ToolsTree
+ * Essentially just a DefaultTreeModel, but provides methods for
+ * loading the available tools from a configuration file
+ * 
+ * @author timpalpant
+ *
+ */
+public class ToolsTreeModel extends DefaultTreeModel {
+	
+	public static final Path DEFAULT_CONFIGURATION_FILE = Paths.get("toolConf.xml");
+	
+	private static final Logger log = Logger.getLogger(ToolsTreeModel.class);
+
+	private static final long serialVersionUID = -6587614270922489960L;
+
+	public ToolsTreeModel() {
+		super(new DefaultMutableTreeNode("Tools"));
+	}
+	
+	public static ToolsTreeModel loadDefaultConfig() throws ClassNotFoundException, ParserConfigurationException, SAXException, IOException {
+		return loadConfig(ResourceManager.getResourceDirectory().resolve(DEFAULT_CONFIGURATION_FILE));
+	}
+	
+	public static ToolsTreeModel loadConfig(Path p) throws ParserConfigurationException, SAXException, IOException, ClassNotFoundException {
+		ToolsTreeModel model = new ToolsTreeModel();
+		DefaultMutableTreeNode root = (DefaultMutableTreeNode) model.getRoot();
+		
+		// Populate the TreeModel with the tools in the default configuration file
+		log.debug("Loading tools from: " + DEFAULT_CONFIGURATION_FILE.toAbsolutePath());
+		DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
+		DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
+		Document doc = dBuilder.parse(p.toFile());
+		
+		// Iterate over the sections
+		NodeList sections = doc.getElementsByTagName("section");
+		log.debug("Found "+sections.getLength()+" sections");
+		for (int i = 0; i < sections.getLength(); i++) {
+			Node section = sections.item(i);
+			String sectionName = section.getAttributes().getNamedItem("name").getNodeValue();
+			log.debug("Loading section: " + sectionName);
+			DefaultMutableTreeNode sectionNode = new DefaultMutableTreeNode(sectionName);
+			root.add(sectionNode);
+			NodeList tools = section.getChildNodes();
+			
+			// Iterate over the tools in each section
+			for (int j = 0; j < tools.getLength(); j++) {
+				Node tool = tools.item(j);
+				if (tool.getNodeType() == Node.ELEMENT_NODE && tool.getNodeName().equalsIgnoreCase("tool")) {
+					String toolName = tool.getAttributes().getNamedItem("name").getNodeValue();
+					log.debug("Loading tool: " + toolName);
+					String toolClassName = tool.getAttributes().getNamedItem("class").getNodeValue();
+					Class<? extends CommandLineTool> toolClass = (Class<? extends CommandLineTool>) Class.forName(toolClassName);
+					ToolsTreeNode toolNode = new ToolsTreeNode(toolName, toolClass);
+					sectionNode.add(toolNode);
+				}
+			}
+		}
+		
+		return model;
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gui/edu/unc/genomics/ToolsTreeNode.java	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,44 @@
+package edu.unc.genomics;
+
+import javax.swing.tree.DefaultMutableTreeNode;
+
+/**
+ * A node in the ToolsTreeModel
+ * Contains a class and a name for the tool
+ * TODO Add help for each tool
+ * 
+ * @author timpalpant
+ *
+ */
+public class ToolsTreeNode extends DefaultMutableTreeNode {
+
+	private static final long serialVersionUID = -9067416927466519457L;
+
+	private final String name;
+	private final Class<? extends CommandLineTool> clazz;
+
+	/**
+	 * @param userObject
+	 */
+	public ToolsTreeNode(String name, Class<? extends CommandLineTool> clazz) {
+		super(name, false);
+		
+		this.name = name;
+		this.clazz = clazz;
+	}
+
+	/**
+	 * @return the name
+	 */
+	public String getName() {
+		return name;
+	}
+
+	/**
+	 * @return the clazz
+	 */
+	public Class<? extends CommandLineTool> getClazz() {
+		return clazz;
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gui/javax/swing/layout/SpringUtilities.java	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,225 @@
+/*
+ * Copyright (c) 1995, 2008, Oracle and/or its affiliates. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ *   - Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ *
+ *   - Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *
+ *   - Neither the name of Oracle or the names of its
+ *     contributors may be used to endorse or promote products derived
+ *     from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */ 
+
+package javax.swing.layout;
+
+import javax.swing.*;
+import javax.swing.SpringLayout;
+import java.awt.*;
+
+/**
+ * A 1.4 file that provides utility methods for
+ * creating form- or grid-style layouts with SpringLayout.
+ * These utilities are used by several programs, such as
+ * SpringBox and SpringCompactGrid.
+ */
+public class SpringUtilities {
+    /**
+     * A debugging utility that prints to stdout the component's
+     * minimum, preferred, and maximum sizes.
+     */
+    public static void printSizes(Component c) {
+        System.out.println("minimumSize = " + c.getMinimumSize());
+        System.out.println("preferredSize = " + c.getPreferredSize());
+        System.out.println("maximumSize = " + c.getMaximumSize());
+    }
+
+    /**
+     * Aligns the first <code>rows</code> * <code>cols</code>
+     * components of <code>parent</code> in
+     * a grid. Each component is as big as the maximum
+     * preferred width and height of the components.
+     * The parent is made just big enough to fit them all.
+     *
+     * @param rows number of rows
+     * @param cols number of columns
+     * @param initialX x location to start the grid at
+     * @param initialY y location to start the grid at
+     * @param xPad x padding between cells
+     * @param yPad y padding between cells
+     */
+    public static void makeGrid(Container parent,
+                                int rows, int cols,
+                                int initialX, int initialY,
+                                int xPad, int yPad) {
+        SpringLayout layout;
+        try {
+            layout = (SpringLayout)parent.getLayout();
+        } catch (ClassCastException exc) {
+            System.err.println("The first argument to makeGrid must use SpringLayout.");
+            return;
+        }
+
+        Spring xPadSpring = Spring.constant(xPad);
+        Spring yPadSpring = Spring.constant(yPad);
+        Spring initialXSpring = Spring.constant(initialX);
+        Spring initialYSpring = Spring.constant(initialY);
+        int max = rows * cols;
+
+        //Calculate Springs that are the max of the width/height so that all
+        //cells have the same size.
+        Spring maxWidthSpring = layout.getConstraints(parent.getComponent(0)).
+                                    getWidth();
+        Spring maxHeightSpring = layout.getConstraints(parent.getComponent(0)).
+                                    getHeight();
+        for (int i = 1; i < max; i++) {
+            SpringLayout.Constraints cons = layout.getConstraints(
+                                            parent.getComponent(i));
+
+            maxWidthSpring = Spring.max(maxWidthSpring, cons.getWidth());
+            maxHeightSpring = Spring.max(maxHeightSpring, cons.getHeight());
+        }
+
+        //Apply the new width/height Spring. This forces all the
+        //components to have the same size.
+        for (int i = 0; i < max; i++) {
+            SpringLayout.Constraints cons = layout.getConstraints(
+                                            parent.getComponent(i));
+
+            cons.setWidth(maxWidthSpring);
+            cons.setHeight(maxHeightSpring);
+        }
+
+        //Then adjust the x/y constraints of all the cells so that they
+        //are aligned in a grid.
+        SpringLayout.Constraints lastCons = null;
+        SpringLayout.Constraints lastRowCons = null;
+        for (int i = 0; i < max; i++) {
+            SpringLayout.Constraints cons = layout.getConstraints(
+                                                 parent.getComponent(i));
+            if (i % cols == 0) { //start of new row
+                lastRowCons = lastCons;
+                cons.setX(initialXSpring);
+            } else { //x position depends on previous component
+                cons.setX(Spring.sum(lastCons.getConstraint(SpringLayout.EAST),
+                                     xPadSpring));
+            }
+
+            if (i / cols == 0) { //first row
+                cons.setY(initialYSpring);
+            } else { //y position depends on previous row
+                cons.setY(Spring.sum(lastRowCons.getConstraint(SpringLayout.SOUTH),
+                                     yPadSpring));
+            }
+            lastCons = cons;
+        }
+
+        //Set the parent's size.
+        SpringLayout.Constraints pCons = layout.getConstraints(parent);
+        pCons.setConstraint(SpringLayout.SOUTH,
+                            Spring.sum(
+                                Spring.constant(yPad),
+                                lastCons.getConstraint(SpringLayout.SOUTH)));
+        pCons.setConstraint(SpringLayout.EAST,
+                            Spring.sum(
+                                Spring.constant(xPad),
+                                lastCons.getConstraint(SpringLayout.EAST)));
+    }
+
+    /* Used by makeCompactGrid. */
+    private static SpringLayout.Constraints getConstraintsForCell(
+                                                int row, int col,
+                                                Container parent,
+                                                int cols) {
+        SpringLayout layout = (SpringLayout) parent.getLayout();
+        Component c = parent.getComponent(row * cols + col);
+        return layout.getConstraints(c);
+    }
+
+    /**
+     * Aligns the first <code>rows</code> * <code>cols</code>
+     * components of <code>parent</code> in
+     * a grid. Each component in a column is as wide as the maximum
+     * preferred width of the components in that column;
+     * height is similarly determined for each row.
+     * The parent is made just big enough to fit them all.
+     *
+     * @param rows number of rows
+     * @param cols number of columns
+     * @param initialX x location to start the grid at
+     * @param initialY y location to start the grid at
+     * @param xPad x padding between cells
+     * @param yPad y padding between cells
+     */
+    public static void makeCompactGrid(Container parent,
+                                       int rows, int cols,
+                                       int initialX, int initialY,
+                                       int xPad, int yPad) {
+        SpringLayout layout;
+        try {
+            layout = (SpringLayout)parent.getLayout();
+        } catch (ClassCastException exc) {
+            System.err.println("The first argument to makeCompactGrid must use SpringLayout.");
+            return;
+        }
+
+        //Align all cells in each column and make them the same width.
+        Spring x = Spring.constant(initialX);
+        for (int c = 0; c < cols; c++) {
+            Spring width = Spring.constant(0);
+            for (int r = 0; r < rows; r++) {
+                width = Spring.max(width,
+                                   getConstraintsForCell(r, c, parent, cols).
+                                       getWidth());
+            }
+            for (int r = 0; r < rows; r++) {
+                SpringLayout.Constraints constraints =
+                        getConstraintsForCell(r, c, parent, cols);
+                constraints.setX(x);
+                constraints.setWidth(width);
+            }
+            x = Spring.sum(x, Spring.sum(width, Spring.constant(xPad)));
+        }
+
+        //Align all cells in each row and make them the same height.
+        Spring y = Spring.constant(initialY);
+        for (int r = 0; r < rows; r++) {
+            Spring height = Spring.constant(0);
+            for (int c = 0; c < cols; c++) {
+                height = Spring.max(height,
+                                    getConstraintsForCell(r, c, parent, cols).
+                                        getHeight());
+            }
+            for (int c = 0; c < cols; c++) {
+                SpringLayout.Constraints constraints =
+                        getConstraintsForCell(r, c, parent, cols);
+                constraints.setY(y);
+                constraints.setHeight(height);
+            }
+            y = Spring.sum(y, Spring.sum(height, Spring.constant(yPad)));
+        }
+
+        //Set the parent's size.
+        SpringLayout.Constraints pCons = layout.getConstraints(parent);
+        pCons.setConstraint(SpringLayout.SOUTH, y);
+        pCons.setConstraint(SpringLayout.EAST, x);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/launch4j.xml	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,26 @@
+<launch4jConfig>
+  <dontWrapJar>false</dontWrapJar>
+  <headerType>gui</headerType>
+  <jar>dist/java-genomics-toolkit.jar</jar>
+  <outfile>dist/Genomics Toolkit.exe</outfile>
+  <errTitle></errTitle>
+  <cmdLine></cmdLine>
+  <chdir></chdir>
+  <priority>normal</priority>
+  <downloadUrl>http://java.com/download</downloadUrl>
+  <supportUrl>http://github.com/timpalpant/java-genomics-toolkit</supportUrl>
+  <customProcName>false</customProcName>
+  <stayAlive>false</stayAlive>
+  <manifest>META-INF/MANIFEST.MF</manifest>
+  <icon></icon>
+  <classPath>
+    <mainClass>edu.unc.genomics.GenomicsToolkit</mainClass>
+    <cp>../lib/*</cp>
+  </classPath>
+  <jre>
+    <path></path>
+    <minVersion>1.7.0</minVersion>
+    <maxVersion></maxVersion>
+    <jdkPreference>preferJre</jdkPreference>
+  </jre>
+</launch4jConfig>
\ No newline at end of file
Binary file lib/BigWig.jar has changed
Binary file lib/commons-lang3-3.1.jar has changed
Binary file lib/commons-math-2.2.jar has changed
Binary file lib/jarbundler-2.2.0.jar has changed
Binary file lib/java-genomics-io.jar has changed
Binary file lib/jcommander-1.20.jar has changed
Binary file lib/jtransforms-2.4.jar has changed
Binary file lib/launch4j.jar has changed
Binary file lib/log4j-1.2.15.jar has changed
Binary file lib/macify-1.4.jar has changed
Binary file lib/sam-1.56.jar has changed
Binary file lib/swing-layout.jar has changed
Binary file lib/xstream.jar has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/log4j.properties	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,10 @@
+log4j.rootLogger=debug, stdout
+
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+
+# Pattern to output the caller's file name and line number.
+log4j.appender.stdout.layout.ConversionPattern=%5p - %m%n
+
+# Only output errors from the BigWig library
+log4j.logger.org.broad.igv.bbfile=ERROR
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/resources/assemblies/ce2.len	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,7 @@
+chrI	15080483
+chrII	15279308
+chrIII	13783313
+chrIV	17493791
+chrM	13794
+chrV	20922231
+chrX	17718849
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/resources/assemblies/ce3.len	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,7 @@
+chrI	15080552
+chrII	15279311
+chrIII	13783317
+chrIV	17493785
+chrM	13794
+chrV	20922231
+chrX	17718850
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/resources/assemblies/ce4.len	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,7 @@
+chrI	15072419
+chrII	15279316
+chrIII	13783681
+chrIV	17493784
+chrM	13794
+chrV	20919398
+chrX	17718852
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/resources/assemblies/ce5.len	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,7 @@
+chrV	20919568
+chrX	17718851
+chrIV	17493785
+chrII	15279316
+chrI	15072421
+chrIII	13783681
+chrM	13794
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/resources/assemblies/ce6.len	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,7 @@
+chrV	20919568
+chrX	17718854
+chrIV	17493785
+chrII	15279323
+chrI	15072421
+chrIII	13783681
+chrM	13794
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/resources/assemblies/ce7.len	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,7 @@
+chrV	20924143
+chrX	17718854
+chrIV	17493784
+chrII	15279324
+chrI	15072421
+chrIII	13783682
+chrM	13794
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/resources/assemblies/ce8.len	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,7 @@
+chrV	20924143
+chrX	17718854
+chrIV	17493784
+chrII	15279323
+chrI	15072421
+chrIII	13783685
+chrM	13794
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/resources/assemblies/ce9.len	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,7 @@
+chrV	20924143
+chrX	17718854
+chrIV	17493784
+chrII	15279323
+chrI	15072421
+chrIII	13783685
+chrM	13794
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/resources/assemblies/dm1.len	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,11 @@
+chr4	1237870
+chrU	8248647
+chrX	21780003
+chr2L	22217931
+chr2R	20302755
+chr2h	1651714
+chr3L	23352213
+chr3R	27890790
+chr3h	1961095
+chrXh	359526
+chrYh	321294
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/resources/assemblies/dm2.len	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,13 @@
+chr4	1281640
+chrM	19517
+chrU	8724946
+chrX	22224390
+chr2L	22407834
+chr2R	20766785
+chr2h	1694122
+chr3L	23771897
+chr3R	27905053
+chr3h	2955737
+chr4h	88110
+chrXh	359526
+chrYh	396896
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/resources/assemblies/dm3.len	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,15 @@
+chr2L	23011544
+chr2LHet	368872
+chr2R	21146708
+chr2RHet	3288761
+chr3L	24543557
+chr3LHet	2555491
+chr3R	27905053
+chr3RHet	2517507
+chr4	1351857
+chrU	10049037
+chrUextra	29004656
+chrX	22422827
+chrXHet	204112
+chrYHet	347038
+chrM	19517
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/resources/assemblies/hg15.len	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,44 @@
+chr1	245203898
+chr2	243315028
+chr3	199411731
+chr4	191610523
+chr5	180967295
+chr6	170740541
+chr7	158431299
+chr8	145908738
+chr9	134505819
+chrM	16571
+chrX	152634166
+chrY	50961097
+chr1_random	12562665
+chr2_random	1464032
+chr3_random	423185
+chr4_random	1219494
+chr6_random	12061844
+chr7_random	1057565
+chr8_random	427716
+chr9_random	2536476
+chrX_random	4859112
+chrY_random	191708
+chr10	135480874
+chr11	134978784
+chr12	133464434
+chr13	114151656
+chr14	105311216
+chr15	100114055
+chr16	89995999
+chr17	81691216
+chr18	77753510
+chr19	63790860
+chr20	63644868
+chr21	46976537
+chr22	49476972
+chr10_random	710249
+chr11_random	150110
+chr12_random	590431
+chr13_random	414659
+chr15_random	366089
+chr16_random	24360
+chr17_random	337440
+chr19_random	301858
+chrUn_random	611077
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/resources/assemblies/hg16.len	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,42 @@
+chr1	246127941
+chr2	243615958
+chr3	199344050
+chr4	191731959
+chr5	181034922
+chr6	170914576
+chr7	158545518
+chr8	146308819
+chr9	136372045
+chrM	16571
+chrX	153692391
+chrY	50286555
+chr1_random	6515988
+chr2_random	1104831
+chr3_random	749256
+chr4_random	648024
+chr5_random	143687
+chr6_random	2055751
+chr7_random	632637
+chr8_random	1499381
+chr9_random	2766341
+chrX_random	3403558
+chr10	135037215
+chr11	134482954
+chr12	132078379
+chr13	113042980
+chr14	105311216
+chr15	100256656
+chr16	90041932
+chr17	81860266
+chr18	76115139
+chr19	63811651
+chr20	63741868
+chr21	46976097
+chr22	49396972
+chr10_random	1043775
+chr13_random	189598
+chr15_random	1132826
+chr17_random	2549222
+chr18_random	4262
+chr19_random	92689
+chrUn_random	3349625
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/resources/assemblies/hg17.len	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,46 @@
+chr10	135413628
+chr10_random	113275
+chr11	134452384
+chr12	132449811
+chr12_random	466818
+chr13	114142980
+chr13_random	186858
+chr14	106368585
+chr15	100338915
+chr15_random	784346
+chr16	88827254
+chr16_random	105485
+chr17	78774742
+chr17_random	2618010
+chr18	76117153
+chr18_random	4262
+chr19	63811651
+chr19_random	301858
+chr1	245522847
+chr1_random	3897131
+chr20	62435964
+chr21	46944323
+chr22	49554710
+chr22_random	257318
+chr2	243018229
+chr2_random	418158
+chr3	199505740
+chr3_random	970716
+chr4	191411218
+chr4_random	1030282
+chr5	180857866
+chr5_random	143687
+chr6	170975699
+chr6_random	1875562
+chr6_hla_hap1	139182
+chr6_hla_hap2	150447
+chr7	158628139
+chr7_random	778964
+chr8	146274826
+chr8_random	943810
+chr9	138429268
+chr9_random	1312665
+chrM	16571
+chrX	154824264
+chrX_random	1719168
+chrY	57701691
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/resources/assemblies/hg18.len	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,49 @@
+chr1	247249719
+chr1_random	1663265
+chr10	135374737
+chr10_random	113275
+chr11	134452384
+chr11_random	215294
+chr12	132349534
+chr13	114142980
+chr13_random	186858
+chr14	106368585
+chr15	100338915
+chr15_random	784346
+chr16	88827254
+chr16_random	105485
+chr17	78774742
+chr17_random	2617613
+chr18	76117153
+chr18_random	4262
+chr19	63811651
+chr19_random	301858
+chr2	242951149
+chr2_random	185571
+chr20	62435964
+chr21	46944323
+chr21_random	1679693
+chr22	49691432
+chr22_random	257318
+chr22_h2_hap1	63661
+chr3	199501827
+chr3_random	749256
+chr4	191273063
+chr4_random	842648
+chr5	180857866
+chr5_random	143687
+chr5_h2_hap1	1794870
+chr6	170899992
+chr6_random	1875562
+chr6_cox_hap1	4731698
+chr6_qbl_hap2	4565931
+chr7	158821424
+chr7_random	549659
+chr8	146274826
+chr8_random	943810
+chr9	140273252
+chr9_random	1146434
+chrM	16571
+chrX	154913754
+chrX_random	1719168
+chrY	57772954
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/resources/assemblies/hg19.len	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,93 @@
+chr1	249250621
+chr2	243199373
+chr3	198022430
+chr4	191154276
+chr5	180915260
+chr6	171115067
+chr7	159138663
+chrX	155270560
+chr8	146364022
+chr9	141213431
+chr10	135534747
+chr11	135006516
+chr12	133851895
+chr13	115169878
+chr14	107349540
+chr15	102531392
+chr16	90354753
+chr17	81195210
+chr18	78077248
+chr20	63025520
+chrY	59373566
+chr19	59128983
+chr22	51304566
+chr21	48129895
+chr6_ssto_hap7	4928567
+chr6_mcf_hap5	4833398
+chr6_cox_hap2	4795371
+chr6_mann_hap4	4683263
+chr6_apd_hap1	4622290
+chr6_qbl_hap6	4611984
+chr6_dbb_hap3	4610396
+chr17_ctg5_hap1	1680828
+chr4_ctg9_hap1	590426
+chr1_gl000192_random	547496
+chrUn_gl000225	211173
+chr4_gl000194_random	191469
+chr4_gl000193_random	189789
+chr9_gl000200_random	187035
+chrUn_gl000222	186861
+chrUn_gl000212	186858
+chr7_gl000195_random	182896
+chrUn_gl000223	180455
+chrUn_gl000224	179693
+chrUn_gl000219	179198
+chr17_gl000205_random	174588
+chrUn_gl000215	172545
+chrUn_gl000216	172294
+chrUn_gl000217	172149
+chr9_gl000199_random	169874
+chrUn_gl000211	166566
+chrUn_gl000213	164239
+chrUn_gl000220	161802
+chrUn_gl000218	161147
+chr19_gl000209_random	159169
+chrUn_gl000221	155397
+chrUn_gl000214	137718
+chrUn_gl000228	129120
+chrUn_gl000227	128374
+chr1_gl000191_random	106433
+chr19_gl000208_random	92689
+chr9_gl000198_random	90085
+chr17_gl000204_random	81310
+chrUn_gl000233	45941
+chrUn_gl000237	45867
+chrUn_gl000230	43691
+chrUn_gl000242	43523
+chrUn_gl000243	43341
+chrUn_gl000241	42152
+chrUn_gl000236	41934
+chrUn_gl000240	41933
+chr17_gl000206_random	41001
+chrUn_gl000232	40652
+chrUn_gl000234	40531
+chr11_gl000202_random	40103
+chrUn_gl000238	39939
+chrUn_gl000244	39929
+chrUn_gl000248	39786
+chr8_gl000196_random	38914
+chrUn_gl000249	38502
+chrUn_gl000246	38154
+chr17_gl000203_random	37498
+chr8_gl000197_random	37175
+chrUn_gl000245	36651
+chrUn_gl000247	36422
+chr9_gl000201_random	36148
+chrUn_gl000235	34474
+chrUn_gl000239	33824
+chr21_gl000210_random	27682
+chrUn_gl000231	27386
+chrUn_gl000229	19913
+chrM	16571
+chrUn_gl000226	15008
+chr18_gl000207_random	4262
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/resources/assemblies/hg19Haps.len	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,9 @@
+chr6_ssto_hap7	4928567
+chr6_mcf_hap5	4833398
+chr6_cox_hap2	4795371
+chr6_mann_hap4	4683263
+chr6_apd_hap1	4622290
+chr6_qbl_hap6	4611984
+chr6_dbb_hap3	4610396
+chr17_ctg5_hap1	1680828
+chr4_ctg9_hap1	590426
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/resources/assemblies/hg19Patch2.len	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,80 @@
+chr6_ssto_hap7	4928567
+chr6_mcf_hap5	4833398
+chr6_cox_hap2	4795371
+chr6_mann_hap4	4683263
+chr6_apd_hap1	4622290
+chr6_qbl_hap6	4611984
+chr6_dbb_hap3	4610396
+chr17_ctg5_hap1	1680828
+chr5_ctg1_gl339449	1620324
+chr4_ctg9_hap1	590426
+chr17_gl383560	534288
+chr17_gl383558	457041
+chr8_gl383535	429806
+chr17_gl383561	406963
+chr10_gl383543	392792
+chr15_ctg8_gl383555	388773
+chr19_ctg3_gl383573	385657
+chr4_ctg6_gl383528	376187
+chr1_ctg31_gl383520	366579
+chr17_gl383559	338640
+chr9_gl339450	330164
+chr10_ctg5_gl383546	309802
+chr15_ctg4_gl383554	296527
+chr18_ctg1_gl383567	289831
+chr17_ctg1_gl383563	270261
+chr17_ctg4_gl383565	223995
+chr8_gl383536	203777
+chr21_ctg1_gl383579	201198
+chr18_ctg2_gl383571	198278
+chr16_ctg3_gl383556	192462
+chr19_ctg3_gl383576	188024
+chr12_ctg5_gl383551	184319
+chr1_ctg31_gl383518	182439
+chr3_ctg2_gl383526	180671
+chr10_ctg2_gl383545	179254
+chr5_ctg5_gl383531	173459
+chr3_gl383523	171362
+chr9_ctg35_gl383541	171286
+chr19_ctg3_gl383575	170227
+chr12_ctg2_gl383550	169178
+chr18_ctg2_gl383569	167950
+chr12_gl383548	165247
+chr18_ctg1_gl383570	164789
+chr4_ctg12_gl383527	164536
+chr9_ctg1_gl383539	162988
+chr18_ctg2_gl383572	159547
+chr22_ctg1_gl383582	158507
+chr19_ctg3_gl383574	155864
+chr12_ctg2_gl383553	154881
+chr11_ctg1_gl383547	154407
+chr2_ctg1_gl383521	143390
+chr12_ctg2_gl383552	138655
+chr17_ctg4_gl383564	133151
+chr20_ctg1_gl383577	128385
+chr10_gl383544	128378
+chr6_ctg5_gl383533	124736
+chr2_ctg12_gl383522	123821
+chr4_ctg9_gl383529	121345
+chr12_ctg2_gl383549	120804
+chr7_ctg6_gl383534	119383
+chr21_ctg1_gl383581	116690
+chr1_ctg31_gl383519	110268
+chr18_ctg2_gl383568	104552
+chr5_ctg2_gl383530	101241
+chr22_ctg2_gl383583	96924
+chr17_ctg4_gl383566	90219
+chr16_ctg3_gl383557	89672
+chr5_ctg1_gl383532	82728
+chr3_gl383524	78793
+chr21_ctg1_gl383580	74652
+chr9_ctg35_gl383540	71551
+chr3_gl383525	65063
+chr21_ctg1_gl383578	63917
+chr9_gl383537	62435
+chr9_ctg35_gl383542	60032
+chr1_gl383517	49352
+chr1_gl383516	49316
+chr9_gl383538	49281
+chr17_gl383562	45551
+chrM_rCRS	16569
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/resources/assemblies/klac.len	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,6 @@
+1	1062590
+2	1320834
+3	1753957
+4	1715506
+5	2234072
+6	2602197
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/resources/assemblies/kwal.len	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,459 @@
+0	2534
+1	125497
+2	14630
+3	14849
+4	21406
+5	143962
+6	27307
+7	88161
+8	132901
+9	16396
+10	8070
+11	186703
+12	36232
+13	51137
+14	15683
+15	19814
+16	24938
+17	58820
+18	38681
+19	5673
+20	23346
+21	4497
+22	41879
+23	29929
+24	38007
+25	7906
+26	32971
+27	213938
+28	44808
+29	35672
+30	18684
+31	57206
+32	39731
+33	128244
+34	41627
+35	25640
+36	83302
+37	18219
+38	7667
+39	19053
+40	3209
+41	26587
+42	27452
+43	129376
+44	73166
+45	12013
+46	86564
+47	106408
+48	11182
+49	9044
+50	17620
+51	17937
+52	12313
+53	13758
+54	21810
+55	238839
+56	2172
+57	2068
+58	24309
+59	27367
+60	19249
+61	49414
+62	2239
+63	9166
+64	3092
+65	5670
+66	2379
+67	89261
+68	119088
+69	16610
+70	68860
+71	7266
+72	40707
+73	49099
+74	3103
+75	15753
+76	37215
+77	9752
+78	2842
+79	3547
+80	8340
+81	2898
+82	26275
+83	1877
+84	7360
+85	10758
+86	12945
+87	2482
+88	1710
+89	10942
+90	2738
+91	4243
+92	4063
+93	4079
+94	95425
+95	1533
+96	26470
+97	1680
+98	8165
+99	1842
+100	10872
+101	72566
+102	41877
+103	25474
+104	80558
+105	89805
+106	18077
+107	7530
+108	62247
+109	3586
+110	8739
+111	2464
+112	12564
+113	4895
+114	68417
+115	2717
+116	36429
+117	33291
+118	43283
+119	32251
+120	6350
+121	27512
+122	3350
+123	92481
+124	139895
+125	7256
+126	69178
+127	2478
+128	65621
+129	16657
+130	116273
+131	4418
+132	42306
+133	2406
+134	7442
+135	34971
+136	5638
+137	25742
+138	24660
+139	33008
+140	12961
+141	3891
+142	11359
+143	4509
+144	14574
+145	12379
+146	1563
+147	14893
+148	40168
+149	38705
+150	11968
+151	8479
+152	45612
+153	41502
+154	3140
+155	7073
+156	9905
+157	20593
+158	4160
+159	193906
+160	62941
+161	118475
+162	6963
+163	22133
+164	16355
+165	75530
+166	54174
+167	1343
+168	12950
+169	3447
+170	2919
+171	62747
+172	18589
+173	35297
+174	21491
+175	27381
+176	72161
+177	20372
+178	2700
+179	3322
+180	14134
+181	28845
+182	12905
+183	95360
+184	183765
+185	88813
+186	33036
+187	8378
+188	85033
+189	25272
+190	55902
+191	32868
+192	15238
+193	7566
+194	18007
+195	55872
+196	40080
+197	67181
+198	33220
+199	2793
+200	3391
+201	3701
+202	26101
+203	3511
+204	59973
+205	19265
+206	71746
+207	60023
+208	13067
+209	16995
+210	23815
+211	28487
+212	33526
+213	3586
+214	8067
+215	40367
+216	6559
+217	4358
+218	20016
+219	49821
+220	4268
+223	1302
+224	93571
+225	50086
+226	2886
+227	52355
+228	16270
+229	17925
+230	23564
+231	18755
+232	31964
+233	1570
+234	2415
+235	14560
+236	1731
+237	81585
+238	28707
+239	17167
+240	2875
+241	25088
+242	31259
+243	26745
+244	154632
+245	115160
+246	72184
+247	33539
+248	57737
+249	14547
+250	33679
+251	31357
+252	103558
+253	45320
+254	49503
+255	143040
+256	2466
+257	4108
+258	2372
+259	1928
+260	167397
+261	15852
+262	1441
+263	1554
+264	2637
+265	24142
+266	3777
+267	7711
+268	18551
+269	4004
+270	8972
+271	26934
+272	87860
+273	19114
+274	30786
+275	9656
+278	8868
+279	3258
+280	21868
+281	1988
+282	4323
+283	1223
+284	2472
+285	6576
+286	5548
+287	3198
+288	4014
+289	9582
+290	162263
+291	2221
+292	1850
+293	1652
+294	20909
+295	17606
+296	2866
+297	1704
+298	9311
+299	1009
+301	4475
+303	9229
+304	1977
+305	11008
+311	23764
+312	4784
+313	1296
+314	4092
+316	8619
+317	2626
+318	2050
+319	1424
+320	5110
+322	6341
+323	4249
+324	1824
+325	1783
+326	6231
+327	5048
+328	4865
+329	1570
+330	3539
+331	13212
+332	1641
+333	5872
+334	5842
+335	5050
+336	43798
+337	1403
+338	1507
+339	2056
+340	1548
+341	1516
+343	2948
+344	93162
+345	10302
+346	1452
+347	4043
+348	2486
+349	2696
+350	1996
+351	6210
+352	747
+353	5694
+354	4998
+355	4074
+356	4769
+357	4339
+358	36291
+359	9937
+360	2677
+361	2847
+362	1661
+363	52128
+364	3720
+365	1654
+366	2713
+367	1250
+368	3279
+369	7143
+372	8172
+373	1057
+376	113992
+377	1076
+378	2288
+379	1144
+380	4811
+381	9891
+386	4220
+387	1417
+388	41838
+389	1218
+390	2234
+391	1105
+392	871
+393	3493
+394	1983
+395	1310
+396	28400
+397	919
+398	1362
+399	3442
+400	1525
+401	1671
+402	1120
+403	799
+404	1763
+405	1818
+406	1786
+407	1123
+408	1388
+409	839
+410	2870
+411	1955
+412	2189
+413	1100
+414	2057
+415	991
+416	3094
+417	3187
+418	1185
+419	3118
+420	1230
+421	1421
+422	1603
+423	1641
+424	1882
+425	1479
+426	4008
+427	1785
+428	2536
+429	1193
+430	2549
+431	1682
+432	3994
+433	1014
+434	745
+435	1117
+436	3520
+437	1677
+438	676
+439	2979
+440	1726
+441	5119
+442	5142
+443	935
+444	1354
+446	2597
+447	2611
+448	1179
+449	5365
+450	1259
+451	5395
+452	1972
+453	3195
+454	816
+455	7081
+456	1241
+457	21922
+458	1100
+459	927
+460	1100
+461	5552
+462	5577
+463	5460
+665	2006
+666	1381
+667	1781
+668	1407
+690	4541
+691	2971
+692	1226
+693	1100
+694	1218
+695	5394
+696	4542
+697	14183
+698	5428
+699	577
+700	2530
+710	16854
+711	2634
+712	5627
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/resources/assemblies/sacCer1.len	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,17 @@
+chr1	230208
+chr10	745446
+chr11	666445
+chr12	1078173
+chr13	924430
+chr14	784328
+chr15	1091285
+chr16	948060
+chr2	813136
+chr3	316613
+chr4	1531914
+chr5	576869
+chr6	270148
+chr7	1090944
+chr8	562639
+chr9	439885
+chrM	85779
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/resources/assemblies/sacCer2.len	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,18 @@
+chrIV	1531919
+chrXV	1091289
+chrVII	1090947
+chrXII	1078175
+chrXVI	948062
+chrXIII	924429
+chrII	813178
+chrXIV	784333
+chrX	745742
+chrXI	666454
+chrV	576869
+chrVIII	562643
+chrIX	439885
+chrIII	316617
+chrVI	270148
+chrI	230208
+chrM	85779
+2micron	6318
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/resources/assemblies/sacCer3.len	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,17 @@
+chrI	230218
+chrII	813184
+chrIII	316620
+chrIV	1531933
+chrIX	439888
+chrV	576874
+chrVI	270161
+chrVII	1090940
+chrVIII	562643
+chrX	745751
+chrXI	666816
+chrXII	1078177
+chrXIII	924431
+chrXIV	784333
+chrXV	1091291
+chrXVI	948066
+chrM	85779
Binary file resources/images/add_icon.png has changed
Binary file resources/images/add_icon_dark.png has changed
Binary file resources/images/arrow-circle.png has changed
Binary file resources/images/beachball.png has changed
Binary file resources/images/bug.png has changed
Binary file resources/images/delete_icon_dark.png has changed
Binary file resources/images/delete_icon_grey.png has changed
Binary file resources/images/eye_icon.png has changed
Binary file resources/images/eye_icon_dark.png has changed
Binary file resources/images/eye_icon_grey.png has changed
Binary file resources/images/folder.png has changed
Binary file resources/images/folder_page.png has changed
Binary file resources/images/icon_error_sml.gif has changed
Binary file resources/images/icon_info_sml.gif has changed
Binary file resources/images/icon_success_sml.gif has changed
Binary file resources/images/icon_warning_sml.gif has changed
Binary file resources/images/information-white.png has changed
Binary file resources/images/mag_glass.png has changed
Binary file resources/images/osx-spinner.gif has changed
Binary file resources/images/pencil_icon.png has changed
Binary file resources/images/pencil_icon_dark.png has changed
Binary file resources/images/pencil_icon_grey.png has changed
Binary file resources/images/sticky-note-text.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/resources/toolConf.xml	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- This is the tool configuration file for the ToolRunner GUI -->
+<tools>
+	<section name="Converters">
+		<tool name="IntervalToWig" class="edu.unc.genomics.converters.IntervalToWig" />
+		<tool name="RomanNumeralize" class="edu.unc.genomics.converters.RomanNumeralize" />
+	</section>
+	
+	<section name="NGS">
+		<tool name="Autocorrelation" class="edu.unc.genomics.ngs.Autocorrelation" />
+		<tool name="BaseAlignCounts" class="edu.unc.genomics.ngs.BaseAlignCounts" />
+		<tool name="FindAbsoluteMaxima" class="edu.unc.genomics.ngs.FindAbsoluteMaxima" />
+		<tool name="IntervalLengthDistribution" class="edu.unc.genomics.ngs.IntervalLengthDistribution" />
+		<tool name="IntervalStats" class="edu.unc.genomics.ngs.IntervalStats" />
+		<tool name="PowerSpectrum" class="edu.unc.genomics.ngs.PowerSpectrum" />
+		<tool name="RollingReadLength" class="edu.unc.genomics.ngs.RollingReadLength" />
+	</section>
+	
+	<section name="Nucleosomes">
+		<tool name="FindBoundaryNucleosomes" class="edu.unc.genomics.nucleosomes.FindBoundaryNucleosomes" />
+		<tool name="GreedyCaller" class="edu.unc.genomics.nucleosomes.GreedyCaller" />
+		<tool name="MapDyads" class="edu.unc.genomics.nucleosomes.MapDyads" />
+		<tool name="NRLCalculator" class="edu.unc.genomics.nucleosomes.NRLCalculator" />
+		<tool name="Phasogram" class="edu.unc.genomics.nucleosomes.Phasogram" />
+	</section>
+	
+	<section name="Visualization">
+		<tool name="IntervalAverager" class="edu.unc.genomics.visualization.IntervalAverager" />
+		<tool name="KMeans" class="edu.unc.genomics.visualization.KMeans" />
+		<tool name="MatrixAligner" class="edu.unc.genomics.visualization.MatrixAligner" />
+	</section>
+	
+	<section name="WigMath">
+		<tool name="Add" class="edu.unc.genomics.wigmath.Add" />
+		<tool name="Average" class="edu.unc.genomics.wigmath.Average" />
+		<tool name="Divide" class="edu.unc.genomics.wigmath.Divide" />
+		<tool name="GaussianSmooth" class="edu.unc.genomics.wigmath.GaussianSmooth" />
+		<tool name="LogTransform" class="edu.unc.genomics.wigmath.LogTransform" />
+		<tool name="MovingAverageSmooth" class="edu.unc.genomics.wigmath.MovingAverageSmooth" />
+		<tool name="Multiply" class="edu.unc.genomics.wigmath.Multiply" />
+		<tool name="Scale" class="edu.unc.genomics.wigmath.Scale" />
+		<tool name="Subtract" class="edu.unc.genomics.wigmath.Subtract" />
+		<tool name="WigSummary" class="edu.unc.genomics.wigmath.WigSummary" />
+		<tool name="ZScore" class="edu.unc.genomics.wigmath.ZScore" />
+	</section>
+</tools>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/edu/unc/config/GalaxyConfig.java	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,42 @@
+package edu.unc.config;
+
+import java.io.IOException;
+import java.nio.file.Path;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.w3c.dom.Document;
+import org.xml.sax.SAXException;
+
+public class GalaxyConfig {
+	
+	/**
+	 * Parse a Galaxy configuration file
+	 * @param p
+	 * @return
+	 * @throws ParserConfigurationException 
+	 * @throws IOException 
+	 * @throws SAXException 
+	 */
+	public static GalaxyConfig parse(Path p) throws ParserConfigurationException, SAXException, IOException {
+		DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
+		DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
+		Document doc = dBuilder.parse(p.toFile());
+		return parse(doc);
+	}
+	
+	/**
+	 * Parse a Galaxy configuration XML
+	 * @param doc
+	 * @return
+	 */
+	public static GalaxyConfig parse(Document doc) {
+		GalaxyConfig config = new GalaxyConfig();
+		
+		// TODO Implement parser
+		
+		return config;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/edu/unc/genomics/AssemblyConverter.java	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,43 @@
+package edu.unc.genomics;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.zip.DataFormatException;
+
+import com.beust.jcommander.IStringConverter;
+import com.beust.jcommander.ParameterException;
+
+/**
+ * @author timpalpant
+ *
+ */
+public class AssemblyConverter implements IStringConverter<Assembly> {
+	
+	public static final Path ASSEMBLIES_DIR = Paths.get("resources", "assemblies");
+
+	@Override
+	public Assembly convert(String value) throws ParameterException {
+		// Look for the assembly in the resources/assemblies directory
+		Path p = ASSEMBLIES_DIR.resolve(value+".len");
+		
+		// If it does not exist in the assemblies directory, check if it is a path to a file
+		if (!Files.isReadable(p)) {
+			PathConverter converter = new PathConverter();
+			p = converter.convert(value);
+			// If it does not exist, then throw an exception that the assembly cannot be found
+			if (!Files.isReadable(p)) {
+				throw new ParameterException("Cannot find Assembly file: " + value);
+			}
+		}
+		
+		// Attempt to load the assembly from file
+		try {
+			return new Assembly(p);
+		} catch (IOException | DataFormatException e) {
+			throw new ParameterException("Error loading Assembly from file: " + p);
+		}
+	}
+	
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/edu/unc/genomics/AssemblyFactory.java	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,26 @@
+/**
+ * 
+ */
+package edu.unc.genomics;
+
+import com.beust.jcommander.IStringConverterFactory;
+
+/**
+ * @author timpalpant
+ *
+ */
+public class AssemblyFactory implements IStringConverterFactory {
+
+	/* (non-Javadoc)
+	 * @see com.beust.jcommander.IStringConverterFactory#getConverter(java.lang.Class)
+	 */
+	@Override
+	public Class<AssemblyConverter> getConverter(Class forType) {
+		if (forType.equals(Assembly.class)) {
+			return AssemblyConverter.class;
+		} else {
+			return null;
+		}
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/edu/unc/genomics/CommandLineTool.java	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,73 @@
+package edu.unc.genomics;
+
+import java.io.IOException;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.ParameterException;
+
+/**
+ * A command-line script
+ * @author timpalpant
+ *
+ */
+public abstract class CommandLineTool {
+	
+	/**
+	 * JCommander command-line argument parser
+	 */
+	private final JCommander jc = new JCommander(this);
+	
+	public CommandLineTool() {
+		// Add factories for parsing Paths, Assemblies, IntervalFiles, and WigFiles
+		jc.addConverterFactory(new PathFactory());
+		jc.addConverterFactory(new AssemblyFactory());
+		jc.addConverterFactory(new IntervalFileFactory());
+		jc.addConverterFactory(new WigFileFactory());
+		
+		// Set the program name to be the class name
+		jc.setProgramName(this.getClass().getSimpleName());
+	}
+	
+	/**
+	 * The default bite-size to use for applications that process files in chunks
+	 */
+	public static final int DEFAULT_CHUNK_SIZE = 500_000;
+	
+	/**
+	 * Do the main computation of this tool
+	 * @throws IOException
+	 */
+	public abstract void run() throws IOException;
+	
+	/**
+	 * Parse command-line arguments and run the tool
+	 * Exit on parameter exceptions
+	 * @param args
+	 */
+	public void instanceMain(String[] args) throws CommandLineToolException {
+		try {
+			toolRunnerMain(args);
+		} catch (ParameterException e) {
+			System.err.println(e.getMessage());
+			jc.usage();
+			System.exit(-1);
+		}
+	}
+	
+	/**
+	 * Parse command-line arguments and run the tool
+	 * @param args
+	 * @throws ParameterException if there are invalid/missing parameters
+	 * @throws CommandLineToolException if an exception occurs while running the tool
+	 */
+	public void toolRunnerMain(String[] args) throws ParameterException, CommandLineToolException {
+		jc.parse(args);
+		
+		try {
+			run();
+		} catch (IOException e) {
+			e.printStackTrace();
+			throw new CommandLineToolException("IO error while running tool");
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/edu/unc/genomics/CommandLineToolException.java	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,58 @@
+package edu.unc.genomics;
+
+/**
+ * @author timpalpant
+ *
+ */
+public class CommandLineToolException extends RuntimeException {
+
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = 4740440799806133636L;
+
+	/**
+	 * 
+	 */
+	public CommandLineToolException() {
+		// TODO Auto-generated constructor stub
+	}
+
+	/**
+	 * @param message
+	 */
+	public CommandLineToolException(String message) {
+		super(message);
+		// TODO Auto-generated constructor stub
+	}
+
+	/**
+	 * @param cause
+	 */
+	public CommandLineToolException(Throwable cause) {
+		super(cause);
+		// TODO Auto-generated constructor stub
+	}
+
+	/**
+	 * @param message
+	 * @param cause
+	 */
+	public CommandLineToolException(String message, Throwable cause) {
+		super(message, cause);
+		// TODO Auto-generated constructor stub
+	}
+
+	/**
+	 * @param message
+	 * @param cause
+	 * @param enableSuppression
+	 * @param writableStackTrace
+	 */
+	public CommandLineToolException(String message, Throwable cause,
+			boolean enableSuppression, boolean writableStackTrace) {
+		super(message, cause, enableSuppression, writableStackTrace);
+		// TODO Auto-generated constructor stub
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/edu/unc/genomics/IntervalFileConverter.java	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,28 @@
+package edu.unc.genomics;
+
+import java.io.IOException;
+import java.nio.file.Path;
+
+import com.beust.jcommander.IStringConverter;
+import com.beust.jcommander.ParameterException;
+
+import edu.unc.genomics.io.IntervalFile;
+
+/**
+ * @author timpalpant
+ *
+ */
+public class IntervalFileConverter implements IStringConverter<IntervalFile<? extends Interval>> {
+
+	@Override
+	public IntervalFile<? extends Interval> convert(String value) throws ParameterException {
+		PathConverter converter = new PathConverter();
+		Path p = converter.convert(value);
+		try {
+			return IntervalFile.autodetect(p);
+		} catch (IOException e) {
+			throw new ParameterException("IOException while attempting to autodetect interval file type");
+		}
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/edu/unc/genomics/IntervalFileFactory.java	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,25 @@
+package edu.unc.genomics;
+
+import com.beust.jcommander.IStringConverterFactory;
+
+import edu.unc.genomics.io.IntervalFile;
+
+/**
+ * @author timpalpant
+ *
+ */
+public class IntervalFileFactory implements IStringConverterFactory {
+
+	/* (non-Javadoc)
+	 * @see com.beust.jcommander.IStringConverterFactory#getConverter(java.lang.Class)
+	 */
+	@Override
+	public Class<IntervalFileConverter> getConverter(Class forType) {
+		if (forType.equals(IntervalFile.class)) {
+			return IntervalFileConverter.class;
+		} else {
+			return null;
+		}
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/edu/unc/genomics/PathConverter.java	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,19 @@
+package edu.unc.genomics;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import com.beust.jcommander.IStringConverter;
+
+/**
+ * @author timpalpant
+ *
+ */
+public class PathConverter implements IStringConverter<Path> {
+
+	@Override
+	public Path convert(String value) {
+		return Paths.get(value);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/edu/unc/genomics/PathFactory.java	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,27 @@
+/**
+ * 
+ */
+package edu.unc.genomics;
+
+import java.nio.file.Path;
+
+import com.beust.jcommander.IStringConverterFactory;
+
+/**
+ * @author timpalpant
+ *
+ */
+public class PathFactory implements IStringConverterFactory {
+	/* (non-Javadoc)
+	 * @see com.beust.jcommander.IStringConverterFactory#getConverter(java.lang.Class)
+	 */
+	@Override
+	public Class<PathConverter> getConverter(Class forType) {
+		if (forType.equals(Path.class)) {
+			return PathConverter.class;
+		} else {
+			return null;
+		}
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/edu/unc/genomics/PositiveIntegerValidator.java	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,23 @@
+package edu.unc.genomics;
+
+import com.beust.jcommander.IParameterValidator;
+import com.beust.jcommander.ParameterException;
+
+/**
+ * @author timpalpant
+ *
+ */
+public class PositiveIntegerValidator implements IParameterValidator {
+
+	/* (non-Javadoc)
+	 * @see com.beust.jcommander.IParameterValidator#validate(java.lang.String, java.lang.String)
+	 */
+	@Override
+	public void validate(String name, String value) throws ParameterException {		
+		int n = Integer.parseInt(value);
+		if (n <= 0) {
+			throw new ParameterException("Parameter "+name+" must be > 0 (was "+value+")");
+		}
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/edu/unc/genomics/ReadablePathValidator.java	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,30 @@
+/**
+ * 
+ */
+package edu.unc.genomics;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import com.beust.jcommander.IParameterValidator;
+import com.beust.jcommander.ParameterException;
+
+/**
+ * @author timpalpant
+ *
+ */
+public class ReadablePathValidator implements IParameterValidator {
+
+	/* (non-Javadoc)
+	 * @see com.beust.jcommander.IParameterValidator#validate(java.lang.String, java.lang.String)
+	 */
+	@Override
+	public void validate(String name, String value) throws ParameterException {
+		PathConverter converter = new PathConverter();
+		Path p = converter.convert(value);
+		if (!Files.isReadable(p)) {
+			throw new ParameterException("Parameter " + name + " should be a readable file");
+		}
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/edu/unc/genomics/WigFileConverter.java	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,29 @@
+package edu.unc.genomics;
+
+import java.io.IOException;
+import java.nio.file.Path;
+
+import com.beust.jcommander.IStringConverter;
+import com.beust.jcommander.ParameterException;
+
+import edu.unc.genomics.io.WigFile;
+import edu.unc.genomics.io.WigFileException;
+
+/**
+ * @author timpalpant
+ *
+ */
+public class WigFileConverter implements IStringConverter<WigFile> {
+
+	@Override
+	public WigFile convert(String value) throws ParameterException {
+		PathConverter converter = new PathConverter();
+		Path p = converter.convert(value);
+		try {
+			return WigFile.autodetect(p);
+		} catch (WigFileException | IOException e) {
+			throw new ParameterException("Error autodetecting and initializing BigWig/Wig file");
+		}
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/edu/unc/genomics/WigFileFactory.java	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,25 @@
+package edu.unc.genomics;
+
+import com.beust.jcommander.IStringConverterFactory;
+
+import edu.unc.genomics.io.WigFile;
+
+/**
+ * @author timpalpant
+ *
+ */
+public class WigFileFactory implements IStringConverterFactory {
+
+	/* (non-Javadoc)
+	 * @see com.beust.jcommander.IStringConverterFactory#getConverter(java.lang.Class)
+	 */
+	@Override
+	public Class<WigFileConverter> getConverter(Class forType) {
+		if (forType.equals(WigFile.class)) {
+			return WigFileConverter.class;
+		} else {
+			return null;
+		}
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/edu/unc/genomics/converters/IntervalToWig.java	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,91 @@
+package edu.unc.genomics.converters;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Iterator;
+
+import org.apache.log4j.Logger;
+
+import com.beust.jcommander.Parameter;
+
+import edu.ucsc.genome.TrackHeader;
+import edu.unc.genomics.Assembly;
+import edu.unc.genomics.Interval;
+import edu.unc.genomics.CommandLineTool;
+import edu.unc.genomics.ValuedInterval;
+import edu.unc.genomics.io.IntervalFile;
+
+public class IntervalToWig extends CommandLineTool {
+
+	private static final Logger log = Logger.getLogger(IntervalToWig.class);
+
+	@Parameter(names = {"-i", "--input"}, description = "Input file (Bed/BedGraph)", required = true)
+	public IntervalFile<? extends Interval> intervalFile;
+	@Parameter(names = {"-a", "--assembly"}, description = "Genome assembly", required = true)
+	public Assembly assembly;
+	@Parameter(names = {"-o", "--output"}, description = "Output file (Wig)", required = true)
+	public Path outputFile;
+	
+	@Override
+	public void run() throws IOException {
+		log.info(intervalFile.count() + " entries in input");
+		
+		log.debug("Initializing output file");
+		try (BufferedWriter writer = Files.newBufferedWriter(outputFile, Charset.defaultCharset())) {
+			// Write the Wiggle track header to the output file
+			TrackHeader header = new TrackHeader("wiggle_0");
+			header.setName("Converted " + intervalFile.getPath().getFileName());
+			header.setDescription("Converted " + intervalFile.getPath().getFileName());
+			writer.write(header.toString());
+			writer.newLine();
+			
+			// Process each chromosome in the assembly
+			for (String chr : assembly) {
+				log.debug("Processing chromosome " + chr);
+				// Write the contig header to the output file
+				writer.write("fixedStep chrom="+chr+" start=1 step=1 span=1");
+				writer.newLine();
+				
+				int start = 1;
+				while (start < assembly.getChrLength(chr)) {
+					int stop = start + DEFAULT_CHUNK_SIZE - 1;
+					int length = stop - start + 1;
+					int[] count = new int[length];
+					float[] sum = new float[length];
+					
+					Iterator<? extends Interval> it = intervalFile.query(chr, start, stop);
+					while (it.hasNext()) {
+						ValuedInterval entry = (ValuedInterval) it.next();
+						if (entry.getValue() != null) {
+							for (int i = entry.getStart(); i <= entry.getStop(); i++) {
+								sum[i-start] += entry.getValue().floatValue();
+								count[i-start]++;
+							}
+						}
+					}
+					
+					// Write the average at each base pair to the output file
+					for (int i = 0; i < sum.length; i++) {
+						if (count[i] == 0) {
+							writer.write(String.valueOf(Float.NaN));
+						} else {
+							writer.write(String.valueOf(sum[i]/count[i]));
+						}
+						writer.newLine();
+					}
+					
+					// Process the next chunk
+					start = stop + 1;
+				}
+			}
+		}
+	}
+	
+	public static void main(String[] args) {
+		new IntervalToWig().instanceMain(args);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/edu/unc/genomics/converters/RomanNumeralize.java	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,61 @@
+package edu.unc.genomics.converters;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.log4j.Logger;
+
+import com.beust.jcommander.Parameter;
+
+import edu.unc.genomics.CommandLineTool;
+import edu.unc.genomics.ReadablePathValidator;
+import edu.unc.utils.RomanNumeral;
+
+public class RomanNumeralize extends CommandLineTool {
+
+	private static final Logger log = Logger.getLogger(RomanNumeralize.class);
+
+	@Parameter(names = {"-i", "--input"}, description = "Input file", required = true, validateWith = ReadablePathValidator.class)
+	public Path inputFile;
+	@Parameter(names = {"-o", "--output"}, description = "Output file", required = true)
+	public Path outputFile;
+	
+	/**
+	 * Pattern for finding "chr12" tokens (will find "chr1" through "chr99")
+	 */
+	Pattern p = Pattern.compile("/chr[\\d]{1,2}/i");
+	
+	@Override
+	public void run() throws IOException {
+		try (BufferedReader reader = Files.newBufferedReader(inputFile, Charset.defaultCharset());
+				 BufferedWriter writer = Files.newBufferedWriter(outputFile, Charset.defaultCharset())) {
+			log.debug("Copying input to output and replacing with Roman Numerals");
+			String line;
+			while ((line = reader.readLine()) != null) {
+				Matcher m = p.matcher(line);
+				StringBuffer converted = new StringBuffer(line.length());
+				while (m.find()) {
+					String chrNum = line.substring(m.start()+3, m.end());
+					int arabic = Integer.parseInt(chrNum);
+					String roman = RomanNumeral.int2roman(arabic);
+					m.appendReplacement(converted, "chr"+roman);
+				}
+				m.appendTail(converted);
+				
+				writer.write(converted.toString());
+				writer.newLine();
+			}
+		}
+	}
+	
+	public static void main(String[] args) {
+		new RomanNumeralize().instanceMain(args);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/edu/unc/genomics/ngs/Autocorrelation.java	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,84 @@
+package edu.unc.genomics.ngs;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Iterator;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.log4j.Logger;
+import org.broad.igv.bbfile.WigItem;
+
+import com.beust.jcommander.Parameter;
+
+import edu.emory.mathcs.jtransforms.fft.FloatFFT_1D;
+import edu.unc.genomics.CommandLineTool;
+import edu.unc.genomics.Interval;
+import edu.unc.genomics.PositiveIntegerValidator;
+import edu.unc.genomics.io.IntervalFile;
+import edu.unc.genomics.io.WigFile;
+import edu.unc.genomics.io.WigFileException;
+
+public class Autocorrelation extends CommandLineTool {
+	
+	private static final Logger log = Logger.getLogger(Autocorrelation.class);
+
+	@Parameter(names = {"-i", "--input"}, description = "Input file", required = true)
+	public WigFile wig;
+	@Parameter(names = {"-l", "--loci"}, description = "Genomic loci (Bed format)", required = true)
+	public IntervalFile<? extends Interval> loci;
+	@Parameter(names = {"-o", "--output"}, description = "Output file", required = true)
+	public Path outputFile;
+	@Parameter(names = {"-m", "--max"}, description = "Autocorrelation limit (bp)", validateWith = PositiveIntegerValidator.class)
+	public int limit = 200;
+	
+	private void abs2(float[] data) {
+		for (int i = 0; i < data.length; i+=2) {
+			data[i] = data[i]*data[i] + data[i+1]*data[i+1];
+			data[i+1] = 0;
+		}
+	}
+	
+	@Override
+	public void run() throws IOException {
+		try (BufferedWriter writer = Files.newBufferedWriter(outputFile, Charset.defaultCharset())) {
+			log.debug("Computing autocorrelation for each window");
+			int skipped = 0;
+			for (Interval interval : loci) {
+				if (interval.length() < limit) {
+					log.debug("Skipping interval: " + interval.toString());
+					skipped++;
+					continue;
+				}
+				
+				Iterator<WigItem> wigIter;
+				try {
+					wigIter = wig.query(interval);
+				} catch (IOException | WigFileException e) {
+					log.debug("Skipping interval: " + interval.toString());
+					skipped++;
+					continue;
+				}
+				
+				float[] data = WigFile.flattenData(wigIter, interval.getStart(), interval.getStop());
+				
+				// Compute the autocorrelation with the Wiener-Khinchin theorem
+				FloatFFT_1D fft = new FloatFFT_1D(data.length);
+				fft.realForward(data);
+				abs2(data);
+				fft.realInverse(data, true);
+	
+				writer.write(StringUtils.join(data, "\t"));
+				writer.newLine();
+			}
+			
+			log.info("Skipped " + skipped + " intervals");
+		}
+	}
+	
+	public static void main(String[] args) {
+		new Autocorrelation().instanceMain(args);
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/edu/unc/genomics/ngs/BaseAlignCounts.java	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,94 @@
+package edu.unc.genomics.ngs;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Iterator;
+
+import org.apache.log4j.Logger;
+
+import com.beust.jcommander.Parameter;
+
+import edu.ucsc.genome.TrackHeader;
+import edu.unc.genomics.Assembly;
+import edu.unc.genomics.CommandLineTool;
+import edu.unc.genomics.Interval;
+import edu.unc.genomics.io.IntervalFile;
+
+public class BaseAlignCounts extends CommandLineTool {
+	
+	private static final Logger log = Logger.getLogger(BaseAlignCounts.class);
+		
+	@Parameter(names = {"-i", "--input"}, description = "Input file (reads)", required = true)
+	public IntervalFile<? extends Interval> intervalFile;
+	@Parameter(names = {"-a", "--assembly"}, description = "Genome assembly", required = true)
+	public Assembly assembly;
+	@Parameter(names = {"-x", "--extend"}, description = "Extend reads from 5' end (default = read length)")
+	public Integer extend;
+	@Parameter(names = {"-o", "--output"}, description = "Output file (Wig)", required = true)
+	public Path outputFile;
+	
+	@Override
+	public void run() throws IOException {
+		log.debug("Initializing output file");
+		int mapped = 0;
+		try (BufferedWriter writer = Files.newBufferedWriter(outputFile, Charset.defaultCharset())) {
+			// Write the Wiggle track header to the output file
+			TrackHeader header = new TrackHeader("wiggle_0");
+			header.setName("Converted " + intervalFile.getPath().getFileName());
+			header.setDescription("Converted " + intervalFile.getPath().getFileName());
+			writer.write(header.toString());
+			writer.newLine();
+			
+			// Process each chromosome in the assembly
+			for (String chr : assembly) {
+				log.debug("Processing chromosome " + chr);
+				// Write the contig header to the output file
+				writer.write("fixedStep chrom="+chr+" start=1 step=1 span=1");
+				writer.newLine();
+				
+				int start = 1;
+				while (start < assembly.getChrLength(chr)) {
+					int stop = start + DEFAULT_CHUNK_SIZE - 1;
+					int length = stop - start + 1;
+					int[] count = new int[length];
+					
+					Iterator<? extends Interval> it = intervalFile.query(chr, start, stop);
+					while (it.hasNext()) {
+						Interval entry = it.next();
+						int entryStop = entry.getStop();
+						if (extend != null) {
+							if (entry.isWatson()) {
+								entryStop = entry.getStart() + extend;
+							} else {
+								entryStop = entry.getStart() - extend;
+							}
+						}
+						
+						for (int i = entry.getStart(); i <= entryStop; i++) {
+							count[i-start]++;
+						}
+						mapped++;
+					}
+					
+					// Write the count at each base pair to the output file
+					for (int i = 0; i < count.length; i++) {
+						writer.write(count[i]);
+						writer.newLine();
+					}
+					
+					// Process the next chunk
+					start = stop + 1;
+				}
+			}
+		}
+		
+		log.info("Mapped "+mapped+" reads");
+	}
+	
+	public static void main(String[] args) {
+		new BaseAlignCounts().instanceMain(args);
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/edu/unc/genomics/ngs/FindAbsoluteMaxima.java	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,99 @@
+package edu.unc.genomics.ngs;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+import org.broad.igv.bbfile.WigItem;
+
+import com.beust.jcommander.Parameter;
+
+import edu.unc.genomics.CommandLineTool;
+import edu.unc.genomics.Interval;
+import edu.unc.genomics.io.IntervalFile;
+import edu.unc.genomics.io.WigFile;
+import edu.unc.genomics.io.WigFileException;
+
+public class FindAbsoluteMaxima extends CommandLineTool {
+
+	private static final Logger log = Logger.getLogger(FindAbsoluteMaxima.class);
+
+	@Parameter(description = "Input files", required = true)
+	public List<String> inputFiles = new ArrayList<String>();
+	@Parameter(names = {"-l", "--loci"}, description = "Loci file (Bed)", required = true)
+	public IntervalFile<? extends Interval> lociFile;
+	@Parameter(names = {"-o", "--output"}, description = "Output file", required = true)
+	public Path outputFile;
+	
+	private List<WigFile> wigs = new ArrayList<>();
+	
+	@Override
+	public void run() throws IOException {		
+		log.debug("Initializing input Wig file(s)");
+		for (String inputFile : inputFiles) {
+			try {
+				WigFile wig = WigFile.autodetect(Paths.get(inputFile));
+				wigs.add(wig);
+			} catch (WigFileException e) {
+				log.error("Error initializing Wig input file: " + inputFile);
+				e.printStackTrace();
+				throw new RuntimeException("Error initializing Wig input file: " + inputFile);
+			}
+		}
+		
+		log.debug("Initializing output file");
+		int count = 0, skipped = 0;
+		try (BufferedWriter writer = Files.newBufferedWriter(outputFile, Charset.defaultCharset())) {
+			writer.write("#Chr\tStart\tStop\tID\tValue\tStrand");
+			for (String inputFile : inputFiles) {
+				Path p = Paths.get(inputFile);
+				writer.write("\t" + p.getFileName().toString());
+			}
+			writer.newLine();
+			
+			log.debug("Iterating over all intervals and finding maxima");
+			for (Interval interval : lociFile) {
+				writer.write(interval.toBed());
+				for (WigFile wig : wigs) {
+					float maxValue = -Float.MAX_VALUE;
+					int maxima = -1;
+					try {
+						Iterator<WigItem> results = wig.query(interval);
+						while (results.hasNext()) {
+							WigItem item = results.next();
+							if (item.getWigValue() > maxValue) {
+								maxValue = item.getWigValue();
+								maxima = (item.getStartBase() + item.getEndBase()) / 2;
+							}
+						}
+						writer.write("\t" + maxima);
+					} catch (WigFileException e) {
+						writer.write("\t" + Float.NaN);
+						skipped++;
+					}
+				}
+				writer.newLine();
+				count++;
+			}
+		}
+		
+		lociFile.close();
+		for (WigFile wig : wigs) {
+			wig.close();
+		}
+		log.info(count + " intervals processed");
+		log.info(skipped + " interval skipped");
+	}
+	
+	public static void main(String[] args) {
+		new FindAbsoluteMaxima().instanceMain(args);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/edu/unc/genomics/ngs/IntervalLengthDistribution.java	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,59 @@
+package edu.unc.genomics.ngs;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import org.apache.commons.math.stat.Frequency;
+import org.apache.log4j.Logger;
+
+import com.beust.jcommander.Parameter;
+
+import edu.unc.genomics.CommandLineTool;
+import edu.unc.genomics.Interval;
+import edu.unc.genomics.io.IntervalFile;
+
+public class IntervalLengthDistribution extends CommandLineTool {
+
+	private static final Logger log = Logger.getLogger(IntervalLengthDistribution.class);
+
+	@Parameter(names = {"-i", "--input"}, description = "Interval file", required = true)
+	public IntervalFile<? extends Interval> inputFile;
+	@Parameter(names = {"-o", "--output"}, description = "Output file", required = true)
+	public Path outputFile;
+	
+	
+	@Override
+	public void run() throws IOException {
+		log.debug("Generating histogram of interval lengths");
+		Frequency freq = new Frequency();
+		int min = Integer.MAX_VALUE;
+		int max = -1;
+		for (Interval i : inputFile) {
+			int L = i.length();
+			freq.addValue(L);
+			
+			if (L < min) {
+				min = L;
+			}
+			if (L > max) {
+				max = L;
+			}
+		}
+		
+		log.debug("Writing histogram output");
+		try (BufferedWriter writer = Files.newBufferedWriter(outputFile, Charset.defaultCharset())) {
+			for (int i = min; i <= max; i++) {
+				writer.write(i+"\t"+freq.getCount(i));
+				writer.newLine();
+			}
+		}
+	}
+	
+	public static void main(String[] args) {
+		new IntervalLengthDistribution().instanceMain(args);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/edu/unc/genomics/ngs/IntervalStats.java	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,102 @@
+package edu.unc.genomics.ngs;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.math.stat.descriptive.SummaryStatistics;
+import org.apache.log4j.Logger;
+import org.broad.igv.bbfile.WigItem;
+
+import com.beust.jcommander.Parameter;
+
+import edu.unc.genomics.CommandLineTool;
+import edu.unc.genomics.Interval;
+import edu.unc.genomics.io.IntervalFile;
+import edu.unc.genomics.io.WigFile;
+import edu.unc.genomics.io.WigFileException;
+
+public class IntervalStats extends CommandLineTool {
+
+	private static final Logger log = Logger.getLogger(IntervalStats.class);
+
+	@Parameter(description = "Input files", required = true)
+	public List<String> inputFiles = new ArrayList<String>();
+	@Parameter(names = {"-l", "--loci"}, description = "Loci file (Bed)", required = true)
+	public IntervalFile<? extends Interval> lociFile;
+	@Parameter(names = {"-o", "--output"}, description = "Output file", required = true)
+	public Path outputFile;
+	
+	private List<WigFile> wigs = new ArrayList<>();
+	
+	@Override
+	public void run() throws IOException {		
+		log.debug("Initializing input Wig file(s)");
+		for (String inputFile : inputFiles) {
+			try {
+				WigFile wig = WigFile.autodetect(Paths.get(inputFile));
+				wigs.add(wig);
+			} catch (WigFileException e) {
+				log.error("Error initializing Wig input file: " + inputFile);
+				e.printStackTrace();
+				throw new RuntimeException("Error initializing Wig input file: " + inputFile);
+			}
+		}
+		
+		log.debug("Initializing output file");
+		int count = 0, skipped = 0;
+		try (BufferedWriter writer = Files.newBufferedWriter(outputFile, Charset.defaultCharset())) {
+			writer.write("#Chr\tStart\tStop\tID\tValue\tStrand");
+			for (String inputFile : inputFiles) {
+				Path p = Paths.get(inputFile);
+				writer.write("\t" + p.getFileName().toString());
+			}
+			writer.newLine();
+			
+			log.debug("Iterating over all intervals and computing statistics");
+			SummaryStatistics stats = new SummaryStatistics();
+			for (Interval interval : lociFile) {
+				List<Double> means = new ArrayList<>(wigs.size());
+				for (WigFile wig : wigs) {
+					stats.clear();
+					try {
+						Iterator<WigItem> result = wig.query(interval);
+						while(result.hasNext()) {
+							WigItem item = result.next();
+							for (int i = item.getStartBase(); i <= item.getEndBase(); i++) {
+								stats.addValue(item.getWigValue());
+							}
+						}
+						means.add(stats.getMean());
+					} catch (WigFileException e) {
+						means.add(Double.NaN);
+						skipped++;
+					}
+				}
+				
+				writer.write(interval.toBed() + "\t" + StringUtils.join(means, "\t"));
+				writer.newLine();
+				count++;
+			}
+		}
+		
+		lociFile.close();
+		for (WigFile wig : wigs) {
+			wig.close();
+		}
+		log.info(count + " intervals processed");
+		log.info(skipped + " interval skipped");
+	}
+	
+	public static void main(String[] args) {
+		new IntervalStats().instanceMain(args);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/edu/unc/genomics/ngs/PowerSpectrum.java	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,106 @@
+package edu.unc.genomics.ngs;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Iterator;
+
+import org.apache.log4j.Logger;
+import org.broad.igv.bbfile.WigItem;
+
+import com.beust.jcommander.Parameter;
+
+import edu.emory.mathcs.jtransforms.fft.FloatFFT_1D;
+import edu.unc.genomics.CommandLineTool;
+import edu.unc.genomics.Interval;
+import edu.unc.genomics.io.IntervalFile;
+import edu.unc.genomics.io.WigFile;
+import edu.unc.genomics.io.WigFileException;
+
+public class PowerSpectrum extends CommandLineTool {
+	
+	private static final Logger log = Logger.getLogger(PowerSpectrum.class);
+
+	@Parameter(names = {"-i", "--input"}, description = "Input file (Wig)", required = true)
+	public WigFile inputFile;
+	@Parameter(names = {"-l", "--loci"}, description = "Genomic loci (Bed format)", required = true)
+	public IntervalFile<? extends Interval> loci;
+	@Parameter(names = {"-o", "--output"}, description = "Output file (tabular)", required = true)
+	public Path outputFile;
+		
+	/**
+	 * Computes the power spectrum from FFT data
+	 * taking into accound even/odd length arrays
+	 * refer to JTransforms documentation for layout of the FFT data
+	 * @param f
+	 * @return
+	 */
+	private float[] abs2(float[] f) {
+		int n = f.length;
+		float[] ps = new float[n/2+1];
+		// DC component
+		ps[0] = (f[0]*f[0]) / (n*n); 
+		
+		// Even
+		if (n % 2 == 0) {
+			for (int k = 1; k < n/2; k++) {
+				ps[k] = f[2*k]*f[2*k] + f[2*k+1]*f[2*k+1];
+			}
+			ps[n/2] = f[1]*f[1];
+		// Odd
+		} else {
+			for (int k = 1; k < (n-1)/2; k++) {
+				ps[k] = f[2*k]*f[2*k] + f[2*k+1]*f[2*k+1];
+			}
+			
+			ps[(n-1)/2] = f[n-1]*f[n-1] + f[1]*f[1];
+		}
+		
+		return ps;
+	}
+	
+	public void run() throws IOException {		
+		try (BufferedWriter writer = Files.newBufferedWriter(outputFile, Charset.defaultCharset())) {
+			log.debug("Computing power spectrum for each window");
+			int skipped = 0;
+			for (Interval interval : loci) {
+				Iterator<WigItem> wigIter;
+				try {
+					wigIter = inputFile.query(interval);
+				} catch (IOException | WigFileException e) {
+					log.debug("Skipping interval: " + interval.toString());
+					skipped++;
+					continue;
+				}
+				
+				float[] data = WigFile.flattenData(wigIter, interval.getStart(), interval.getStop());
+				// Compute the power spectrum
+				FloatFFT_1D fft = new FloatFFT_1D(data.length);
+				fft.realForward(data);
+				float[] ps = abs2(data);
+				// and normalize the power spectrum
+				float sum = 0;
+				for (int i = 1; i < ps.length; i++) {
+					sum += ps[i];
+				}
+				for (int i = 1; i < ps.length; i++) {
+					ps[i] /= sum;
+				}
+	
+				writer.write(interval.toBed());
+				for (int i = 1; i < Math.min(ps.length, 40); i++) {
+					writer.write("\t"+ps[i]);
+				}
+				writer.newLine();
+			}
+			
+			log.info("Skipped " + skipped + " intervals");
+		}		
+	}
+	
+	public static void main(String[] args) {
+		new PowerSpectrum().instanceMain(args);
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/edu/unc/genomics/ngs/RollingReadLength.java	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,89 @@
+package edu.unc.genomics.ngs;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Iterator;
+
+import org.apache.log4j.Logger;
+
+import com.beust.jcommander.Parameter;
+
+import edu.ucsc.genome.TrackHeader;
+import edu.unc.genomics.Assembly;
+import edu.unc.genomics.CommandLineTool;
+import edu.unc.genomics.Interval;
+import edu.unc.genomics.io.IntervalFile;
+
+public class RollingReadLength extends CommandLineTool {
+	
+	private static final Logger log = Logger.getLogger(RollingReadLength.class);
+
+	@Parameter(names = {"-i", "--input"}, description = "Input file (reads)", required = true)
+	public IntervalFile<? extends Interval> intervalFile;
+	@Parameter(names = {"-a", "--assembly"}, description = "Genome assembly", required = true)
+	public Assembly assembly;
+	@Parameter(names = {"-o", "--output"}, description = "Output file (Wig)", required = true)
+	public Path outputFile;
+	
+	@Override
+	public void run() throws IOException {
+		log.debug("Initializing output file");
+		int mapped = 0;
+		try (BufferedWriter writer = Files.newBufferedWriter(outputFile, Charset.defaultCharset())) {
+			// Write the Wiggle track header to the output file
+			TrackHeader header = new TrackHeader("wiggle_0");
+			header.setName("Converted " + intervalFile.getPath().getFileName());
+			header.setDescription("Converted " + intervalFile.getPath().getFileName());
+			writer.write(header.toString());
+			writer.newLine();
+			
+			// Process each chromosome in the assembly
+			for (String chr : assembly) {
+				log.debug("Processing chromosome " + chr);
+				// Write the contig header to the output file
+				writer.write("fixedStep chrom="+chr+" start=1 step=1 span=1");
+				writer.newLine();
+				
+				int start = 1;
+				while (start < assembly.getChrLength(chr)) {
+					int stop = start + DEFAULT_CHUNK_SIZE - 1;
+					int length = stop - start + 1;
+					int[] sum = new int[length];
+					int[] count = new int[length];
+					
+					Iterator<? extends Interval> it = intervalFile.query(chr, start, stop);
+					while (it.hasNext()) {
+						Interval entry = it.next();
+						for (int i = entry.getStart(); i <= entry.getStop(); i++) {
+							sum[i-start] += entry.length();
+							count[i-start]++;
+						}
+						mapped++;
+					}
+					
+					// Write the average at each base pair to the output file
+					for (int i = 0; i < sum.length; i++) {
+						if (count[i] == 0) {
+							writer.write(String.valueOf(Float.NaN));
+						} else {
+							writer.write(String.valueOf(sum[i]/count[i]));
+						}
+						writer.newLine();
+					}
+					
+					// Process the next chunk
+					start = stop + 1;
+				}
+			}
+		}
+		
+		log.info("Mapped "+mapped+" reads");
+	}
+	
+	public static void main(String[] args) {
+		new RollingReadLength().instanceMain(args);
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/edu/unc/genomics/nucleosomes/FindBoundaryNucleosomes.java	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,98 @@
+package edu.unc.genomics.nucleosomes;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.log4j.Logger;
+
+import com.beust.jcommander.Parameter;
+
+import edu.unc.genomics.CommandLineTool;
+import edu.unc.genomics.Interval;
+import edu.unc.genomics.ReadablePathValidator;
+import edu.unc.genomics.io.IntervalFile;
+
+public class FindBoundaryNucleosomes extends CommandLineTool {
+	
+	private static final Logger log = Logger.getLogger(FindBoundaryNucleosomes.class);
+
+	@Parameter(names = {"-i", "--input"}, description = "Input file (nucleosome calls)", required = true, validateWith = ReadablePathValidator.class)
+	public Path inputFile;
+	@Parameter(names = {"-l", "--loci"}, description = "Boundary loci (Bed format)", required = true)
+	public IntervalFile<? extends Interval> lociFile;
+	@Parameter(names = {"-o", "--output"}, description = "Output file", required = true)
+	public Path outputFile;
+	
+	private Map<String,List<NucleosomeCall>> nucs = new HashMap<>();
+	
+	private List<NucleosomeCall> getIntervalNucleosomes(Interval i) {
+		List<NucleosomeCall> intervalNucs = new ArrayList<>();
+		for (NucleosomeCall call : nucs.get(i.getChr())) {
+			if (call.getDyad() >= i.low() && call.getDyad() <= i.high()) {
+				intervalNucs.add(call);
+			}
+		}
+		
+		return intervalNucs;
+	}
+	
+	@Override
+	public void run() throws IOException {		
+		log.debug("Initializing input file");
+		NucleosomeCallsFile nucsFile = new NucleosomeCallsFile(inputFile);
+		log.debug("Loading all nucleosomes");
+		for (NucleosomeCall nuc : nucsFile) {
+			if (nuc == null) continue;
+			if (!nucs.containsKey(nuc.getChr())) {
+				nucs.put(nuc.getChr(), new ArrayList<NucleosomeCall>());
+			}
+			nucs.get(nuc.getChr()).add(nuc);
+		}
+		nucsFile.close();
+		
+		log.debug("Initializing output file");
+		int skipped = 0;
+		try (BufferedWriter writer = Files.newBufferedWriter(outputFile, Charset.defaultCharset())) {
+			log.debug("Finding boundary nucleosomes for each interval");
+			NucleosomeCall.DyadComparator comparator = new NucleosomeCall.DyadComparator();
+			for (Interval interval : lociFile) {
+				writer.write(interval.toBed());
+				
+				// Get all of the nucleosomes within this interval
+				List<NucleosomeCall> intervalNucs = getIntervalNucleosomes(interval);
+	
+				if (intervalNucs.size() > 0) {
+					// Sort the list by nucleosome position
+					Collections.sort(intervalNucs, comparator);
+					if (interval.isCrick()) {
+						Collections.reverse(intervalNucs);
+					}
+					
+					int fivePrime = intervalNucs.get(0).getDyad();
+					int threePrime = intervalNucs.get(intervalNucs.size()-1).getDyad();
+					writer.write("\t"+fivePrime+"\t"+threePrime);
+				} else {
+					skipped++;
+					writer.write("\tNA\tNA");
+				}
+				
+				writer.newLine();
+			}
+		}
+		
+		lociFile.close();
+		log.info("Skipped "+skipped+" intervals with 0 nucleosomes");
+	}
+	
+	public static void main(String[] args) {
+		new FindBoundaryNucleosomes().instanceMain(args);
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/edu/unc/genomics/nucleosomes/GreedyCaller.java	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,120 @@
+package edu.unc.genomics.nucleosomes;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Iterator;
+
+import org.apache.log4j.Logger;
+import org.broad.igv.bbfile.WigItem;
+
+import com.beust.jcommander.Parameter;
+import edu.unc.genomics.CommandLineTool;
+import edu.unc.genomics.CommandLineToolException;
+import edu.unc.genomics.ReadablePathValidator;
+import edu.unc.genomics.io.WigFile;
+import edu.unc.genomics.io.WigFileException;
+import edu.unc.utils.SortUtils;
+
+public class GreedyCaller extends CommandLineTool {
+	
+	private static final Logger log = Logger.getLogger(GreedyCaller.class);
+	
+	private static final int CHUNK_SIZE = 500_000;
+
+	@Parameter(names = {"-d", "--dyads"}, description = "Dyad counts file", required = true, validateWith = ReadablePathValidator.class)
+	public WigFile dyadsFile;
+	@Parameter(names = {"-s", "--smoothed"}, description = "Smoothed dyad counts file", required = true, validateWith = ReadablePathValidator.class)
+	public WigFile smoothedDyadsFile;
+	@Parameter(names = {"-n", "--size"}, description = "Nucleosome size (bp)")
+	public int nucleosomeSize = 147;
+	@Parameter(names = {"-o", "--output"}, description = "Output file", required = true)
+	public Path outputFile;
+	
+	public void run() throws IOException {
+		int halfNuc = nucleosomeSize / 2;
+		
+		try (BufferedWriter writer = Files.newBufferedWriter(outputFile, Charset.defaultCharset())) {
+			for (String chr : smoothedDyadsFile.chromosomes()) {
+				log.debug("Processing chromosome "+chr);
+				int chunkStart = smoothedDyadsFile.getChrStart(chr);
+				int chrStop = smoothedDyadsFile.getChrStop(chr);
+				while (chunkStart < chrStop) {
+					int chunkStop = chunkStart + CHUNK_SIZE;
+					int paddedStart = chunkStart - nucleosomeSize;
+					int paddedStop = chunkStop + nucleosomeSize;
+					log.debug("Processing chunk "+chunkStart+"-"+chunkStop);
+					
+					log.debug("Loading data and sorting");
+					Iterator<WigItem> dyadsIter;
+					Iterator<WigItem> smoothedIter;
+					try {
+						dyadsIter = dyadsFile.query(chr, paddedStart, paddedStop);
+						smoothedIter = smoothedDyadsFile.query(chr, paddedStart, paddedStop);
+					} catch (IOException | WigFileException e) {
+						e.printStackTrace();
+						throw new CommandLineToolException("Error loading data from Wig file");
+					}
+					
+					float[] dyads = WigFile.flattenData(dyadsIter, paddedStart, paddedStop);
+					float[] smoothed = WigFile.flattenData(smoothedIter, paddedStart, paddedStop);
+					int[] sortedIndices = SortUtils.indexSort(smoothed);
+					
+					// Proceed through the data in descending order
+					log.debug("Calling nucleosomes");
+					for (int j = sortedIndices.length; j >= 0; j++) {
+						int i = sortedIndices[j];
+						int dyad = paddedStart + i;
+						
+						if (smoothed[i] > 0) {
+							int nucStart = Math.max(1, dyad-halfNuc);
+							int nucStop = Math.min(dyad+halfNuc, chrStop);
+							NucleosomeCall call = new NucleosomeCall(chr, nucStart, nucStop);
+							call.setDyad(dyad);
+							
+							double occupancy = 0;
+							double weightedSum = 0;
+							double smoothedSum = 0;
+							double sumOfSquares = 0;
+							for (int bp = nucStart; bp <= nucStop; bp++) {
+								occupancy += dyads[bp-paddedStart];
+								weightedSum += bp * dyads[bp-paddedStart];
+								smoothedSum += smoothed[bp-paddedStart];
+								sumOfSquares += bp * bp * dyads[bp-paddedStart];
+							}
+							call.setOccupancy(occupancy);
+							
+							if (occupancy > 0) {
+								call.setDyadMean(Math.round(weightedSum/occupancy));
+								call.setConditionalPosition(smoothed[i] / smoothedSum);
+								double variance = (sumOfSquares - weightedSum*call.getDyadMean()) / occupancy;
+								call.setDyadStdev(Math.sqrt(variance));
+								
+								// Only write nucleosomes within the current chunk to disk
+								if (chunkStart <= dyad && dyad <= chunkStop) {
+									writer.write(call.toString());
+									writer.newLine();
+								}
+								
+								// Don't allow nucleosome calls overlapping this nucleosome
+								int low = Math.max(i-nucleosomeSize, 0);
+								int high = Math.min(i-nucleosomeSize, paddedStop-1);
+								for (int k = low; k <= high; k++) {
+									smoothed[k] = 0;
+								}
+							}
+						}
+					}
+					
+					chunkStart = chunkStop + 1;
+				}
+			}
+		}
+	}
+	
+	public static void main(String[] args) {
+		new GreedyCaller().instanceMain(args);
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/edu/unc/genomics/nucleosomes/MapDyads.java	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,88 @@
+package edu.unc.genomics.nucleosomes;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Iterator;
+
+import org.apache.log4j.Logger;
+
+import com.beust.jcommander.Parameter;
+
+import edu.ucsc.genome.TrackHeader;
+import edu.unc.genomics.Assembly;
+import edu.unc.genomics.CommandLineTool;
+import edu.unc.genomics.Interval;
+import edu.unc.genomics.PositiveIntegerValidator;
+import edu.unc.genomics.io.IntervalFile;
+
+public class MapDyads extends CommandLineTool {
+	
+	private static final Logger log = Logger.getLogger(MapDyads.class);
+
+	@Parameter(names = {"-i", "--input"}, description = "Input file (reads)", required = true)
+	public IntervalFile<? extends Interval> inputFile;
+	@Parameter(names = {"-s", "--size"}, description = "Mononucleosome length (default: read length)", validateWith = PositiveIntegerValidator.class)
+	public Integer nucleosomeSize;
+	@Parameter(names = {"-a", "--assembly"}, description = "Genome assembly", required = true)
+	public Assembly assembly;
+	@Parameter(names = {"-o", "--output"}, description = "Output file (Wig)", required = true)
+	public Path outputFile;
+		
+	@Override
+	public void run() throws IOException {
+		log.debug("Initializing output file");
+		int mapped = 0;
+		try (BufferedWriter writer = Files.newBufferedWriter(outputFile, Charset.defaultCharset())) {
+			// Write the Wiggle track header to the output file
+			TrackHeader header = new TrackHeader("wiggle_0");
+			header.setName("Converted " + inputFile.getPath().getFileName());
+			header.setDescription("Converted " + inputFile.getPath().getFileName());
+			writer.write(header.toString());
+			writer.newLine();
+			
+			// Process each chromosome in the assembly
+			for (String chr : assembly) {
+				log.debug("Processing chromosome " + chr);
+				// Write the contig header to the output file
+				writer.write("fixedStep chrom="+chr+" start=1 step=1 span=1");
+				writer.newLine();
+				
+				int start = 1;
+				while (start < assembly.getChrLength(chr)) {
+					int stop = start + DEFAULT_CHUNK_SIZE - 1;
+					int length = stop - start + 1;
+					int[] count = new int[length];
+					
+					Iterator<? extends Interval> it = inputFile.query(chr, start, stop);
+					while (it.hasNext()) {
+						Interval entry = it.next();
+						if (nucleosomeSize == null) {
+							count[entry.center()-start]++;
+						} else {
+							count[entry.getStart()+nucleosomeSize-start]++;
+						}
+						mapped++;
+					}
+					
+					// Write the average at each base pair to the output file
+					for (int i = 0; i < count.length; i++) {
+						writer.write(count[i]);
+						writer.newLine();
+					}
+					
+					// Process the next chunk
+					start = stop + 1;
+				}
+			}
+		}
+		
+		log.info("Mapped "+mapped+" reads");
+	}
+	
+	public static void main(String[] args) {
+		new MapDyads().instanceMain(args);
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/edu/unc/genomics/nucleosomes/NRLCalculator.java	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,93 @@
+package edu.unc.genomics.nucleosomes;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.log4j.Logger;
+
+import com.beust.jcommander.Parameter;
+
+import edu.unc.genomics.CommandLineTool;
+import edu.unc.genomics.Interval;
+import edu.unc.genomics.ReadablePathValidator;
+import edu.unc.genomics.io.IntervalFile;
+
+public class NRLCalculator extends CommandLineTool {
+	
+	private static final Logger log = Logger.getLogger(NRLCalculator.class);
+
+	@Parameter(names = {"-i", "--input"}, description = "Input file (nucleosome calls)", required = true, validateWith = ReadablePathValidator.class)
+	public Path inputFile;
+	@Parameter(names = {"-l", "--loci"}, description = "Genomic loci (Bed format)", required = true)
+	public IntervalFile<? extends Interval> lociFile;
+	@Parameter(names = {"-o", "--output"}, description = "Output file (NRL for each gene)", required = true)
+	public Path outputFile;
+	
+	private Map<String,List<NucleosomeCall>> nucs = new HashMap<>();
+	
+	private List<NucleosomeCall> getIntervalNucleosomes(Interval i) {
+		List<NucleosomeCall> intervalNucs = new ArrayList<>();
+		for (NucleosomeCall call : nucs.get(i.getChr())) {
+			if (call.getDyad() >= i.low() && call.getDyad() <= i.high()) {
+				intervalNucs.add(call);
+			}
+		}
+		
+		return intervalNucs;
+	}
+	
+	@Override
+	public void run() throws IOException {		
+		log.debug("Initializing input file");
+		NucleosomeCallsFile nucsFile = new NucleosomeCallsFile(inputFile);
+		log.debug("Loading all nucleosomes");
+		for (NucleosomeCall nuc : nucsFile) {
+			if (nuc == null) continue;
+			if (!nucs.containsKey(nuc.getChr())) {
+				nucs.put(nuc.getChr(), new ArrayList<NucleosomeCall>());
+			}
+			nucs.get(nuc.getChr()).add(nuc);
+		}
+		nucsFile.close();
+		
+		log.debug("Initializing output file");
+		try (BufferedWriter writer = Files.newBufferedWriter(outputFile, Charset.defaultCharset())) {
+			log.debug("Calculating nucleosome spacing for each interval");
+			NucleosomeCall.DyadComparator comparator = new NucleosomeCall.DyadComparator();
+			for (Interval interval : lociFile) {
+				writer.write(interval.toBed());
+				
+				// Get all of the nucleosomes within this interval
+				List<NucleosomeCall> intervalNucs = getIntervalNucleosomes(interval);
+	
+				if (intervalNucs.size() > 1) {
+					// Sort the list by nucleosome position
+					Collections.sort(intervalNucs, comparator);
+					if (interval.isCrick()) {
+						Collections.reverse(intervalNucs);
+					}
+					
+					for (int i = 1; i < Math.min(intervalNucs.size(), 10); i++) {
+						writer.write("\t" + Math.abs(intervalNucs.get(i).getDyad()-intervalNucs.get(i-1).getDyad()));
+					}
+				}
+				
+				writer.newLine();
+			}
+		}
+		
+		lociFile.close();
+	}
+	
+	public static void main(String[] args) throws IOException {
+		new NRLCalculator().instanceMain(args);
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/edu/unc/genomics/nucleosomes/NucleosomeCall.java	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,174 @@
+package edu.unc.genomics.nucleosomes;
+
+import java.util.Comparator;
+
+import edu.unc.genomics.ValuedInterval;
+import edu.unc.genomics.io.IntervalFileFormatException;
+
+/**
+ * @author timpalpant
+ *
+ */
+public class NucleosomeCall extends ValuedInterval implements Comparable<NucleosomeCall> {
+
+	private static final long serialVersionUID = 6522702303121259979L;
+	
+	private int dyad;
+	private double dyadStdev;
+	private double dyadMean;
+	private double conditionalPosition;
+	private int length;
+	private double lengthStdev;
+
+	/**
+	 * @param chr
+	 * @param start
+	 * @param stop
+	 */
+	public NucleosomeCall(String chr, int start, int stop) {
+		super(chr, start, stop);
+	}
+	
+	public static NucleosomeCall parse(String line) {
+		if (line.startsWith("#")) return null;
+		
+		String[] entry = line.split("\t");
+		if (entry.length < 10) {
+			throw new IntervalFileFormatException("Invalid nucleosome call has < 10 columns");
+		}
+		
+		String chr = entry[0];
+		int start = Integer.parseInt(entry[1]);
+		int stop = Integer.parseInt(entry[2]);
+		
+		NucleosomeCall call = new NucleosomeCall(chr, start, stop);
+		call.setLength(Integer.parseInt(entry[3]));
+		call.setLengthStdev(Double.parseDouble(entry[4]));
+		call.setDyad(Integer.parseInt(entry[5]));
+		call.setDyadStdev(Double.parseDouble(entry[6]));
+		call.setConditionalPosition(Double.parseDouble(entry[7]));
+		call.setDyadMean(Double.parseDouble(entry[8]));
+		call.setValue(Double.parseDouble(entry[9]));
+		
+		return call;
+	}
+	
+	@Override
+	public String toString() {
+		return chr+"\t"+start+"\t"+stop+"\t"+length()+"\t"+lengthStdev+"\t"+dyad+"\t"+dyadStdev+"\t"+conditionalPosition+"\t"+dyadMean+"\t"+occupancy();
+	}
+
+	/**
+	 * @return the dyad
+	 */
+	public int getDyad() {
+		return dyad;
+	}
+
+	/**
+	 * @param dyad the dyad to set
+	 */
+	public void setDyad(int dyad) {
+		this.dyad = dyad;
+	}
+
+	/**
+	 * @return the dyadStdev
+	 */
+	public double getDyadStdev() {
+		return dyadStdev;
+	}
+
+	/**
+	 * @param dyadStdev the dyadStdev to set
+	 */
+	public void setDyadStdev(double dyadStdev) {
+		this.dyadStdev = dyadStdev;
+	}
+
+	/**
+	 * @return the dyadMean
+	 */
+	public double getDyadMean() {
+		return dyadMean;
+	}
+
+	/**
+	 * @param dyadMean the dyadMean to set
+	 */
+	public void setDyadMean(double dyadMean) {
+		this.dyadMean = dyadMean;
+	}
+
+	/**
+	 * @return the conditionalPosition
+	 */
+	public double getConditionalPosition() {
+		return conditionalPosition;
+	}
+
+	/**
+	 * @param conditionalPosition the conditionalPosition to set
+	 */
+	public void setConditionalPosition(double conditionalPosition) {
+		this.conditionalPosition = conditionalPosition;
+	}
+
+	/**
+	 * @return the length
+	 */
+	public int getLength() {
+		return length;
+	}
+
+	/**
+	 * @param length the length to set
+	 */
+	public void setLength(int length) {
+		this.length = length;
+	}
+
+	/**
+	 * @return the lengthStdev
+	 */
+	public double getLengthStdev() {
+		return lengthStdev;
+	}
+
+	/**
+	 * @param lengthStdev the lengthStdev to set
+	 */
+	public void setLengthStdev(double lengthStdev) {
+		this.lengthStdev = lengthStdev;
+	}
+	
+	public double occupancy() {
+		return value;
+	}
+	
+	public void setOccupancy(double value) {
+		this.value = value;
+	}
+	
+	@Override
+	public int compareTo(NucleosomeCall o) {
+		DyadComparator comparator = new DyadComparator();
+		return comparator.compare(this, o);
+	}
+	
+	public static class DyadComparator implements Comparator<NucleosomeCall> {
+
+		@Override
+		public int compare(NucleosomeCall o1, NucleosomeCall o2) {
+			if (o1.getDyad() == o2.getDyad()) {
+				return 0;
+			} else if (o1.getDyad() < o2.getDyad()) {
+				return -1;
+			} else {
+				return 1;
+			}
+		}
+		
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/edu/unc/genomics/nucleosomes/NucleosomeCallsFile.java	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,31 @@
+/**
+ * 
+ */
+package edu.unc.genomics.nucleosomes;
+
+import java.io.IOException;
+import java.nio.file.Path;
+
+import edu.unc.genomics.IntervalFactory;
+import edu.unc.genomics.io.TextIntervalFile;
+
+/**
+ * @author timpalpant
+ *
+ */
+public class NucleosomeCallsFile extends TextIntervalFile<NucleosomeCall> {
+
+	public NucleosomeCallsFile(Path p) throws IOException {
+		super(p, new NucleosomeCallFactory());
+	}
+	
+	public static class NucleosomeCallFactory implements IntervalFactory<NucleosomeCall> {
+		
+		@Override
+		public NucleosomeCall parse(String line) {
+			return NucleosomeCall.parse(line);
+		}
+
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/edu/unc/genomics/nucleosomes/Phasogram.java	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,71 @@
+package edu.unc.genomics.nucleosomes;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import org.apache.log4j.Logger;
+
+import com.beust.jcommander.Parameter;
+
+import edu.unc.genomics.CommandLineTool;
+import edu.unc.genomics.CommandLineToolException;
+import edu.unc.genomics.io.WigFile;
+import edu.unc.genomics.io.WigFileException;
+
+public class Phasogram extends CommandLineTool {
+	
+	private static final Logger log = Logger.getLogger(Phasogram.class);
+
+	@Parameter(names = {"-i", "--input"}, description = "Input wig file (read counts)", required = true)
+	public WigFile inputFile;
+	@Parameter(names = {"-m", "--max"}, description = "Maximum phase shift", required = true)
+	public int maxPhase;
+	@Parameter(names = {"-o", "--output"}, description = "Output file (histogram)", required = true)
+	public Path outputFile;
+		
+	public void run() throws IOException {
+		long[] phaseCounts = new long[maxPhase+1];
+		
+		// Process each chromosome in the assembly
+		for (String chr : inputFile.chromosomes()) {
+			log.debug("Processing chromosome " + chr);
+						
+			int start = inputFile.getChrStart(chr);
+			while (start < inputFile.getChrStop(chr)) {
+				int stop = start + DEFAULT_CHUNK_SIZE - 1;
+								
+				try {
+					float[] data = WigFile.flattenData(inputFile.query(chr, start, stop), start, stop);
+					for (int i = 0; i < data.length-maxPhase; i++) {
+						for (int j = 0; j <= maxPhase; j++) {
+							phaseCounts[j] += data[i];
+						}
+					}
+					
+				} catch (WigFileException e) {
+					log.fatal("Error querying data from Wig file!");
+					e.printStackTrace();
+					throw new CommandLineToolException("Error querying data from Wig file!");
+				}
+				
+				// Process the next chunk
+				start = stop - maxPhase + 1;
+			}
+		}
+		
+		log.debug("Writing output to disk");
+		try (BufferedWriter writer = Files.newBufferedWriter(outputFile, Charset.defaultCharset())) {
+			for (int i = 0; i < phaseCounts.length; i++) {
+				writer.write(i+"\t"+phaseCounts[i]);
+				writer.newLine();
+			}
+		}
+	}
+	
+	public static void main(String[] args) {
+		new Phasogram().instanceMain(args);
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/edu/unc/genomics/visualization/IntervalAverager.java	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,118 @@
+package edu.unc.genomics.visualization;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.log4j.Logger;
+import org.broad.igv.bbfile.WigItem;
+
+import com.beust.jcommander.Parameter;
+
+import edu.unc.genomics.BedEntry;
+import edu.unc.genomics.CommandLineTool;
+import edu.unc.genomics.ReadablePathValidator;
+import edu.unc.genomics.io.BedFile;
+import edu.unc.genomics.io.WigFile;
+import edu.unc.genomics.io.WigFileException;
+
+public class IntervalAverager extends CommandLineTool {
+	
+	private static final Logger log = Logger.getLogger(IntervalAverager.class);
+	
+	@Parameter(names = {"-i", "--input"}, description = "Input file (Wig)", required = true)
+	public WigFile inputFile;
+	@Parameter(names = {"-l", "--loci"}, description = "Loci file (Bed)", required = true, validateWith = ReadablePathValidator.class)
+	public Path lociFile;
+	@Parameter(names = {"-o", "--output"}, description = "Output file (matrix2png format)", required = true)
+	public Path outputFile;
+	
+	private List<BedEntry> loci;
+	
+	@Override
+	public void run() throws IOException {		
+		log.debug("Loading alignment intervals");
+		try (BedFile bed = new BedFile(lociFile)) {
+			loci = bed.loadAll();
+		}
+		
+		// Compute the matrix dimensions
+		int leftMax = Integer.MIN_VALUE;
+		int rightMax = Integer.MIN_VALUE;
+		for (BedEntry entry : loci) {
+			int left = Math.abs(entry.getValue().intValue()-entry.getStart());
+			int right = Math.abs(entry.getValue().intValue()-entry.getStop());
+			if (left > leftMax) {
+				leftMax = left;
+			}
+			if (right > rightMax) {
+				rightMax = right;
+			}
+		}
+		
+		int m = loci.size();
+		int n = leftMax + rightMax + 1;
+		int alignmentPoint = leftMax;
+		log.info("Intervals aligned into: " + m+"x"+n + " matrix");
+		log.info("Alignment point: " + alignmentPoint);
+				
+		float[] sum = new float[n];
+		int[] counts = new int[n];
+		int count = 0, skipped = 0;	
+		log.debug("Iterating over all intervals");
+		for (BedEntry entry : loci) {
+			Iterator<WigItem> result = null;
+			try {
+				result = inputFile.query(entry);
+			} catch (WigFileException e) {
+				skipped++;
+				continue;
+			}
+			
+			float[] data = WigFile.flattenData(result, entry.getStart(), entry.getStop());
+			// Reverse if on the Crick strand
+			if (entry.isCrick()) {
+				ArrayUtils.reverse(data);
+			}
+			
+			// Locus alignment point (entry value) should be positioned over the global alignment point
+			int n1 = alignmentPoint - Math.abs(entry.getValue().intValue()-entry.getStart());
+			int n2 = alignmentPoint + Math.abs(entry.getValue().intValue()-entry.getStop());
+			for (int bp = n1; bp <= n2; bp++) {
+				sum[bp] += data[bp-n1];
+				counts[bp]++;
+			}
+		}
+		
+		inputFile.close();
+		log.info(count + " intervals processed");
+		log.info(skipped + " intervals skipped");
+		
+		log.debug("Computing average");
+		float[] avg = new float[n];
+		for (int i = 0; i < n; i++) {
+			if (counts[i] == 0) {
+				avg[i] = Float.NaN;
+			} else {
+				avg[i] = sum[i] / counts[i];
+			}
+		}
+		
+		log.debug("Writing average to output");
+		try (BufferedWriter writer = Files.newBufferedWriter(outputFile, Charset.defaultCharset())) {
+			for (int i = 0; i < n; i++) {
+				writer.write(i-alignmentPoint + "\t" + avg[i]);
+				writer.newLine();				
+			}
+		}
+	}
+	
+	public static void main(String[] args) {
+		new IntervalAverager().instanceMain(args);
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/edu/unc/genomics/visualization/KMeans.java	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,120 @@
+package edu.unc.genomics.visualization;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.math.stat.clustering.Cluster;
+import org.apache.commons.math.stat.clustering.KMeansPlusPlusClusterer;
+import org.apache.log4j.Logger;
+
+import com.beust.jcommander.Parameter;
+
+import edu.unc.genomics.CommandLineTool;
+import edu.unc.genomics.ReadablePathValidator;
+import edu.unc.genomics.io.IntervalFileSnifferException;
+import edu.unc.genomics.io.WigFileException;
+
+public class KMeans extends CommandLineTool {
+
+	private static final Logger log = Logger.getLogger(KMeans.class);
+
+	@Parameter(names = {"-i", "--input"}, description = "Input file (matrix2png format)", required = true, validateWith = ReadablePathValidator.class)
+	public Path inputFile;
+	@Parameter(names = {"-k", "--clusters"}, description = "Number of clusters")
+	public int k = 10;
+	@Parameter(names = {"-1", "--min"}, description = "Minimum column to use for clustering")
+	public int minCol = 1;
+	@Parameter(names = {"-2", "--max"}, description = "Maximum column to use for clustering")
+	public Integer maxCol;
+	@Parameter(names = {"-o", "--output"}, description = "Output file (clustered matrix2png format)", required = true)
+	public Path outputFile;
+	
+	private Map<String, String> rows = new HashMap<String, String>();
+	private List<KMeansRow> data = new ArrayList<KMeansRow>();
+	
+	@Override
+	public void run() throws IOException {
+		log.debug("Loading data from the input matrix");
+		String headerLine = "";
+		try (BufferedReader reader = Files.newBufferedReader(inputFile, Charset.defaultCharset())) {
+			// Header line
+			int lineNum = 1;
+			headerLine = reader.readLine();
+			int numColsInMatrix = StringUtils.countMatches(headerLine, "\t");
+			
+			// Validate the range info
+			if (maxCol != null) {
+				if (maxCol > numColsInMatrix) {
+					throw new RuntimeException("Invalid range of data specified for clustering ("+maxCol+" > "+numColsInMatrix+")");
+				}
+			} else {
+				maxCol = numColsInMatrix;
+			}
+			
+			// Loop over the rows and load the data
+			String line;
+			while ((line = reader.readLine()) != null) {
+				lineNum++;
+				if (StringUtils.countMatches(line, "\t") != numColsInMatrix) {
+					throw new RuntimeException("Irregular input matrix does not have same number of columns on line " + lineNum);
+				}
+				
+				int delim = line.indexOf('\t');
+				String id = line.substring(0, delim);
+				String[] row = line.substring(delim+1).split("\t");
+				String[] subset = Arrays.copyOfRange(row, minCol, maxCol);
+				float[] rowData = new float[subset.length];
+				for (int i = 0; i < subset.length; i++) {
+					try {
+						rowData[i] = Float.parseFloat(subset[i]);
+					} catch (NumberFormatException e) {
+						rowData[i] = Float.NaN;
+					}
+				}
+				data.add(new KMeansRow(id, rowData));
+				rows.put(id, line);
+			}
+		}
+		
+		// Perform the clustering
+		log.debug("Clustering the data");
+		Random rng = new Random();
+		KMeansPlusPlusClusterer<KMeansRow> clusterer = new KMeansPlusPlusClusterer<KMeansRow>(rng);
+		List<Cluster<KMeansRow>> clusters = clusterer.cluster(data, k, 50);
+		
+		// Write to output
+		log.debug("Writing clustered data to output file");
+		try (BufferedWriter writer = Files.newBufferedWriter(outputFile, Charset.defaultCharset())) {
+			writer.write(headerLine);
+			writer.newLine();
+			int n = 1;
+			int count = 1;
+			for (Cluster<KMeansRow> cluster : clusters) {
+				int numRowsInCluster = cluster.getPoints().size();
+				int stop = count + numRowsInCluster - 1;
+				log.info("Cluster "+(n++)+": rows "+count+"-"+stop);
+				count = stop+1;
+				for (KMeansRow row : cluster.getPoints()) {
+					writer.write(rows.get(row.getId()));
+					writer.newLine();
+				}
+			}
+		}
+	}
+	
+	public static void main(String[] args) throws IOException, WigFileException, IntervalFileSnifferException {
+		new KMeans().instanceMain(args);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/edu/unc/genomics/visualization/KMeansRow.java	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,110 @@
+package edu.unc.genomics.visualization;
+
+import java.io.Serializable;
+import java.util.Collection;
+
+import org.apache.commons.math.stat.clustering.Clusterable;
+
+/**
+ * @author timpalpant
+ * Holds a row of data for the KMeans program
+ */
+class KMeansRow implements Clusterable<KMeansRow>, Serializable {
+
+	private static final long serialVersionUID = -323598431692368500L;
+	
+	private final String id;
+	/** Point coordinates. */
+  private final float[] point;
+
+	/**
+   * Build an instance wrapping an float array.
+   * <p>The wrapped array is referenced, it is <em>not</em> copied.</p>
+   * @param point the n-dimensional point in integer space
+   */
+	public KMeansRow(final String id, final float[] point) {
+		this.point = point;
+		this.id = id;
+	}
+
+  /**
+   * Get the n-dimensional point in float space.
+   * @return a reference (not a copy!) to the wrapped array
+   */
+  public float[] getPoint() {
+      return point;
+  }
+
+  /** {@inheritDoc} */
+  public double distanceFrom(final KMeansRow p) {
+  		double sumSquares = 0;
+  		float[] otherPoint = p.getPoint();
+  		for (int i = 0; i < point.length; i++) {
+  			sumSquares += Math.pow(point[i]-otherPoint[i], 2);
+  		}
+      return Math.sqrt(sumSquares);
+  }
+
+  /** {@inheritDoc} */
+  public KMeansRow centroidOf(final Collection<KMeansRow> points) {
+      float[] centroid = new float[getPoint().length];
+      for (KMeansRow p : points) {
+          for (int i = 0; i < centroid.length; i++) {
+              centroid[i] += p.getPoint()[i];
+          }
+      }
+      for (int i = 0; i < centroid.length; i++) {
+          centroid[i] /= points.size();
+      }
+      return new KMeansRow(id, centroid);
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public boolean equals(final Object other) {
+      if (!(other instanceof KMeansRow)) {
+          return false;
+      }
+      final float[] otherPoint = ((KMeansRow) other).getPoint();
+      if (point.length != otherPoint.length) {
+          return false;
+      }
+      for (int i = 0; i < point.length; i++) {
+          if (point[i] != otherPoint[i]) {
+              return false;
+          }
+      }
+      return true;
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public int hashCode() {
+      int hashCode = 0;
+      for (Float i : point) {
+          hashCode += i.hashCode() * 13 + 7;
+      }
+      return hashCode;
+  }
+
+  /**
+   * {@inheritDoc}
+   * @since 2.1
+   */
+  @Override
+  public String toString() {
+      final StringBuilder buff = new StringBuilder(id);
+      for (float value : getPoint()) {
+      	buff.append("\t").append(value);
+      }
+      return buff.toString();
+  }
+
+	/**
+	 * @return the id
+	 */
+	public String getId() {
+		return id;
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/edu/unc/genomics/visualization/MatrixAligner.java	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,145 @@
+package edu.unc.genomics.visualization;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.log4j.Logger;
+import org.broad.igv.bbfile.WigItem;
+
+import com.beust.jcommander.Parameter;
+
+import edu.unc.genomics.BedEntry;
+import edu.unc.genomics.CommandLineTool;
+import edu.unc.genomics.ReadablePathValidator;
+import edu.unc.genomics.io.BedFile;
+import edu.unc.genomics.io.WigFile;
+import edu.unc.genomics.io.WigFileException;
+
+public class MatrixAligner extends CommandLineTool {
+
+	private static final Logger log = Logger.getLogger(MatrixAligner.class);
+
+	@Parameter(names = {"-i", "--input"}, description = "Input file (Wig)", required = true)
+	public WigFile inputFile;
+	@Parameter(names = {"-l", "--loci"}, description = "Loci file (Bed)", required = true, validateWith = ReadablePathValidator.class)
+	public Path lociFile;
+	@Parameter(names = {"-m", "--max"}, description = "Truncate width (base pairs)")
+	public Integer maxWidth;
+	@Parameter(names = {"-o", "--output"}, description = "Output file (matrix2png format)", required = true)
+	public Path outputFile;
+	
+	private List<BedEntry> loci;
+	
+	@Override
+	public void run() throws IOException {		
+		log.debug("Loading alignment intervals");
+		try (BedFile bed = new BedFile(lociFile)) {
+			loci = bed.loadAll();
+		}
+		
+		// Compute the matrix dimensions
+		int leftMax = Integer.MIN_VALUE;
+		int rightMax = Integer.MIN_VALUE;
+		for (BedEntry entry : loci) {
+			int left = Math.abs(entry.getValue().intValue()-entry.getStart());
+			int right = Math.abs(entry.getValue().intValue()-entry.getStop());
+			if (left > leftMax) {
+				leftMax = left;
+			}
+			if (right > rightMax) {
+				rightMax = right;
+			}
+		}
+		
+		int m = loci.size();
+		int n = leftMax + rightMax + 1;
+		int alignmentPoint = leftMax;
+		log.info("Intervals aligned into: " + m+"x"+n + " matrix");
+		log.info("Alignment point: " + alignmentPoint);
+		
+		int leftBound = 0;
+		int rightBound = n-1;
+		if (maxWidth != null && maxWidth < n) {
+			log.info("Truncated to: " + m+"x"+maxWidth);
+			int leftAlignDistance = alignmentPoint;
+			int rightAlignDistance = n - alignmentPoint - 1;
+			int halfMax = maxWidth / 2;
+			
+			if (halfMax < leftAlignDistance && halfMax < rightAlignDistance) {
+				leftBound = alignmentPoint - halfMax;
+				rightBound = alignmentPoint + halfMax;
+			} else {
+				if (leftAlignDistance <= rightAlignDistance) {
+					rightBound = maxWidth;
+				} else {
+					leftBound = n - maxWidth;
+				}
+			}
+		}
+		
+		log.debug("Initializing output file");
+		int count = 0, skipped = 0;
+		try (BufferedWriter writer = Files.newBufferedWriter(outputFile, Charset.defaultCharset())) {
+			writer.write("ID");
+			for (int i = leftBound-alignmentPoint; i <= rightBound-alignmentPoint; i++) {
+				writer.write("\t"+i);
+			}
+			writer.newLine();
+			
+			log.debug("Iterating over all intervals");
+			String[] row = new String[n];
+			for (BedEntry entry : loci) {
+				Iterator<WigItem> result = null;
+				try {
+					result = inputFile.query(entry);
+				} catch (WigFileException e) {
+					skipped++;
+					continue;
+				}
+				
+				float[] data = WigFile.flattenData(result, entry.getStart(), entry.getStop());
+				// Reverse if on the crick strand
+				if (entry.isCrick()) {
+					ArrayUtils.reverse(data);
+				}
+				
+				// Position the data in the matrix
+				// Locus alignment point (entry value) should be positioned over the matrix alignment point
+				int n1 = alignmentPoint - Math.abs(entry.getValue().intValue()-entry.getStart());
+				int n2 = alignmentPoint + Math.abs(entry.getValue().intValue()-entry.getStop());
+				assert data.length == n2-n1+1;
+				
+				Arrays.fill(row, "-");
+				for (int i = 0; i < data.length; i++) {
+					if (!Float.isNaN(data[i])) {
+						row[n1+i] = String.valueOf(data[i]);
+					}
+				}
+				
+				// Write to output
+				String id = ((entry.getId() == null) ? entry.getId() : "Row "+(count++));
+				writer.write(id);
+				for (int i = leftBound; i <= rightBound; i++) {
+					writer.write("\t"+row[i]);
+				}
+				writer.newLine();
+			}
+		}
+		
+		inputFile.close();
+		log.info(count + " intervals processed");
+		log.info(skipped + " intervals skipped");
+	}
+	
+	public static void main(String[] args) {
+		new MatrixAligner().instanceMain(args);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/edu/unc/genomics/visualization/StripMatrix.java	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,56 @@
+package edu.unc.genomics.visualization;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import org.apache.log4j.Logger;
+
+import com.beust.jcommander.Parameter;
+
+import edu.unc.genomics.CommandLineTool;
+import edu.unc.genomics.ReadablePathValidator;
+
+public class StripMatrix extends CommandLineTool {
+	
+	private static final Logger log = Logger.getLogger(StripMatrix.class);
+
+	@Parameter(names = {"-i", "--input"}, description = "Input file (matrix2png format)", required = true, validateWith = ReadablePathValidator.class)
+	public Path inputFile;
+	@Parameter(names = {"-o", "--output"}, description = "Output file (tabular)", required = true)
+	public Path outputFile;
+		
+	public void run() throws IOException {		
+		try (BufferedReader reader = Files.newBufferedReader(inputFile, Charset.defaultCharset())) {
+			try (BufferedWriter writer = Files.newBufferedWriter(outputFile, Charset.defaultCharset())) {
+				String line = reader.readLine();
+				// Always copy the first (header) line
+				writer.write(line);
+				writer.newLine();
+				while ((line = reader.readLine()) != null) {
+					String[] row = line.split("\t");
+					for (int i = 1; i < row.length; i++) {
+						String cell = row[i];
+						if (cell.equalsIgnoreCase("-")) {
+							writer.write("NaN");
+						} else {
+							writer.write(cell);
+						}
+						
+						if (i > 1) {
+							writer.write("\t");
+						}
+					}
+					writer.newLine();
+				}
+			}
+		}
+	}
+	
+	public static void main(String[] args) {
+		new StripMatrix().instanceMain(args);
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/edu/unc/genomics/wigmath/Add.java	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,76 @@
+package edu.unc.genomics.wigmath;
+
+import java.io.IOException;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+import org.broad.igv.bbfile.WigItem;
+
+import com.beust.jcommander.Parameter;
+
+import edu.unc.genomics.CommandLineToolException;
+import edu.unc.genomics.io.WigFile;
+import edu.unc.genomics.io.WigFileException;
+
+public class Add extends WigMathTool {
+
+	private static final Logger log = Logger.getLogger(Add.class);
+
+	@Parameter(description = "Input files", required = true)
+	public List<String> inputFiles = new ArrayList<String>();
+
+	@Override
+	public void setup() {
+		if (inputFiles.size() < 2) {
+			throw new CommandLineToolException("No reason to add < 2 files.");
+		}
+		
+		log.debug("Initializing input files");
+		for (String inputFile : inputFiles) {
+			try {
+				addInputFile(WigFile.autodetect(Paths.get(inputFile)));
+			} catch (IOException | WigFileException e) {
+				log.error("Error initializing input Wig file: " + inputFile);
+				e.printStackTrace();
+				throw new CommandLineToolException(e.getMessage());
+			}
+		}
+		log.debug("Initialized " + inputs.size() + " input files");
+	}
+	
+	@Override
+	public float[] compute(String chr, int start, int stop) throws IOException, WigFileException {
+		log.debug("Computing sum for chunk "+chr+":"+start+"-"+stop);
+		
+		int length = stop - start + 1;
+		float[] sum = new float[length];
+		
+		for (WigFile wig : inputs) {
+			Iterator<WigItem> data = wig.query(chr, start, stop);
+			while (data.hasNext()) {
+				WigItem item = data.next();
+				for (int i = item.getStartBase(); i <= item.getEndBase(); i++) {
+					if (i-start >= 0 && i-start < sum.length) {
+						sum[i-start] += item.getWigValue();
+					}
+				}
+			}
+		}
+		
+		return sum;
+	}
+	
+	
+	/**
+	 * @param args
+	 * @throws WigFileException 
+	 * @throws IOException 
+	 */
+	public static void main(String[] args) throws IOException, WigFileException {
+		new Add().instanceMain(args);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/edu/unc/genomics/wigmath/Average.java	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,84 @@
+package edu.unc.genomics.wigmath;
+
+import java.io.IOException;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+import org.broad.igv.bbfile.WigItem;
+
+import com.beust.jcommander.Parameter;
+
+import edu.unc.genomics.CommandLineToolException;
+import edu.unc.genomics.io.WigFile;
+import edu.unc.genomics.io.WigFileException;
+
+public class Average extends WigMathTool {
+
+	private static final Logger log = Logger.getLogger(Average.class);
+
+	@Parameter(description = "Input files", required = true)
+	public List<String> inputFiles = new ArrayList<String>();
+	
+	int numFiles;
+
+	@Override
+	public void setup() {
+		if (inputFiles.size() < 2) {
+			throw new CommandLineToolException("No reason to add < 2 files.");
+		}
+		
+		log.debug("Initializing input files");
+		for (String inputFile : inputFiles) {
+			try {
+				addInputFile(WigFile.autodetect(Paths.get(inputFile)));
+			} catch (IOException | WigFileException e) {
+				log.error("Error initializing input Wig file: " + inputFile);
+				e.printStackTrace();
+				throw new CommandLineToolException(e.getMessage());
+			}
+		}
+		log.debug("Initialized " + inputs.size() + " input files");
+		
+		numFiles = inputs.size();
+	}
+	
+	@Override
+	public float[] compute(String chr, int start, int stop) throws IOException, WigFileException {
+		log.debug("Computing average for chunk "+chr+":"+start+"-"+stop);
+		
+		int length = stop - start + 1;
+		float[] avg = new float[length];
+		
+		for (WigFile wig : inputs) {
+			Iterator<WigItem> data = wig.query(chr, start, stop);
+			while (data.hasNext()) {
+				WigItem item = data.next();
+				for (int i = item.getStartBase(); i <= item.getEndBase(); i++) {
+					if (i-start >= 0 && i-start < avg.length) {
+						avg[i-start] += item.getWigValue();
+					}
+				}
+			}
+		}
+		
+		for (int i = 0; i < avg.length; i++) {
+			avg[i] = avg[i] / numFiles;
+		}
+		
+		return avg;
+	}
+	
+	
+	/**
+	 * @param args
+	 * @throws WigFileException 
+	 * @throws IOException 
+	 */
+	public static void main(String[] args) throws IOException, WigFileException {
+		new Average().instanceMain(args);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/edu/unc/genomics/wigmath/Divide.java	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,64 @@
+package edu.unc.genomics.wigmath;
+
+import java.io.IOException;
+import java.util.Iterator;
+
+import org.apache.log4j.Logger;
+import org.broad.igv.bbfile.WigItem;
+
+import com.beust.jcommander.Parameter;
+
+import edu.unc.genomics.io.WigFile;
+import edu.unc.genomics.io.WigFileException;
+
+public class Divide extends WigMathTool {
+
+	private static final Logger log = Logger.getLogger(Divide.class);
+
+	@Parameter(names = {"-n", "--numerator"}, description = "Dividend / Numerator (file 1)", required = true)
+	public WigFile dividendFile;
+	@Parameter(names = {"-d", "--denominator"}, description = "Divisor / Denominator (file 2)", required = true)
+	public WigFile divisorFile;
+
+	@Override
+	public void setup() {		
+		inputs.add(dividendFile);
+		inputs.add(divisorFile);
+		log.debug("Initialized " + inputs.size() + " input files");
+	}
+	
+	@Override
+	public float[] compute(String chr, int start, int stop) throws IOException, WigFileException {
+		log.debug("Computing difference for chunk "+chr+":"+start+"-"+stop);
+		
+		Iterator<WigItem> dividendData = dividendFile.query(chr, start, stop);
+		Iterator<WigItem> divisorData = divisorFile.query(chr, start, stop);
+		
+		float[] result = WigFile.flattenData(dividendData, start, stop);
+		while (divisorData.hasNext()) {
+			WigItem item = divisorData.next();
+			for (int i = item.getStartBase(); i <= item.getEndBase(); i++) {
+				if (i-start >= 0 && i-start < result.length) {
+					if (item.getWigValue() != 0) {
+						result[i-start] /= item.getWigValue();
+					} else {
+						result[i-start] = Float.NaN;
+					}
+				}
+			}
+		}
+		
+		return result;
+	}
+	
+	
+	/**
+	 * @param args
+	 * @throws WigFileException 
+	 * @throws IOException 
+	 */
+	public static void main(String[] args) throws IOException, WigFileException {
+		new Divide().instanceMain(args);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/edu/unc/genomics/wigmath/GaussianSmooth.java	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,74 @@
+package edu.unc.genomics.wigmath;
+
+import java.io.IOException;
+import java.util.Iterator;
+
+import org.apache.log4j.Logger;
+import org.broad.igv.bbfile.WigItem;
+
+import com.beust.jcommander.Parameter;
+
+import edu.unc.genomics.io.WigFile;
+import edu.unc.genomics.io.WigFileException;
+
+public class GaussianSmooth extends WigMathTool {
+
+	private static final Logger log = Logger.getLogger(GaussianSmooth.class);
+
+	@Parameter(names = {"-i", "--input"}, description = "Input file", required = true)
+	public WigFile inputFile;
+	@Parameter(names = {"-s", "--stdev"}, description = "Standard deviation of Gaussian (bp)")
+	public int stdev = 20;
+	
+	float[] filter;
+
+	@Override
+	public void setup() {
+		inputs.add(inputFile);
+		
+		// Use a window size equal to +/- 3 SD's
+		filter = new float[6*stdev+1];
+		float sum = 0;
+		for (int i = 0; i < filter.length; i++) {
+			float x = i - 3*stdev;
+			float value = (float) Math.exp(-(x*x) / (2*stdev*stdev));
+			filter[i] = value;
+			sum += value;
+		}
+		for (int i = 0; i < filter.length; i++) {
+			filter[i] /= sum;
+		}
+	}
+	
+	@Override
+	public float[] compute(String chr, int start, int stop) throws IOException, WigFileException {
+		log.debug("Smoothing chunk "+chr+":"+start+"-"+stop);
+		
+		// Pad the query for smoothing
+		int paddedStart = Math.max(start-3*stdev, inputFile.getChrStart(chr));
+		int paddedStop = Math.min(stop+3*stdev, inputFile.getChrStop(chr));
+		Iterator<WigItem> result = inputFile.query(chr, paddedStart, paddedStop);
+		float[] data = WigFile.flattenData(result, start-3*stdev, stop+3*stdev);
+		
+		// Convolve the data with the filter
+		float[] smoothed = new float[stop-start+1];
+		for (int i = 0; i < smoothed.length; i++) {
+			for (int j = 0; j < filter.length; j++) {
+				smoothed[i] += data[i+j] * filter[j];
+			}
+		}
+		
+		return smoothed;
+	}
+	
+	
+	/**
+	 * @param args
+	 * @throws WigFileException 
+	 * @throws IOException 
+	 */
+	public static void main(String[] args) throws IOException, WigFileException {
+		new GaussianSmooth().instanceMain(args);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/edu/unc/genomics/wigmath/LogTransform.java	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,55 @@
+package edu.unc.genomics.wigmath;
+
+import java.io.IOException;
+import java.util.Iterator;
+
+import org.apache.log4j.Logger;
+import org.broad.igv.bbfile.WigItem;
+
+import com.beust.jcommander.Parameter;
+
+import edu.unc.genomics.io.WigFile;
+import edu.unc.genomics.io.WigFileException;
+
+public class LogTransform extends WigMathTool {
+
+	private static final Logger log = Logger.getLogger(LogTransform.class);
+
+	@Parameter(names = {"-i", "--input"}, description = "Input file", required = true)
+	public WigFile inputFile;
+	@Parameter(names = {"-b", "--base"}, description = "Logarithm base (default = 2)")
+	public double base = 2;
+	
+	private double baseChange;
+
+	@Override
+	public void setup() {
+		baseChange = Math.log(base);
+		inputs.add(inputFile);
+	}
+	
+	@Override
+	public float[] compute(String chr, int start, int stop) throws IOException, WigFileException {
+		log.debug("Computing difference for chunk "+chr+":"+start+"-"+stop);
+		
+		Iterator<WigItem> data = inputFile.query(chr, start, stop);
+		float[] result = WigFile.flattenData(data, start, stop);
+		
+		for (int i = 0; i < result.length; i++) {
+			result[i] = (float) (Math.log(result[i]) / baseChange);
+		}
+		
+		return result;
+	}
+	
+	
+	/**
+	 * @param args
+	 * @throws WigFileException 
+	 * @throws IOException 
+	 */
+	public static void main(String[] args) throws IOException, WigFileException {
+		new LogTransform().instanceMain(args);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/edu/unc/genomics/wigmath/MovingAverageSmooth.java	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,67 @@
+package edu.unc.genomics.wigmath;
+
+import java.io.IOException;
+import java.util.Iterator;
+
+import org.apache.commons.math.stat.descriptive.DescriptiveStatistics;
+import org.apache.log4j.Logger;
+import org.broad.igv.bbfile.WigItem;
+
+import com.beust.jcommander.Parameter;
+
+import edu.unc.genomics.PositiveIntegerValidator;
+import edu.unc.genomics.io.WigFile;
+import edu.unc.genomics.io.WigFileException;
+
+public class MovingAverageSmooth extends WigMathTool {
+
+	private static final Logger log = Logger.getLogger(MovingAverageSmooth.class);
+
+	@Parameter(names = {"-i", "--input"}, description = "Input file", required = true)
+	public WigFile inputFile;
+	@Parameter(names = {"-w", "--width"}, description = "Width of kernel (bp)")
+	public int width = 10;
+	
+	WigFile input;
+	DescriptiveStatistics stats;
+
+	@Override
+	public void setup() {
+		inputs.add(inputFile);
+		
+		log.debug("Initializing statistics");
+		stats = new DescriptiveStatistics();
+		stats.setWindowSize(width);
+	}
+	
+	@Override
+	public float[] compute(String chr, int start, int stop) throws IOException, WigFileException {
+		log.debug("Smoothing chunk "+chr+":"+start+"-"+stop);
+		// Pad the query so that we can provide values for the ends
+		int queryStart = Math.max(start-width/2, input.getChrStart(chr));
+		int queryStop = Math.min(stop+width/2, input.getChrStop(chr));
+		Iterator<WigItem> result = input.query(chr, queryStart, queryStop);
+		float[] data = WigFile.flattenData(result, queryStart, queryStop);
+		
+		float[] smoothed = new float[stop-start+1];
+		for (int bp = start; bp <= stop; bp++) {
+			stats.addValue(data[bp-queryStart]);
+			if (bp-start-width/2 >= 0) {
+				smoothed[bp-start-width/2] = (float) stats.getMean();
+			}
+		}
+		
+		return smoothed;
+	}
+	
+	
+	/**
+	 * @param args
+	 * @throws WigFileException 
+	 * @throws IOException 
+	 */
+	public static void main(String[] args) throws IOException, WigFileException {
+		new MovingAverageSmooth().instanceMain(args);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/edu/unc/genomics/wigmath/Multiply.java	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,73 @@
+package edu.unc.genomics.wigmath;
+
+import java.io.IOException;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+import org.broad.igv.bbfile.WigItem;
+
+import com.beust.jcommander.Parameter;
+
+import edu.unc.genomics.io.WigFile;
+import edu.unc.genomics.io.WigFileException;
+
+public class Multiply extends WigMathTool {
+
+	private static final Logger log = Logger.getLogger(Multiply.class);
+
+	@Parameter(description = "Input files", required = true)
+	public List<String> inputFiles = new ArrayList<String>();
+
+	@Override
+	public void setup() {
+		log.debug("Initializing input files");
+		for (String inputFile : inputFiles) {
+			try {
+				addInputFile(WigFile.autodetect(Paths.get(inputFile)));
+			} catch (IOException | WigFileException e) {
+				log.error("Error initializing input Wig file: " + inputFile);
+				e.printStackTrace();
+				System.exit(-1);
+			}
+		}
+		log.debug("Initialized " + inputs.size() + " input files");
+	}
+	
+	@Override
+	public float[] compute(String chr, int start, int stop) throws IOException, WigFileException {
+		log.debug("Computing sum for chunk "+chr+":"+start+"-"+stop);
+		
+		int length = stop - start + 1;
+		float[] product = new float[length];
+		Arrays.fill(product, 1);
+		
+		for (WigFile wig : inputs) {
+			Iterator<WigItem> data = wig.query(chr, start, stop);
+			while (data.hasNext()) {
+				WigItem item = data.next();
+				for (int i = item.getStartBase(); i <= item.getEndBase(); i++) {
+					if (i-start >= 0 && i-start < product.length) {
+						product[i-start] *= item.getWigValue();
+					}
+				}
+			}
+		}
+		
+		return product;
+	}
+	
+	
+	/**
+	 * @param args
+	 * @throws WigFileException 
+	 * @throws IOException 
+	 */
+	public static void main(String[] args) throws IOException, WigFileException {
+		new Multiply().instanceMain(args);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/edu/unc/genomics/wigmath/Scale.java	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,57 @@
+package edu.unc.genomics.wigmath;
+
+import java.io.IOException;
+import java.util.Iterator;
+
+import org.apache.log4j.Logger;
+import org.broad.igv.bbfile.WigItem;
+
+import com.beust.jcommander.Parameter;
+
+import edu.unc.genomics.io.WigFile;
+import edu.unc.genomics.io.WigFileException;
+
+public class Scale extends WigMathTool {
+
+	private static final Logger log = Logger.getLogger(Scale.class);
+
+	@Parameter(names = {"-i", "--input"}, description = "Input file", required = true)
+	public WigFile inputFile;
+	@Parameter(names = {"-m", "--multiplier"}, description = "Multiplier (scale factor, default = 1/mean)")
+	public Double multiplier;
+	
+
+	@Override
+	public void setup() {
+		inputs.add(inputFile);
+		
+		if (multiplier == null) {
+			multiplier = inputFile.numBases() / inputFile.total();
+		}
+	}
+	
+	@Override
+	public float[] compute(String chr, int start, int stop) throws IOException, WigFileException {
+		log.debug("Computing difference for chunk "+chr+":"+start+"-"+stop);
+		
+		Iterator<WigItem> data = inputFile.query(chr, start, stop);
+		float[] result = WigFile.flattenData(data, start, stop);
+		
+		for (int i = 0; i < result.length; i++) {
+			result[i] = (float) (multiplier * result[i]);
+		}
+		
+		return result;
+	}
+	
+	
+	/**
+	 * @param args
+	 * @throws WigFileException 
+	 * @throws IOException 
+	 */
+	public static void main(String[] args) throws IOException, WigFileException {
+		new Scale().instanceMain(args);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/edu/unc/genomics/wigmath/Subtract.java	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,61 @@
+package edu.unc.genomics.wigmath;
+
+import java.io.IOException;
+import java.util.Iterator;
+
+import org.apache.log4j.Logger;
+import org.broad.igv.bbfile.WigItem;
+
+import com.beust.jcommander.Parameter;
+
+import edu.unc.genomics.io.WigFile;
+import edu.unc.genomics.io.WigFileException;
+
+public class Subtract extends WigMathTool {
+
+	private static final Logger log = Logger.getLogger(Subtract.class);
+
+	@Parameter(names = {"-m", "--minuend"}, description = "Minuend (top - file 1)", required = true)
+	public WigFile minuendFile;
+	@Parameter(names = {"-s", "--subtrahend"}, description = "Subtrahend (bottom - file 2)", required = true)
+	public WigFile subtrahendFile;
+
+	@Override
+	public void setup() {
+		log.debug("Initializing input files");
+		inputs.add(minuendFile);
+		inputs.add(subtrahendFile);
+		log.debug("Initialized " + inputs.size() + " input files");
+	}
+	
+	@Override
+	public float[] compute(String chr, int start, int stop) throws IOException, WigFileException {
+		log.debug("Computing difference for chunk "+chr+":"+start+"-"+stop);
+		
+		Iterator<WigItem> minuendData = minuendFile.query(chr, start, stop);
+		Iterator<WigItem> subtrahendData = subtrahendFile.query(chr, start, stop);
+		
+		float[] result = WigFile.flattenData(minuendData, start, stop);
+		while (subtrahendData.hasNext()) {
+			WigItem item = subtrahendData.next();
+			for (int i = item.getStartBase(); i <= item.getEndBase(); i++) {
+				if (i-start >= 0 && i-start < result.length) {
+					result[i-start] -= item.getWigValue();
+				}
+			}
+		}
+		
+		return result;
+	}
+	
+	
+	/**
+	 * @param args
+	 * @throws WigFileException 
+	 * @throws IOException 
+	 */
+	public static void main(String[] args) throws IOException, WigFileException {
+		new Subtract().instanceMain(args);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/edu/unc/genomics/wigmath/WigMathTool.java	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,170 @@
+package edu.unc.genomics.wigmath;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.log4j.Logger;
+
+import com.beust.jcommander.Parameter;
+
+import edu.unc.genomics.CommandLineTool;
+import edu.unc.genomics.io.WigFile;
+import edu.unc.genomics.io.WigFileException;
+
+/**
+ * Abstract class for writing programs to do computation on Wig files
+ * Concrete subclasses must implement the compute method
+ * 
+ * @author timpalpant
+ *
+ */
+public abstract class WigMathTool extends CommandLineTool {
+	
+	private static final Logger log = Logger.getLogger(WigMathTool.class);
+	
+	public static final int DEFAULT_CHUNK_SIZE = 500_000;
+	
+	// TODO: Variable resolution output
+	
+	@Parameter(names = {"-o", "--output"}, description = "Output file", required = true)
+	public Path outputFile;
+	
+	protected List<WigFile> inputs = new ArrayList<WigFile>();
+	
+	public void addInputFile(WigFile wig) {
+		inputs.add(wig);
+	}
+	
+	/**
+	 * Setup the computation. Should add all input Wig files
+	 * with addInputFile() during setup
+	 */
+	public abstract void setup();
+	
+	/**
+	 * Do the computation on a chunk and return the results
+	 * Must return (stop-start+1) values
+	 * 
+	 * @param chr
+	 * @param start
+	 * @param stop
+	 * @return the results of the computation for this chunk
+	 * @throws IOException
+	 * @throws WigFileException
+	 */
+	public abstract float[] compute(String chr, int start, int stop)
+			 throws IOException, WigFileException;
+	
+	@Override
+	public void run() throws IOException {
+		log.debug("Executing setup operations");
+		setup();
+		
+		log.debug("Processing files and writing result to disk");
+		try (BufferedWriter writer = Files.newBufferedWriter(outputFile, Charset.defaultCharset())) {
+			// Write the Wig header
+			writer.write("track type=wiggle_0");
+			writer.newLine();
+			
+			Set<String> chromosomes = getCommonChromosomes(inputs);
+			log.debug("Found " + chromosomes.size() + " chromosomes in common between all inputs");
+			for (String chr : chromosomes) {
+				int start = getMaxChrStart(inputs, chr);
+				int stop = getMinChrStop(inputs, chr);
+				log.debug("Processing chromosome " + chr + " region " + start + "-" + stop);
+				
+				// Write the chromosome header to output
+				writer.write("fixedStep chrom="+chr+" start="+start+" step=1 span=1");
+				writer.newLine();
+				
+				// Process the chromosome in chunks
+				int bp = start;
+				while (bp < stop) {
+					int chunkStart = bp;
+					int chunkStop = Math.min(bp+DEFAULT_CHUNK_SIZE-1, stop);
+					int expectedLength = chunkStop - chunkStart + 1;
+					log.debug("Processing chunk "+chr+":"+chunkStart+"-"+chunkStop);
+					
+					float[] result = null;
+					try {
+						result = compute(chr, chunkStart, chunkStop);
+					} catch (WigFileException e) {
+						log.fatal("Wig file error while processing chunk " + chr + " region " + start + "-" + stop);
+						e.printStackTrace();
+						throw new RuntimeException("Wig file error while processing chunk " + chr + " region " + start + "-" + stop);
+					}
+					
+					if (result.length != expectedLength) {
+						log.error("Expected result length="+expectedLength+", got="+result.length);
+						throw new RuntimeException("Result is not the expected length!");
+					}
+	
+					for (int i = 0; i < result.length; i++) {
+						writer.write(Float.toString(result[i]));
+						writer.newLine();
+					}
+					
+					bp = chunkStop + 1;
+				}
+			}
+		}
+		
+		for (WigFile wig : inputs) {
+			wig.close();
+		}
+	}
+	
+	public int getMaxChrStart(List<WigFile> wigs, String chr) {
+		int max = -1;
+		for (WigFile wig : wigs) {
+			if (wig.getChrStart(chr) > max) {
+				max = wig.getChrStart(chr);
+			}
+		}
+		
+		return max;
+	}
+	
+	public int getMinChrStop(List<WigFile> wigs, String chr) {
+		if (wigs.size() == 0) {
+			return -1;
+		}
+		
+		int min = Integer.MAX_VALUE;
+		for (WigFile wig : wigs) {
+			if (wig.getChrStop(chr) < min) {
+				min = wig.getChrStop(chr);
+			}
+		}
+		
+		return min;
+	}
+	
+	public Set<String> getCommonChromosomes(List<WigFile> wigs) {
+		if (wigs.size() == 0) {
+			return new HashSet<String>();
+		}
+		
+		Set<String> chromosomes = wigs.get(0).chromosomes();
+		Iterator<String> it = chromosomes.iterator();
+		while(it.hasNext()) {
+			String chr = it.next();
+			for (WigFile wig : wigs) {
+				if (!wig.includes(chr)) {
+					it.remove();
+					break;
+				}
+			}
+		}
+
+		return chromosomes;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/edu/unc/genomics/wigmath/WigSummary.java	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,44 @@
+package edu.unc.genomics.wigmath;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import org.apache.log4j.Logger;
+
+import com.beust.jcommander.Parameter;
+
+import edu.unc.genomics.CommandLineTool;
+import edu.unc.genomics.io.WigFile;
+import edu.unc.genomics.io.WigFileException;
+import edu.unc.genomics.ngs.Autocorrelation;
+
+public class WigSummary extends CommandLineTool {
+
+	private static final Logger log = Logger.getLogger(Autocorrelation.class);
+
+	@Parameter(names = {"-i", "--input"}, description = "Input file", required = true)
+	public WigFile inputFile;
+	@Parameter(names = {"-o", "--output"}, description = "Output file")
+	public Path outputFile;
+		
+	public void run() throws IOException {		
+		String summary = inputFile.toString();
+		
+		if (outputFile != null) {
+			log.debug("Writing to output file");
+			try (BufferedWriter writer = Files.newBufferedWriter(outputFile, Charset.defaultCharset())) {
+				writer.write(summary);
+			}
+		} else {
+			System.out.println(summary);
+		}
+	}
+	
+	public static void main(String[] args) throws IOException, WigFileException {
+		new WigSummary().instanceMain(args);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/edu/unc/genomics/wigmath/ZScore.java	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,60 @@
+package edu.unc.genomics.wigmath;
+
+import java.io.IOException;
+import java.util.Iterator;
+
+import org.apache.log4j.Logger;
+import org.broad.igv.bbfile.WigItem;
+
+import com.beust.jcommander.Parameter;
+
+import edu.unc.genomics.CommandLineToolException;
+import edu.unc.genomics.io.WigFile;
+import edu.unc.genomics.io.WigFileException;
+
+public class ZScore extends WigMathTool {
+
+	private static final Logger log = Logger.getLogger(ZScore.class);
+
+	@Parameter(names = {"-i", "--input"}, description = "Input file", required = true)
+	public WigFile inputFile;
+	
+	double mean;
+	double stdev;
+
+	@Override
+	public void setup() {
+		inputs.add(inputFile);
+		
+		mean = inputFile.mean();
+		stdev = inputFile.stdev();
+		if(stdev == 0) {
+			log.error("Cannot Z-score a file with stdev = 0!");
+			throw new CommandLineToolException("Cannot Z-score a file with stdev = 0!");
+		}
+	}
+	
+	@Override
+	public float[] compute(String chr, int start, int stop) throws IOException, WigFileException {
+		log.debug("Computing difference for chunk "+chr+":"+start+"-"+stop);
+		Iterator<WigItem> data = inputFile.query(chr, start, stop);
+		float[] result = WigFile.flattenData(data, start, stop);
+		
+		for (int i = 0; i < result.length; i++) {
+			result[i] = (float)((result[i] - mean) / stdev);
+		}
+		
+		return result;
+	}
+	
+	
+	/**
+	 * @param args
+	 * @throws WigFileException 
+	 * @throws IOException 
+	 */
+	public static void main(String[] args) throws IOException, WigFileException {
+		new ZScore().instanceMain(args);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/edu/unc/utils/RomanNumeral.java	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,57 @@
+package edu.unc.utils;
+
+// Rudimentary Class for doing Arabic Integer -> Roman Numeral conversion
+// Adapted by Timothy Palpant
+// File   : gui/componenents/calculators/Roman.java
+// Description: A static method for converting binary integers to Roman numbers.
+// Illustrates: Static inner value class, StringBuffer, throw exceptions.
+// Author : Fred Swartz - 2006-12-29 - Placed in public domain.
+public class RomanNumeral {
+
+	// This could be alternatively be done with parallel arrays.
+	// Another alternative would be Pair<Integer, String>
+	final static RomanValue[] ROMAN_VALUE_TABLE = {
+		new RomanValue(1000, "M"),
+		new RomanValue( 900, "CM"),
+		new RomanValue( 500, "D"),
+		new RomanValue( 400, "CD"),
+		new RomanValue( 100, "C"),
+		new RomanValue(  90, "XC"),
+		new RomanValue(  50, "L"),
+		new RomanValue(  40, "XL"),
+		new RomanValue(  10, "X"),
+		new RomanValue(   9, "IX"),
+		new RomanValue(   5, "V"),
+		new RomanValue(   4, "IV"),
+		new RomanValue(   1, "I")
+	};
+
+	public static String int2roman(int n) {
+		if (n >= 4000  || n < 1) {
+			throw new NumberFormatException("Numbers must be in range 1-3999");
+		}
+		StringBuilder result = new StringBuilder(10);
+
+		//... Start with largest value, and work toward smallest.
+		for (RomanValue equiv : ROMAN_VALUE_TABLE) {
+			//... Remove as many of this value as possible (maybe none).
+			while (n >= equiv.intVal) {
+				n -= equiv.intVal;            // Subtract value.
+				result.append(equiv.romVal);  // Add roman equivalent.
+			}
+		}
+		return result.toString();
+	}
+
+	private static class RomanValue {
+		//... No need to make this fields private because they are
+		//    used only in this private value class.
+		int    intVal;     // Integer value.
+		String romVal;     // Equivalent roman numeral.
+
+		RomanValue(int dec, String rom) {
+			this.intVal = dec;
+			this.romVal = rom;
+		}
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/edu/unc/utils/SortUtils.java	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,60 @@
+package edu.unc.utils;
+
+/**
+ * Custom sorting utilities
+ * see: http://stackoverflow.com/questions/951848/java-array-sort-quick-way-to-get-a-sorted-list-of-indices-of-an-array
+ * @author timpalpant
+ *
+ */
+public class SortUtils {
+	public static int[] indexSort(float[] main) {
+		int[] index = new int[main.length];
+		for (int i = 0; i < index.length; i++) {
+			index[i] = i;
+		}
+		
+		quicksort(main, index, 0, index.length-1);
+		
+		return index;
+	}
+
+	// quicksort a[left] to a[right]
+	public static void quicksort(float[] a, int[] index, int left, int right) {
+		if (right <= left)
+			return;
+		int i = partition(a, index, left, right);
+		quicksort(a, index, left, i - 1);
+		quicksort(a, index, i + 1, right);
+	}
+
+	// partition a[left] to a[right], assumes left < right
+	private static int partition(float[] a, int[] index, int left, int right) {
+		int i = left - 1;
+		int j = right;
+		while (true) {
+			while (a[index[++i]] < a[index[right]])
+				// find item on left to swap
+				; // a[right] acts as sentinel
+			while (a[index[right]] < a[index[--j]])
+				// find item on right to swap
+				if (j == left)
+					break; // don't go out-of-bounds
+			if (i >= j)
+				break; // check if pointers cross
+			exch(a, index, i, j); // swap two elements into place
+		}
+		exch(a, index, i, right); // swap with partition element
+		return i;
+	}
+
+	// exchange a[i] and a[j]
+	private static void exch(float[] a, int[] index, int i, int j) {
+		// Don't swap the data
+		// float swap = a[i];
+		// a[i] = a[j];
+		// a[j] = swap;
+		int b = index[i];
+		index[i] = index[j];
+		index[j] = b;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/stubFile.sh	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,4 @@
+#!/usr/bin/env bash
+
+DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
+java -Dlog4j.configuration=log4j.properties -cp "$DIR/../Resources/Java/*" edu.unc.genomics.ToolRunner
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/toolRunner.sh	Mon Feb 13 22:12:06 2012 -0500
@@ -0,0 +1,14 @@
+#!/usr/bin/env bash
+
+if [ $# -eq 0 ]
+then
+  echo "USAGE: toolRunner.sh APPNAME [ARGS]";
+  exit;
+fi
+
+if [ "$1" = "list" ]
+then
+  find src/edu/unc/genomics/**/*.java -exec basename -s .java {} \;
+fi
+
+java -Dlog4j.configuration=log4j.properties -cp .:build:lib/* edu.unc.genomics."$@"
\ No newline at end of file