Library view now shows when a library has archived data. 0.2.2
authorBrandon King <kingb@caltech.edu>
Tue, 16 Jun 2009 22:57:33 +0000 (22:57 +0000)
committerBrandon King <kingb@caltech.edu>
Tue, 16 Jun 2009 22:57:33 +0000 (22:57 +0000)
142 files changed:
trunk/TODO.txt [new file with mode: 0644]
trunk/docs/Conv_CaltechDB_Nov112008.txt [new file with mode: 0644]
trunk/docs/Conv_StanfordDB_2009Jan20.txt [new file with mode: 0644]
trunk/docs/conv_caltech_v0.1_made_for.py [new file with mode: 0644]
trunk/docs/conv_caltech_v0.1_to_htsw.py [new file with mode: 0644]
trunk/docs/gaworkflow.xmi [new file with mode: 0644]
trunk/docs/htsworkflow.ini.example [new file with mode: 0644]
trunk/htsworkflow/__init__.py [new file with mode: 0644]
trunk/htsworkflow/automation/__init__.py [new file with mode: 0644]
trunk/htsworkflow/automation/copier.py [new file with mode: 0644]
trunk/htsworkflow/automation/runner.py [new file with mode: 0644]
trunk/htsworkflow/automation/spoolwatcher.py [new file with mode: 0644]
trunk/htsworkflow/automation/test/test_runner.py [new file with mode: 0644]
trunk/htsworkflow/frontend/__init__.py [new file with mode: 0644]
trunk/htsworkflow/frontend/analysis/__init__.py [new file with mode: 0644]
trunk/htsworkflow/frontend/analysis/admin.py [new file with mode: 0644]
trunk/htsworkflow/frontend/analysis/main.py [new file with mode: 0644]
trunk/htsworkflow/frontend/analysis/models.py [new file with mode: 0644]
trunk/htsworkflow/frontend/analysis/urls.py [new file with mode: 0644]
trunk/htsworkflow/frontend/eland_config/__init__.py [new file with mode: 0644]
trunk/htsworkflow/frontend/eland_config/admin.py [new file with mode: 0644]
trunk/htsworkflow/frontend/eland_config/forms.py [new file with mode: 0644]
trunk/htsworkflow/frontend/eland_config/models.py [new file with mode: 0644]
trunk/htsworkflow/frontend/eland_config/urls.py [new file with mode: 0644]
trunk/htsworkflow/frontend/eland_config/views.py [new file with mode: 0644]
trunk/htsworkflow/frontend/experiments/__init__.py [new file with mode: 0755]
trunk/htsworkflow/frontend/experiments/admin.py [new file with mode: 0644]
trunk/htsworkflow/frontend/experiments/experiments.py [new file with mode: 0755]
trunk/htsworkflow/frontend/experiments/models.py [new file with mode: 0755]
trunk/htsworkflow/frontend/experiments/urls.py [new file with mode: 0755]
trunk/htsworkflow/frontend/experiments/views.py [new file with mode: 0755]
trunk/htsworkflow/frontend/inventory/__init__.py [new file with mode: 0644]
trunk/htsworkflow/frontend/inventory/admin.py [new file with mode: 0644]
trunk/htsworkflow/frontend/inventory/models.py [new file with mode: 0644]
trunk/htsworkflow/frontend/inventory/urls.py [new file with mode: 0644]
trunk/htsworkflow/frontend/inventory/views.py [new file with mode: 0644]
trunk/htsworkflow/frontend/manage.py [new file with mode: 0644]
trunk/htsworkflow/frontend/reports/LibraryInfo.xml [new file with mode: 0644]
trunk/htsworkflow/frontend/reports/__init__.py [new file with mode: 0644]
trunk/htsworkflow/frontend/reports/admin.py [new file with mode: 0644]
trunk/htsworkflow/frontend/reports/libinfopar.py [new file with mode: 0644]
trunk/htsworkflow/frontend/reports/models.py [new file with mode: 0644]
trunk/htsworkflow/frontend/reports/reports.py [new file with mode: 0755]
trunk/htsworkflow/frontend/reports/urls.py [new file with mode: 0644]
trunk/htsworkflow/frontend/reports/utils.py [new file with mode: 0644]
trunk/htsworkflow/frontend/samples/__init__.py [new file with mode: 0644]
trunk/htsworkflow/frontend/samples/admin.py [new file with mode: 0644]
trunk/htsworkflow/frontend/samples/changelist.py [new file with mode: 0644]
trunk/htsworkflow/frontend/samples/models.py [new file with mode: 0644]
trunk/htsworkflow/frontend/samples/results.py [new file with mode: 0644]
trunk/htsworkflow/frontend/samples/tests.py [new file with mode: 0644]
trunk/htsworkflow/frontend/samples/views.py [new file with mode: 0644]
trunk/htsworkflow/frontend/settings.py [new file with mode: 0644]
trunk/htsworkflow/frontend/static/css/base.css [new file with mode: 0644]
trunk/htsworkflow/frontend/static/css/changelists.css [new file with mode: 0644]
trunk/htsworkflow/frontend/static/css/click-table.css [new file with mode: 0644]
trunk/htsworkflow/frontend/static/css/data-browse-index.css [new file with mode: 0644]
trunk/htsworkflow/frontend/static/css/forms.css [new file with mode: 0644]
trunk/htsworkflow/frontend/static/css/global.css [new file with mode: 0644]
trunk/htsworkflow/frontend/static/css/layout.css [new file with mode: 0644]
trunk/htsworkflow/frontend/static/css/null.css [new file with mode: 0644]
trunk/htsworkflow/frontend/static/css/patch-iewin.css [new file with mode: 0644]
trunk/htsworkflow/frontend/static/img/changelist-bg.gif [new file with mode: 0644]
trunk/htsworkflow/frontend/static/img/default-bg.gif [new file with mode: 0644]
trunk/htsworkflow/frontend/static/img/hdd_unmount.png [new file with mode: 0755]
trunk/htsworkflow/frontend/static/img/icon_searchbox.png [new file with mode: 0644]
trunk/htsworkflow/frontend/static/img/nav-bg-reverse.gif [new file with mode: 0644]
trunk/htsworkflow/frontend/static/img/nav-bg.gif [new file with mode: 0644]
trunk/htsworkflow/frontend/static/img/readme.txt [new file with mode: 0644]
trunk/htsworkflow/frontend/templates/admin/base_site.html [new file with mode: 0644]
trunk/htsworkflow/frontend/templates/admin/index.html [new file with mode: 0644]
trunk/htsworkflow/frontend/templates/base.html [new file with mode: 0644]
trunk/htsworkflow/frontend/templates/base_site.html [new file with mode: 0644]
trunk/htsworkflow/frontend/templates/experiments/detail.html [new file with mode: 0644]
trunk/htsworkflow/frontend/templates/experiments/flowcellSheet.html [new file with mode: 0644]
trunk/htsworkflow/frontend/templates/experiments/index.html [new file with mode: 0644]
trunk/htsworkflow/frontend/templates/reports/report.html [new file with mode: 0644]
trunk/htsworkflow/frontend/templates/samples/library_detail.html [new file with mode: 0644]
trunk/htsworkflow/frontend/templates/samples/library_index.html [new file with mode: 0644]
trunk/htsworkflow/frontend/templates/search_form.html [new file with mode: 0644]
trunk/htsworkflow/frontend/urls.py [new file with mode: 0644]
trunk/htsworkflow/pipelines/__init__.py [new file with mode: 0644]
trunk/htsworkflow/pipelines/bustard.py [new file with mode: 0644]
trunk/htsworkflow/pipelines/configure_run.py [new file with mode: 0644]
trunk/htsworkflow/pipelines/eland.py [new file with mode: 0644]
trunk/htsworkflow/pipelines/firecrest.py [new file with mode: 0644]
trunk/htsworkflow/pipelines/genome_mapper.py [new file with mode: 0644]
trunk/htsworkflow/pipelines/gerald.py [new file with mode: 0644]
trunk/htsworkflow/pipelines/ipar.py [new file with mode: 0644]
trunk/htsworkflow/pipelines/recipe_parser.py [new file with mode: 0644]
trunk/htsworkflow/pipelines/retrieve_config.py [new file with mode: 0644]
trunk/htsworkflow/pipelines/run_status.py [new file with mode: 0644]
trunk/htsworkflow/pipelines/runfolder.py [new file with mode: 0644]
trunk/htsworkflow/pipelines/summary.py [new file with mode: 0644]
trunk/htsworkflow/pipelines/test/__init__.py [new file with mode: 0644]
trunk/htsworkflow/pipelines/test/simulate_runfolder.py [new file with mode: 0644]
trunk/htsworkflow/pipelines/test/test_genome_mapper.py [new file with mode: 0644]
trunk/htsworkflow/pipelines/test/test_runfolder026.py [new file with mode: 0644]
trunk/htsworkflow/pipelines/test/test_runfolder030.py [new file with mode: 0644]
trunk/htsworkflow/pipelines/test/test_runfolder110.py [new file with mode: 0644]
trunk/htsworkflow/pipelines/test/test_runfolder_ipar100.py [new file with mode: 0644]
trunk/htsworkflow/pipelines/test/test_runfolder_ipar130.py [new file with mode: 0644]
trunk/htsworkflow/pipelines/test/test_runfolder_pair.py [new file with mode: 0644]
trunk/htsworkflow/pipelines/test/testdata/IPAR1.01.params [new file with mode: 0644]
trunk/htsworkflow/pipelines/test/testdata/Summary-ipar130.htm [new file with mode: 0644]
trunk/htsworkflow/pipelines/test/testdata/Summary-paired-pipeline110.htm [new file with mode: 0644]
trunk/htsworkflow/pipelines/test/testdata/Summary-pipeline100.htm [new file with mode: 0644]
trunk/htsworkflow/pipelines/test/testdata/Summary-pipeline110.htm [new file with mode: 0644]
trunk/htsworkflow/pipelines/test/testdata/bustard-config132.xml [new file with mode: 0644]
trunk/htsworkflow/pipelines/test/testdata/gerald_config_0.2.6.xml [new file with mode: 0644]
trunk/htsworkflow/pipelines/test/testdata/gerald_config_1.0.xml [new file with mode: 0644]
trunk/htsworkflow/util/__init__.py [new file with mode: 0644]
trunk/htsworkflow/util/alphanum.py [new file with mode: 0644]
trunk/htsworkflow/util/ethelp.py [new file with mode: 0644]
trunk/htsworkflow/util/fctracker.py [new file with mode: 0644]
trunk/htsworkflow/util/hdquery.py [new file with mode: 0644]
trunk/htsworkflow/util/makebed.py [new file with mode: 0755]
trunk/htsworkflow/util/mount.py [new file with mode: 0644]
trunk/htsworkflow/util/opener.py [new file with mode: 0644]
trunk/htsworkflow/util/queuecommands.py [new file with mode: 0644]
trunk/htsworkflow/util/test/test_alphanum.py [new file with mode: 0644]
trunk/htsworkflow/util/test/test_ethelp.py [new file with mode: 0644]
trunk/htsworkflow/util/test/test_makebed.py [new file with mode: 0644]
trunk/htsworkflow/util/test/test_queuecommands.py [new file with mode: 0644]
trunk/scripts/configure_pipeline [new file with mode: 0644]
trunk/scripts/copier [new file with mode: 0644]
trunk/scripts/elandseq [new file with mode: 0755]
trunk/scripts/gerald2bed.py [new file with mode: 0644]
trunk/scripts/library.py [new file with mode: 0644]
trunk/scripts/make-library-tree [new file with mode: 0644]
trunk/scripts/makebed [new file with mode: 0755]
trunk/scripts/mark_archived_data [new file with mode: 0755]
trunk/scripts/rerun_eland.py [new file with mode: 0644]
trunk/scripts/retrieve_config [new file with mode: 0644]
trunk/scripts/runfolder [new file with mode: 0644]
trunk/scripts/runner [new file with mode: 0644]
trunk/scripts/spoolwatcher [new file with mode: 0644]
trunk/scripts/srf [new file with mode: 0644]
trunk/setup.py [new file with mode: 0644]
trunk/templates/config_form.html [new file with mode: 0644]
trunk/test/test_copier.py [new file with mode: 0644]
trunk/test/tree.py [new file with mode: 0644]

diff --git a/trunk/TODO.txt b/trunk/TODO.txt
new file mode 100644 (file)
index 0000000..27f2fdd
--- /dev/null
@@ -0,0 +1,49 @@
+Improvements:
+
+* Auto restart spoolwatcher's watch
+
+  * use dbus to detect newly mounted drive (hard to do)
+  * spoolwatcher hangs out on jabber and accepts a "make new drive" command
+    (which runs a script to format/mount/watch the drive) (security hole)
+  * a script is added to jumpgate which does the drive formatting/mounting/etc
+    and then when finishes sends an xml-rpc message to spoolwatcher to
+    start watching again. 
+
+* Change umask group for the rsync to be writable for multiple users
+  really really needs to be 002
+* the directory also needs to be set group id
+* need to make sure that there's a final rsync that finishes without coping any files
+* demon processes need to restart
+
+  * most likely solution, they should detect if there's a currently running 
+    version and stop running. This'd allow a cron script to restart them
+
+* The machine to machine communication needs to be via xml-rpc
+
+  * Update benderjab and/or xmppy to send/receive xml-rpc messages
+
+* Add logging to everything
+
+  * high priority messages go out as jabber messages
+  * low priority go to disk
+  * some subset of recent messages should be stored in ram so they can 
+    be retrieved by a user jabber status message (or a web page view)
+
+* Generate config files
+
+  * For goat (from brandon's web interface)
+  * For bed file generator
+
+* Need longish term storage to make generating bed and mapped read files
+  from multiple lanes on multiple flow cells
+
+* View current status page.
+
+  * once xml-rpc is working it should be easier to update the jumpgate
+    web page to report the current status of a run.
+
+    * sequencing
+    * waiting on copy (final rsync)
+    * running goat
+    * running pipeline
+    * ... (more processing)
diff --git a/trunk/docs/Conv_CaltechDB_Nov112008.txt b/trunk/docs/Conv_CaltechDB_Nov112008.txt
new file mode 100644 (file)
index 0000000..9d6997a
--- /dev/null
@@ -0,0 +1,76 @@
+Conversion SQLs for Caltech DB (schema of 2008Aug08 @ 5:06 PM) 
+______________________________________________________________
+
+
+Step by step do the following:
+
+''' Note: Instead of '?' you can put one of your machine names.
+sqlite> ALTER TABLE fctracker_flowcell ADD cluster_mac_id varchar(50) NOT NULL DEFAULT '?';
+sqlite> ALTER TABLE fctracker_flowcell ADD seq_mac_id varchar(50) NOT NULL DEFAULT '?';
+sqlite> ALTER TABLE fctracker_library RENAME TO PREV_fctracker_library;
+
+Now, do a syncdb. The output should look like this (assuming you have migrated to the new models.py):
+sh-3.2# pym syncdb
+Creating table fctracker_cellline
+Creating table fctracker_library
+Creating table fctracker_primer
+Creating table fctracker_antibody
+Creating table fctracker_condition
+Creating table exp_track_datarun
+Creating table exp_track_flowcell
+Creating table analys_track_project
+Creating table analys_track_task
+Creating table htsw_reports_progressreport
+Installing index for fctracker.Library model
+Failed to install index for fctracker.Library model: index fctracker_library_library_species_id already existsInstalling index for fctracker.Primer model
+Installing index for fctracker.Antibody model
+Installing index for exp_track.DataRun model
+Installing index for exp_track.FlowCell model
+Installing index for analys_track.Task model
+Installing index for htsw_reports.ProgressReport model
+sh-3.2# 
+
+''' Copy all records from "fctracker_flowcell" to "exp_track_flowcell" table. (Why? Because, Flowcell table moves now from the "fctracker" to the "exp_track" component). 
+sqlite> insert into experiments_flowcell select * from fctracker_flowcell;
+
+''' Now to fctracker_library, a bit more complex case
+
+'''Back to the sqlite prompt..
+sqlite> insert into samples_cellline (cellline_name,notes) values('Unknown','Unknown');
+sqlite> insert into samples_condition (condition_name,notes) values('Unknown','Unknown');
+''' Now we can put 1 in these fields for the Library insert.
+''' Note: avg_lib_size field is missing in Caltech DB (although it's in the models.py Trac), so I put default value 225.
+
+''' Now the actual migration to the new fctracker_library table
+''' (This version looses data, the current Nov 11, 2008 schema, has made_for as a one to many 
+''' relationship to the auth_user table, instead of being a text field. Here I just assigned
+''' the made for to a (semi)-random user.
+sqlite> INSERT INTO samples_library (library_id,library_name,library_species_id,experiment_type,cell_line_id,condition_id,replicate,made_by,creation_date,made_for_id,stopping_point,amplified_from_sample_id,undiluted_concentration,ten_nM_dilution,successful_pM,avg_lib_size,notes) select library_id,library_name,library_species_id,'unknown',1,1,1,made_by,creation_date,12,stopping_point,amplified_from_sample_id,undiluted_concentration,ten_nM_dilution,successful_pM,0,notes from PREV_fctracker_library; 
+
+''' Set the right values for "experiment_type"
+sqlite> update samples_library set experiment_type = "RNA-seq" where library_idin (select library_id from prev_fctracker_library where RNASeq = 1);
+''' YOU CAN ADD SIMILAR SQL CMD TO SET THE VALUE FOR "avg_lib_size" FIELD (WHICH IS NOW SET TO 0) ...  
+
+----------------------------------------------------------------------------------------
+THAT SHOULD BE IT --- NOW YOUR WEB SITE SHOULD SUCESSFULY LOAD THE NEW DB WITH YOUR DATA.
+
+2009 Jan 13 
+
+I had a working database and then merged in a few more changes from
+stanford. I ended up needing to do the following:
+
+alter table analysis_task add task_params varchar(200) null;
+alter table samples_cellline add nickname varchar(20) null;
+alter table samples_condition add nickname varchar(20) null;
+
+Those changes might happen automatically when reconverting from our 
+original database, or they might not.
+
+CREATE TABLE "samples_library_tags" (
+    "id" integer NOT NULL PRIMARY KEY,
+    "library_id" varchar(30) NOT NULL REFERENCES "samples_library" ("library_id"),
+    "tag_id" integer NOT NULL REFERENCES "samples_tag" ("id"),
+    UNIQUE ("library_id", "tag_id")
+)
+;
+
diff --git a/trunk/docs/Conv_StanfordDB_2009Jan20.txt b/trunk/docs/Conv_StanfordDB_2009Jan20.txt
new file mode 100644 (file)
index 0000000..aed4bfa
--- /dev/null
@@ -0,0 +1,21 @@
+# mostly I just renamed tables
+# 
+
+alter table analys_track_projects rename to analysis_projects;
+alter table analys_track_project rename to analysis_project;
+alter table analys_track_project_tasks rename to analysis_project_tasks;
+alter table analys_track_task rename to analysis_task;
+alter table exp_track_datarun rename to experiments_datarun;
+alter table exp_track_flowcell rename to experiments_flowcell;
+alter table fctracker_affiliation rename to samples_affiliation;
+alter table fctracker_antibody rename to samples_antibody;
+alter table fctracker_cellline rename to samples_cellline;
+alter table fctracker_condition rename to samples_condition;
+alter table fctracker_flowcell rename to samples_flowcell;
+alter table fctracker_library rename to samples_library;
+alter table fctracker_library_affiliations rename to samples_library_affiliations;
+alter table fctracker_library_tags rename to samples_library_tags;
+alter table fctracker_primary rename to samples_primer;
+alter table fctracker_species rename to samples_species;
+alter table fctracker_tag rename to samples_tag;
+alter table htsw_reports_progressreport rename to reports_progressreport;
diff --git a/trunk/docs/conv_caltech_v0.1_made_for.py b/trunk/docs/conv_caltech_v0.1_made_for.py
new file mode 100644 (file)
index 0000000..206de00
--- /dev/null
@@ -0,0 +1,108 @@
+"""
+Read the made-for field and split them up into different affiliations
+while fixing the different spellings for some of our users
+"""
+import os
+
+script_dir = os.path.split(__file__)[0]
+settings_path = os.path.join(script_dir, 'htsworkflow','frontend')
+os.environ['DJANGO_SETTINGS_MODULE'] = 'htsworkflow.frontend.settings'
+
+from htsworkflow.frontend.samples import models as samples
+
+def main():
+    # names  ( {'target name': ('current name', 'current name') )
+    names = [
+      'Unknown',
+      'Adam Rosenthal',
+      'Adler Dillman',
+      'Ali',
+      'Ali/EHD',
+      'Ali/PWS',
+      'Andrew Medina-Marino',
+      'Brian Williams',
+      'Davidson',
+      'Elowitz',
+      'Erich Schwarz',
+      'Georgi Warinov',
+      'Gilberto Desalvo',
+      'Gilberto Hernandez',
+      'Gordon Kwan',
+      'Hudson-Alpha',
+      'James Puckett',
+      'Jingli Zhang',
+      'Ellen Rothenberg',
+      'Jose Luis',
+      'Katherine Fisher',
+      'Meyerowitz',
+      'Ryan',
+      'Demo',
+      'Angela Stathopoulos',
+      'Steven Kuntz',
+      'Tony',
+      'Tristan',
+      'Yuling Jiao',
+      u'Anil Ozdemir',
+    ]
+
+    name_map = {
+      '': ('Unknown',) ,
+      'Adam Rosenthal': ('Adam Rosenthal',),
+      'Adler Dillman': ('Adler Dillman',),
+      'Ali': ('Ali',),
+      'Ali/EHD': ('Ali/EHD',),
+      'Ali/PWS': ('Ali/PWS',),
+      'Andrew Medina-Marina': ('Andrew Medina-Marino',),
+      'Andrew Medina-Marino': ('Andrew Medina-Marino',),
+      'Brian': ('Brian Williams',),
+      'Brian Williams': ('Brian Williams',),
+      'Davidson': ('Davidson',),
+      'Elowitz': ('Elowitz',),
+      'Erich Schwarz': ('Erich Schwarz',),
+      'Erich Schwartz': ('Erich Schwarz',),
+      'Georgi Warinov': ('Georgi Warinov',),
+      'Gilberto Desalvo': ('Gilberto Desalvo',),
+      'Gilberto Hernandez': ('Gilberto Hernandez',),
+      'Gordon Kwan': ('Gordon Kwan',),
+      'Gordon': ('Gordon Kwan',),
+      'Alpha-Hudson': ('Hudson-Alpha',),
+      'Hudson-Alpha': ('Hudson-Alpha',),
+      'James Puckett': ('James Puckett',),
+      'Jingli Zhang, Rothenberg': ('Jingli Zhang', 'Ellen Rothenberg',),
+      'Jingli Zhang': ('Jingli Zhang',),
+      'Jose Luis': ('Jose Luis',),
+      'Katherine Fisher': ('Katherine Fisher',),
+      'Katherine, Gigio': ('Katherine Fisher', 'Gilberto Desalvo',),
+      'Meyerowitz': ('Meyerowitz',),
+      'Ryan, Demo': ('Ryan', 'Demo',),
+      'Stathopoulos': ('Angela Stathopoulos',),
+      'Steve Kuntz': ('Steven Kuntz',),
+      'Steven Kuntz': ('Steven Kuntz',),
+      'Tony': ('Tony',),
+      'Tristan': ('Tristan',),
+      'Yuling Jiao': ('Yuling Jiao',),
+      u'Anil Ozdemir': (u'Anil Ozdemir',),
+    }
+
+    affiliations = {}
+    for name in names:
+      aff = samples.Affiliation(name=name)
+      affiliations[name] = aff
+      aff.save()
+
+    for lib in samples.Library.objects.all():
+      made_list = name_map[lib.made_for]
+      assert type(made_list) == type((None,))
+      affiliation_list = []
+      for n in made_list:
+        lib.affiliations.add(affiliations[n])
+      lib.save()
+
+if __name__ == "__main__":
+  print "don't run this unless you know what its for"
+  print "it converts the caltech 'made_for' field into a set of"
+  print "affiliations."
+  print ""
+  print "The user lists are hard coded and exist mostly for my"
+  print "convienence."
+  main()
diff --git a/trunk/docs/conv_caltech_v0.1_to_htsw.py b/trunk/docs/conv_caltech_v0.1_to_htsw.py
new file mode 100644 (file)
index 0000000..e69d9fa
--- /dev/null
@@ -0,0 +1,188 @@
+import shutil
+import sqlite3
+import sys
+
+def main(cmdline=None):
+    if len(cmdline) == 1:
+       dest='/tmp/fctracker.db'
+    else:
+      dest = cmdline[1]
+    shutil.copy(cmdline[0], dest)
+    conn = sqlite3.connect(dest)
+    c = conn.cursor()
+    c.execute('drop table fctracker_elandresult');
+    c.execute('''CREATE TABLE "experiments_clusterstation" (
+      "id" integer NOT NULL PRIMARY KEY,
+      "name" varchar(50) NOT NULL UNIQUE);''')
+    c.execute('''INSERT INTO experiments_clusterstation (name) values ("station");''')
+    c.execute('''CREATE TABLE "experiments_sequencer" (
+      "id" integer NOT NULL PRIMARY KEY,
+      "name" varchar(50) NOT NULL UNIQUE);''')
+    c.execute('''INSERT INTO experiments_sequencer (name) values ("HWI-EAS229");''')
+
+    c.execute('''CREATE TABLE "experiments_flowcell" (
+    "id" integer NOT NULL PRIMARY KEY,
+    "flowcell_id" varchar(20) NOT NULL UNIQUE,
+    "run_date" datetime NOT NULL,
+    "advanced_run" bool NOT NULL,
+    "paired_end" bool NOT NULL,
+    "read_length" integer NOT NULL,
+    "lane_1_library_id" integer NOT NULL REFERENCES "samples_library" ("id"),
+    "lane_2_library_id" integer NOT NULL REFERENCES "samples_library" ("id"),
+    "lane_3_library_id" integer NOT NULL REFERENCES "samples_library" ("id"),
+    "lane_4_library_id" integer NOT NULL REFERENCES "samples_library" ("id"),
+    "lane_5_library_id" integer NOT NULL REFERENCES "samples_library" ("id"),
+    "lane_6_library_id" integer NOT NULL REFERENCES "samples_library" ("id"),
+    "lane_7_library_id" integer NOT NULL REFERENCES "samples_library" ("id"),
+    "lane_8_library_id" integer NOT NULL REFERENCES "samples_library" ("id"),
+    "lane_1_pM" decimal NOT NULL,
+    "lane_2_pM" decimal NOT NULL,
+    "lane_3_pM" decimal NOT NULL,
+    "lane_4_pM" decimal NOT NULL,
+    "lane_5_pM" decimal NOT NULL,
+    "lane_6_pM" decimal NOT NULL,
+    "lane_7_pM" decimal NOT NULL,
+    "lane_8_pM" decimal NOT NULL,
+    "lane_1_cluster_estimate" integer NULL,
+    "lane_2_cluster_estimate" integer NULL,
+    "lane_3_cluster_estimate" integer NULL,
+    "lane_4_cluster_estimate" integer NULL,
+    "lane_5_cluster_estimate" integer NULL,
+    "lane_6_cluster_estimate" integer NULL,
+    "lane_7_cluster_estimate" integer NULL,
+    "lane_8_cluster_estimate" integer NULL,
+    "cluster_station_id" integer NOT NULL REFERENCES "experiments_clusterstation" ("id"),
+    "sequencer_id" integer NOT NULL REFERENCES "experiments_sequencer" ("id"),
+    "notes" text NOT NULL
+);''')
+    c.execute('''insert into experiments_flowcell 
+        (id, flowcell_id, run_date, advanced_run, paired_end, read_length,
+         lane_1_library_id, lane_2_library_id, lane_3_library_id,
+         lane_4_library_id, lane_5_library_id, lane_6_library_id,
+         lane_7_library_id, lane_8_library_id, lane_1_pm,
+         lane_2_pM, lane_3_pM, lane_4_pM, lane_5_pM, lane_6_pM,
+         lane_7_pM, lane_8_pM, lane_1_cluster_estimate,
+         lane_2_cluster_estimate, lane_3_cluster_estimate, 
+         lane_4_cluster_estimate, lane_5_cluster_estimate,
+         lane_6_cluster_estimate, lane_7_cluster_estimate, 
+         lane_8_cluster_estimate, cluster_station_id, sequencer_id,
+         notes) 
+      select
+         id, flowcell_id, run_date, advanced_run, paired_end, read_length,
+         lane_1_library_id, lane_2_library_id, lane_3_library_id,
+         lane_4_library_id, lane_5_library_id, lane_6_library_id,
+         lane_7_library_id, lane_8_library_id, lane_1_pm,
+         lane_2_pM, lane_3_pM, lane_4_pM, lane_5_pM, lane_6_pM,
+         lane_7_pM, lane_8_pM, lane_1_cluster_estimate,
+         lane_2_cluster_estimate, lane_3_cluster_estimate, 
+         lane_4_cluster_estimate, lane_5_cluster_estimate,
+         lane_6_cluster_estimate, lane_7_cluster_estimate, 
+         lane_8_cluster_estimate, 1, 1,
+         notes from fctracker_flowcell;''')
+    c.execute('''drop table fctracker_flowcell;''')
+
+    # create samples.cellline
+    c.execute('''CREATE TABLE "samples_cellline" (
+    "id" integer NOT NULL PRIMARY KEY,
+    "cellline_name" varchar(100) NOT NULL UNIQUE,
+    "nickname" varchar(20) NULL,
+    "notes" text NOT NULL);''')
+    c.execute('''insert into samples_cellline (cellline_name,notes) values("Unknown","Unknown");''')
+
+    # Create samples.condition
+    c.execute('''CREATE TABLE "samples_condition" (
+    "id" integer NOT NULL PRIMARY KEY,
+    "condition_name" varchar(2000) NOT NULL UNIQUE,
+    "nickname" varchar(20) NULL,
+    "notes" text NOT NULL);''')
+    c.execute('''insert into samples_condition (condition_name,notes) values("Unknown","Unknown");''')
+
+    # create samples.experiment type
+    c.execute('''CREATE TABLE "samples_experimenttype" (
+          "id" integer NOT NULL PRIMARY KEY,
+          "name" varchar(50) NOT NULL UNIQUE);''')
+    for et in [ ('Unknown',),
+                ('ChIP-seq',),
+                ('Sheared',),
+                ('RNA-seq',),
+                ('Methyl-seq',),
+                ('DIP-seq',),
+                ('De Novo',)]:
+        c.execute('insert into samples_experimenttype (name) values (?)', et)
+
+    # create samples.library
+    c.execute('''CREATE TABLE "samples_library" (
+    "id" integer NOT NULL PRIMARY KEY,
+    "library_id" varchar(30) NOT NULL,
+    "library_name" varchar(100) NOT NULL UNIQUE,
+    "library_species_id" integer NOT NULL REFERENCES "samples_species" ("id"),
+    "hidden" bool NOT NULL,
+    "cell_line_id" integer NOT NULL REFERENCES "samples_cellline" ("id"),
+    "condition_id" integer NOT NULL REFERENCES "samples_condition" ("id"),
+    "antibody_id" integer NULL REFERENCES "samples_antibody" ("id"),
+    "replicate" smallint unsigned NOT NULL,
+    "experiment_type_id" NOT NULL REFERENCES "samples_experimenttype" ("id"),
+    "creation_date" date NULL,
+    "made_for" varchar(50) NOT NULL,
+    "made_by" varchar(50) NOT NULL,
+    "stopping_point" varchar(25) NOT NULL,
+    "amplified_from_sample_id" integer NULL,
+    "undiluted_concentration" decimal NULL,
+    "successful_pM" decimal NULL,
+    "ten_nM_dilution" bool NOT NULL,
+    "avg_lib_size" integer NULL,
+    "notes" text NOT NULL);''')
+    c.execute('''INSERT INTO samples_library 
+      (id,library_id,library_name,library_species_id, hidden, experiment_type_id,
+       cell_line_id,condition_id,replicate,made_by,creation_date,
+       made_for,stopping_point,amplified_from_sample_id,
+       undiluted_concentration,ten_nM_dilution,successful_pM,
+       avg_lib_size,notes) 
+select library_id,library_id,library_name,library_species_id, 0, 1,
+       1,           1,           1,        made_by,creation_date,
+       made_for,stopping_point,amplified_from_sample_id,
+       undiluted_concentration,ten_nM_dilution,successful_pM,
+       225,notes from fctracker_library;''');
+
+    # mark gel isolates as "hidden"
+    c.execute('''update samples_library set hidden=1  
+              where stopping_point = "1A" or stopping_point = "1Ab";''');
+
+    # get pk for RNA-seq experiment type
+    c.execute('select id from samples_experimenttype where name = "RNA-seq";')
+    rna_seq_id = list(c)[0]
+    # change everything marked as rnaseq to experiment_type rnaseq
+    c.execute('''update samples_library set experiment_type_id=?  where library_id in (select library_id from fctracker_library where RNASeq = 1);''', rna_seq_id)
+    #c.execute('''drop table fctracker_library;''') 
+
+    # add affiliation linking table
+    c.execute('''CREATE TABLE "samples_library_affiliations" (
+    "id" integer NOT NULL PRIMARY KEY,
+    "library_id" integer NOT NULL REFERENCES "samples_library" ("id"),
+    "affiliation_id" integer NOT NULL REFERENCES "samples_affiliation" ("id"),
+    UNIQUE ("library_id", "affiliation_id"));''')
+
+    # add library to tags linking table
+    c.execute('''CREATE TABLE "samples_library_tags" (
+    "id" integer NOT NULL PRIMARY KEY,
+    "library_id" integer NOT NULL REFERENCES "samples_library" ("id"),
+    "tag_id" integer NOT NULL REFERENCES "samples_tag" ("id"),
+    UNIQUE ("library_id", "tag_id"));''')
+
+
+
+    #
+    c.execute('''CREATE TABLE "samples_species" (
+    "id" integer NOT NULL PRIMARY KEY,
+    "scientific_name" varchar(256) NOT NULL,
+    "common_name" varchar(256) NOT NULL);''')
+    c.execute('''insert into samples_species 
+        (id, scientific_name, common_name)
+    select
+         id, scientific_name, common_name
+    from fctracker_species;''')
+    c.execute('''drop table fctracker_species''')
+    conn.commit()
+
+if __name__ == "__main__":
+    main(sys.argv[1:])
diff --git a/trunk/docs/gaworkflow.xmi b/trunk/docs/gaworkflow.xmi
new file mode 100644 (file)
index 0000000..41c227a
--- /dev/null
@@ -0,0 +1,452 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<XMI xmlns:UML="http://schema.omg.org/spec/UML/1.3" verified="false" timestamp="2008-01-11T15:30:52" xmi.version="1.2" >
+ <XMI.header>
+  <XMI.documentation>
+   <XMI.exporter>umbrello uml modeller http://uml.sf.net</XMI.exporter>
+   <XMI.exporterVersion>1.5.8</XMI.exporterVersion>
+   <XMI.exporterEncoding>UnicodeUTF8</XMI.exporterEncoding>
+  </XMI.documentation>
+  <XMI.metamodel xmi.name="UML" href="UML.xml" xmi.version="1.3" />
+ </XMI.header>
+ <XMI.content>
+  <UML:Model isSpecification="false" isLeaf="false" isRoot="false" xmi.id="m1" isAbstract="false" name="UML Model" >
+   <UML:Namespace.ownedElement>
+    <UML:Stereotype isSpecification="false" isLeaf="false" visibility="public" namespace="m1" xmi.id="folder" isRoot="false" isAbstract="false" name="folder" />
+    <UML:Stereotype isSpecification="false" isLeaf="false" visibility="public" namespace="m1" xmi.id="datatype" isRoot="false" isAbstract="false" name="datatype" />
+    <UML:Model stereotype="folder" isSpecification="false" isLeaf="false" visibility="public" namespace="m1" xmi.id="Logical View" isRoot="false" isAbstract="false" name="Logical View" >
+     <UML:Namespace.ownedElement>
+      <UML:Package stereotype="folder" isSpecification="false" isLeaf="false" visibility="public" namespace="Logical View" xmi.id="Datatypes" isRoot="false" isAbstract="false" name="Datatypes" >
+       <UML:Namespace.ownedElement>
+        <UML:DataType stereotype="datatype" isSpecification="false" isLeaf="false" visibility="public" namespace="Datatypes" xmi.id="zHGiO7QUZRiX" isRoot="false" isAbstract="false" name="int" />
+        <UML:DataType stereotype="datatype" isSpecification="false" isLeaf="false" visibility="public" namespace="Datatypes" xmi.id="LzF7NnxDIQZy" isRoot="false" isAbstract="false" name="char" />
+        <UML:DataType stereotype="datatype" isSpecification="false" isLeaf="false" visibility="public" namespace="Datatypes" xmi.id="rF1bLfYctE0z" isRoot="false" isAbstract="false" name="bool" />
+        <UML:DataType stereotype="datatype" isSpecification="false" isLeaf="false" visibility="public" namespace="Datatypes" xmi.id="eTY593cV1paM" isRoot="false" isAbstract="false" name="float" />
+        <UML:DataType stereotype="datatype" isSpecification="false" isLeaf="false" visibility="public" namespace="Datatypes" xmi.id="dYZty7spMrOf" isRoot="false" isAbstract="false" name="double" />
+        <UML:DataType stereotype="datatype" isSpecification="false" isLeaf="false" visibility="public" namespace="Datatypes" xmi.id="UuHO7GNlhtpq" isRoot="false" isAbstract="false" name="short" />
+        <UML:DataType stereotype="datatype" isSpecification="false" isLeaf="false" visibility="public" namespace="Datatypes" xmi.id="xPJyEuIpQpjO" isRoot="false" isAbstract="false" name="long" />
+        <UML:DataType stereotype="datatype" isSpecification="false" isLeaf="false" visibility="public" namespace="Datatypes" xmi.id="wZCGOYvqRDEY" isRoot="false" isAbstract="false" name="unsigned int" />
+        <UML:DataType stereotype="datatype" isSpecification="false" isLeaf="false" visibility="public" namespace="Datatypes" xmi.id="RZH8T4yOTGKi" isRoot="false" isAbstract="false" name="unsigned short" />
+        <UML:DataType stereotype="datatype" isSpecification="false" isLeaf="false" visibility="public" namespace="Datatypes" xmi.id="AcXaREwMl6c9" isRoot="false" isAbstract="false" name="unsigned long" />
+        <UML:DataType stereotype="datatype" isSpecification="false" isLeaf="false" visibility="public" namespace="Datatypes" xmi.id="dYy4jCaAkpGB" isRoot="false" isAbstract="false" name="string" />
+       </UML:Namespace.ownedElement>
+      </UML:Package>
+      <UML:Class isSpecification="false" isLeaf="false" visibility="public" namespace="Logical View" xmi.id="lZR99u8H0ec9" isRoot="false" isAbstract="false" name="SpoolWatcher" />
+      <UML:Class isSpecification="false" isLeaf="false" visibility="public" namespace="Logical View" xmi.id="GkJy5VAr2ZyC" isRoot="false" isAbstract="false" name="Copier" >
+       <UML:Classifier.feature>
+        <UML:Operation isSpecification="false" isLeaf="false" visibility="public" xmi.id="74kpdjSI8Fmc" isRoot="false" isAbstract="false" isQuery="false" name="startCopy" />
+        <UML:Operation isSpecification="false" isLeaf="false" visibility="public" xmi.id="ZEGJnJU2LB4i" isRoot="false" isAbstract="false" isQuery="false" name="sequencingFinished" >
+         <UML:BehavioralFeature.parameter>
+          <UML:Parameter isSpecification="false" visibility="private" xmi.id="0F6mtJp0LYow" value="" type="dYy4jCaAkpGB" name="runDir" />
+         </UML:BehavioralFeature.parameter>
+        </UML:Operation>
+       </UML:Classifier.feature>
+      </UML:Class>
+      <UML:Class isSpecification="false" isLeaf="false" visibility="public" namespace="Logical View" xmi.id="0UoIUrhibyff" isRoot="false" isAbstract="false" name="Runner" >
+       <UML:Classifier.feature>
+        <UML:Operation isSpecification="false" isLeaf="false" visibility="public" xmi.id="t3UPqTGoZoip" isRoot="false" isAbstract="false" isQuery="false" name="sequencingFinished" >
+         <UML:BehavioralFeature.parameter>
+          <UML:Parameter isSpecification="false" visibility="private" xmi.id="RRzKx8Z14iIF" value="" type="dYy4jCaAkpGB" name="runDir" />
+         </UML:BehavioralFeature.parameter>
+        </UML:Operation>
+        <UML:Operation isSpecification="false" isLeaf="false" visibility="public" xmi.id="4yuLbdrUeJtU" isRoot="false" isAbstract="false" isQuery="false" name="pipelineFinished" >
+         <UML:BehavioralFeature.parameter>
+          <UML:Parameter isSpecification="false" visibility="private" xmi.id="n7GKuy9LN9fK" value="" type="dYy4jCaAkpGB" name="runDir" />
+         </UML:BehavioralFeature.parameter>
+        </UML:Operation>
+       </UML:Classifier.feature>
+      </UML:Class>
+      <UML:Class isSpecification="false" isLeaf="false" visibility="public" namespace="Logical View" xmi.id="ZEsHfPFxkUD5" isRoot="false" isAbstract="false" name="Jumpgate" >
+       <UML:Classifier.feature>
+        <UML:Operation isSpecification="false" isLeaf="false" visibility="public" xmi.id="crYTwksc84xx" isRoot="false" isAbstract="false" isQuery="false" name="rsync" />
+       </UML:Classifier.feature>
+      </UML:Class>
+      <UML:Class isSpecification="false" isLeaf="false" visibility="public" namespace="Logical View" xmi.id="fOCNrWNOlFdQ" isRoot="false" isAbstract="false" name="Cellcenter" >
+       <UML:Classifier.feature>
+        <UML:Operation isSpecification="false" isLeaf="false" visibility="public" xmi.id="bKJKYukBj6Uz" isRoot="false" isAbstract="false" isQuery="false" name="configPipeline" />
+        <UML:Operation isSpecification="false" isLeaf="false" visibility="public" xmi.id="dtnBd3KDtsgp" isRoot="false" isAbstract="false" isQuery="false" name="runPipeline" />
+       </UML:Classifier.feature>
+      </UML:Class>
+      <UML:Association isSpecification="false" visibility="public" namespace="Logical View" xmi.id="YzXPgsx8JY4f" name="" >
+       <UML:Association.connection>
+        <UML:AssociationEnd isSpecification="false" visibility="public" changeability="changeable" isNavigable="false" xmi.id="Xd2ysTXicS6i" aggregation="none" type="wDCVwIFrhMaS" name="" />
+        <UML:AssociationEnd isSpecification="false" visibility="public" changeability="changeable" isNavigable="true" xmi.id="iqOSoVoV96r4" aggregation="none" type="aary2tTYpejI" name="" />
+       </UML:Association.connection>
+      </UML:Association>
+     </UML:Namespace.ownedElement>
+     <XMI.extension xmi.extender="umbrello" >
+      <diagrams>
+       <diagram snapgrid="0" showattsig="1" fillcolor="#ffffc0" linewidth="0" zoom="100" showgrid="0" showopsig="1" usefillcolor="1" snapx="10" canvaswidth="908" snapy="10" showatts="1" xmi.id="qsXVOiQh2k6g" documentation="" type="1" showops="1" showpackage="0" name="class diagram" localid="" showstereotype="0" showscope="1" snapcsgrid="0" font="Sans Serif,8,-1,5,50,0,0,0,0,0" linecolor="#ff0000" canvasheight="598" >
+        <widgets/>
+        <messages/>
+        <associations/>
+       </diagram>
+       <diagram snapgrid="1" showattsig="1" fillcolor="#ffffc0" linewidth="0" zoom="100" showgrid="1" showopsig="1" usefillcolor="1" snapx="10" canvaswidth="908" snapy="10" showatts="1" xmi.id="ngoLeq2pOCZE" documentation="" type="3" showops="1" showpackage="0" name="Automation Messages" localid="dqOLIZOnXkGB" showstereotype="0" showscope="1" snapcsgrid="0" font="Sans Serif,8,-1,5,50,0,0,0,0,0" linecolor="#ff0000" canvasheight="598" >
+        <widgets>
+         <objectwidget usesdiagramfillcolor="0" width="112" x="150" fillcolor="#ffffc0" y="50" instancename="" linewidth="none" height="28" usefillcolor="1" isinstance="0" xmi.id="lZR99u8H0ec9" decon="0" localid="5MaOitMlK05R" multipleinstance="0" drawasactor="0" usesdiagramusefillcolor="1" font="Sans Serif,8,-1,5,50,0,1,0,0,0" linecolor="#ff0000" />
+         <objectwidget usesdiagramfillcolor="0" width="64" x="300" fillcolor="#ffffc0" y="50" instancename="" linewidth="none" height="28" usefillcolor="1" isinstance="0" xmi.id="GkJy5VAr2ZyC" decon="0" localid="R6RQi1ZnL9A3" multipleinstance="0" drawasactor="0" usesdiagramusefillcolor="1" font="Sans Serif,8,-1,5,50,0,1,0,0,0" linecolor="#ff0000" />
+         <objectwidget usesdiagramfillcolor="0" width="68" x="420" fillcolor="#ffffc0" y="50" instancename="" linewidth="none" height="28" usefillcolor="1" isinstance="0" xmi.id="0UoIUrhibyff" decon="0" localid="RnWUKUelY3BH" multipleinstance="0" drawasactor="0" usesdiagramusefillcolor="1" font="Sans Serif,8,-1,5,50,0,1,0,0,0" linecolor="#ff0000" />
+         <objectwidget usesdiagramfillcolor="0" width="84" x="40" fillcolor="#ffffc0" y="50" instancename="" linewidth="none" height="28" usefillcolor="1" isinstance="0" xmi.id="ZEsHfPFxkUD5" decon="0" localid="X0MTdrbPt7CP" multipleinstance="0" drawasactor="0" usesdiagramusefillcolor="1" font="Sans Serif,8,-1,5,50,0,1,0,0,0" linecolor="#ff0000" />
+         <objectwidget usesdiagramfillcolor="0" width="88" x="550" fillcolor="#ffffc0" y="50" instancename="" linewidth="none" height="28" usefillcolor="1" isinstance="0" xmi.id="fOCNrWNOlFdQ" decon="0" localid="dqOLIZOnXkGB" multipleinstance="0" drawasactor="0" usesdiagramusefillcolor="1" font="Sans Serif,8,-1,5,50,0,1,0,0,0" linecolor="#ff0000" />
+         <notewidget usesdiagramfillcolor="1" width="84" x="610" fillcolor="none" y="320" linewidth="none" height="50" usefillcolor="1" isinstance="0" xmi.id="yQmtlfQN4OB7" text="Other Analysis" usesdiagramusefillcolor="1" font="Sans Serif,8,-1,5,50,0,0,0,0,0" linecolor="none" />
+        </widgets>
+        <messages>
+         <messagewidget usesdiagramfillcolor="1" width="124" x="207" fillcolor="none" y="122" operation="74kpdjSI8Fmc" linewidth="none" widgetbid="R6RQi1ZnL9A3" height="8" usefillcolor="1" seqnum="" textid="nCbzGolht3FI" widgetaid="5MaOitMlK05R" isinstance="0" xmi.id="74kpdjSI8Fmc" sequencemessagetype="1001" usesdiagramusefillcolor="1" font="Sans Serif,8,-1,5,50,0,0,0,0,0" linecolor="none" >
+          <floatingtext usesdiagramfillcolor="1" width="89" x="210" fillcolor="none" y="100" linewidth="none" posttext="" role="704" height="22" usefillcolor="1" pretext="" isinstance="0" xmi.id="nCbzGolht3FI" text=": startCopy()" usesdiagramusefillcolor="1" font="Sans Serif,8,-1,5,50,0,0,0,0,0" linecolor="none" />
+         </messagewidget>
+         <messagewidget usesdiagramfillcolor="1" width="124" x="207" fillcolor="none" y="156" operation="74kpdjSI8Fmc" linewidth="none" widgetbid="R6RQi1ZnL9A3" height="8" usefillcolor="1" seqnum="" textid="6DI3dhqqGq64" widgetaid="5MaOitMlK05R" isinstance="0" xmi.id="74kpdjSI8Fmc" sequencemessagetype="1001" usesdiagramusefillcolor="1" font="Sans Serif,8,-1,5,50,0,0,0,0,0" linecolor="none" >
+          <floatingtext usesdiagramfillcolor="1" width="89" x="210" fillcolor="none" y="130" linewidth="none" posttext="" role="704" height="22" usefillcolor="1" pretext="" isinstance="0" xmi.id="6DI3dhqqGq64" text=": startCopy()" usesdiagramusefillcolor="1" font="Sans Serif,8,-1,5,50,0,0,0,0,0" linecolor="none" />
+         </messagewidget>
+         <messagewidget usesdiagramfillcolor="1" width="124" x="207" fillcolor="none" y="211" operation="74kpdjSI8Fmc" linewidth="none" widgetbid="R6RQi1ZnL9A3" height="8" usefillcolor="1" seqnum="" textid="HQIrp1PvEiws" widgetaid="5MaOitMlK05R" isinstance="0" xmi.id="74kpdjSI8Fmc" sequencemessagetype="1001" usesdiagramusefillcolor="1" font="Sans Serif,8,-1,5,50,0,0,0,0,0" linecolor="none" >
+          <floatingtext usesdiagramfillcolor="1" width="89" x="230" fillcolor="none" y="190" linewidth="none" posttext="" role="704" height="22" usefillcolor="1" pretext="" isinstance="0" xmi.id="HQIrp1PvEiws" text=": startCopy()" usesdiagramusefillcolor="1" font="Sans Serif,8,-1,5,50,0,0,0,0,0" linecolor="none" />
+         </messagewidget>
+         <messagewidget usesdiagramfillcolor="1" width="124" x="207" fillcolor="none" y="253" operation="ZEGJnJU2LB4i" linewidth="none" widgetbid="R6RQi1ZnL9A3" height="8" usefillcolor="1" seqnum="" textid="fVIZsE2zucZA" widgetaid="5MaOitMlK05R" isinstance="0" xmi.id="ZEGJnJU2LB4i" sequencemessagetype="1001" usesdiagramusefillcolor="1" font="Sans Serif,8,-1,5,50,0,0,0,0,0" linecolor="none" >
+          <floatingtext usesdiagramfillcolor="1" width="243" x="210" fillcolor="none" y="230" linewidth="none" posttext="" role="704" height="22" usefillcolor="1" pretext="" isinstance="0" xmi.id="fVIZsE2zucZA" text=": sequencingFinished(runDir : string)" usesdiagramusefillcolor="1" font="Sans Serif,8,-1,5,50,0,0,0,0,0" linecolor="none" />
+         </messagewidget>
+         <messagewidget usesdiagramfillcolor="1" width="120" x="333" fillcolor="none" y="282" operation="t3UPqTGoZoip" linewidth="none" widgetbid="RnWUKUelY3BH" height="8" usefillcolor="1" seqnum="" textid="uyOuUAqAf6hV" widgetaid="R6RQi1ZnL9A3" isinstance="0" xmi.id="t3UPqTGoZoip" sequencemessagetype="1001" usesdiagramusefillcolor="1" font="Sans Serif,8,-1,5,50,0,0,0,0,0" linecolor="none" >
+          <floatingtext usesdiagramfillcolor="1" width="243" x="340" fillcolor="none" y="260" linewidth="none" posttext="" role="704" height="22" usefillcolor="1" pretext="" isinstance="0" xmi.id="uyOuUAqAf6hV" text=": sequencingFinished(runDir : string)" usesdiagramusefillcolor="1" font="Sans Serif,8,-1,5,50,0,0,0,0,0" linecolor="none" />
+         </messagewidget>
+         <messagewidget usesdiagramfillcolor="1" width="258" x="74" fillcolor="none" y="243" operation="crYTwksc84xx" linewidth="none" widgetbid="X0MTdrbPt7CP" height="32" usefillcolor="1" seqnum="" textid="R4DtGIV3Lxs4" widgetaid="R6RQi1ZnL9A3" isinstance="0" xmi.id="crYTwksc84xx" sequencemessagetype="1000" usesdiagramusefillcolor="1" font="Sans Serif,8,-1,5,50,0,0,0,0,0" linecolor="none" >
+          <floatingtext usesdiagramfillcolor="1" width="61" x="80" fillcolor="none" y="220" linewidth="none" posttext="" role="704" height="22" usefillcolor="1" pretext="" isinstance="0" xmi.id="R4DtGIV3Lxs4" text=": rsync()" usesdiagramusefillcolor="1" font="Sans Serif,8,-1,5,50,0,0,0,0,0" linecolor="none" />
+         </messagewidget>
+         <messagewidget usesdiagramfillcolor="1" width="138" x="455" fillcolor="none" y="302" operation="bKJKYukBj6Uz" linewidth="none" widgetbid="dqOLIZOnXkGB" height="8" usefillcolor="1" seqnum="" textid="jD2f9bGadRRu" widgetaid="RnWUKUelY3BH" isinstance="0" xmi.id="bKJKYukBj6Uz" sequencemessagetype="1001" usesdiagramusefillcolor="1" font="Sans Serif,8,-1,5,50,0,0,0,0,0" linecolor="none" >
+          <floatingtext usesdiagramfillcolor="1" width="118" x="460" fillcolor="none" y="280" linewidth="none" posttext="" role="704" height="22" usefillcolor="1" pretext="" isinstance="0" xmi.id="jD2f9bGadRRu" text=": configPipeline()" usesdiagramusefillcolor="1" font="Sans Serif,8,-1,5,50,0,0,0,0,0" linecolor="none" />
+         </messagewidget>
+         <messagewidget usesdiagramfillcolor="1" width="138" x="455" fillcolor="none" y="330" operation="dtnBd3KDtsgp" linewidth="none" widgetbid="dqOLIZOnXkGB" height="8" usefillcolor="1" seqnum="" textid="oy8nMfs4rsj0" widgetaid="RnWUKUelY3BH" isinstance="0" xmi.id="dtnBd3KDtsgp" sequencemessagetype="1001" usesdiagramusefillcolor="1" font="Sans Serif,8,-1,5,50,0,0,0,0,0" linecolor="none" >
+          <floatingtext usesdiagramfillcolor="1" width="99" x="470" fillcolor="none" y="310" linewidth="none" posttext="" role="704" height="22" usefillcolor="1" pretext="" isinstance="0" xmi.id="oy8nMfs4rsj0" text=": runPipeline()" usesdiagramusefillcolor="1" font="Sans Serif,8,-1,5,50,0,0,0,0,0" linecolor="none" />
+         </messagewidget>
+         <messagewidget usesdiagramfillcolor="1" width="258" x="74" fillcolor="none" y="145" operation="crYTwksc84xx" linewidth="none" widgetbid="X0MTdrbPt7CP" height="32" usefillcolor="1" seqnum="" textid="VoaFYvDhgi96" widgetaid="R6RQi1ZnL9A3" isinstance="0" xmi.id="crYTwksc84xx" sequencemessagetype="1000" usesdiagramusefillcolor="1" font="Sans Serif,8,-1,5,50,0,0,0,0,0" linecolor="none" >
+          <floatingtext usesdiagramfillcolor="1" width="61" x="80" fillcolor="none" y="120" linewidth="none" posttext="" role="704" height="22" usefillcolor="1" pretext="" isinstance="0" xmi.id="VoaFYvDhgi96" text=": rsync()" usesdiagramusefillcolor="1" font="Sans Serif,8,-1,5,50,0,0,0,0,0" linecolor="none" />
+         </messagewidget>
+        </messages>
+        <associations/>
+       </diagram>
+       <diagram snapgrid="0" showattsig="1" fillcolor="#ffffc0" linewidth="0" zoom="100" showgrid="0" showopsig="1" usefillcolor="1" snapx="10" canvaswidth="908" snapy="10" showatts="1" xmi.id="RiyrnSAT4B8Q" documentation="" type="6" showops="1" showpackage="0" name="Workflow" localid="" showstereotype="0" showscope="1" snapcsgrid="0" font="Sans Serif,8,-1,5,50,0,0,0,0,0" linecolor="#ff0000" canvasheight="598" >
+        <widgets>
+         <activitywidget usesdiagramfillcolor="1" width="18" activityname="" x="339" fillcolor="none" y="42" linewidth="none" height="18" usefillcolor="1" isinstance="0" xmi.id="nDqhFC5OuUo0" documentation="" activitytype="2" usesdiagramusefillcolor="1" font="Sans Serif,8,-1,5,50,0,0,0,0,0" linecolor="none" />
+         <activitywidget usesdiagramfillcolor="1" width="85" activityname="Sequencing" x="306" fillcolor="none" y="92" linewidth="none" height="28" usefillcolor="1" isinstance="0" xmi.id="cmmyYRDzACpv" documentation="" activitytype="1" usesdiagramusefillcolor="1" font="Sans Serif,8,-1,5,50,0,0,0,0,0" linecolor="none" />
+         <activitywidget usesdiagramfillcolor="0" width="199" activityname="Copy Files to Analysis Server" x="250" fillcolor="#ffffc0" y="215" linewidth="none" height="28" usefillcolor="1" isinstance="0" xmi.id="cxOg8W7uhvbu" documentation="" activitytype="1" usesdiagramusefillcolor="0" font="Sans Serif,8,-1,5,50,0,0,0,0,0" linecolor="#ff0000" />
+         <activitywidget usesdiagramfillcolor="1" width="91" activityname="Run Pipeline" x="303" fillcolor="none" y="273" linewidth="none" height="28" usefillcolor="1" isinstance="0" xmi.id="DvKN59Gi6eMD" documentation="" activitytype="1" usesdiagramusefillcolor="1" font="Sans Serif,8,-1,5,50,0,0,0,0,0" linecolor="none" />
+         <activitywidget usesdiagramfillcolor="1" width="157" activityname="Write to external drive" x="271" fillcolor="none" y="154" linewidth="none" height="28" usefillcolor="1" isinstance="0" xmi.id="9R7qMgjRKzQT" documentation="" activitytype="1" usesdiagramusefillcolor="1" font="Sans Serif,8,-1,5,50,0,0,0,0,0" linecolor="none" />
+         <activitywidget usesdiagramfillcolor="1" width="142" activityname="Move External Drive" x="501" fillcolor="none" y="154" linewidth="none" height="28" usefillcolor="1" isinstance="0" xmi.id="BvRo4ZPN3mCG" documentation="" activitytype="1" usesdiagramusefillcolor="1" font="Sans Serif,8,-1,5,50,0,0,0,0,0" linecolor="none" />
+         <activitywidget usesdiagramfillcolor="1" width="89" activityname="Install Drive" x="136" fillcolor="none" y="153" linewidth="none" height="28" usefillcolor="1" isinstance="0" xmi.id="5eRwnFjqlrtq" documentation="" activitytype="1" usesdiagramusefillcolor="1" font="Sans Serif,8,-1,5,50,0,0,0,0,0" linecolor="none" />
+         <activitywidget usesdiagramfillcolor="1" width="77" activityname="Save Data" x="523" fillcolor="none" y="264" linewidth="none" height="28" usefillcolor="1" isinstance="0" xmi.id="aBdJn5SgWGi6" documentation="" activitytype="1" usesdiagramusefillcolor="1" font="Sans Serif,8,-1,5,50,0,0,0,0,0" linecolor="none" />
+        </widgets>
+        <messages/>
+        <associations>
+         <assocwidget totalcounta="2" indexa="1" visibilityB="200" totalcountb="2" indexb="1" linewidth="none" widgetbid="nDqhFC5OuUo0" widgetaid="cmmyYRDzACpv" roleBdoc="" documentation="" roleAdoc="" type="515" changeabilityA="900" changeabilityB="900" linecolor="none" visibilityA="200" >
+          <linepath>
+           <startpoint startx="348" starty="92" />
+           <endpoint endx="348" endy="60" />
+          </linepath>
+         </assocwidget>
+         <assocwidget totalcounta="2" indexa="1" visibilityB="200" totalcountb="2" indexb="1" linewidth="none" widgetbid="cmmyYRDzACpv" widgetaid="9R7qMgjRKzQT" roleBdoc="" documentation="" roleAdoc="" type="515" changeabilityA="900" changeabilityB="900" linecolor="none" visibilityA="200" >
+          <linepath>
+           <startpoint startx="349" starty="154" />
+           <endpoint endx="348" endy="120" />
+          </linepath>
+         </assocwidget>
+         <assocwidget totalcounta="2" indexa="1" visibilityB="200" totalcountb="2" indexb="1" linewidth="none" widgetbid="9R7qMgjRKzQT" widgetaid="cxOg8W7uhvbu" roleBdoc="" documentation="" roleAdoc="" type="515" changeabilityA="900" changeabilityB="900" linecolor="none" visibilityA="200" >
+          <linepath>
+           <startpoint startx="349" starty="215" />
+           <endpoint endx="349" endy="182" />
+          </linepath>
+         </assocwidget>
+         <assocwidget totalcounta="2" indexa="1" visibilityB="200" totalcountb="2" indexb="1" linewidth="none" widgetbid="cxOg8W7uhvbu" widgetaid="DvKN59Gi6eMD" roleBdoc="" documentation="" roleAdoc="" type="515" changeabilityA="900" changeabilityB="900" linecolor="none" visibilityA="200" >
+          <linepath>
+           <startpoint startx="348" starty="273" />
+           <endpoint endx="349" endy="243" />
+          </linepath>
+         </assocwidget>
+         <assocwidget totalcounta="2" indexa="1" visibilityB="200" totalcountb="2" indexb="1" linewidth="none" widgetbid="9R7qMgjRKzQT" widgetaid="BvRo4ZPN3mCG" roleBdoc="" documentation="" roleAdoc="" type="515" changeabilityA="900" changeabilityB="900" linecolor="none" visibilityA="200" >
+          <linepath>
+           <startpoint startx="501" starty="168" />
+           <endpoint endx="428" endy="168" />
+          </linepath>
+         </assocwidget>
+         <assocwidget totalcounta="2" indexa="1" visibilityB="200" totalcountb="2" indexb="1" linewidth="none" widgetbid="9R7qMgjRKzQT" widgetaid="5eRwnFjqlrtq" roleBdoc="" documentation="" roleAdoc="" type="515" changeabilityA="900" changeabilityB="900" linecolor="none" visibilityA="200" >
+          <linepath>
+           <startpoint startx="225" starty="167" />
+           <endpoint endx="271" endy="168" />
+          </linepath>
+         </assocwidget>
+         <assocwidget totalcounta="2" indexa="1" visibilityB="200" totalcountb="2" indexb="1" linewidth="none" widgetbid="DvKN59Gi6eMD" widgetaid="aBdJn5SgWGi6" roleBdoc="" documentation="" roleAdoc="" type="515" changeabilityA="900" changeabilityB="900" linecolor="none" visibilityA="200" >
+          <linepath>
+           <startpoint startx="523" starty="278" />
+           <endpoint endx="394" endy="287" />
+          </linepath>
+         </assocwidget>
+        </associations>
+       </diagram>
+      </diagrams>
+     </XMI.extension>
+    </UML:Model>
+    <UML:Model stereotype="folder" isSpecification="false" isLeaf="false" visibility="public" namespace="m1" xmi.id="Use Case View" isRoot="false" isAbstract="false" name="Use Case View" >
+     <UML:Namespace.ownedElement>
+      <UML:Actor isSpecification="false" isLeaf="false" visibility="public" namespace="Use Case View" xmi.id="wDCVwIFrhMaS" isRoot="false" isAbstract="false" name="Operator" />
+      <UML:UseCase comment="Provide information about whats on the flow cell" isSpecification="false" isLeaf="false" visibility="public" namespace="Use Case View" xmi.id="aary2tTYpejI" isRoot="false" isAbstract="false" name="Record Flowcell" />
+      <UML:UseCase isSpecification="false" isLeaf="false" visibility="public" namespace="Use Case View" xmi.id="v00EW2tPUmHx" isRoot="false" isAbstract="false" name="Load Flowcell" />
+      <UML:Association isSpecification="false" visibility="public" namespace="Use Case View" xmi.id="D32Eg5GAZoTf" name="" >
+       <UML:Association.connection>
+        <UML:AssociationEnd isSpecification="false" visibility="public" changeability="changeable" isNavigable="false" xmi.id="im2Ma7pqk1go" aggregation="none" type="wDCVwIFrhMaS" name="" />
+        <UML:AssociationEnd isSpecification="false" visibility="public" changeability="changeable" isNavigable="true" xmi.id="JGXPBjJbeiAi" aggregation="none" type="v00EW2tPUmHx" name="" />
+       </UML:Association.connection>
+      </UML:Association>
+      <UML:Actor isSpecification="false" isLeaf="false" visibility="public" namespace="Use Case View" xmi.id="xXmvstAj2F1b" isRoot="false" isAbstract="false" name="Scientist" />
+      <UML:UseCase isSpecification="false" isLeaf="false" visibility="public" namespace="Use Case View" xmi.id="6W9A6jRKMyiY" isRoot="false" isAbstract="false" name="Invent Experiment" />
+      <UML:Association isSpecification="false" visibility="public" namespace="Use Case View" xmi.id="HXNdOVEJ2yFu" name="" >
+       <UML:Association.connection>
+        <UML:AssociationEnd isSpecification="false" visibility="public" changeability="changeable" isNavigable="false" xmi.id="yITcydItLy4l" aggregation="none" type="xXmvstAj2F1b" name="" />
+        <UML:AssociationEnd isSpecification="false" visibility="public" changeability="changeable" isNavigable="true" xmi.id="a9t0EMzPekMx" aggregation="none" type="6W9A6jRKMyiY" name="" />
+       </UML:Association.connection>
+      </UML:Association>
+      <UML:Actor isSpecification="false" isLeaf="false" visibility="public" namespace="Use Case View" xmi.id="zIN0Fcs4yUuY" isRoot="false" isAbstract="false" name="Tech" />
+      <UML:Association isSpecification="false" visibility="public" namespace="Use Case View" xmi.id="6IDDq0N6EshP" name="" >
+       <UML:Association.connection>
+        <UML:AssociationEnd isSpecification="false" visibility="public" changeability="changeable" isNavigable="false" xmi.id="sAgD6kygrqe4" aggregation="none" type="6W9A6jRKMyiY" name="" />
+        <UML:AssociationEnd isSpecification="false" visibility="public" changeability="changeable" isNavigable="true" xmi.id="CcTqMqnYuKTD" aggregation="none" type="zIN0Fcs4yUuY" name="" />
+       </UML:Association.connection>
+      </UML:Association>
+      <UML:UseCase isSpecification="false" isLeaf="false" visibility="public" namespace="Use Case View" xmi.id="RAZe0E33zvnD" isRoot="false" isAbstract="false" name="Make Library/Flowcell" />
+      <UML:UseCase isSpecification="false" isLeaf="false" visibility="public" namespace="Use Case View" xmi.id="pbO2oXRaYvK4" isRoot="false" isAbstract="false" name="Run Sequencer" />
+      <UML:UseCase isSpecification="false" isLeaf="false" visibility="public" namespace="Use Case View" xmi.id="Fbgs2TKUbr8c" isRoot="false" isAbstract="false" name="Analyze Results" />
+      <UML:Association isSpecification="false" visibility="public" namespace="Use Case View" xmi.id="5Bd35QEuzaun" name="" >
+       <UML:Association.connection>
+        <UML:AssociationEnd isSpecification="false" visibility="public" changeability="changeable" isNavigable="false" xmi.id="oOnDUNhItqyD" aggregation="none" type="zIN0Fcs4yUuY" name="" />
+        <UML:AssociationEnd isSpecification="false" visibility="public" changeability="changeable" isNavigable="true" xmi.id="agjwu5fvAtpc" aggregation="none" type="RAZe0E33zvnD" name="" />
+       </UML:Association.connection>
+      </UML:Association>
+      <UML:Association isSpecification="false" visibility="public" namespace="Use Case View" xmi.id="1pz0NtlUD78R" name="" >
+       <UML:Association.connection>
+        <UML:AssociationEnd isSpecification="false" visibility="public" changeability="changeable" isNavigable="false" xmi.id="btsXOnyABmNd" aggregation="none" type="RAZe0E33zvnD" name="" />
+        <UML:AssociationEnd isSpecification="false" visibility="public" changeability="changeable" isNavigable="true" xmi.id="72tyrqBRWuOO" aggregation="none" type="pbO2oXRaYvK4" name="" />
+       </UML:Association.connection>
+      </UML:Association>
+      <UML:Association isSpecification="false" visibility="public" namespace="Use Case View" xmi.id="wf97mFWW4OYF" name="" >
+       <UML:Association.connection>
+        <UML:AssociationEnd isSpecification="false" visibility="public" changeability="changeable" isNavigable="false" xmi.id="x1K7u7hTya0K" aggregation="none" type="zIN0Fcs4yUuY" name="" />
+        <UML:AssociationEnd isSpecification="false" visibility="public" changeability="changeable" isNavigable="true" xmi.id="LxUWjq7qsyoj" aggregation="none" type="pbO2oXRaYvK4" name="" />
+       </UML:Association.connection>
+      </UML:Association>
+      <UML:UseCase isSpecification="false" isLeaf="false" visibility="public" namespace="Use Case View" xmi.id="0WdsQGkGLh4j" isRoot="false" isAbstract="false" name="Run Pipeline" />
+      <UML:Association isSpecification="false" visibility="public" namespace="Use Case View" xmi.id="emmY8xZHM4vp" name="" >
+       <UML:Association.connection>
+        <UML:AssociationEnd isSpecification="false" visibility="public" changeability="changeable" isNavigable="false" xmi.id="6Zs37qLSnowW" aggregation="none" type="pbO2oXRaYvK4" name="" />
+        <UML:AssociationEnd isSpecification="false" visibility="public" changeability="changeable" isNavigable="true" xmi.id="e3KU18Co5L7u" aggregation="none" type="0WdsQGkGLh4j" name="" />
+       </UML:Association.connection>
+      </UML:Association>
+      <UML:Association isSpecification="false" visibility="public" namespace="Use Case View" xmi.id="v4mH9h65lcXr" name="pipelineFinished" >
+       <UML:Association.connection>
+        <UML:AssociationEnd isSpecification="false" visibility="public" changeability="changeable" isNavigable="false" xmi.id="qogMqYmu7cvE" aggregation="none" type="0WdsQGkGLh4j" name="" />
+        <UML:AssociationEnd isSpecification="false" visibility="public" changeability="changeable" isNavigable="true" xmi.id="THgG5cnhd59c" aggregation="none" type="Fbgs2TKUbr8c" name="" />
+       </UML:Association.connection>
+      </UML:Association>
+      <UML:Association isSpecification="false" visibility="public" namespace="Use Case View" xmi.id="m2hVcDMd6HZv" name="" >
+       <UML:Association.connection>
+        <UML:AssociationEnd isSpecification="false" visibility="public" changeability="changeable" isNavigable="false" xmi.id="ELOHeVCUKsq8" aggregation="none" type="Fbgs2TKUbr8c" name="" />
+        <UML:AssociationEnd isSpecification="false" visibility="public" changeability="changeable" isNavigable="true" xmi.id="Ob2VDwhgBNvG" aggregation="none" type="xXmvstAj2F1b" name="" />
+       </UML:Association.connection>
+      </UML:Association>
+      <UML:Actor isSpecification="false" isLeaf="false" visibility="public" namespace="Use Case View" xmi.id="orBOqPqaQhh9" isRoot="false" isAbstract="false" name="Unix Operator" />
+      <UML:Association isSpecification="false" visibility="public" namespace="Use Case View" xmi.id="zrptlXTBrDvp" name="sequencerFinished" >
+       <UML:Association.connection>
+        <UML:AssociationEnd isSpecification="false" visibility="public" changeability="changeable" isNavigable="false" xmi.id="3RTNHJVgsVB4" aggregation="none" type="pbO2oXRaYvK4" name="" />
+        <UML:AssociationEnd isSpecification="false" visibility="public" changeability="changeable" isNavigable="true" xmi.id="PtiaEdoAHji3" aggregation="none" type="orBOqPqaQhh9" name="" />
+       </UML:Association.connection>
+      </UML:Association>
+      <UML:Association isSpecification="false" visibility="public" namespace="Use Case View" xmi.id="x3IpEDeI7z8A" name="" >
+       <UML:Association.connection>
+        <UML:AssociationEnd isSpecification="false" visibility="public" changeability="changeable" isNavigable="false" xmi.id="mr08PF6QeZnu" aggregation="none" type="orBOqPqaQhh9" name="" />
+        <UML:AssociationEnd isSpecification="false" visibility="public" changeability="changeable" isNavigable="true" xmi.id="rGajljlBaUln" aggregation="none" type="0WdsQGkGLh4j" name="" />
+       </UML:Association.connection>
+      </UML:Association>
+      <UML:UseCase isSpecification="false" isLeaf="false" visibility="public" namespace="Use Case View" xmi.id="fRDOlDmrPkQK" isRoot="false" isAbstract="false" name="Add experiment to FCTracker" />
+      <UML:Association isSpecification="false" visibility="public" namespace="Use Case View" xmi.id="W1lC1TWN9J6x" name="" >
+       <UML:Association.connection>
+        <UML:AssociationEnd isSpecification="false" visibility="public" changeability="changeable" isNavigable="false" xmi.id="uOynvlhbBsxw" aggregation="none" type="RAZe0E33zvnD" name="" />
+        <UML:AssociationEnd isSpecification="false" visibility="public" changeability="changeable" isNavigable="true" xmi.id="4jnTTClxDUAD" aggregation="none" type="fRDOlDmrPkQK" name="" />
+       </UML:Association.connection>
+      </UML:Association>
+      <UML:Association isSpecification="false" visibility="public" namespace="Use Case View" xmi.id="xgY5yc2s10ew" name="" >
+       <UML:Association.connection>
+        <UML:AssociationEnd isSpecification="false" visibility="public" changeability="changeable" isNavigable="false" xmi.id="mJVvCSrEBdhd" aggregation="none" type="fRDOlDmrPkQK" name="" />
+        <UML:AssociationEnd isSpecification="false" visibility="public" changeability="changeable" isNavigable="true" xmi.id="nQayukBpPiuN" aggregation="none" type="pbO2oXRaYvK4" name="" />
+       </UML:Association.connection>
+      </UML:Association>
+      <UML:Association isSpecification="false" visibility="public" namespace="Use Case View" xmi.id="KuDJvrGfBlaL" name="" >
+       <UML:Association.connection>
+        <UML:AssociationEnd isSpecification="false" visibility="public" changeability="changeable" isNavigable="false" xmi.id="CPZN0EyncZmU" aggregation="none" type="pbO2oXRaYvK4" name="" />
+        <UML:AssociationEnd isSpecification="false" visibility="public" changeability="changeable" isNavigable="true" xmi.id="HvJedC1y6DHz" aggregation="none" type="orBOqPqaQhh9" name="" />
+       </UML:Association.connection>
+      </UML:Association>
+     </UML:Namespace.ownedElement>
+     <XMI.extension xmi.extender="umbrello" >
+      <diagrams>
+       <diagram snapgrid="0" showattsig="1" fillcolor="#ffffc0" linewidth="0" zoom="100" showgrid="0" showopsig="1" usefillcolor="1" snapx="10" canvaswidth="908" snapy="10" showatts="1" xmi.id="chIzX4xQS4VY" documentation="" type="2" showops="1" showpackage="0" name="Basic Pipeline Use" localid="" showstereotype="0" showscope="1" snapcsgrid="0" font="Sans Serif,8,-1,5,50,0,0,0,0,0" linecolor="#ff0000" canvasheight="598" >
+        <widgets>
+         <actorwidget usesdiagramfillcolor="1" width="66" x="114" fillcolor="none" y="41" linewidth="none" height="63" usefillcolor="1" isinstance="0" xmi.id="xXmvstAj2F1b" usesdiagramusefillcolor="1" font="Sans Serif,8,-1,5,50,0,0,0,0,0" linecolor="none" />
+         <usecasewidget usesdiagramfillcolor="1" width="144" x="227" fillcolor="none" y="35" linewidth="none" height="53" usefillcolor="1" isinstance="0" xmi.id="6W9A6jRKMyiY" usesdiagramusefillcolor="1" font="Sans Serif,8,-1,5,50,0,0,0,0,0" linecolor="none" />
+         <actorwidget usesdiagramfillcolor="1" width="41" x="405" fillcolor="none" y="44" linewidth="none" height="63" usefillcolor="1" isinstance="0" xmi.id="zIN0Fcs4yUuY" usesdiagramusefillcolor="1" font="Sans Serif,8,-1,5,50,0,0,0,0,0" linecolor="none" />
+         <usecasewidget usesdiagramfillcolor="0" width="169" x="423" fillcolor="#ffffc0" y="117" linewidth="none" height="53" usefillcolor="1" isinstance="0" xmi.id="RAZe0E33zvnD" usesdiagramusefillcolor="0" font="Sans Serif,8,-1,5,75,0,0,0,0,0" linecolor="#ff0000" />
+         <usecasewidget usesdiagramfillcolor="1" width="120" x="435" fillcolor="none" y="326" linewidth="none" height="53" usefillcolor="1" isinstance="0" xmi.id="pbO2oXRaYvK4" usesdiagramusefillcolor="1" font="Sans Serif,8,-1,5,75,0,0,0,0,0" linecolor="none" />
+         <usecasewidget usesdiagramfillcolor="1" width="126" x="68" fillcolor="none" y="142" linewidth="none" height="53" usefillcolor="1" isinstance="0" xmi.id="Fbgs2TKUbr8c" usesdiagramusefillcolor="1" font="Sans Serif,8,-1,5,50,0,0,0,0,0" linecolor="none" />
+         <usecasewidget usesdiagramfillcolor="1" width="101" x="136" fillcolor="none" y="304" linewidth="none" height="53" usefillcolor="1" isinstance="0" xmi.id="0WdsQGkGLh4j" usesdiagramusefillcolor="1" font="Sans Serif,8,-1,5,50,0,0,0,0,0" linecolor="none" />
+         <actorwidget usesdiagramfillcolor="1" width="101" x="309" fillcolor="none" y="325" linewidth="none" height="63" usefillcolor="1" isinstance="0" xmi.id="orBOqPqaQhh9" usesdiagramusefillcolor="1" font="Sans Serif,8,-1,5,50,0,0,0,0,0" linecolor="none" />
+         <notewidget usesdiagramfillcolor="1" width="134" x="302" fillcolor="none" y="394" linewidth="none" height="79" usefillcolor="1" isinstance="0" xmi.id="FKXM4QcNkVr5" text="GAWorkflow Replaces this person" usesdiagramusefillcolor="1" font="Sans Serif,8,-1,5,50,0,0,0,0,0" linecolor="none" />
+         <notewidget usesdiagramfillcolor="1" width="144" x="187" fillcolor="none" y="253" linewidth="none" height="50" usefillcolor="1" isinstance="0" xmi.id="t032iSzd0y7F" text="Solexa Pipeline" usesdiagramusefillcolor="1" font="Sans Serif,8,-1,5,50,0,0,0,0,0" linecolor="none" />
+         <notewidget usesdiagramfillcolor="1" width="95" x="13" fillcolor="none" y="201" linewidth="none" height="88" usefillcolor="1" isinstance="0" xmi.id="5fYgSqBh8i2C" text="Peak Calling, MRPM, etc." usesdiagramusefillcolor="1" font="Sans Serif,8,-1,5,50,0,0,0,0,0" linecolor="none" />
+         <usecasewidget usesdiagramfillcolor="1" width="226" x="418" fillcolor="none" y="226" linewidth="none" height="53" usefillcolor="1" isinstance="0" xmi.id="fRDOlDmrPkQK" usesdiagramusefillcolor="1" font="Sans Serif,8,-1,5,50,0,0,0,0,0" linecolor="none" />
+        </widgets>
+        <messages/>
+        <associations>
+         <assocwidget totalcounta="2" indexa="1" totalcountb="2" indexb="1" linewidth="none" widgetbid="6W9A6jRKMyiY" widgetaid="xXmvstAj2F1b" xmi.id="HXNdOVEJ2yFu" type="512" linecolor="none" >
+          <linepath>
+           <startpoint startx="180" starty="72" />
+           <endpoint endx="227" endy="61" />
+          </linepath>
+         </assocwidget>
+         <assocwidget totalcounta="2" indexa="1" totalcountb="2" indexb="1" linewidth="none" widgetbid="zIN0Fcs4yUuY" widgetaid="6W9A6jRKMyiY" xmi.id="6IDDq0N6EshP" type="512" linecolor="none" >
+          <linepath>
+           <startpoint startx="371" starty="61" />
+           <endpoint endx="405" endy="75" />
+          </linepath>
+         </assocwidget>
+         <assocwidget totalcounta="2" indexa="1" totalcountb="2" indexb="1" linewidth="none" widgetbid="RAZe0E33zvnD" widgetaid="zIN0Fcs4yUuY" xmi.id="5Bd35QEuzaun" type="512" linecolor="none" >
+          <linepath>
+           <startpoint startx="446" starty="75" />
+           <endpoint endx="507" endy="117" />
+          </linepath>
+         </assocwidget>
+         <assocwidget totalcounta="2" indexa="1" totalcountb="2" indexb="1" linewidth="none" widgetbid="Fbgs2TKUbr8c" widgetaid="0WdsQGkGLh4j" xmi.id="v4mH9h65lcXr" type="512" linecolor="none" >
+          <linepath>
+           <startpoint startx="186" starty="304" />
+           <endpoint endx="131" endy="195" />
+           <point x="147" y="245" />
+          </linepath>
+          <floatingtext usesdiagramfillcolor="1" width="114" x="140" fillcolor="none" y="244" linewidth="none" posttext="" role="703" height="22" usefillcolor="1" pretext="" isinstance="0" xmi.id="5YxNFdO1TO7q" text="pipelineFinished" usesdiagramusefillcolor="1" font="Sans Serif,8,-1,5,50,0,0,0,0,0" linecolor="none" />
+         </assocwidget>
+         <assocwidget totalcounta="2" indexa="1" totalcountb="2" indexb="1" linewidth="none" widgetbid="xXmvstAj2F1b" widgetaid="Fbgs2TKUbr8c" xmi.id="m2hVcDMd6HZv" type="512" linecolor="none" >
+          <linepath>
+           <startpoint startx="131" starty="142" />
+           <endpoint endx="147" endy="104" />
+          </linepath>
+         </assocwidget>
+         <assocwidget totalcounta="2" indexa="1" totalcountb="2" indexb="1" linewidth="none" widgetbid="0WdsQGkGLh4j" widgetaid="orBOqPqaQhh9" xmi.id="x3IpEDeI7z8A" type="512" linecolor="none" >
+          <linepath>
+           <startpoint startx="309" starty="356" />
+           <endpoint endx="237" endy="330" />
+          </linepath>
+         </assocwidget>
+         <assocwidget totalcounta="2" indexa="1" totalcountb="2" indexb="1" linewidth="none" widgetbid="fRDOlDmrPkQK" widgetaid="RAZe0E33zvnD" xmi.id="W1lC1TWN9J6x" type="512" linecolor="none" >
+          <linepath>
+           <startpoint startx="507" starty="170" />
+           <endpoint endx="531" endy="226" />
+          </linepath>
+         </assocwidget>
+         <assocwidget totalcounta="2" indexa="1" totalcountb="2" indexb="1" linewidth="none" widgetbid="pbO2oXRaYvK4" widgetaid="fRDOlDmrPkQK" xmi.id="xgY5yc2s10ew" type="512" linecolor="none" >
+          <linepath>
+           <startpoint startx="531" starty="279" />
+           <endpoint endx="495" endy="326" />
+          </linepath>
+         </assocwidget>
+         <assocwidget totalcounta="2" indexa="1" totalcountb="2" indexb="1" linewidth="none" widgetbid="orBOqPqaQhh9" widgetaid="pbO2oXRaYvK4" xmi.id="KuDJvrGfBlaL" type="512" linecolor="none" >
+          <linepath>
+           <startpoint startx="435" starty="352" />
+           <endpoint endx="410" endy="356" />
+          </linepath>
+         </assocwidget>
+        </associations>
+       </diagram>
+      </diagrams>
+     </XMI.extension>
+    </UML:Model>
+    <UML:Model stereotype="folder" isSpecification="false" isLeaf="false" visibility="public" namespace="m1" xmi.id="Component View" isRoot="false" isAbstract="false" name="Component View" >
+     <UML:Namespace.ownedElement/>
+    </UML:Model>
+    <UML:Model stereotype="folder" isSpecification="false" isLeaf="false" visibility="public" namespace="m1" xmi.id="Deployment View" isRoot="false" isAbstract="false" name="Deployment View" >
+     <UML:Namespace.ownedElement>
+      <UML:Node isSpecification="false" isLeaf="false" visibility="public" namespace="Deployment View" xmi.id="vP7jn92WuoSw" isRoot="false" isAbstract="false" name="Cellcenter" />
+     </UML:Namespace.ownedElement>
+     <XMI.extension xmi.extender="umbrello" >
+      <diagrams>
+       <diagram snapgrid="0" showattsig="1" fillcolor="#ffffc0" linewidth="0" zoom="100" showgrid="0" showopsig="1" usefillcolor="1" snapx="10" canvaswidth="908" snapy="10" showatts="1" xmi.id="tO955ukeZk7l" documentation="" type="8" showops="1" showpackage="0" name="Caltech GAWorkflow" localid="" showstereotype="0" showscope="1" snapcsgrid="0" font="Sans Serif,8,-1,5,50,0,0,0,0,0" linecolor="#ff0000" canvasheight="598" >
+        <widgets>
+         <nodewidget usesdiagramfillcolor="1" width="104" x="296" fillcolor="none" y="179" linewidth="none" height="66" usefillcolor="1" isinstance="0" xmi.id="vP7jn92WuoSw" usesdiagramusefillcolor="1" font="Sans Serif,8,-1,5,75,0,0,0,0,0" linecolor="none" />
+        </widgets>
+        <messages/>
+        <associations/>
+       </diagram>
+      </diagrams>
+     </XMI.extension>
+    </UML:Model>
+    <UML:Model stereotype="folder" isSpecification="false" isLeaf="false" visibility="public" namespace="m1" xmi.id="Entity Relationship Model" isRoot="false" isAbstract="false" name="Entity Relationship Model" >
+     <UML:Namespace.ownedElement/>
+    </UML:Model>
+   </UML:Namespace.ownedElement>
+  </UML:Model>
+ </XMI.content>
+ <XMI.extensions xmi.extender="umbrello" >
+  <docsettings viewid="chIzX4xQS4VY" documentation="" uniqueid="HvJedC1y6DHz" />
+  <listview>
+   <listitem open="1" type="800" label="Views" >
+    <listitem open="0" type="801" id="Logical View" >
+     <listitem open="0" type="807" id="qsXVOiQh2k6g" label="class diagram" />
+     <listitem open="0" type="809" id="RiyrnSAT4B8Q" label="Workflow" />
+     <listitem open="0" type="810" id="ngoLeq2pOCZE" label="Automation Messages" />
+     <listitem open="1" type="813" id="fOCNrWNOlFdQ" >
+      <listitem open="0" type="815" id="bKJKYukBj6Uz" />
+      <listitem open="0" type="815" id="dtnBd3KDtsgp" />
+     </listitem>
+     <listitem open="1" type="813" id="GkJy5VAr2ZyC" >
+      <listitem open="0" type="815" id="74kpdjSI8Fmc" />
+      <listitem open="0" type="815" id="ZEGJnJU2LB4i" />
+     </listitem>
+     <listitem open="1" type="813" id="ZEsHfPFxkUD5" >
+      <listitem open="0" type="815" id="crYTwksc84xx" />
+     </listitem>
+     <listitem open="1" type="813" id="0UoIUrhibyff" >
+      <listitem open="0" type="815" id="t3UPqTGoZoip" />
+      <listitem open="0" type="815" id="4yuLbdrUeJtU" />
+     </listitem>
+     <listitem open="1" type="813" id="lZR99u8H0ec9" />
+     <listitem open="0" type="830" id="Datatypes" >
+      <listitem open="1" type="829" id="rF1bLfYctE0z" />
+      <listitem open="1" type="829" id="LzF7NnxDIQZy" />
+      <listitem open="1" type="829" id="dYZty7spMrOf" />
+      <listitem open="1" type="829" id="eTY593cV1paM" />
+      <listitem open="1" type="829" id="zHGiO7QUZRiX" />
+      <listitem open="1" type="829" id="xPJyEuIpQpjO" />
+      <listitem open="1" type="829" id="UuHO7GNlhtpq" />
+      <listitem open="1" type="829" id="dYy4jCaAkpGB" />
+      <listitem open="1" type="829" id="wZCGOYvqRDEY" />
+      <listitem open="1" type="829" id="AcXaREwMl6c9" />
+      <listitem open="1" type="829" id="RZH8T4yOTGKi" />
+     </listitem>
+    </listitem>
+    <listitem open="1" type="802" id="Use Case View" >
+     <listitem open="0" type="805" id="chIzX4xQS4VY" label="Basic Pipeline Use" />
+     <listitem open="1" type="811" id="wDCVwIFrhMaS" />
+     <listitem open="1" type="811" id="xXmvstAj2F1b" />
+     <listitem open="1" type="811" id="zIN0Fcs4yUuY" />
+     <listitem open="1" type="811" id="orBOqPqaQhh9" />
+     <listitem open="1" type="812" id="fRDOlDmrPkQK" />
+     <listitem open="1" type="812" id="Fbgs2TKUbr8c" />
+     <listitem open="1" type="812" id="6W9A6jRKMyiY" />
+     <listitem open="1" type="812" id="v00EW2tPUmHx" />
+     <listitem open="1" type="812" id="RAZe0E33zvnD" />
+     <listitem open="1" type="812" id="aary2tTYpejI" />
+     <listitem open="1" type="812" id="0WdsQGkGLh4j" />
+     <listitem open="1" type="812" id="pbO2oXRaYvK4" />
+    </listitem>
+    <listitem open="1" type="821" id="Component View" />
+    <listitem open="1" type="827" id="Deployment View" >
+     <listitem open="0" type="825" id="tO955ukeZk7l" label="Caltech GAWorkflow" />
+     <listitem open="1" type="828" id="vP7jn92WuoSw" />
+    </listitem>
+    <listitem open="1" type="836" id="Entity Relationship Model" />
+   </listitem>
+  </listview>
+  <codegeneration>
+   <codegenerator language="C++" />
+  </codegeneration>
+ </XMI.extensions>
+</XMI>
diff --git a/trunk/docs/htsworkflow.ini.example b/trunk/docs/htsworkflow.ini.example
new file mode 100644 (file)
index 0000000..e00b8ca
--- /dev/null
@@ -0,0 +1,22 @@
+[frontend]
+; database engine, currently only sqlite3 will work right
+database_engine=sqlite3
+; location of the sqlite3 database
+database_name=/htsworkflow/htswfrontend/dev_tracker.db
+
+; settings for what email server to use
+email_host = localhost
+email_port = 25
+
+; default timezone for this server
+time_zone = America/Los_Angels
+
+; set the default picomolarity when creating a new flowcell
+default_pm = 5
+
+
+[allowed_hosts]
+localhost=localhost
+
+[allowed_analysis_hosts]
+localhost=localhost
diff --git a/trunk/htsworkflow/__init__.py b/trunk/htsworkflow/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/trunk/htsworkflow/automation/__init__.py b/trunk/htsworkflow/automation/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/trunk/htsworkflow/automation/copier.py b/trunk/htsworkflow/automation/copier.py
new file mode 100644 (file)
index 0000000..9ec1e2b
--- /dev/null
@@ -0,0 +1,288 @@
+import ConfigParser
+import copy
+import logging
+import logging.handlers
+import os
+import re
+import shlex
+import subprocess
+import sys
+import time
+import traceback
+
+from benderjab import rpc
+
+def runfolder_validate(fname):
+    """
+    Return True if fname looks like a runfolder name
+    """
+    if re.match("^[0-9]{6}_[-A-Za-z0-9_]*$", fname):
+        return True
+    else:
+        return False
+    
+class rsync(object):
+  def __init__(self, sources, dest, pwfile):
+    self.cmd = ['/usr/bin/rsync', ]
+    self.pwfile = os.path.expanduser(pwfile)
+    self.cmd.append('--password-file=%s' % (self.pwfile))
+    self.source_base_list = [ self._normalize_rsync_source(x) for x in sources]
+    self.dest_base = dest
+    self.processes = {}
+    self.exit_code = None
+
+  def list(self):
+    """
+    Get a directory listing for all our sources
+    """
+    logging.debug("searching for entries in: %s" % (self.source_base_list,))
+    entries = []
+    for source in self.source_base_list:
+        logging.debug("Scanning %s" % (source,))
+        args = copy.copy(self.cmd)
+        args.append(source)
+
+        logging.debug("Rsync cmd:" + " ".join(args))
+        short_process = subprocess.Popen(args, stdout=subprocess.PIPE)
+        exit_code = short_process.wait()
+        stdout = short_process.stdout
+        # We made sure source ends in a / earlier
+        cur_list = [ source+subdir for subdir in self.list_filter(stdout)]
+        entries.extend(cur_list)
+    logging.debug(u"Found the following: %s" % (unicode(entries)))
+    return entries
+
+  def list_filter(self, lines):
+    """
+    parse rsync directory listing
+    """
+    dirs_to_copy = []
+    direntries = [ x[0:42].split() + [x[43:-1]] for x in lines ]
+    logging.debug(u'direntries: %s' % (unicode(direntries),))
+    for permissions, size, filedate, filetime, filename in direntries:
+      if permissions[0] == 'd':
+        # hey its a directory, the first step to being something we want to 
+        # copy
+        if re.match("[0-9]{6}", filename):
+          # it starts with something that looks like a 6 digit date
+          # aka good enough for me
+          dirs_to_copy.append(filename)
+    return dirs_to_copy
+
+  def create_copy_process(self, urlname):
+    args = copy.copy(self.cmd)
+    # args.append('--dry-run') # Makes testing easier
+    # we want to copy everything
+    args.append('-rlt') 
+    # from here
+    args.append(urlname)
+    # to here
+    args.append(self.dest_base)
+    logging.debug("Rsync cmd:" + " ".join(args))
+    return subprocess.Popen(args)
+  def copy(self):
+    """
+    copy any interesting looking directories over
+    return list of items that we started copying.
+    """
+    # clean up any lingering non-running processes
+    self.poll()
+    
+    # what's available to copy?
+    dirs_to_copy = self.list()
+    
+    # lets start copying
+    started = []
+    for d in dirs_to_copy:
+      process = self.processes.get(d, None)
+      
+      if process is None:
+        # we don't have a process, so make one
+        logging.info("rsyncing %s" % (d))
+        self.processes[d] = self.create_copy_process(d)
+        started.append(d)           
+    return started
+
+  def _normalize_rsync_source(self, source):
+      """
+      Make sure that we have a reasonable looking source
+      a source must be a directory/collection.
+      """
+      # we must be a directory
+      if source[-1] != '/':
+        source += '/'
+      # I suppose we could check to see if we start with rsync:// or something
+      return source
+
+  def poll(self):
+      """
+      check currently running processes to see if they're done
+      
+      return path roots that have finished.
+      """
+      for dir_key, proc_value in self.processes.items():
+          retcode = proc_value.poll()
+          if retcode is None:
+              # process hasn't finished yet
+              pass
+          elif retcode == 0:
+              logging.info("finished rsyncing %s, exitcode %d" %( dir_key, retcode))
+              del self.processes[dir_key]
+          else:
+              logging.error("rsync failed for %s, exit code %d" % (dir_key, retcode))
+              
+  def __len__(self):
+      """
+      Return how many active rsync processes we currently have
+      
+      Call poll first to close finished processes.
+      """
+      return len(self.processes)
+  
+  def keys(self):
+      """
+      Return list of current run folder names
+      """
+      return self.processes.keys()
+
+class CopierBot(rpc.XmlRpcBot):
+    def __init__(self, section=None, configfile=None):
+        #if configfile is None:
+        #    configfile = '~/.htsworkflow'
+            
+        super(CopierBot, self).__init__(section, configfile)
+        
+        # options for rsync command
+        self.cfg['rsync_password_file'] = None
+        self.cfg['rsync_source'] = None
+        self.cfg['rsync_destination'] = None 
+        
+        # options for reporting we're done 
+        self.cfg['notify_users'] = None
+        self.cfg['notify_runner'] = None
+                            
+        self.pending = []
+        self.rsync = None
+        self.notify_users = None
+        self.notify_runner = None
+        
+        self.register_function(self.startCopy)
+        self.register_function(self.sequencingFinished)
+        self.eventTasks.append(self.update)
+       
+    def _init_rsync(self):
+        """
+        Initalize rsync class
+
+        This is only accessible for test purposes.
+        """
+        # we can't call any logging function until after start finishes.
+        # this got moved to a seperate function from run to help with test code
+        if self.rsync is None:
+            self.rsync = rsync(self.sources, self.destination, self.password)
+
+    def read_config(self, section=None, configfile=None):
+        """
+        read the config file
+        """
+        super(CopierBot, self).read_config(section, configfile)
+        
+        self.sources = shlex.split(self._check_required_option('rsync_sources'))
+        self.password = self._check_required_option('rsync_password_file')
+        self.destination = self._check_required_option('rsync_destination')
+        
+        self.notify_users = self._parse_user_list(self.cfg['notify_users'])
+        try:
+          self.notify_runner = \
+             self._parse_user_list(self.cfg['notify_runner'],
+                                   require_resource=True)
+        except bot.JIDMissingResource:
+            msg = 'need a full jabber ID + resource for xml-rpc destinations'
+            print >>sys.stderr, msg
+            raise bot.JIDMissingResource(msg)
+
+    def run(self):
+        """
+        Start application
+        """
+        self._init_rsync()
+        super(CopierBot, self).run()
+
+    def startCopy(self, *args):
+        """
+        start our copy
+        """
+        logging.info("starting copy scan, %s" % (args,))
+        started = self.rsync.copy()
+        logging.info("copying:" + " ".join(started)+".")
+        return started
+        
+    def sequencingFinished(self, runDir, *args):
+        """
+        The run was finished, if we're done copying, pass the message on        
+        """
+        # close any open processes
+        self.rsync.poll()
+        
+        # see if we're still copying
+        if runfolder_validate(runDir):
+            logging.info("recevied sequencing finshed for %s" % (runDir))
+            self.pending.append(runDir)
+            self.startCopy()
+            return "PENDING"
+        else:
+            errmsg = "received bad runfolder name (%s)" % (runDir)
+            logging.warning(errmsg)
+            # maybe I should use a different error message
+            raise RuntimeError(errmsg)
+    
+    def reportSequencingFinished(self, runDir):
+        """
+        Send the sequencingFinished message to the interested parties
+        """
+        if self.notify_users is not None:
+            for u in self.notify_users:
+                self.send(u, 'Sequencing run %s finished' % (runDir))
+        if self.notify_runner is not None:
+            for r in self.notify_runner:
+                self.rpc_send(r, (runDir,), 'sequencingFinished')
+        logging.info("forwarding sequencingFinshed message for %s" % (runDir))
+        
+    def update(self, *args):
+        """
+        Update our current status.
+        Report if we've finished copying files.
+        """
+        self.rsync.poll()
+        for p in self.pending:
+            if p not in self.rsync.keys():
+                self.reportSequencingFinished(p)
+                self.pending.remove(p)
+        
+    def _parser(self, msg, who):
+        """
+        Parse xmpp chat messages
+        """
+        help = u"I can [copy], or report current [status]"
+        if re.match(u"help", msg):
+            reply = help
+        elif re.match("copy", msg):            
+            started = self.startCopy()
+            reply = u"started copying " + ", ".join(started)
+        elif re.match(u"status", msg):
+            msg = [u"Currently %d rsync processes are running." % (len(self.rsync))]
+            for d in self.rsync.keys():
+              msg.append(u"  " + d)
+            reply = os.linesep.join(msg)
+        else:
+            reply = u"I didn't understand '%s'" % (unicode(msg))
+        return reply
+
+def main(args=None):
+    bot = CopierBot()
+    bot.main(args)
+    
+if __name__ == "__main__":
+  sys.exit(main(sys.argv[1:]))
+
diff --git a/trunk/htsworkflow/automation/runner.py b/trunk/htsworkflow/automation/runner.py
new file mode 100644 (file)
index 0000000..45d4ffc
--- /dev/null
@@ -0,0 +1,224 @@
+#!/usr/bin/env python
+from glob import glob
+import logging
+import os
+import re
+import sys
+import time
+import threading
+
+from benderjab import rpc
+
+from htsworkflow.pipelines.configure_run import *
+
+#s_fc = re.compile('FC[0-9]+')
+s_fc = re.compile('_[0-9a-zA-Z]*$')
+
+
+def _get_flowcell_from_rundir(run_dir):
+    """
+    Returns flowcell string based on run_dir.
+    Returns None and logs error if flowcell can't be found.
+    """
+    junk, dirname = os.path.split(run_dir)
+    mo = s_fc.search(dirname)
+    if not mo:
+        logging.error('RunDir 2 FlowCell error: %s' % (run_dir))
+        return None
+
+    return dirname[mo.start()+1:]
+    
+
+
+class Runner(rpc.XmlRpcBot):
+    """
+    Manage running pipeline jobs.
+    """    
+    def __init__(self, section=None, configfile=None):
+        #if configfile is None:
+        #    self.configfile = "~/.htsworkflow"
+        super(Runner, self).__init__(section, configfile)
+        
+        self.cfg['notify_users'] = None
+        self.cfg['genome_dir'] = None
+        self.cfg['base_analysis_dir'] = None
+
+        self.cfg['notify_users'] = None
+        self.cfg['notify_postanalysis'] = None
+
+        self.conf_info_dict = {}
+        
+        self.register_function(self.sequencingFinished)
+        #self.eventTasks.append(self.update)
+
+    
+    def read_config(self, section=None, configfile=None):
+        super(Runner, self).read_config(section, configfile)
+
+        self.genome_dir = self._check_required_option('genome_dir')
+        self.base_analysis_dir = self._check_required_option('base_analysis_dir')
+
+        self.notify_users = self._parse_user_list(self.cfg['notify_users'])
+        #FIXME: process notify_postpipeline cfg
+        
+    
+    def _parser(self, msg, who):
+        """
+        Parse xmpp chat messages
+        """
+        help = u"I can send [start] a run, or report [status]"
+        if re.match(u"help", msg):
+            reply = help
+        elif re.match("status", msg):
+            words = msg.split()
+            if len(words) == 2:
+                reply = self.getStatusReport(words[1])
+            else:
+                reply = u"Status available for: %s" \
+                        % (', '.join([k for k in self.conf_info_dict.keys()]))
+        elif re.match(u"start", msg):
+            words = msg.split()
+            if len(words) == 2:
+                self.sequencingFinished(words[1])
+                reply = u"starting run for %s" % (words[1])
+            else:
+                reply = u"need runfolder name"
+        elif re.match(u"path", msg):
+           reply = u"My path is: " + unicode(os.environ['PATH'])
+        else:
+            reply = u"I didn't understand '%s'" %(msg)
+
+        logging.debug("reply: " + str(reply))
+        return reply
+
+
+    def getStatusReport(self, fc_num):
+        """
+        Returns text status report for flow cell number 
+        """
+        if fc_num not in self.conf_info_dict:
+            return "No record of a %s run." % (fc_num)
+
+        status = self.conf_info_dict[fc_num].status
+
+        if status is None:
+            return "No status information for %s yet." \
+                   " Probably still in configure step. Try again later." % (fc_num)
+
+        output = status.statusReport()
+
+        return '\n'.join(output)
+    
+            
+    def sequencingFinished(self, run_dir):
+        """
+        Sequenceing (and copying) is finished, time to start pipeline
+        """
+        logging.debug("received sequencing finished message")
+
+        # Setup config info object
+        ci = ConfigInfo()
+        ci.base_analysis_dir = self.base_analysis_dir
+        ci.analysis_dir = os.path.join(self.base_analysis_dir, run_dir)        
+
+        # get flowcell from run_dir name
+        flowcell = _get_flowcell_from_rundir(run_dir)
+
+        # Store ci object in dictionary
+        self.conf_info_dict[flowcell] = ci
+
+
+        # Launch the job in it's own thread and turn.
+        self.launchJob(run_dir, flowcell, ci)
+        return "started"
+        
+        
+    def pipelineFinished(self, run_dir):
+        # need to strip off self.watch_dir from rundir I suspect.
+        logging.info("pipeline finished in" + str(run_dir))
+        #pattern = self.watch_dir
+        #if pattern[-1] != os.path.sep:
+        #    pattern += os.path.sep
+        #stripped_run_dir = re.sub(pattern, "", run_dir)
+        #logging.debug("stripped to " + stripped_run_dir)
+
+        # Notify each user that the run has finished.
+        if self.notify_users is not None:
+            for u in self.notify_users:
+                self.send(u, 'Pipeline run %s finished' % (run_dir))
+                
+        #if self.notify_runner is not None:
+        #    for r in self.notify_runner:
+        #        self.rpc_send(r, (stripped_run_dir,), 'sequencingFinished')
+
+    def reportMsg(self, msg):
+
+        if self.notify_users is not None:
+            for u in self.notify_users:
+                self.send(u, msg)
+
+
+    def _runner(self, run_dir, flowcell, conf_info):
+
+        # retrieve config step
+        cfg_filepath = os.path.join(conf_info.analysis_dir,
+                                    'config-auto.txt')
+        status_retrieve_cfg = retrieve_config(conf_info,
+                                          flowcell,
+                                          cfg_filepath,
+                                          self.genome_dir)
+        if status_retrieve_cfg:
+            logging.info("Runner: Retrieve config: success")
+            self.reportMsg("Retrieve config (%s): success" % (run_dir))
+        else:
+            logging.error("Runner: Retrieve config: failed")
+            self.reportMsg("Retrieve config (%s): FAILED" % (run_dir))
+
+        
+        # configure step
+        if status_retrieve_cfg:
+            status = configure(conf_info)
+            if status:
+                logging.info("Runner: Configure: success")
+                self.reportMsg("Configure (%s): success" % (run_dir))
+                self.reportMsg(
+                    os.linesep.join(glob(os.path.join(run_dir,'Data','C*')))
+                )
+            else:
+                logging.error("Runner: Configure: failed")
+                self.reportMsg("Configure (%s): FAILED" % (run_dir))
+
+            #if successful, continue
+            if status:
+                # Setup status cmdline status monitor
+                #startCmdLineStatusMonitor(ci)
+                
+                # running step
+                print 'Running pipeline now!'
+                run_status = run_pipeline(conf_info)
+                if run_status is True:
+                    logging.info('Runner: Pipeline: success')
+                    self.reportMsg("Pipeline run (%s): Finished" % (run_dir,))
+                else:
+                    logging.info('Runner: Pipeline: failed')
+                    self.reportMsg("Pipeline run (%s): FAILED" % (run_dir))
+
+
+    def launchJob(self, run_dir, flowcell, conf_info):
+        """
+        Starts up a thread for running the pipeline
+        """
+        t = threading.Thread(target=self._runner,
+                        args=[run_dir, flowcell, conf_info])
+        t.setDaemon(True)
+        t.start()
+        
+
+        
+def main(args=None):
+    bot = Runner()
+    return bot.main(args)
+    
+if __name__ == "__main__":
+    sys.exit(main(sys.argv[1:]))
+    
diff --git a/trunk/htsworkflow/automation/spoolwatcher.py b/trunk/htsworkflow/automation/spoolwatcher.py
new file mode 100644 (file)
index 0000000..25c012d
--- /dev/null
@@ -0,0 +1,319 @@
+#!/usr/bin/env python
+import logging
+import os
+import re
+import shlex
+import sys
+import time
+
+from htsworkflow.util import mount
+
+# this uses pyinotify
+import pyinotify
+from pyinotify import EventsCodes
+
+from benderjab import rpc
+
+def get_top_dir(root, path):
+    """
+    Return the directory in path that is a subdirectory of root.
+    e.g.
+
+    >>> print get_top_dir('/a/b/c', '/a/b/c/d/e/f')
+    d
+    >>> print get_top_dir('/a/b/c/', '/a/b/c/d/e/f')
+    d
+    >>> print get_top_dir('/a/b/c', '/g/e/f')
+    None
+    >>> print get_top_dir('/a/b/c', '/a/b/c')
+    <BLANKLINE>
+    """
+    if path.startswith(root):
+        subpath = path[len(root):]
+        if subpath.startswith('/'):
+            subpath = subpath[1:]
+        return subpath.split(os.path.sep)[0]
+    else:
+        return None
+
+class WatcherEvents(object):
+    # two events need to be tracked
+    # one to send startCopy
+    # one to send OMG its broken
+    # OMG its broken needs to stop when we've seen enough
+    #  cycles
+    # this should be per runfolder. 
+    # read the xml files 
+    def __init__(self):
+        pass
+        
+
+class Handler(pyinotify.ProcessEvent):
+    def __init__(self, watchmanager, bot, ipar=False):
+        """
+        ipar flag indicates we should wait for ipar to finish, instead of 
+             just the run finishing
+        """
+        self.last_event = {}
+        self.watchmanager = watchmanager
+        self.bot = bot
+        self.ipar_mode = ipar
+        if self.ipar_mode:
+            self.last_file = 'IPAR_Netcopy_Complete.txt'.lower()
+        else:
+            self.last_file = "run.completed".lower()
+
+    def process_IN_CREATE(self, event):
+        for wdd in self.bot.wdds:
+            for watch_path in self.bot.watchdirs:
+                if event.path.startswith(watch_path):
+                    target = get_top_dir(watch_path, event.path)
+                    self.last_event.setdefault(watch_path, {})[target] = time.time()
+
+                    msg = "Create: %s %s %s" % (event.path, event.name, target)
+
+                    if event.name.lower() == self.last_file:
+                        try:
+                            self.bot.sequencingFinished(event.path)
+                        except IOError, e:
+                            logging.error("Couldn't send sequencingFinished")
+                    logging.debug(msg)
+
+    def process_IN_DELETE(self, event):
+        logging.debug("Remove: %s" %  os.path.join(event.path, event.name))
+        pass
+
+    def process_IN_UNMOUNT(self, event):
+        pathname = os.path.join(event.path, event.name)
+        logging.debug("IN_UNMOUNT: %s" % (pathname,))
+        self.bot.unmount_watch(event.path)
+
+class SpoolWatcher(rpc.XmlRpcBot):
+    """
+    Watch a directory and send a message when another process is done writing.
+    
+    This monitors a directory tree using inotify (linux specific) and
+    after some files having been written will send a message after <timeout>
+    seconds of no file writing.
+    
+    (Basically when the solexa machine finishes dumping a round of data
+    this'll hopefully send out a message saying hey look theres data available
+    
+    """
+    # these params need to be in the config file
+    # I wonder where I should put the documentation
+    #:Parameters:
+    #    `watchdirs` - list of directories to monitor for modifications
+    #    `profile` - specify which .htsworkflow profile to use
+    #    `write_timeout` - how many seconds to wait for writes to finish to
+    #                      the spool
+    #    `notify_timeout` - how often to timeout from notify
+    
+    def __init__(self, section=None, configfile=None):
+        #if configfile is None:
+        #    self.configfile = "~/.htsworkflow"
+        super(SpoolWatcher, self).__init__(section, configfile)
+        
+        self.cfg['watchdirs'] = None
+        self.cfg['write_timeout'] = 10
+        self.cfg['notify_users'] = None
+        self.cfg['notify_runner'] = None
+        self.cfg['wait_for_ipar'] = 0
+       
+        self.watchdirs = []
+        self.watchdir_url_map = {}
+        self.notify_timeout = 0.001
+
+        self.wm = None 
+        self.notify_users = None
+        self.notify_runner = None
+        self.wdds = []
+
+        # keep track if the specified mount point is currently mounted
+        self.mounted_points = {}
+        # keep track of which mount points tie to which watch directories
+        # so maybe we can remount them.
+        self.mounts_to_watches = {}
+        
+        self.eventTasks.append(self.process_notify)
+
+    def read_config(self, section=None, configfile=None):
+        # Don't give in to the temptation to use logging functions here, 
+        # need to wait until after we detach in start
+        super(SpoolWatcher, self).read_config(section, configfile)
+        
+        self.watchdirs = shlex.split(self._check_required_option('watchdirs'))
+        # see if there's an alternate url that should be used for the watchdir
+        for watchdir in self.watchdirs:
+            self.watchdir_url_map[watchdir] = self.cfg.get(watchdir, watchdir)
+
+        self.write_timeout = int(self.cfg['write_timeout'])
+        self.wait_for_ipar = int(self.cfg['wait_for_ipar'])
+        
+        self.notify_users = self._parse_user_list(self.cfg['notify_users'])
+        try:
+          self.notify_runner = \
+             self._parse_user_list(self.cfg['notify_runner'],
+                                   require_resource=True)
+        except bot.JIDMissingResource:
+            msg = 'need a full jabber ID + resource for xml-rpc destinations'
+            raise bot.JIDMissingResource(msg)
+
+        self.handler = None
+        self.notifier = None
+
+    def add_watch(self, watchdirs=None):
+        """
+        start watching watchdir or self.watchdir
+        we're currently limited to watching one directory tree.
+        """
+        # create the watch managers if we need them
+        if self.wm is None:
+            self.wm = pyinotify.WatchManager()
+            self.handler = Handler(self.wm, self, self.wait_for_ipar)
+            self.notifier = pyinotify.Notifier(self.wm, self.handler)
+
+        # the one tree limit is mostly because self.wdd is a single item
+        # but managing it as a list might be a bit more annoying
+        if watchdirs is None:
+            watchdirs = self.watchdirs
+
+        mask = EventsCodes.IN_CREATE | EventsCodes.IN_UNMOUNT
+        # rec traverses the tree and adds all the directories that are there
+        # at the start.
+        # auto_add will add in new directories as they are created
+        for w in watchdirs:
+            mount_location = mount.find_mount_point_for(w)
+            self.mounted_points[mount_location] = True
+            mounts = self.mounts_to_watches.get(mount_location, [])
+            if w not in mounts:
+                mounts.append(w)
+                self.mounts_to_watches[mount_location] = mounts
+
+            logging.info(u"Watching:"+unicode(w))
+            self.wdds.append(self.wm.add_watch(w, mask, rec=True, auto_add=True))
+
+    def unmount_watch(self, event_path):
+        # remove backwards so we don't get weirdness from 
+        # the list getting shorter
+        for i in range(len(self.wdds),0, -1):
+            wdd = self.wdds[i]
+            logging.info(u'unmounting: '+unicode(wdd.items()))
+            self.wm.rm_watch(wdd.values())
+            del self.wdds[i]
+        self.mounted = False
+
+    def make_copy_url(self, watchdir, list_event_dir):
+        root_copy_url = self.watchdir_url_map[watchdir]
+        if root_copy_url[-1] != '/':
+            root_copy_url += '/'
+        copy_url = root_copy_url + list_event_dir
+        logging.debug('Copy url: %s' % (copy_url,))
+        return copy_url
+                  
+    def process_notify(self, *args):
+        if self.notifier is None:
+            # nothing to do yet
+            return
+        # process the queue of events as explained above
+        self.notifier.process_events()
+        #check events waits timeout
+        if self.notifier.check_events(self.notify_timeout):
+            # read notified events and enqeue them
+            self.notifier.read_events()
+            # should we do something?
+        # has something happened?
+        for watchdir, last_events in self.handler.last_event.items():
+            for last_event_dir, last_event_time in last_events.items():
+                time_delta = time.time() - last_event_time
+                if time_delta > self.write_timeout:
+                    copy_url = self.make_copy_url(watchdir, last_event_dir)
+                    self.startCopy(copy_url)
+                    self.handler.last_event[watchdir] = {}
+        # handle unmounted filesystems
+        for mount_point, was_mounted in self.mounted_points.items():
+            if not was_mounted and mount.is_mounted(mount_point):
+                # we've been remounted. Huzzah!
+                # restart the watch
+                for watch in self.mounts_to_watches[mount_point]:
+                    self.add_watch(watch)
+                    logging.info(
+                        "%s was remounted, restarting watch" % \
+                            (mount_point)
+                    )
+                self.mounted_points[mount_point] = True
+
+    def _parser(self, msg, who):
+        """
+        Parse xmpp chat messages
+        """
+        help = u"I can send [copy] message, or squencer [finished]"
+        if re.match(u"help", msg):
+            reply = help
+        elif re.match("copy", msg):            
+            self.startCopy()
+            reply = u"sent copy message"
+        elif re.match(u"finished", msg):
+            words = msg.split()
+            if len(words) == 2:
+                self.sequencingFinished(words[1])
+                reply = u"sending sequencing finished for %s" % (words[1])
+            else:
+                reply = u"need runfolder name"
+        else:
+            reply = u"I didn't understand '%s'" %(msg)            
+        return reply
+        
+    def run(self):
+        """
+        Start application
+        """
+        # we have to configure pyinotify after BenderJab.start is called
+        # as weird things happen to pyinotify if the stdio is closed
+        # after it's initialized.
+        self.add_watch()
+        super(SpoolWatcher, self).run()
+        
+    def stop(self):
+        """
+        shutdown application
+        """
+        # destroy the inotify's instance on this interrupt (stop monitoring)
+        if self.notifier is not None:
+            self.notifier.stop()
+        super(SpoolWatcher, self).stop()
+    
+    def startCopy(self, copy_url=None):
+        logging.debug("writes seem to have stopped")
+        if self.notify_runner is not None:
+            for r in self.notify_runner:
+                self.rpc_send(r, tuple(), 'startCopy')
+        if self.notify_users is not None:
+            for u in self.notify_users:
+                self.send(u, 'startCopy %s.' % (copy_url,))
+        
+    def sequencingFinished(self, run_dir):
+        # need to strip off self.watchdirs from rundir I suspect.
+        logging.info("run.completed in " + str(run_dir))
+        pattern = self.watch_dir
+        if pattern[-1] != os.path.sep:
+            pattern += os.path.sep
+        stripped_run_dir = re.sub(pattern, "", run_dir)
+        logging.debug("stripped to " + stripped_run_dir)
+        if self.notify_users is not None:
+            for u in self.notify_users:
+                self.send(u, 'Sequencing run %s finished' % (stripped_run_dir))
+        if self.notify_runner is not None:
+            for r in self.notify_runner:
+                self.rpc_send(r, (stripped_run_dir,), 'sequencingFinished')
+
+def main(args=None):
+    bot = SpoolWatcher()
+    return bot.main(args)
+    
+if __name__ == "__main__":
+    ret = main(sys.argv[1:])
+    #sys.exit(ret)
+
+# TODO:
+# send messages to copier specifying which mount to copy
diff --git a/trunk/htsworkflow/automation/test/test_runner.py b/trunk/htsworkflow/automation/test/test_runner.py
new file mode 100644 (file)
index 0000000..6c3b9df
--- /dev/null
@@ -0,0 +1,46 @@
+import unittest
+
+
+import os
+from htsworkflow.automation.copier import runfolder_validate
+
+def extract_runfolder_path(watchdir, event):
+  runfolder_path = watchdir
+  path = event.path
+  if not path.startswith(watchdir):
+    return None
+
+  fragments = path[len(watchdir):].split(os.path.sep)
+  for f in fragments:
+    runfolder_path = os.path.join(runfolder_path, f)
+    if runfolder_validate(f):
+      return runfolder_path
+  return None
+
+class Event(object):
+  def __init__(self, path=None, name=None):
+    self.path = path
+    self.name = name
+
+class testRunner(unittest.TestCase):
+
+    def test_extract_runfolder(self):
+        watchdir = os.path.join('root', 'server', 'mount')
+        runfolder = os.path.join(watchdir, '080909_HWI-EAS229_0052_1234ABCD')
+        ipar = os.path.join(runfolder, 'Data', 'IPAR_1.01')
+        other = os.path.join(watchdir, 'other')
+
+        event = Event( path=runfolder )
+        self.failUnlessEqual(extract_runfolder_path(watchdir, event), runfolder)
+        
+        event = Event( path=ipar )
+        self.failUnlessEqual(extract_runfolder_path(watchdir, event), runfolder)
+
+        event = Event( path=other)
+        self.failUnlessEqual(extract_runfolder_path(watchdir, event), None )
+        
+def suite():
+    return unittest.makeSuite(testRunner,'test')
+
+if __name__ == "__main__":
+    unittest.main(defaultTest="suite")
diff --git a/trunk/htsworkflow/frontend/__init__.py b/trunk/htsworkflow/frontend/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/trunk/htsworkflow/frontend/analysis/__init__.py b/trunk/htsworkflow/frontend/analysis/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/trunk/htsworkflow/frontend/analysis/admin.py b/trunk/htsworkflow/frontend/analysis/admin.py
new file mode 100644 (file)
index 0000000..d3f903e
--- /dev/null
@@ -0,0 +1,31 @@
+from htsworkflow.frontend.analysis.models import Task, Project
+from django.contrib import admin
+from django.utils.translation import ugettext_lazy as _
+
+class ProjectOptions(admin.ModelAdmin):
+  list_display = ('ProjTitle','ProjectTasks')
+  list_filter = ()
+  search_fieldsets = ['project_name','=tasks__subject1__library_id','=tasks__subject2__library_id','tasks__subject1__library_name','tasks__subject2__library_name','project_notes']
+  fieldsets = (
+    (None, {
+      'fields': (('project_name'),('tasks'),('project_notes'))}),
+  )
+  filter_horizontal = ('tasks',)
+
+class TaskOptions(admin.ModelAdmin):
+  list_display = ('task_name','apply_calc','subject1','subject2','task_params','InProjects','submitted_on','task_status')
+  list_filter = ('apply_calc',)
+  search_fieldsets = ['task_name','id','=subject1__library_id','=subject2__library_id']
+  fieldsets = (
+      (None, {
+        'fields': (('task_name'),('apply_calc'),('subject1'),('subject2'),('task_params'))
+         }),
+        ('system fields', {
+           'classes': ('collapse',),
+         'fields': (('submitted_on'),('task_status','run_note'))
+        }),
+      )
+
+admin.site.register(Project, ProjectOptions)
+admin.site.register(Task, TaskOptions)
+
diff --git a/trunk/htsworkflow/frontend/analysis/main.py b/trunk/htsworkflow/frontend/analysis/main.py
new file mode 100644 (file)
index 0000000..b5217dc
--- /dev/null
@@ -0,0 +1,118 @@
+# some core functions of analysis manager module
+from django.http import HttpResponse
+from datetime import datetime
+from string import *
+import re
+from htsworkflow.frontend import settings
+from htsworkflow.frontend.analysis.models import Task, Project
+from django.core.exceptions import ObjectDoesNotExist
+
+def updStatus(request):
+    ClIP = request.META['REMOTE_ADDR']
+    #Check client access permission                                                                                                                                       
+    granted = False
+    if (settings.ALLOWED_ANALYS_IPS.has_key(ClIP)):  granted = True
+    if not granted: return HttpResponse("access denied.")
+
+    output=''
+    taskid=-1;
+    # Check required param
+    if request.has_key('taskid'): taskid = request['taskid']
+    else:  return HttpResponse('missing param task id')
+
+    try:
+      rec = Task.objects.get(id=taskid)
+      mytimestamp = datetime.now().__str__()
+      mytimestamp = re.sub(pattern=":[^:]*$",repl="",string=mytimestamp)
+      if request.has_key('msg'):
+        rec.task_status += ", "+request['msg']+" ("+mytimestamp+")"
+      else :
+        rec.task_status = "Registered ("+mytimestamp+")"
+      rec.save()
+      output = "Hello "+settings.ALLOWED_ANALYS_IPS[ClIP]+". Updated status for task "+taskid
+    except ObjectDoesNotExist:
+      output = "entry not found: taskid="+taskid
+
+    return HttpResponse(output)
+    
+      
+def getProjects(request):
+    ClIP = request.META['REMOTE_ADDR']
+    #Check client access permission 
+    granted = False
+    if (settings.ALLOWED_ANALYS_IPS.has_key(ClIP)):  granted = True
+    if not granted: return HttpResponse("access denied.")
+
+    outputfile = ''
+    
+    All=False
+    if (request.has_key('mode')):
+      if request['mode']=='all':
+        All=True
+
+    try:                                                                                   
+      if(All):
+        rec = Project.objects.all().distinct()
+      else:
+        rec = Project.objects.filter(tasks__task_status__exact='defined').distinct()
+      
+      outputfile = '<?xml version="1.0" ?>'
+      outputfile += '\n<Projects Client="'+settings.ALLOWED_ANALYS_IPS[ClIP]+'">'
+      for p in rec:
+        outputfile += '\n'
+        outputfile += '\n<Project ProjectId="'+p.id.__str__()+'" Name="'+p.project_name+'">'
+        prj_tasks = p.tasks.all()
+        for t in prj_tasks:
+          outputfile += '\n'
+          if (t.apply_calc == 'QuEST' or t.apply_calc == 'WingPeaks' or t.apply_calc == 'MACS'):
+            outputfile += '\n<PeakCalling TaskId="'+t.id.__str__()+'" Name="'+t.task_name+'" Caller="'+t.apply_calc+'" Genome="'+t.subject1.library_species.use_genome_build+'">'
+            if t.subject1:
+              outputfile += '\n<Signal Library="'+t.subject1.library_id+'"/>'
+              if t.subject2:
+                outputfile += '\n<Background Library="'+t.subject2.library_id+'"/>'
+              else:
+                outputfile += '\n<Err>Background Library Missing</Err>'
+            else:
+              outputfile += '\n<Err>Signal Library Missing</Err>'
+            outputfile += '\n<params>'+t.task_params.__str__()+'</params>'
+            outputfile += '\n</PeakCalling>'
+          
+          if (t.apply_calc == 'Methylseq'):
+            outputfile += '\n<Methylseq TaskId="'+t.id.__str__()+'" Name="'+t.task_name+'" Genome="'+t.subject1.library_species.use_genome_build+'">'
+            if t.subject1:
+              outputfile += '\n<Hpa2 Library="'+t.subject1.library_id+'"/>'
+              if t.subject2:
+                outputfile += '\n<Msp1 Library="'+t.subject2.library_id+'"/>'
+              else:
+                outputfile += '\n<Err>Msp1 Library Missing</Err>'
+            else:
+              outputfile += '\n<Err>Hpa2 Library Missing</Err>'
+            outputfile += '\n<params>'+t.task_params.__str__()+'</params>'
+            outputfile += '\n</Methylseq>' 
+
+          if (t.apply_calc == 'ProfileReads' or t.apply_calc == 'qPCR'):
+            outputfile += '\n<'+t.apply_calc+' TaskId="'+t.id.__str__()+'" Name="'+t.task_name+'" Genome="'+t.subject1.library_species.use_genome_build+'" Library="'+t.subject1.library_id+'"/>'
+
+          if (t.apply_calc == 'CompareLibs'):
+            outputfile += '\n<CompareLibraries TaskId="'+t.id.__str__()+'" TF="'+t.task_name+'" Genome="'+t.subject1.library_species.use_genome_build+'">'
+            if t.subject1:
+              outputfile += '\n<Library Library="'+t.subject1.library_id+'"/>'
+            else:
+              outputfile += '\n<Err>Library Missing</Err>'
+            if t.subject2:
+              outputfile += '\n<Library Library="'+t.subject2.library_id+'"/>'
+            else:
+              outputfile += '\n<Err>Library Missing</Err>'
+            outputfile += '\n<params>'+t.task_params.__str__()+'</params>'
+            outputfile += '\n</CompareLibraries>'
+
+          #if (t.apply_calc == 'ComparePeakCalls'):                                                                                                                            
+          # <ComparePeakCalls Genome="hg18" Caller1="QuEST" Set1="A549 GR Dex ChIP" Caller2="QuEST" Set2="A549 GR EtOH ChIP" />                                                
+          # outputfile += '\n<ComparePeakCalls TaskId='+t.id.__str__()+' Genome="'+t.subject1.library_species.use_genome_build+'" Caller1="'+t.pcaller1+'" Caller1="'+t.pcaller1+'" Caller2="'+t.pcaller2+'" Set1="'+t.set1+'" Set1="'+t.set2+'"/>' 
+          # TO DO: Define these new fields in Task: PCaller1 (QuEST,WingPeaks), PCaller2, Set1(FK to self), Set2 (FK..) ALL NULL=TRUE                                  
+        outputfile += '\n</Project>'
+      outputfile += '\n</Projects>'
+    except ObjectDoesNotExist:
+      outputfile = "<?xml version='1.0' ?><Projects></Projects>"
+
+    return HttpResponse(outputfile, mimetype='text/plain')
diff --git a/trunk/htsworkflow/frontend/analysis/models.py b/trunk/htsworkflow/frontend/analysis/models.py
new file mode 100644 (file)
index 0000000..e2ddff4
--- /dev/null
@@ -0,0 +1,101 @@
+from django.db import models
+from datetime import datetime
+from htsworkflow.frontend import settings
+from htsworkflow.frontend.samples.models import Library 
+from string import *
+
+class Task(models.Model):
+  task_name = models.CharField(max_length=50,unique=True, db_index=True)
+  subject1 = models.ForeignKey(Library,related_name='sbj1_library',verbose_name="Subject1 (Signal/Hpa2)")
+  subject2 = models.ForeignKey(Library,related_name='sbj2_library',verbose_name="Subject2 (Control/Msp1)",blank=True,null=True)
+  CALCS = (
+      ('QuEST', 'QuEST Peak Calling'),
+      ('WingPeaks', 'Wing Peak Calling'),
+      ('MACS', 'MACS Peak Calling'),
+      ('qPCR', 'In Silico qPCR'),
+      ('CompareLibs', 'Compare Libaraies'),
+      ('ComparePeakCalls','Compare Peak Calls'),
+      ('ProfileReads','Profile Reads'),
+      ('Methylseq','Methylseq'),
+    )
+  apply_calc = models.CharField(max_length=50,choices=CALCS,verbose_name='Applied Calculation')
+  ## userid = # logged in user
+  task_params = models.CharField(max_length=200,blank=True,null=True,default="")
+  task_status = models.CharField(max_length=500,blank=True,null=True,default='defined')
+  results_location = models.CharField(max_length=2000,blank=True,null=True) 
+  submitted_on = models.DateTimeField(default=datetime.now())
+  run_note = models.CharField(max_length=500,blank=True,null=True)
+  
+  def __str__(self):
+      return '"%s" - %s on [%s]/[%s]' % (self.task_name,self.apply_calc,self.subject1,self.subject2)
+
+  def InProjects(self):
+      return '...'
+      ps = self.project_set.all()
+      pstr = 'In '
+      return pstr
+      for p in ps:
+        pstr += '%s, ' % (p.project_name) 
+      return pstr
+
+class Project(models.Model):
+    project_name = models.CharField(max_length=50,unique=True, db_index=True)
+    tasks = models.ManyToManyField(Task,related_name='project_tasks',null=True) 
+    project_notes = models.CharField(max_length=500,blank=True,null=True)
+    
+    def __str__(self):
+      return '%s' % (self.project_name)
+
+    def ProjectTasks(self):
+      ptasks = self.tasks.all().order_by('id')
+      surl = settings.TASKS_PROJS_SERVER+'/projects/'
+      tstr = '<script>'
+      tstr += 'function togView(eid){'
+      tstr += 'f=document.getElementById(eid);'
+      tstr += 'if(f.height==0){'
+      tstr += 'f.height=600;'
+      tstr += 'f.style.border=\'solid #cccccc 3px\';'
+      tstr += '}else{'
+      tstr += 'f.height=0;'
+      tstr += 'f.style.border=\'none\';'
+      tstr += '}'
+      tstr += '}'
+      tstr += '</script>'
+      Style = ''
+      if len(ptasks) > 8:  Style = ' style="height:200px;overflow:auto" '
+      tstr += '<div '+Style+'>'
+      tstr += '<table><tr><th>Tasks</th><th>Job Status</th>'
+      isregistered = False
+      for t in ptasks:
+        taskdesc = t.task_name+'<div style="font-size:80%">Details: '+t.apply_calc+' on '+t.subject1.library_id
+        if t.subject2 is not None:
+          taskdesc += ' and '+t.subject2.library_id
+        taskdesc += ' (TaskId:'+t.id.__str__()+')'
+        tstr += '<tr><td width=250>%s</td><td>%s</td></tr>'  % (taskdesc,replace(t.task_status,'Complete','<span style="color:green;font-weight:bolder">Complete</span>'))
+        if t.task_status != 'defined': isregistered = True
+
+      tstr += '</table>'
+      tstr += '</div>' 
+      tstr += '<div>'
+      tstr += '<div align=center>'
+      if isregistered:
+        tstr += '<a onClick="togView(\'RFrame'+self.id.__str__()+'\');" href="'+surl+self.id.__str__()+'/" title="View Results Page" target="RFrame'+self.id.__str__()+'">VIEW PROJECT RESULTS</a>'
+        tstr += '<a href="'+surl+self.id.__str__()+'/" title="View Results Page" target="_blank" style="margin-left:10px">(view in new window)</a>'
+      else:
+        tstr += 'REGISTERING ...'    
+      tstr += '</div>'    
+      tstr += '<iframe width="100%" height="0" frameborder="0" style="background-color:#ffffff" name="RFrame'+self.id.__str__()+'" id="RFrame'+self.id.__str__()+'"/></iframe>'
+      tstr += '</div>'
+      return tstr
+
+    ProjectTasks.allow_tags = True
+
+    def ProjTitle(self):
+      ptasks = self.tasks.all().order_by('id')
+      tasks_counter = '<span style="color:#666666;font-size:85%">('+len(ptasks).__str__() + ' tasks)</span>'
+      htmlstr = '%s<br/>%s'  % (self.project_name,tasks_counter)
+      return htmlstr
+
+    ProjTitle.allow_tags = True
+
diff --git a/trunk/htsworkflow/frontend/analysis/urls.py b/trunk/htsworkflow/frontend/analysis/urls.py
new file mode 100644 (file)
index 0000000..b1be3d2
--- /dev/null
@@ -0,0 +1,6 @@
+from django.conf.urls.defaults import *
+
+urlpatterns = patterns('',
+    (r'^updStatus$', 'htsworkflow.frontend.analysis.main.updStatus'),
+    (r'^getProjects/$', 'htsworkflow.frontend.analysis.main.getProjects'),
+)
diff --git a/trunk/htsworkflow/frontend/eland_config/__init__.py b/trunk/htsworkflow/frontend/eland_config/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/trunk/htsworkflow/frontend/eland_config/admin.py b/trunk/htsworkflow/frontend/eland_config/admin.py
new file mode 100644 (file)
index 0000000..56107ff
--- /dev/null
@@ -0,0 +1,4 @@
+from django.contrib import admin
+from django.utils.translation import ugettext_lazy as _
+
+
diff --git a/trunk/htsworkflow/frontend/eland_config/forms.py b/trunk/htsworkflow/frontend/eland_config/forms.py
new file mode 100644 (file)
index 0000000..a2245f9
--- /dev/null
@@ -0,0 +1,163 @@
+from django import forms
+from django.forms.util import ErrorList
+
+
+SPECIES_LIST = [#('--choose--', '--Choose--'),
+                ('hg18', 'Homo sapiens (Hg18)'),
+                ('Mm8', 'Mus musculus (Mm8)'),
+                ('arabv6', 'Arabadopsis Thaliana v6'),
+                ('other', 'Other species (Include in description)')]
+
+
+class DivErrorList(ErrorList):
+  def __unicode__(self):
+    return self.as_divs()
+  
+  def as_divs(self):
+    if not self: return u''
+    return u'<div class="errorlist">%s</div>' % (''.join([u'<div class="error">%s</div>' % e for e in self]))
+
+
+
+class ConfigForm(forms.Form):
+  
+  flow_cell_number = forms.CharField(min_length=2)
+  run_date = forms.DateTimeField()
+  advanced_run = forms.BooleanField(required=False)
+  read_length = forms.IntegerField(min_value=1, initial=32)
+  #eland_repeat = forms.BooleanField()
+  
+  #needs a for loop or something to allow for n configurations
+  #analysis_type = forms.ChoiceField(choices=[('eland','eland')])
+  lane1_species = forms.ChoiceField(choices=SPECIES_LIST)
+  lane1_description = forms.CharField(widget=forms.TextInput(attrs={'size':'60'}))
+  
+  lane2_species = forms.ChoiceField(choices=SPECIES_LIST)
+  lane2_description = forms.CharField(widget=forms.TextInput(attrs={'size':'60'}))
+  
+  lane3_species = forms.ChoiceField(choices=SPECIES_LIST)
+  lane3_description = forms.CharField(widget=forms.TextInput(attrs={'size':'60'}))
+  
+  lane4_species = forms.ChoiceField(choices=SPECIES_LIST)
+  lane4_description = forms.CharField(widget=forms.TextInput(attrs={'size':'60'}))
+  
+  lane5_species = forms.ChoiceField(choices=SPECIES_LIST)
+  lane5_description = forms.CharField(widget=forms.TextInput(attrs={'size':'60'}))
+  
+  lane6_species = forms.ChoiceField(choices=SPECIES_LIST)
+  lane6_description = forms.CharField(widget=forms.TextInput(attrs={'size':'60'}))
+  
+  lane7_species = forms.ChoiceField(choices=SPECIES_LIST)
+  lane7_description = forms.CharField(widget=forms.TextInput(attrs={'size':'60'}))
+  
+  lane8_species = forms.ChoiceField(choices=SPECIES_LIST)
+  lane8_description = forms.CharField(widget=forms.TextInput(attrs={'size':'60'}))
+  
+  notes = forms.CharField(widget=forms.Textarea(attrs={'cols':'70'}), required=False)
+  
+  #lane_specific_read_length = forms.IntegerField(min_value=1)
+  
+  #eland_genome_lanes = forms.MultipleChoiceField(choices=[('lane1','1'),
+  #                                              ('lane2','2'),
+  #                                              ('lane3','3'),
+  #                                              ('lane4','4'),
+  #                                              ('lane5','5'),
+  #                                              ('lane6','6'),
+  #                                              ('lane7','7'),
+  #                                              ('lane8','8') ])
+  
+  #eland_genome = forms.ChoiceField(choices=)
+  
+  #use_bases_lanes = forms.MultipleChoiceField(choices=[('lane1','1'),
+  #                                              ('lane2','2'),
+  #                                              ('lane3','3'),
+  #                                              ('lane4','4'),
+  #                                              ('lane5','5'),
+  #                                              ('lane6','6'),
+  #                                              ('lane7','7'),
+  #                                              ('lane8','8') ])
+  
+  #use_bases_mask = forms.CharField()
+  
+  #sequence_format = forms.ChoiceField(choices=[('scarf', 'scarf')])
+  
+  
+  
+  #subject = forms.CharField(max_length=100)
+  #message = forms.CharField()
+  #sender = forms.EmailField()
+  #cc_myself = forms.BooleanField()
+  
+  def as_custom(self):
+    """
+    Displays customized html output
+    """
+    html = []
+    
+    fcn = self['flow_cell_number']
+    
+    html.append(fcn.label_tag() + ': ' + str(fcn) + str(fcn.errors) + '<br />')
+    
+    run_date = self['run_date']
+    html.append(run_date.label_tag() + ': ' + str(run_date) + str(run_date.errors) + '<br />')
+    
+    arun = self['advanced_run']
+    html.append(arun.label_tag() + ': ' + str(arun) + str(arun.errors) + '<br />')
+    
+    rl = self['read_length']
+    html.append(rl.label_tag() + ': ' + str(rl) + str(rl.errors) + '<br /><br />')
+    
+    html.append('<table border="0">')
+    html.append(' <tr><td>%s</td><td>%s</td><td>%s</td></tr>' \
+                % ('Lane', 'Species', 'Description'))
+    
+    l1s = self['lane1_species']
+    l1d = self['lane1_description']
+    html.append(' <tr><td>%s</td><td>%s %s</td><td>%s %s</td></tr>' \
+                % ('1', str(l1s), str(l1s.errors), str(l1d), str(l1d.errors)))
+    
+    l2s = self['lane2_species']
+    l2d = self['lane2_description']
+    html.append(' <tr><td>%s</td><td>%s %s</td><td>%s %s</td></tr>' \
+                % ('2', str(l2s), str(l2s.errors), str(l2d), str(l2d.errors)))
+    
+    l3s = self['lane3_species']
+    l3d = self['lane3_description']
+    html.append(' <tr><td>%s</td><td>%s %s</td><td>%s %s</td></tr>' \
+                % ('3', str(l3s), str(l3s.errors), str(l3d), str(l3d.errors)))
+    
+    l4s = self['lane4_species']
+    l4d = self['lane4_description']
+    html.append(' <tr><td>%s</td><td>%s %s</td><td>%s %s</td></tr>' \
+                % ('4', str(l4s), str(l4s.errors), str(l4d), str(l4d.errors)))
+    
+    l5s = self['lane5_species']
+    l5d = self['lane5_description']
+    html.append(' <tr><td>%s</td><td>%s %s</td><td>%s %s</td></tr>' \
+                % ('5', str(l5s), str(l5s.errors), str(l5d), str(l5d.errors)))
+    
+    l6s = self['lane6_species']
+    l6d = self['lane6_description']
+    html.append(' <tr><td>%s</td><td>%s %s</td><td>%s %s</td></tr>' \
+                % ('6', str(l6s), str(l6s.errors), str(l6d), str(l6d.errors)))
+    
+    l7s = self['lane7_species']
+    l7d = self['lane7_description']
+    html.append(' <tr><td>%s</td><td>%s %s</td><td>%s %s</td></tr>' \
+                % ('7', str(l7s), str(l7s.errors), str(l7d), str(l7d.errors)))
+    
+    l8s = self['lane8_species']
+    l8d = self['lane8_description']
+    html.append(' <tr><td>%s</td><td>%s %s</td><td>%s %s</td></tr>' \
+                % ('8', str(l8s), str(l8s.errors), str(l8d), str(l8d.errors)))
+    
+    html.append('</table><br />')
+    
+    notes = self['notes']
+    html.append('<p>Notes:</p>')
+    html.append(' %s<br />' % (str(notes)))
+    
+    return '\n'.join(html)
+    
+    
+    
diff --git a/trunk/htsworkflow/frontend/eland_config/models.py b/trunk/htsworkflow/frontend/eland_config/models.py
new file mode 100644 (file)
index 0000000..71a8362
--- /dev/null
@@ -0,0 +1,3 @@
+from django.db import models
+
+# Create your models here.
diff --git a/trunk/htsworkflow/frontend/eland_config/urls.py b/trunk/htsworkflow/frontend/eland_config/urls.py
new file mode 100644 (file)
index 0000000..129f57c
--- /dev/null
@@ -0,0 +1,10 @@
+from django.conf.urls.defaults import *
+
+urlpatterns = patterns('',
+    # Example:
+    
+    (r'^(?P<flowcell>\w+)/$', 'htsworkflow.frontend.eland_config.views.config'),
+    (r'^$', 'htsworkflow.frontend.eland_config.views.config'),
+    #(r'^$', 'htsworkflow.frontend.eland_config.views.index')
+
+)
diff --git a/trunk/htsworkflow/frontend/eland_config/views.py b/trunk/htsworkflow/frontend/eland_config/views.py
new file mode 100644 (file)
index 0000000..02b3f13
--- /dev/null
@@ -0,0 +1,415 @@
+from django.http import HttpResponse
+from django.shortcuts import render_to_response
+from django.core.exceptions import ObjectDoesNotExist
+
+from htsworkflow.frontend.eland_config import forms
+from htsworkflow.frontend import settings
+from htsworkflow.frontend.experiments import models
+
+import os
+import glob
+# Create your views here.
+
+
+def _validate_input(data):
+  #if data.find('..') == -1 or data.find('/') == -1 or data.find('\\') == -1:
+  return data.replace('..', '').replace('/', '_').replace('\\', '_')
+
+#def contact(request):
+#    if request.method == 'POST':
+#        form = ContactForm(request.POST)
+#        if form.is_valid():
+#            # Do form processing here...
+#            return HttpResponseRedirect('/url/on_success/')
+#    else:
+#        form = ContactForm()
+#    return
+
+
+
+#def _saveConfigFile(form):
+#  """
+#  Given a valid form, save eland config to file based on flowcell number.
+#  """
+#  assert form.is_valid()
+#  
+#  clean_data = form.cleaned_data
+#  flowcell = clean_data['flow_cell_number'].replace('/','_').replace('..', '__')
+#  
+#  file_path = os.path.join(settings.UPLOADTO_CONFIG_FILE, flowcell)
+#  
+#  f = open(file_path, 'w')
+#  cfg = generateElandConfig(form)
+#  f.write(cfg)
+#  f.close()
+#  
+#
+#def _saveToDb(form):
+#  """
+#  Save info to the database.
+#  """
+#  clean_data = form.cleaned_data
+#  
+#  fc_id = clean_data['flow_cell_number']
+#  
+#  try:
+#    fc = models.FlowCell.objects.get(flowcell_id=fc_id)
+#  except models.FlowCell.DoesNotExist:
+#    fc = models.FlowCell()
+#    
+#  fc.flowcell_id = fc_id
+#  fc.run_date = clean_data['run_date']
+#  
+#  #LANE 1
+#  fc.lane1_sample = clean_data['lane1_description']
+#  species_name = clean_data['lane1_species']
+#  try:
+#    specie = models.Specie.objects.get(scientific_name=species_name)
+#  except models.Specie.DoesNotExist:
+#    specie = models.Specie(scientific_name=species_name)
+#    specie.save()
+#  fc.lane1_species = specie
+#  
+#  #LANE 2
+#  fc.lane2_sample = clean_data['lane2_description']
+#  species_name = clean_data['lane2_species']
+#  try:
+#    specie = models.Specie.objects.get(scientific_name=species_name)
+#  except models.Specie.DoesNotExist:
+#    specie = models.Specie(scientific_name=species_name)
+#    specie.save()
+#  fc.lane2_species = specie
+#  
+#  #LANE 3
+#  fc.lane3_sample = clean_data['lane3_description']
+#  species_name = clean_data['lane3_species']
+#  try:
+#    specie = models.Specie.objects.get(scientific_name=species_name)
+#  except models.Specie.DoesNotExist:
+#    specie = models.Specie(scientific_name=species_name)
+#    specie.save()
+#  fc.lane3_species = specie
+#  
+#  #LANE 4
+#  fc.lane4_sample = clean_data['lane4_description']
+#  species_name = clean_data['lane4_species']
+#  try:
+#    specie = models.Specie.objects.get(scientific_name=species_name)
+#  except models.Specie.DoesNotExist:
+#    specie = models.Specie(scientific_name=species_name)
+#    specie.save()
+#  fc.lane4_species = specie
+#  
+#  #LANE 5
+#  fc.lane5_sample = clean_data['lane5_description']
+#  species_name = clean_data['lane5_species']
+#  try:
+#    specie = models.Specie.objects.get(scientific_name=species_name)
+#  except models.Specie.DoesNotExist:
+#    specie = models.Specie(scientific_name=species_name)
+#    specie.save()
+#  fc.lane5_species = specie
+#  
+#  #LANE 6
+#  fc.lane6_sample = clean_data['lane6_description']
+#  species_name = clean_data['lane6_species']
+#  try:
+#    specie = models.Specie.objects.get(scientific_name=species_name)
+#  except models.Specie.DoesNotExist:
+#    specie = models.Specie(scientific_name=species_name)
+#    specie.save()
+#  fc.lane6_species = specie
+#  
+#  #LANE 7
+#  fc.lane7_sample = clean_data['lane7_description']
+#  species_name = clean_data['lane7_species']
+#  try:
+#    specie = models.Specie.objects.get(scientific_name=species_name)
+#  except models.Specie.DoesNotExist:
+#    specie = models.Specie(scientific_name=species_name)
+#    specie.save()
+#  fc.lane7_species = specie
+#  
+#  #LANE 8
+#  fc.lane8_sample = clean_data['lane8_description']
+#  species_name = clean_data['lane8_species']
+#  try:
+#    specie = models.Specie.objects.get(scientific_name=species_name)
+#  except models.Specie.DoesNotExist:
+#    specie = models.Specie(scientific_name=species_name)
+#    specie.save()
+#  fc.lane8_species = specie
+#  
+#  fc.notes = clean_data['notes']
+#  
+#  fc.save()
+#  
+#  return fc
+#  
+#
+#def generateElandConfig(form):
+#  data = []
+#  
+#  form = form.cleaned_data
+#  
+#  BASE_DIR = '/data-store01/compbio/genomes'
+#  
+#  data.append("# FLOWCELL: %s" % (form['flow_cell_number']))
+#  data.append("#")
+#  
+#  notes = form['notes'].replace('\r\n', '\n').replace('\r', '\n')
+#  notes = notes.replace('\n', '\n#  ')
+#  data.append("# NOTES:")
+#  data.append("#  %s\n#" % (notes))
+#  
+#  #Convert all newline conventions to unix style
+#  l1d = form['lane1_description'].replace('\r\n', '\n').replace('\r', '\n')
+#  l2d = form['lane2_description'].replace('\r\n', '\n').replace('\r', '\n')
+#  l3d = form['lane3_description'].replace('\r\n', '\n').replace('\r', '\n')
+#  l4d = form['lane4_description'].replace('\r\n', '\n').replace('\r', '\n')
+#  l5d = form['lane5_description'].replace('\r\n', '\n').replace('\r', '\n')
+#  l6d = form['lane6_description'].replace('\r\n', '\n').replace('\r', '\n')
+#  l7d = form['lane7_description'].replace('\r\n', '\n').replace('\r', '\n')
+#  l8d = form['lane8_description'].replace('\r\n', '\n').replace('\r', '\n')
+#  
+#  # Turn new lines into indented commented newlines
+#  l1d = l1d.replace('\n', '\n#  ')
+#  l2d = l2d.replace('\n', '\n#  ')
+#  l3d = l3d.replace('\n', '\n#  ')
+#  l4d = l4d.replace('\n', '\n#  ')
+#  l5d = l5d.replace('\n', '\n#  ')
+#  l6d = l6d.replace('\n', '\n#  ')
+#  l7d = l7d.replace('\n', '\n#  ')
+#  l8d = l8d.replace('\n', '\n#  ')
+#  
+#  data.append("# Lane1: %s" % (l1d))
+#  data.append("# Lane2: %s" % (l2d))
+#  data.append("# Lane3: %s" % (l3d))
+#  data.append("# Lane4: %s" % (l4d))
+#  data.append("# Lane5: %s" % (l5d))
+#  data.append("# Lane6: %s" % (l6d))
+#  data.append("# Lane7: %s" % (l7d))
+#  data.append("# Lane8: %s" % (l8d))
+#  
+#  #data.append("GENOME_DIR %s" % (BASE_DIR))
+#  #data.append("CONTAM_DIR %s" % (BASE_DIR))
+#  read_length = form['read_length']
+#  data.append("READ_LENGTH %d" % (read_length))
+#  #data.append("ELAND_REPEAT")
+#  data.append("ELAND_MULTIPLE_INSTANCES 8")
+#  
+#  #Construct genome dictionary to figure out what lanes to put
+#  # in the config file.
+#  genome_dict = {}
+#  l1s = form['lane1_species']
+#  genome_dict.setdefault(l1s, []).append('1')
+#  l2s = form['lane2_species']
+#  genome_dict.setdefault(l2s, []).append('2')
+#  l3s = form['lane3_species']
+#  genome_dict.setdefault(l3s, []).append('3')
+#  l4s = form['lane4_species']
+#  genome_dict.setdefault(l4s, []).append('4')
+#  l5s = form['lane5_species']
+#  genome_dict.setdefault(l5s, []).append('5')
+#  l6s = form['lane6_species']
+#  genome_dict.setdefault(l6s, []).append('6')
+#  l7s = form['lane7_species']
+#  genome_dict.setdefault(l7s, []).append('7')
+#  l8s = form['lane8_species']
+#  genome_dict.setdefault(l8s, []).append('8')
+#  
+#  genome_list = genome_dict.keys()
+#  genome_list.sort()
+#  
+#  #Loop through and create entries for each species.
+#  for genome in genome_list:
+#    lanes = ''.join(genome_dict[genome])
+#    data.append('%s:ANALYSIS eland' % (lanes))
+#    data.append('%s:READ_LENGTH %s' % (lanes, read_length))
+#    data.append('%s:ELAND_GENOME %s' % (lanes, os.path.join(BASE_DIR, genome)))
+#    data.append('%s:USE_BASES %s' % (lanes, 'Y'*int(read_length)))
+#    
+#  data.append('SEQUENCE_FORMAT --scarf')
+#  
+#  return '\n'.join(data)
+
+
+def getElandConfig(flowcell, regenerate=False):
+  
+  file_path = os.path.join(settings.UPLOADTO_CONFIG_FILE, flowcell)
+  
+  #If we are regenerating the config file, skip
+  # reading of existing file. If the file doesn't
+  # exist, try to generate it form the DB.
+  if not regenerate and os.path.isfile(file_path):
+    f = open(file_path, 'r')
+    data = f.read()
+    f.close()
+    return data
+  
+  try:
+    fcObj = models.FlowCell.objects.get(flowcell_id__iexact=flowcell)
+  except ObjectDoesNotExist:
+    return None
+  
+  data = []
+  
+  #form = form.cleaned_data
+  
+  BASE_DIR = '/data-store01/compbio/genomes'
+  
+  data.append("# FLOWCELL: %s" % (fcObj.flowcell_id))
+  data.append("#")
+  
+  notes = fcObj.notes.replace('\r\n', '\n').replace('\r', '\n')
+  notes = notes.replace('\n', '\n#  ')
+  data.append("# NOTES:")
+  data.append("#  %s\n#" % (notes))
+  
+  #Convert all newline conventions to unix style
+  l1d = str(fcObj.lane_1_library.library_id) + '|' \
+          + fcObj.lane_1_library.library_name.replace('\r\n', '\n').replace('\r', '\n').replace('%', '%%')
+  l2d = str(fcObj.lane_2_library.library_id) + '|' \
+          + fcObj.lane_2_library.library_name.replace('\r\n', '\n').replace('\r', '\n').replace('%', '%%')
+  l3d = str(fcObj.lane_3_library.library_id) + '|' \
+          + fcObj.lane_3_library.library_name.replace('\r\n', '\n').replace('\r', '\n').replace('%', '%%')
+  l4d = str(fcObj.lane_4_library.library_id) + '|' \
+          + fcObj.lane_4_library.library_name.replace('\r\n', '\n').replace('\r', '\n').replace('%', '%%')
+  
+  l5d = str(fcObj.lane_5_library.library_id) + '|' \
+          + fcObj.lane_5_library.library_name.replace('\r\n', '\n').replace('\r', '\n').replace('%', '%%')
+  l6d = str(fcObj.lane_6_library.library_id) + '|' \
+          + fcObj.lane_6_library.library_name.replace('\r\n', '\n').replace('\r', '\n').replace('%', '%%')
+  l7d = str(fcObj.lane_7_library.library_id) + '|' \
+          + fcObj.lane_7_library.library_name.replace('\r\n', '\n').replace('\r', '\n').replace('%', '%%')
+  l8d = str(fcObj.lane_8_library.library_id) + '|' \
+          + fcObj.lane_8_library.library_name.replace('\r\n', '\n').replace('\r', '\n').replace('%', '%%')
+  
+  # Turn new lines into indented commented newlines
+  l1d = l1d.replace('\n', '\n#  ')
+  l2d = l2d.replace('\n', '\n#  ')
+  l3d = l3d.replace('\n', '\n#  ')
+  l4d = l4d.replace('\n', '\n#  ')
+  l5d = l5d.replace('\n', '\n#  ')
+  l6d = l6d.replace('\n', '\n#  ')
+  l7d = l7d.replace('\n', '\n#  ')
+  l8d = l8d.replace('\n', '\n#  ')
+  
+  data.append("# Lane1: %s" % (l1d))
+  data.append("# Lane2: %s" % (l2d))
+  data.append("# Lane3: %s" % (l3d))
+  data.append("# Lane4: %s" % (l4d))
+  data.append("# Lane5: %s" % (l5d))
+  data.append("# Lane6: %s" % (l6d))
+  data.append("# Lane7: %s" % (l7d))
+  data.append("# Lane8: %s" % (l8d))
+  
+  #data.append("GENOME_DIR %s" % (BASE_DIR))
+  #data.append("CONTAM_DIR %s" % (BASE_DIR))
+  read_length = fcObj.read_length
+  #data.append("ELAND_REPEAT")
+  data.append("ELAND_MULTIPLE_INSTANCES 8")
+  
+  #Construct genome dictionary to figure out what lanes to put
+  # in the config file.
+  genome_dict = {}
+  
+  #l1s = form['lane1_species']
+  l1s = fcObj.lane_1_library.library_species.scientific_name #+ '|' + \
+        #fcObj.lane_1_library.library_species.use_genome_build
+  genome_dict.setdefault(l1s, []).append('1')
+  l2s = fcObj.lane_2_library.library_species.scientific_name #+ '|' + \
+        #fcObj.lane_2_library.library_species.use_genome_build
+  genome_dict.setdefault(l2s, []).append('2')
+  l3s = fcObj.lane_3_library.library_species.scientific_name #+ '|' + \
+        #fcObj.lane_3_library.library_species.use_genome_build
+  genome_dict.setdefault(l3s, []).append('3')
+  l4s = fcObj.lane_4_library.library_species.scientific_name #+ '|' + \
+        #fcObj.lane_4_library.library_species.use_genome_build
+  genome_dict.setdefault(l4s, []).append('4')
+  l5s = fcObj.lane_5_library.library_species.scientific_name #+ '|' + \
+        #fcObj.lane_5_library.library_species.use_genome_build
+  genome_dict.setdefault(l5s, []).append('5')
+  l6s = fcObj.lane_6_library.library_species.scientific_name #+ '|' + \
+        #fcObj.lane_6_library.library_species.use_genome_build
+  genome_dict.setdefault(l6s, []).append('6')
+  l7s = fcObj.lane_7_library.library_species.scientific_name #+ '|' + \
+        #fcObj.lane_7_library.library_species.use_genome_build
+  genome_dict.setdefault(l7s, []).append('7')
+  l8s = fcObj.lane_8_library.library_species.scientific_name #+ '|' + \
+        #fcObj.lane_8_library.library_species.use_genome_build
+  genome_dict.setdefault(l8s, []).append('8')
+  
+  genome_list = genome_dict.keys()
+  genome_list.sort()
+  
+  #Loop through and create entries for each species.
+  for genome in genome_list:
+    lanes = ''.join(genome_dict[genome])
+    if fcObj.paired_end:
+        data.append('%s:ANALYSIS eland_pair' % (lanes))
+    else:
+        data.append('%s:ANALYSIS eland_extended' % (lanes))
+    data.append('%s:READ_LENGTH %s' % (lanes, read_length))
+    data.append('%s:ELAND_GENOME %s' % (lanes, '%%(%s)s' % (genome)))
+    data.append('%s:USE_BASES %s' % (lanes, 'Y'*int(read_length)))
+    
+  data.append('SEQUENCE_FORMAT --fastq')
+  
+  data = '\n'.join(data)
+  
+  f = open(file_path, 'w')
+  f.write(data)
+  f.close()
+  
+  return data
+
+
+
+def config(request, flowcell=None):
+  """
+  Returns eland config file for a given flowcell number,
+  or returns a list of available flowcell numbers.
+  """
+  
+  # Provide INDEX of available Flowcell config files.
+  if flowcell is None:
+    #Find all FC* config files and report an index html file
+    #fc_list = [ os.path.split(file_path)[1] for file_path in glob.glob(os.path.join(settings.UPLOADTO_CONFIG_FILE, 'FC*')) ]
+    fc_list = [ fc.flowcell_id for fc in models.FlowCell.objects.all() ]
+    
+    #Convert FC* list to html links
+    fc_html = [ '<a href="/eland_config/%s/">%s</a>' % (fc_name, fc_name) for fc_name in fc_list ]
+      
+    return HttpResponse('<br />'.join(fc_html))
+  
+  #FIXME: Should validate flowcell input before using.
+  flowcell = _validate_input(flowcell)
+  cfg = getElandConfig(flowcell, regenerate=True)
+  
+  if not cfg:
+    return HttpResponse("Hmm, config file for %s does not seem to exist." % (flowcell))
+  
+  
+  return HttpResponse(cfg, mimetype="text/plain")
+
+
+
+
+#def index(request):
+#  """
+#  Return a form for filling out information about the flowcell
+#  """
+#  if request.method == 'POST':
+#    form = forms.ConfigForm(request.POST, error_class=forms.DivErrorList)
+#    if form.is_valid():
+#      #cfg = generateElandConfig(form)
+#      _saveConfigFile(form)
+#      _saveToDb(form)
+#      return HttpResponse("Eland Config Saved!", mimetype="text/plain")
+#    else:
+#      return render_to_response('config_form.html', {'form': form })
+#  
+#  else:   
+#    fm = forms.ConfigForm(error_class=forms.DivErrorList)
+#    return render_to_response('config_form.html', {'form': fm })
diff --git a/trunk/htsworkflow/frontend/experiments/__init__.py b/trunk/htsworkflow/frontend/experiments/__init__.py
new file mode 100755 (executable)
index 0000000..e69de29
diff --git a/trunk/htsworkflow/frontend/experiments/admin.py b/trunk/htsworkflow/frontend/experiments/admin.py
new file mode 100644 (file)
index 0000000..4e9074b
--- /dev/null
@@ -0,0 +1,80 @@
+from htsworkflow.frontend.experiments.models import FlowCell, DataRun, ClusterStation, Sequencer
+from django.contrib import admin
+from django.utils.translation import ugettext_lazy as _
+
+class DataRunOptions(admin.ModelAdmin):
+  search_fields = [
+      'run_folder',
+      'run_note',
+      'config_params',
+      '=fcid__lane_1_library__library_id',
+      '=fcid__lane_2_library__library_id',
+      '=fcid__lane_3_library__library_id',
+      '=fcid__lane_4_library__library_id',
+      '=fcid__lane_5_library__library_id',
+      '=fcid__lane_6_library__library_id',
+      '=fcid__lane_7_library__library_id',
+      '=fcid__lane_8_library__library_id'
+      'fcid__lane_1_library__library_name',
+      'fcid__lane_2_library__library_name',
+      'fcid__lane_3_library__library_name',
+      'fcid__lane_4_library__library_name',
+      'fcid__lane_5_library__library_name',
+      'fcid__lane_6_library__library_name',
+      'fcid__lane_7_library__library_name',
+      'fcid__lane_8_library__library_name'  ]
+  list_display = [
+      'run_folder', 
+      'Flowcell_Info', 
+      'run_start_time',
+      'main_status', 
+      'run_note',
+  ]
+  list_filter = ('run_status', 'run_start_time')
+
+class FlowCellOptions(admin.ModelAdmin):
+    date_hierarchy = "run_date"
+    save_on_top = True
+    search_fields = ('flowcell_id',
+        'sequencer__name',
+        'cluster_station__name',
+        '=lane_1_library__library_id',
+        '=lane_2_library__library_id',
+        '=lane_3_library__library_id',
+        '=lane_4_library__library_id',
+        '=lane_5_library__library_id',
+        '=lane_6_library__library_id',
+        '=lane_7_library__library_id',
+        '=lane_8_library__library_id',
+        'lane_1_library__library_name',
+        'lane_2_library__library_name',
+        'lane_3_library__library_name',
+        'lane_4_library__library_name',
+        'lane_5_library__library_name',
+        'lane_6_library__library_name',
+        'lane_7_library__library_name',
+        'lane_8_library__library_name')
+    list_display = ('flowcell_id','run_date','Lanes')
+    list_filter = ('sequencer','cluster_station')
+    fieldsets = (
+        (None, {
+            'fields': ('run_date', ('flowcell_id','cluster_station','sequencer'), ('read_length', 'paired_end'),)
+        }),
+        ('Lanes:', {
+           'fields' : (('lane_1_library', 'lane_1_pM', 'lane_1_cluster_estimate'), ('lane_2_library', 'lane_2_pM', 'lane_2_cluster_estimate'), ('lane_3_library', 'lane_3_pM', 'lane_3_cluster_estimate'), ('lane_4_library', 'lane_4_pM', 'lane_4_cluster_estimate'), ('lane_5_library', 'lane_5_pM', 'lane_5_cluster_estimate'), ('lane_6_library', 'lane_6_pM', 'lane_6_cluster_estimate'), ('lane_7_library', 'lane_7_pM', 'lane_7_cluster_estimate'), ('lane_8_library', 'lane_8_pM', 'lane_8_cluster_estimate'),)
+        }),
+        ('Notes:', { 'fields': ('notes',),}),
+    )
+
+class ClusterStationOptions(admin.ModelAdmin):
+    list_display = ('name', )
+    fieldsets = ( ( None, { 'fields': ( 'name', ) } ), )
+
+class SequencerOptions(admin.ModelAdmin):
+    list_display = ('name', )
+    fieldsets = ( ( None, { 'fields': ( 'name', ) } ), )
+
+admin.site.register(DataRun, DataRunOptions)
+admin.site.register(FlowCell, FlowCellOptions)
+admin.site.register(ClusterStation, ClusterStationOptions)
+admin.site.register(Sequencer, SequencerOptions)
diff --git a/trunk/htsworkflow/frontend/experiments/experiments.py b/trunk/htsworkflow/frontend/experiments/experiments.py
new file mode 100755 (executable)
index 0000000..ca224a9
--- /dev/null
@@ -0,0 +1,199 @@
+# some core functions of the exp tracker module
+from django.http import HttpResponse
+from datetime import datetime
+from string import *
+import re
+from htsworkflow.frontend import settings
+from htsworkflow.frontend.experiments.models import FlowCell, DataRun
+from htsworkflow.frontend.samples.models import Library
+from django.core.exceptions import ObjectDoesNotExist
+from django.core.mail import send_mail, mail_admins
+
+def updStatus(request):
+    output=''
+    user = 'none'
+    pswd = ''
+    UpdatedStatus = 'unknown'
+    fcid = 'none'
+    runfolder = 'unknown'
+    ClIP = request.META['REMOTE_ADDR']
+    granted = False    
+
+    if request.has_key('user'):
+      user = request['user']
+
+    #Check access permission 
+    if (user == 'rami' and settings.ALLOWED_IPS.has_key(ClIP)):  granted = True
+    if not granted: return HttpResponse("access denied.")
+
+
+    # ~~~~~~Parameters for the job ~~~~
+    if request.has_key('fcid'):
+      fcid = request['fcid']
+    else:
+      return HttpResponse('missing fcid')
+    
+    if request.has_key('runf'):
+      runfolder = request['runf']
+    else:
+      return HttpResponse('missing runf')
+
+    
+    if request.has_key('updst'):
+      UpdatedStatus = request['updst']
+    else:
+      return HttpResponse('missing status')
+    
+    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
+
+    # Update Data Run status in DB
+    # Try get rec. If not found return 'entry not found + <fcid><runfolder>', if found try update and return updated 
+    try:
+      rec = DataRun.objects.get(run_folder=runfolder)
+      rec.run_status = UpdatedStatus
+
+      #if there's a message update that too
+      mytimestamp = datetime.now().__str__()
+      mytimestamp = re.sub(pattern=":[^:]*$",repl="",string=mytimestamp)
+      if request.has_key('msg'):
+        rec.run_note += ", "+request['msg']+" ("+mytimestamp+")"
+      else :
+        if UpdatedStatus == '1':
+          rec.run_note = "Started ("+mytimestamp+")"
+
+      rec.save()
+      output = "Hello "+settings.ALLOWED_IPS[ClIP]+". Updated to:'"+DataRun.RUN_STATUS_CHOICES[int(UpdatedStatus)][1].__str__()+"'"
+    except ObjectDoesNotExist:
+      output = "entry not found: "+fcid+", "+runfolder
+
+
+    #Notify researcher by email
+    # Doesn't work
+    #send_mail('Exp Tracker', 'Data Run Status '+output, 'rrauch@stanford.edu', ['rrrami@gmail.com'], fail_silently=False)
+    #mail_admins("test subject", "testing , testing", fail_silently=False)
+    # gives error: (49, "Can't assign requested address")
+    return HttpResponse(output)
+
+def generateConfile(request,fcid):
+    #granted = False
+    #ClIP = request.META['REMOTE_ADDR']
+    #if (settings.ALLOWED_IPS.has_key(ClIP)):  granted = True
+
+    #if not granted: return HttpResponse("access denied.")
+
+    cnfgfile = 'READ_LENGTH 25\n'
+    cnfgfile += 'ANALYSIS eland\n'
+    cnfgfile += 'GENOME_FILE all_chr.fa\n'
+    cnfgfile += 'ELAND_MULTIPLE_INSTANCES 8\n'
+    genome_dir = 'GENOME_DIR /Volumes/Genomes/'
+    eland_genome = 'ELAND_GENOME /Volumes/Genomes/'
+    
+    try:                                                                                                                                              
+      rec = FlowCell.objects.get(flowcell_id=fcid)
+      
+      cnfgfile += '1:'+genome_dir+rec.lane_1_library.library_species.use_genome_build+'\n'
+      cnfgfile += '1:'+eland_genome+rec.lane_1_library.library_species.use_genome_build+'\n'
+
+      cnfgfile += '2:'+genome_dir+rec.lane_2_library.library_species.use_genome_build+'\n'
+      cnfgfile += '2:'+eland_genome+rec.lane_2_library.library_species.use_genome_build+'\n'
+      cnfgfile += '3:'+genome_dir+rec.lane_3_library.library_species.use_genome_build+'\n'
+      cnfgfile += '3:'+eland_genome+rec.lane_3_library.library_species.use_genome_build+'\n'
+
+      cnfgfile += '4:'+genome_dir+rec.lane_4_library.library_species.use_genome_build+'\n'
+      cnfgfile += '4:'+eland_genome+rec.lane_4_library.library_species.use_genome_build+'\n'
+      
+      cnfgfile += '5:'+genome_dir+rec.lane_5_library.library_species.use_genome_build+'\n'
+      cnfgfile += '5:'+eland_genome+rec.lane_5_library.library_species.use_genome_build+'\n'
+
+      cnfgfile += '6:'+genome_dir+rec.lane_6_library.library_species.use_genome_build+'\n'
+      cnfgfile += '6:'+eland_genome+rec.lane_6_library.library_species.use_genome_build+'\n'
+
+      cnfgfile += '7:'+genome_dir+rec.lane_7_library.library_species.use_genome_build+'\n'
+      cnfgfile += '7:'+eland_genome+rec.lane_7_library.library_species.use_genome_build+'\n'
+
+      cnfgfile += '8:'+genome_dir+rec.lane_8_library.library_species.use_genome_build+'\n'
+      cnfgfile += '8:'+eland_genome+rec.lane_8_library.library_species.use_genome_build
+
+    except ObjectDoesNotExist:
+      cnfgfile = 'Entry not found for fcid  = '+fcid
+
+    return cnfgfile
+
+def getConfile(req):
+    granted = False
+    ClIP = req.META['REMOTE_ADDR']
+    if (settings.ALLOWED_IPS.has_key(ClIP)):  granted = True
+
+    if not granted: return HttpResponse("access denied. IP: "+ClIP)
+
+    fcid = 'none'
+    cnfgfile = 'Nothing found'
+    runfolder = 'unknown'
+    request = req.REQUEST
+    print request, dir(request)
+    print request['fcid'], request.has_key('fcid')
+    print request['runf']
+    if request.has_key('fcid'):
+      fcid = request['fcid']
+      if request.has_key('runf'):
+        runfolder = request['runf']
+        try:
+          rec = DataRun.objects.get(run_folder=runfolder) #,flowcell_id=fcid)
+          cnfgfile = rec.config_params
+          #match_str = re.compile(r"READ_LENGTH.+$")
+          match_str = re.compile('^READ_LENGTH.+')
+          if not match_str.search(cnfgfile):
+            cnfgfile = generateConfile(request,fcid)
+            if match_str.search(cnfgfile):
+              rec = DataRun.objects.get(run_folder=runfolder) #,flowcell_id=fcid)
+              rec.config_params = cnfgfile
+              rec.save()
+            else:
+              cnfgfile = 'Failed generating config params for RunFolder = '+runfolder +', Flowcell id = '+ fcid+ ' Config Text:\n'+cnfgfile  
+            
+        except ObjectDoesNotExist:
+          cnfgfile = 'Entry not found for RunFolder = '+runfolder
+
+    return HttpResponse(cnfgfile, mimetype='text/plain')
+
+def getLaneLibs(req):
+    granted = False
+    ClIP = req.META['REMOTE_ADDR']
+    if (settings.ALLOWED_IPS.has_key(ClIP)):  granted = True
+
+    if not granted: return HttpResponse("access denied.")
+
+    request = req.REQUEST
+    fcid = 'none'
+    outputfile = ''
+    if request.has_key('fcid'):
+      fcid = request['fcid']
+      try:                                
+        rec = FlowCell.objects.get(flowcell_id=fcid)
+        #Ex: 071211
+        year = datetime.today().year.__str__()
+        year = replace(year,'20','')
+        month = datetime.today().month
+        if month < 10: month = "0"+month.__str__()
+        else: month = month.__str__() 
+        day = datetime.today().day
+        if day < 10: day = "0"+day.__str__()
+        else: day = day.__str__()
+        mydate = year+month+day
+        outputfile = '<?xml version="1.0" ?>'
+        outputfile += '\n<SolexaResult Date="'+mydate+'" Flowcell="'+fcid+'" Client="'+settings.ALLOWED_IPS[ClIP]+'">'
+        outputfile += '\n<Lane Index="1" Name="'+rec.lane_1_library.library_name+'" Library="'+rec.lane_1_library.library_id+'" Genome="'+rec.lane_1_library.library_species.use_genome_build+'" PrimerName="" PrimerSeq=""/>'
+        outputfile += '\n<Lane Index="2" Name="'+rec.lane_2_library.library_name+'" Library="'+rec.lane_2_library.library_id+'" Genome="'+rec.lane_2_library.library_species.use_genome_build+'" PrimerName="" PrimerSeq=""/>'
+        outputfile += '\n<Lane Index="3" Name="'+rec.lane_3_library.library_name+'" Library="'+rec.lane_3_library.library_id+'" Genome="'+rec.lane_3_library.library_species.use_genome_build+'" PrimerName="" PrimerSeq=""/>'
+        outputfile += '\n<Lane Index="4" Name="'+rec.lane_4_library.library_name+'" Library="'+rec.lane_4_library.library_id+'" Genome="'+rec.lane_4_library.library_species.use_genome_build+'" PrimerName="" PrimerSeq=""/>'
+        outputfile += '\n<Lane Index="5" Name="'+rec.lane_5_library.library_name+'" Library="'+rec.lane_5_library.library_id+'" Genome="'+rec.lane_5_library.library_species.use_genome_build+'" PrimerName="" PrimerSeq=""/>'
+        outputfile += '\n<Lane Index="6" Name="'+rec.lane_6_library.library_name+'" Library="'+rec.lane_6_library.library_id+'" Genome="'+rec.lane_6_library.library_species.use_genome_build+'" PrimerName="" PrimerSeq=""/>'
+        outputfile += '\n<Lane Index="7" Name="'+rec.lane_7_library.library_name+'" Library="'+rec.lane_7_library.library_id+'" Genome="'+rec.lane_7_library.library_species.use_genome_build+'" PrimerName="" PrimerSeq=""/>'
+        outputfile += '\n<Lane Index="8" Name="'+rec.lane_8_library.library_name+'" Library="'+rec.lane_8_library.library_id+'" Genome="'+rec.lane_8_library.library_species.use_genome_build+'" PrimerName="" PrimerSeq=""/>'
+        outputfile += '\n</SolexaResult>'
+      except ObjectDoesNotExist:
+        outputfile = 'Flowcell entry not found for: '+fcid
+    else: outputfile = 'Missing input: flowcell id'
+
+    return HttpResponse(outputfile, mimetype='text/plain')
diff --git a/trunk/htsworkflow/frontend/experiments/models.py b/trunk/htsworkflow/frontend/experiments/models.py
new file mode 100755 (executable)
index 0000000..1ea00d9
--- /dev/null
@@ -0,0 +1,161 @@
+from django.db import models
+from htsworkflow.frontend.samples.models import *
+from htsworkflow.frontend.settings import options
+from django.core.exceptions import ObjectDoesNotExist
+import logging
+
+class ClusterStation(models.Model):
+  name = models.CharField(max_length=50, unique=True)
+
+  def __unicode__(self):
+    return unicode(self.name)
+
+class Sequencer(models.Model):
+  name = models.CharField(max_length=50, unique=True)
+
+  def __unicode__(self):
+    return unicode(self.name)
+
+default_pM = 5
+try:
+  default_pM = int(options.get('frontend', 'default_pm'))
+except ValueError,e:
+  logging.error("invalid value for frontend.default_pm")
+
+class FlowCell(models.Model):
+  
+  flowcell_id = models.CharField(max_length=20, unique=True, db_index=True)
+  run_date = models.DateTimeField()
+  advanced_run = models.BooleanField(default=False)
+  paired_end = models.BooleanField(default=False)
+  read_length = models.IntegerField(default=32) #Stanford is currenlty 25
+  
+  lane_1_library = models.ForeignKey(Library, related_name="lane_1_library")
+  lane_2_library = models.ForeignKey(Library, related_name="lane_2_library")
+  lane_3_library = models.ForeignKey(Library, related_name="lane_3_library")
+  lane_4_library = models.ForeignKey(Library, related_name="lane_4_library")
+  lane_5_library = models.ForeignKey(Library, related_name="lane_5_library")
+  lane_6_library = models.ForeignKey(Library, related_name="lane_6_library")
+  lane_7_library = models.ForeignKey(Library, related_name="lane_7_library")
+  lane_8_library = models.ForeignKey(Library, related_name="lane_8_library")
+
+  lane_1_pM = models.DecimalField(max_digits=5, decimal_places=2,blank=False, null=False,default=default_pM)
+  lane_2_pM = models.DecimalField(max_digits=5, decimal_places=2,blank=False, null=False,default=default_pM)
+  lane_3_pM = models.DecimalField(max_digits=5, decimal_places=2,blank=False, null=False,default=default_pM)
+  lane_4_pM = models.DecimalField(max_digits=5, decimal_places=2,blank=False, null=False,default=default_pM)
+  lane_5_pM = models.DecimalField(max_digits=5, decimal_places=2,blank=False, null=False,default=default_pM)
+  lane_6_pM = models.DecimalField(max_digits=5, decimal_places=2,blank=False, null=False,default=default_pM)
+  lane_7_pM = models.DecimalField(max_digits=5, decimal_places=2,blank=False, null=False,default=default_pM)
+  lane_8_pM = models.DecimalField(max_digits=5, decimal_places=2,blank=False, null=False,default=default_pM)
+  
+  lane_1_cluster_estimate = models.IntegerField(blank=True, null=True)
+  lane_2_cluster_estimate = models.IntegerField(blank=True, null=True)
+  lane_3_cluster_estimate = models.IntegerField(blank=True, null=True)
+  lane_4_cluster_estimate = models.IntegerField(blank=True, null=True)
+  lane_5_cluster_estimate = models.IntegerField(blank=True, null=True)
+  lane_6_cluster_estimate = models.IntegerField(blank=True, null=True)
+  lane_7_cluster_estimate = models.IntegerField(blank=True, null=True)
+  lane_8_cluster_estimate = models.IntegerField(blank=True, null=True)
+  # lane_1_primer = models.ForeignKey(Primer,blank=True,null=True,related_name="lane_1_primer")
+  # lane_2_primer = models.ForeignKey(Primer,blank=True,null=True,related_name="lane_2_primer")
+  # lane_3_primer = models.ForeignKey(Primer,blank=True,null=True,related_name="lane_3_primer")
+  # lane_4_primer = models.ForeignKey(Primer,blank=True,null=True,related_name="lane_4_primer")
+  # lane_5_primer = models.ForeignKey(Primer,blank=True,null=True,related_name="lane_5_primer")
+  # lane_6_primer = models.ForeignKey(Primer,blank=True,null=True,related_name="lane_6_primer")
+  # lane_7_primer = models.ForeignKey(Primer,blank=True,null=True,related_name="lane_7_primer")
+  # lane_8_primer = models.ForeignKey(Primer,blank=True,null=True,related_name="lane_8_primer")
+
+  cluster_station = models.ForeignKey(ClusterStation, default=1)
+  sequencer = models.ForeignKey(Sequencer, default=1)
+  
+  notes = models.TextField(blank=True)
+
+  def __unicode__(self):
+      return unicode(self.flowcell_id) 
+
+  def Create_LOG(self):
+    str = ''
+    str +='<a target=_balnk href="/experiments/'+self.flowcell_id+'" title="Create XLS like sheet for this Flowcell ..." ">Create LOG</a>'
+    try:
+      t = DataRun.objects.get(fcid=self.id)
+      str +='<br/><a target=_self href="/admin/experiments/datarun/?q='+self.flowcell_id+'" title="Check Data Runs ..." ">DataRun ..</a>'
+    except ObjectDoesNotExist:
+      str += '<br/><span style="color:red">not sequenced</span>'
+    return str
+  Create_LOG.allow_tags = True 
+
+  def Lanes(self):
+    library_url = '/admin/samples/library/%s' 
+    html = ['<table>']
+    for i in range(1,9):
+        cluster_estimate = getattr(self, 'lane_%d_cluster_estimate' % (i,))
+        if cluster_estimate is not None:
+            cluster_estimate = "%s k" % ((int(cluster_estimate)/1000), )
+        else:
+            cluster_estimate = 'None'
+       library_id = getattr(self, 'lane_%d_library_id' % (i,))
+        library = getattr(self, 'lane_%d_library' % i)
+       element = '<tr><td>%d</td><td><a href="%s">%s</a></td><td>%s</td></tr>'
+        expanded_library_url = library_url %(library_id,)
+        html.append(element % (i, expanded_library_url, library, cluster_estimate))
+    html.append('</table>')
+    return "\n".join(html)
+  Lanes.allow_tags = True
+
+  class Meta:
+    ordering = ["-run_date"]
+  
+### -----------------------
+class DataRun(models.Model):
+  ConfTemplate = "CONFIG PARAMS WILL BE GENERATED BY THE PIPELINE SCRIPT.\nYOU'LL BE ABLE TO EDIT AFTER IF NEEDED."
+  run_folder = models.CharField(max_length=50,unique=True, db_index=True)
+  fcid = models.ForeignKey(FlowCell,verbose_name="Flowcell Id")
+  config_params = models.TextField(default=ConfTemplate)
+  run_start_time = models.DateTimeField()
+  RUN_STATUS_CHOICES = (
+      (0, 'Sequencer running'), ##Solexa Data Pipeline Not Yet Started'),
+      (1, 'Data Pipeline Started'),
+      (2, 'Data Pipeline Interrupted'),
+      (3, 'Data Pipeline Finished'),
+      (4, 'CollectReads Started'),
+      (5, 'CollectReads Finished'),
+      (6, 'QC Finished'),
+      (7, 'DONE'),
+    )
+  run_status = models.IntegerField(choices=RUN_STATUS_CHOICES, default=0)
+  run_note = models.TextField(blank=True)
+
+
+  def main_status(self):
+    str = '<div'
+    if self.run_status >= 5:
+      str += ' style="color:green">'
+      str += '<b>'+self.RUN_STATUS_CHOICES[self.run_status][1]+'</b>'
+      str += '<br/><br/>' #<span style="color:red;font-size:80%;">New!</span>'
+      str +='<br/><a target=_balnk href="'+settings.TASKS_PROJS_SERVER+'/Flowcells/'+self.fcid.flowcell_id+'/'+self.fcid.flowcell_id+'_QC_Summary.html" title="View QC Summaries of this run ..." ">View QC Page</a>'
+    else:
+      str += '>'+self.RUN_STATUS_CHOICES[self.run_status][1]
+
+    str += '</div>'
+    return str
+  main_status.allow_tags = True
+
+  main_status.allow_tags = True
+  
+  def Flowcell_Info(self):
+    str = '<b>'+self.fcid.__str__()+'</b>'
+    str += '  (c: '+self.fcid.cluster_mac_id+',  s: '+self.fcid.seq_mac_id+')'
+    str += '<div style="margin-top:5px;">'    
+    str +='<a title="View Lane List here ..."  onClick="el = document.getElementById(\'LanesOf'+self.fcid.__str__()+'\');if(el) (el.style.display==\'none\'?el.style.display=\'block\':el.style.display=\'none\')" style="cursor:pointer;color: #5b80b2;">View/hide lanes</a>'
+    str += '<div id="LanesOf'+self.fcid.__str__()+'" style="display:block;border:solid #cccccc 1px;width:350px">'
+    LanesList = '1: '+self.fcid.lane_1_library.__str__()+' ('+self.fcid.lane_1_library.library_species.use_genome_build+')<br/>2: '+self.fcid.lane_2_library.__str__()+' ('+self.fcid.lane_2_library.library_species.use_genome_build+')<br/>3: '+self.fcid.lane_3_library.__str__()+' ('+self.fcid.lane_3_library.library_species.use_genome_build+')<br/>4: '+self.fcid.lane_4_library.__str__()+' ('+self.fcid.lane_4_library.library_species.use_genome_build+')<br/>5: '+self.fcid.lane_5_library.__str__()+' ('+self.fcid.lane_5_library.library_species.use_genome_build+')<br/>6: '+self.fcid.lane_6_library.__str__()+' ('+self.fcid.lane_6_library.library_species.use_genome_build+')<br/>7: '+self.fcid.lane_7_library.__str__()+' ('+self.fcid.lane_7_library.library_species.use_genome_build+')<br/>8: '+self.fcid.lane_8_library.__str__()+' ('+self.fcid.lane_8_library.library_species.use_genome_build+')'
+    str += LanesList ## self.fcid.Lanes()
+    str += '</div>'
+    str += '<div><a title="open Flowcell record" href="/admin/exp_track/flowcell/'+self.fcid.id.__str__()+'/" target=_self>Edit Flowcell record</a>'
+    #str += '<span style="color:red;font-size:80%;margin-left:15px;margin-right:3px">New!</span>'
+    str +='<a style="margin-left:15px;" target=_balnk href="/exp_track/'+self.fcid.flowcell_id+'" title="View XLS like sheet for this Flowcell LOG ..." ">GA LOG Page</a>'
+    str += '</div>'
+    str += '</div>'    
+    return str
+  Flowcell_Info.allow_tags = True
diff --git a/trunk/htsworkflow/frontend/experiments/urls.py b/trunk/htsworkflow/frontend/experiments/urls.py
new file mode 100755 (executable)
index 0000000..c4df6a8
--- /dev/null
@@ -0,0 +1,12 @@
+from django.conf.urls.defaults import *
+
+urlpatterns = patterns('',
+                                                                                                      
+    (r'^$', 'htsworkflow.frontend.experiments.views.index'),
+    #(r'^liblist$', 'htsworkflow.frontend.experiments.views.test_Libs'),
+    #(r'^(?P<run_folder>.+)/$', 'gaworkflow.frontend.experiments.views.detail'),
+    (r'^(?P<fcid>.+)/$', 'htsworkflow.frontend.experiments.views.makeFCSheet'),
+    (r'^updStatus$', 'htsworkflow.frontend.experiments.experiments.updStatus'),
+    (r'^getConfile$', 'htsworkflow.frontend.experiments.experiments.getConfile'),
+    (r'^getLanesNames$', 'htsworkflow.frontend.experiments.experiments.getLaneLibs')   
+)
diff --git a/trunk/htsworkflow/frontend/experiments/views.py b/trunk/htsworkflow/frontend/experiments/views.py
new file mode 100755 (executable)
index 0000000..a2d14bb
--- /dev/null
@@ -0,0 +1,34 @@
+# Create your views here.
+#from django.template import Context, loader
+#shortcut to the above modules
+from django.shortcuts import render_to_response, get_object_or_404
+from htsworkflow.frontend.experiments.models import *
+from django.http import HttpResponse
+from django.core.exceptions import ObjectDoesNotExist
+
+def index(request):
+    all_runs = DataRun.objects.order_by('-run_start_time')
+    #t = loader.get_template('experiments/index.html')
+    #c = Context({
+    #    'data_run_list': all_runs,
+    #})
+    #return HttpResponse(t.render(c)) 
+    # shortcut to the above module usage
+    return render_to_response('experiments/index.html',{'data_run_list': all_runs}) 
+    
+def detail(request, run_folder):
+    html_str = '<h2>Exp Track Details Page</h2>'
+    html_str += 'Run Folder: '+run_folder
+    r = get_object_or_404(DataRun,run_folder=run_folder)
+    return render_to_response('experiments/detail.html',{'run_f': r})
+
+def makeFCSheet(request,fcid):
+  # get Flowcell by input fcid
+  # ...
+  rec = None
+  try:
+    rec = FlowCell.objects.get(flowcell_id=fcid)
+  except ObjectDoesNotExist:
+    pass
+  lanes = ['1','2','3','4','5','6','7','8']
+  return render_to_response('experiments/flowcellSheet.html',{'fc': rec})
diff --git a/trunk/htsworkflow/frontend/inventory/__init__.py b/trunk/htsworkflow/frontend/inventory/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/trunk/htsworkflow/frontend/inventory/admin.py b/trunk/htsworkflow/frontend/inventory/admin.py
new file mode 100644 (file)
index 0000000..4e17177
--- /dev/null
@@ -0,0 +1,35 @@
+from django.contrib import admin
+
+from htsworkflow.frontend.inventory.models import Item, ItemInfo, ItemType, Vendor, Location, LongTermStorage, ItemStatus
+
+class ItemAdmin(admin.ModelAdmin):
+    list_display = ('uuid', 'barcode_id','item_type', 'item_info', 'location', 'force_use_uuid', 'creation_date')
+    list_filter = (
+        'item_type',
+    )
+
+class ItemInfoAdmin(admin.ModelAdmin):
+    pass
+
+class ItemTypeAdmin(admin.ModelAdmin):
+    pass
+
+class VendorAdmin(admin.ModelAdmin):
+    pass
+
+class LocationAdmin(admin.ModelAdmin):
+    pass
+
+class LongTermStorageAdmin(admin.ModelAdmin):
+    pass
+
+class ItemStatusAdmin(admin.ModelAdmin):
+    pass
+
+admin.site.register(Item, ItemAdmin)
+admin.site.register(ItemInfo, ItemInfoAdmin)
+admin.site.register(ItemType, ItemTypeAdmin)
+admin.site.register(Vendor, VendorAdmin)
+admin.site.register(Location, LocationAdmin)
+admin.site.register(LongTermStorage, LongTermStorageAdmin)
+admin.site.register(ItemStatus, ItemStatusAdmin)
diff --git a/trunk/htsworkflow/frontend/inventory/models.py b/trunk/htsworkflow/frontend/inventory/models.py
new file mode 100644 (file)
index 0000000..8f54fc3
--- /dev/null
@@ -0,0 +1,126 @@
+from django.db import models
+from django.db.models.signals import pre_save
+
+from htsworkflow.frontend.samples.models import Library
+from htsworkflow.frontend.experiments.models import FlowCell
+
+
+import uuid
+
+def _assign_uuid(sender, instance, **kwargs):
+    """
+    Assigns a UUID to model on save
+    """
+    print 'Entered _assign_uuid'
+    if instance.uuid is None or len(instance.uuid) != 32:
+        instance.uuid = uuid.uuid1().hex
+
+
+class Vendor(models.Model):
+    name = models.CharField(max_length=256)
+    url = models.URLField(blank=True, null=True)
+
+    def __unicode__(self):
+        return u"%s" % (self.name)
+
+
+class Location(models.Model):
+    
+    name = models.CharField(max_length=256, unique=True)
+    location_description = models.TextField()
+    
+    uuid = models.CharField(max_length=32, blank=True, help_text="Leave blank for automatic UUID generation")
+    
+    notes = models.TextField(blank=True, null=True)
+    
+    def __unicode__(self):
+        if len(self.location_description) > 16:
+            return u"%s: %s" % (self.name, self.location_description[0:16]+u"...")
+        else:
+            return u"%s: %s" % (self.name, self.location_description)
+
+pre_save.connect(_assign_uuid, sender=Location)
+
+class ItemInfo(models.Model):
+    model_id = models.CharField(max_length=256, blank=True, null=True)
+    part_number = models.CharField(max_length=256, blank=True, null=True)
+    lot_number = models.CharField(max_length=256, blank=True, null=True)
+    
+    url = models.URLField(blank=True, null=True)
+    
+    qty_purchased = models.IntegerField(default=1)
+    
+    vendor = models.ForeignKey(Vendor)
+    purchase_date = models.DateField(blank=True, null=True)
+    warranty_months = models.IntegerField(blank=True, null=True)
+    
+    notes = models.TextField(blank=True, null=True)
+    
+    def __unicode__(self):
+        name = u''
+        if self.model_id:
+            name += u"model:%s " % (self.model_id)
+        if self.part_number:
+            name += u"part:%s " % (self.part_number)
+        if self.lot_number:
+            name += u"lot:%s " % (self.lot_number)
+            
+        return u"%s: %s" % (name, self.purchase_date)
+
+
+class ItemType(models.Model):
+    
+    name = models.CharField(max_length=64, unique=True)
+    description = models.TextField(blank=True, null=True)
+    
+    def __unicode__(self):
+        return u"%s" % (self.name)
+
+class ItemStatus(models.Model):
+    name = models.CharField(max_length=64, unique=True)
+    notes = models.TextField(blank=True, null=True)
+    
+    def __unicode__(self):
+        return self.name
+
+class Item(models.Model):
+    
+    item_type = models.ForeignKey(ItemType)
+    
+    #Automatically assigned uuid; used for barcode if one is not provided in
+    # barcode_id
+    uuid = models.CharField(max_length=32, blank=True, help_text="Leave blank for automatic UUID generation")
+    
+    # field for existing barcodes; used instead of uuid if provided
+    barcode_id = models.CharField(max_length=256, blank=True, null=True)
+    force_use_uuid = models.BooleanField(default=False)
+    
+    item_info = models.ForeignKey(ItemInfo)
+    
+    location = models.ForeignKey(Location)
+    
+    status = models.ForeignKey(ItemStatus, blank=True, null=True)
+    
+    creation_date = models.DateTimeField(auto_now_add=True)
+    modified_date = models.DateTimeField(auto_now=True)
+    
+    notes = models.TextField(blank=True, null=True)
+    
+    def __unicode__(self):
+        if self.barcode_id is None or len(self.barcode_id) == 0:
+            return u"invu|%s" % (self.uuid)
+        else:
+            return u"invb|%s" % (self.barcode_id)
+            
+pre_save.connect(_assign_uuid, sender=Item)
+
+
+class LongTermStorage(models.Model):
+    
+    flowcell = models.ForeignKey(FlowCell)
+    libraries = models.ManyToManyField(Library)
+
+    storage_devices = models.ManyToManyField(Item)
+    
+    def __unicode__(self):
+        return u"%s: %s" % (str(self.flowcell), ', '.join([ str(s) for s in self.storage_devices.iterator() ]))
\ No newline at end of file
diff --git a/trunk/htsworkflow/frontend/inventory/urls.py b/trunk/htsworkflow/frontend/inventory/urls.py
new file mode 100644 (file)
index 0000000..844208e
--- /dev/null
@@ -0,0 +1,5 @@
+from django.conf.urls.defaults import *
+
+urlpatterns = patterns('',
+     (r'^lts/link/(?P<flowcell>.+)/(?P<serial>.+)/$', 'htsworkflow.frontend.inventory.views.link_flowcell_and_device'),                                                                                                 
+    )
diff --git a/trunk/htsworkflow/frontend/inventory/views.py b/trunk/htsworkflow/frontend/inventory/views.py
new file mode 100644 (file)
index 0000000..19bcb0b
--- /dev/null
@@ -0,0 +1,117 @@
+from htsworkflow.frontend.inventory.models import Item, LongTermStorage
+from htsworkflow.frontend.experiments.models import FlowCell
+
+from django.core.exceptions import ObjectDoesNotExist
+from django.http import HttpResponse
+
+
+def link_flowcell_and_device(request, flowcell, serial):
+    """
+    Updates database records of a flowcell being archived on a device with a particular serial #
+    """
+    assert flowcell is not None
+    assert serial is not None
+    
+    LTS_UPDATED = False
+    SD_UPDATED = False
+    LIBRARY_UPDATED = False
+        
+    ###########################################
+    # Retrieve Storage Device
+    try:
+        sd = Item.objects.get(barcode_id=serial)
+    except ObjectDoesNotExist, e:
+        msg = "Item with barcode_id of %s not found." % (serial)
+        raise ObjectDoesNotExist(msg)
+    
+    ###########################################
+    # Retrieve FlowCell
+    try:    
+        fc = FlowCell.objects.get(flowcell_id=flowcell)
+    except ObjectDoesNotExist, e:
+        msg = "FlowCell with flowcell_id of %s not found." % (flowcell)
+        raise ObjectDoesNotExist(msg)
+    
+    ###########################################
+    # Retrieve or create LongTermStorage Object
+    count = fc.longtermstorage_set.count()
+    lts = None
+    if count > 1:
+        msg = "There really should only be one longtermstorage object per flowcell"
+        raise ValueError, msg
+    elif count == 1:
+        # lts already attached to flowcell
+        lts = fc.longtermstorage_set.all()[0]
+    else:
+        lts = LongTermStorage()
+        # Attach flowcell
+        lts.flowcell = fc
+        # Need a primary keey before linking to storage devices
+        lts.save()
+        LTS_UPDATED = True
+        
+        
+    ############################################
+    # Link Storage to Flowcell
+    
+    # Add a link to this storage device if it is not already linked.
+    if sd not in lts.storage_devices.all():
+        lts.storage_devices.add(sd)
+        SD_UPDATED = True
+    
+    ###########################################
+    # Add Library Links to LTS
+    
+    if fc.lane_1_library not in lts.libraries.all():
+        lts.libraries.add(fc.lane_1_library)
+        LIBRARY_UPDATED = True
+        print 1
+    
+    if fc.lane_2_library not in lts.libraries.all():
+        lts.libraries.add(fc.lane_2_library)
+        LIBRARY_UPDATED = True
+        print 2
+    
+    if fc.lane_3_library not in lts.libraries.all():
+        lts.libraries.add(fc.lane_3_library)
+        LIBRARY_UPDATED = True
+        print 3
+    
+    if fc.lane_4_library not in lts.libraries.all():
+        lts.libraries.add(fc.lane_4_library)
+        LIBRARY_UPDATED = True
+        print 4
+    
+    
+    if fc.lane_5_library not in lts.libraries.all():
+        lts.libraries.add(fc.lane_5_library)
+        LIBRARY_UPDATED = True
+        print 5
+    
+    if fc.lane_6_library not in lts.libraries.all():
+        lts.libraries.add(fc.lane_6_library)
+        LIBRARY_UPDATED = True
+        print 6
+    
+    if fc.lane_7_library not in lts.libraries.all():
+        lts.libraries.add(fc.lane_7_library)
+        LIBRARY_UPDATED = True
+        print 7
+    
+    if fc.lane_8_library not in lts.libraries.all():
+        lts.libraries.add(fc.lane_8_library)
+        LIBRARY_UPDATED = True
+        print 8
+        
+    # Save Changes
+    lts.save()
+    
+    msg = ['Success:']
+    if LTS_UPDATED or SD_UPDATED or LIBRARY_UPDATED:
+        msg.append('  LongTermStorage (LTS) Created: %s' % (LTS_UPDATED))
+        msg.append('   Storage Device Linked to LTS: %s' % (SD_UPDATED))
+        msg.append('       Libraries updated in LTS: %s' % (LIBRARY_UPDATED))
+    else:
+        msg.append('  No Updates Needed.')
+    
+    return HttpResponse('\n'.join(msg))
\ No newline at end of file
diff --git a/trunk/htsworkflow/frontend/manage.py b/trunk/htsworkflow/frontend/manage.py
new file mode 100644 (file)
index 0000000..5e78ea9
--- /dev/null
@@ -0,0 +1,11 @@
+#!/usr/bin/env python
+from django.core.management import execute_manager
+try:
+    import settings # Assumed to be in the same directory.
+except ImportError:
+    import sys
+    sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
+    sys.exit(1)
+
+if __name__ == "__main__":
+    execute_manager(settings)
diff --git a/trunk/htsworkflow/frontend/reports/LibraryInfo.xml b/trunk/htsworkflow/frontend/reports/LibraryInfo.xml
new file mode 100644 (file)
index 0000000..ec630b5
--- /dev/null
@@ -0,0 +1,1214 @@
+<?xml version="1.0" ?>
+<Libraries>
+<Library Name="SL3">
+<Track Flowcell="FC11862" Lane="2" Filename="Flowcells/FC11862/070919_FC11862_s2_NRSF_monoclonal_Jurkat_rep2_SL3.align_25.hg18.txt" Count=" 1423565" Date="070919" />
+</Library>
+<Library Name="SL4">
+<Track Flowcell="FC10073" Lane="3" Filename="Flowcells/FC10073/070821_FC10073_s3_mock_IP_Jurkat_rep1_SL4.align_25.hg18.txt" Count=" 2132972" Date="070821" />
+<Track Flowcell="FC10182" Lane="18" Filename="Flowcells/FC10182/070925_FC10182_s18_mock_IP_Jurkat_rep1_SL4.align_25.hg18.txt" Count=" 4061515" Date="070925" />
+<Track Flowcell="FC11862" Lane="6" Filename="Flowcells/FC11862/070919_FC11862_s6_Mock_IP_Jurkat_rep1_SL4.align_25.hg18.txt" Count=" 2397558" Date="070919" />
+<Track Flowcell="FC11977" Lane="8" Filename="Flowcells/FC11977/070928_FC11977_s8_mock_IP_Jurkat_rep1_SL4.align_25.hg18.txt" Count=" 1088135" Date="070928" />
+<Track Flowcell="FC8053" Lane="78" Filename="Flowcells/FC8053/070724_FC8053_s78_mock_IP_Jurkat_rep1_SL4.align_25.hg18.txt" Count=" 3583766" Date="070724" />
+<Track Flowcell="FC8883" Lane="78" Filename="Flowcells/FC8883/070720_FC8883_s78_mock_IP_Jurkat_rep1_SL4.align_25.hg18.txt" Count=" 5491316" Date="070720" />
+<Track Flowcell="FC8886" Lane="7" Filename="Flowcells/FC8886/070802_FC8886_s7_mock_IP_Jurkat_rep1_SL4.align_25.hg18.txt" Count=" 2417786" Date="070802" />
+</Library>
+<Library Name="SL10">
+<Track Flowcell="FC11977" Lane="7" Filename="Flowcells/FC11977/070928_FC11977_s7_hct_1_msp_SL10.align_25.hg18.txt" Count=" 1801596" Date="070928" />
+<Track Flowcell="FC13593" Lane="678" Filename="Flowcells/FC13593/071002_FC13593_s678_hct_1_msp_SL10.align_25.hg18.txt" Count=" 7978981" Date="071002" />
+</Library>
+<Library Name="SL11">
+<Track Flowcell="FCOLD1" Lane="1" Filename="Flowcells/FCOLD1/070000_FCOLD1_s1_SRF_ChIP_Jurkat_Rep1_SL11.align_25.hg18.txt" Count=" 8721730" Date="070000" />
+</Library>
+<Library Name="SL13">
+<Track Flowcell="FC7225" Lane="67" Filename="Flowcells/FCSL13/070629_FC7225_s67_FoxP2_PFSK1_SL13.align_25.hg18.txt" Count=" 3830788" Date="070629" />
+<Track Flowcell="FC5507" Lane="67" Filename="Flowcells/FCSL13/070703_FC5507_s67_FoxP2_PFSK1_SL13.align_25.hg18.txt" Count=" 2694525" Date="070703" />
+<Track Flowcell="FC8053" Lane="56" Filename="Flowcells/FCSL13/070724_FC8053_s56_FoxP2_PFSK1_SL13.align_25.hg18.txt" Count=" 2214065" Date="070724" />
+<Track Flowcell="FC7857" Lane="8" Filename="Flowcells/FCSL13/070807_FC7857_s8_FoxP2_PFSK1_SL13.align_25.hg18.txt" Count=" 1273565" Date="070807" />
+</Library>
+<Library Name="SL14">
+<Track Flowcell="FC10135" Lane="4" Filename="Flowcells/FC10135/071005_FC10135_s4_FoxP2_polyclonal_pfsk1_SL14.align_25.hg18.txt" Count=" 2438679" Date="071005" />
+<Track Flowcell="FC11977" Lane="6" Filename="Flowcells/FC11977/070928_FC11977_s6_FoxP2_polyclonal_pfsk1_SL14.align_25.hg18.txt" Count=" 2007880" Date="070928" />
+<Track Flowcell="FC13593" Lane="5" Filename="Flowcells/FC13593/071002_FC13593_s5_FoxP2_polyclonal_pfsk1_SL14.align_25.hg18.txt" Count=" 2533720" Date="071002" />
+</Library>
+<Library Name="SL16">
+<Track Flowcell="FC201U7" Lane="12345678" Filename="Flowcells/FC201U7/071205_FC201U7_s12345678_mock_IP_PFSK1_SL16.align_25.hg.txt" Count=" 23476737" Date="071205" />
+</Library>
+<Library Name="SL24">
+<Track Flowcell="FC5512" Lane="123" Filename="Flowcells/FC5512/070609_FC5512_s123_d5_C_hpa_SL24.align_25.hg18.txt" Count=" 1534145" Date="070609" />
+<Track Flowcell="FC5738" Lane="12" Filename="Flowcells/FC5738/070605_FC5738_s12_d5_C_hpa_SL24.align_25.hg18.txt" Count="  267543" Date="070605" />
+<Track Flowcell="FC6058" Lane="6" Filename="Flowcells/FC6058/070608_FC6058_s6_d5_C_hpa_SL24.align_25.hg18.txt" Count=" 2174107" Date="070608" />
+</Library>
+<Library Name="SL26">
+<Track Flowcell="FC5512" Lane="45" Filename="Flowcells/FC5512/070609_FC5512_s45_ES_E_hpa_SL26.align_25.hg18.txt" Count="  833887" Date="070609" />
+<Track Flowcell="FC5738" Lane="4" Filename="Flowcells/FC5738/070605_FC5738_s4_d0_ES_E_hpa_SL26.align_25.hg18.txt" Count="  324128" Date="070605" />
+<Track Flowcell="FC6058" Lane="3" Filename="Flowcells/FC6058/070608_FC6058_s3_ES_E_hpa_SL26.align_25.hg18.txt" Count=" 2385605" Date="070608" />
+<Track Flowcell="FC8880" Lane="78" Filename="Flowcells/FC8880/070810_FC8880_s78_d0_ES_E_hpa_SL26.align_25.hg18.txt" Count="   29965" Date="070810" />
+</Library>
+<Library Name="SL28">
+<Track Flowcell="FC6058" Lane="78" Filename="Flowcells/FC6058/070608_FC6058_s78_d5_D_hpa_SL28.align_25.hg18.txt" Count=" 4785632" Date="070608" />
+</Library>
+<Library Name="SL30">
+<Track Flowcell="FC6058" Lane="4" Filename="Flowcells/FC6058/070608_FC6058_s4_ES_F_hpa_SL30.align_25.hg18.txt" Count=" 2383276" Date="070608" />
+</Library>
+<Library Name="SL33">
+<Track Flowcell="FC10182" Lane="3" Filename="Flowcells/FC10182/070925_FC10182_s3_hct_2_hpa_SL33.align_25.hg18.txt" Count="  740316" Date="070925" />
+<Track Flowcell="FC5399" Lane="3" Filename="Flowcells/FC5399/070713_FC5399_s3_hct_2_hpa_SL33.align_25.hg18.txt" Count="  554691" Date="070713" />
+<Track Flowcell="FC7853" Lane="3" Filename="Flowcells/FC7853/070709_FC7853_s3_hct_2_hpa_SL33.align_25.hg18.txt" Count="  734854" Date="070709" />
+<Track Flowcell="FC8880" Lane="34" Filename="Flowcells/FC8880/070810_FC8880_s34_hct_2_hpa_SL33.align_25.hg18.txt" Count=" 1360438" Date="070810" />
+</Library>
+<Library Name="SL37">
+<Track Flowcell="FC5399" Lane="2" Filename="Flowcells/FC5399/070713_FC5399_s2_liver_2_hpa_SL37.align_25.hg18.txt" Count="  374553" Date="070713" />
+<Track Flowcell="FC7853" Lane="2" Filename="Flowcells/FC7853/070709_FC7853_s2_liver_2_hpa_SL37.align_25.hg18.txt" Count=" 1100144" Date="070709" />
+<Track Flowcell="FC8883" Lane="3" Filename="Flowcells/FC8883/070720_FC8883_s3_liver_2_hpa_SL37.align_25.hg18.txt" Count=" 1276813" Date="070720" />
+</Library>
+<Library Name="SL38">
+<Track Flowcell="FC10182" Lane="7" Filename="Flowcells/FC10182/070925_FC10182_s7_liver_1_msp_SL38.align_25.hg18.txt" Count=" 1039338" Date="070925" />
+<Track Flowcell="FC11977" Lane="34" Filename="Flowcells/FC11977/070928_FC11977_s34_liver_1_msp_SL38.align_25.hg18.txt" Count=" 2094254" Date="070928" />
+<Track Flowcell="FC7857" Lane="5" Filename="Flowcells/FC7857/070807_FC7857_s5_liver_1_msp_SL38.align_25.hg18.txt" Count=" 1075706" Date="070807" />
+</Library>
+<Library Name="SL39">
+<Track Flowcell="FC5399" Lane="1" Filename="Flowcells/FC5399/070713_FC5399_s1_liver_1_hpa_SL39.align_25.hg18.txt" Count="  336859" Date="070713" />
+<Track Flowcell="FC7853" Lane="1" Filename="Flowcells/FC7853/070709_FC7853_s1_liver_1_hpa_SL39.align_25.hg18.txt" Count="  973571" Date="070709" />
+<Track Flowcell="FC8880" Lane="56" Filename="Flowcells/FC8880/070810_FC8880_s56_liver_1_hpa_SL39.align_25.hg18.txt" Count="  876351" Date="070810" />
+<Track Flowcell="FC8883" Lane="4" Filename="Flowcells/FC8883/070720_FC8883_s4_liver_1_hpa_SL39.align_25.hg18.txt" Count=" 1316822" Date="070720" />
+</Library>
+<Library Name="SL40">
+<Track Flowcell="FC11862" Lane="7" Filename="Flowcells/FC11862/070919_FC11862_s7_RNAseq_skeletal_muscle_human_rep1_SL40.align_25.hg18.txt" Count=" 1613090" Date="070919" />
+</Library>
+<Library Name="SL41">
+<Track Flowcell="FC11862" Lane="8" Filename="Flowcells/FC11862/070919_FC11862_s8_RNAseq_skeletal_muscle_human_rep2_SL41.align_25.hg18.txt" Count=" 1036954" Date="070919" />
+</Library>
+<Library Name="SL42">
+<Track Flowcell="FC5507" Lane="12" Filename="Flowcells/FC5507/070703_FC5507_s12_AFP+_1_hpa_SL42.align_25.hg18.txt" Count=" 2297609" Date="070703" />
+<Track Flowcell="FC7225" Lane="1" Filename="Flowcells/FC7225/070629_FC7225_s1_AFP+_1_hpa_SL42.align_25.hg18.txt" Count="  921537" Date="070629" />
+<Track Flowcell="FC7225" Lane="2" Filename="Flowcells/FC7225/070629_FC7225_s2_AFP+_1_hpa_SL42.align_25.hg18.txt" Count=" 1042305" Date="070629" />
+</Library>
+<Library Name="SL43">
+<Track Flowcell="FC5507" Lane="3" Filename="Flowcells/FC5507/070703_FC5507_s3_AFP-_1_hpa_SL43.align_25.hg18.txt" Count=" 1351625" Date="070703" />
+</Library>
+<Library Name="SL44">
+<Track Flowcell="FC5507" Lane="4" Filename="Flowcells/FC5507/070703_FC5507_s4_EB_1_hpa_SL44.align_25.hg18.txt" Count=" 1269627" Date="070703" />
+<Track Flowcell="FC7225" Lane="4" Filename="Flowcells/FC7225/070629_FC7225_s4_EB_1_hpa_SL44.align_25.hg18.txt" Count=" 1681253" Date="070629" />
+</Library>
+<Library Name="SL46">
+<Track Flowcell="FC5507" Lane="5" Filename="Flowcells/FC5507/070703_FC5507_s5_CM_1_hpa_SL46.align_25.hg18.txt" Count=" 1473405" Date="070703" />
+<Track Flowcell="FC7225" Lane="5" Filename="Flowcells/FC7225/070629_FC7225_s5_CM_1_hpa_SL46.align_25.hg18.txt" Count=" 1875969" Date="070629" />
+</Library>
+<Library Name="SL48">
+<Track Flowcell="FC8053" Lane="3" Filename="Flowcells/FC8053/070724_FC8053_s3_sperm_A_hpa_SL48.align_25.hg18.txt" Count="  364972" Date="070724" />
+<Track Flowcell="FC8883" Lane="1" Filename="Flowcells/FC8883/070720_FC8883_s1_sperm_A_hpa_SL48.align_25.hg18.txt" Count="  642474" Date="070720" />
+<Track Flowcell="FC8886" Lane="12" Filename="Flowcells/FC8886/070802_FC8886_s12_sperm_A_hpa_SL48.align_25.hg18.txt" Count=" 1589274" Date="070802" />
+</Library>
+<Library Name="SL49">
+<Track Flowcell="FC8053" Lane="4" Filename="Flowcells/FC8053/070724_FC8053_s4_sperm_B_hpa_SL49.align_25.hg18.txt" Count="  426192" Date="070724" />
+<Track Flowcell="FC8883" Lane="2" Filename="Flowcells/FC8883/070720_FC8883_s2_sperm_B_hpa_SL49.align_25.hg18.txt" Count="  956543" Date="070720" />
+<Track Flowcell="FC8886" Lane="34" Filename="Flowcells/FC8886/070802_FC8886_s34_sperm_B_hpa_SL49.align_25.hg18.txt" Count=" 1697031" Date="070802" />
+</Library>
+<Library Name="SL50">
+<Track Flowcell="FC8879" Lane="3" Filename="Flowcells/FC8879/070717_FC8879_s3_sperm_A_msp_SL50.align_25.hg18.txt" Count="  249065" Date="070717" />
+</Library>
+<Library Name="SL53">
+<Track Flowcell="FC10073" Lane="2" Filename="Flowcells/FC10073/070821_FC10073_s2_d0_ES_C_msp_SL53.align_25.hg18.txt" Count="  459147" Date="070821" />
+<Track Flowcell="FC10182" Lane="2" Filename="Flowcells/FC10182/070925_FC10182_s2_d0_ES_C_msp_SL53.align_25.hg18.txt" Count="  513510" Date="070925" />
+<Track Flowcell="FC11862" Lane="345" Filename="Flowcells/FC11862/070919_FC11862_s345_d0_ES_C_msp_SL53.align_25.hg18.txt" Count=" 1650605" Date="070919" />
+</Library>
+<Library Name="SL54">
+<Track Flowcell="FC10073" Lane="456" Filename="Flowcells/FC10073/070821_FC10073_s456_pancreas_1_hpa_SL54.align_25.hg18.txt" Count=" 4352582" Date="070821" />
+<Track Flowcell="FC10182" Lane="4" Filename="Flowcells/FC10182/070925_FC10182_s4_pancreas_1_hpa_SL54.align_25.hg18.txt" Count=" 1136630" Date="070925" />
+</Library>
+<Library Name="SL56">
+<Track Flowcell="FC11862" Lane="1" Filename="Flowcells/FC11862/070919_FC11862_s1_pancreas_2_hpa_SL56.align_25.hg18.txt" Count=" 1359802" Date="070919" />
+<Track Flowcell="FC11977" Lane="2" Filename="Flowcells/FC11977/070928_FC11977_s2_pancreas_2_hpa_SL56.align_25.hg18.txt" Count=" 1120271" Date="070928" />
+</Library>
+<Library Name="SL58">
+<Track Flowcell="FC10073" Lane="78" Filename="Flowcells/FC10073/070821_FC10073_s78_jurkat_1_hpa_SL58.align_25.hg18.txt" Count="  276664" Date="070821" />
+<Track Flowcell="FC10132" Lane="4" Filename="Flowcells/FC10132/070817_FC10132_s4_jurkat_1_hpa_SL58.align_25.hg18.txt" Count=" 1326942" Date="070817" />
+<Track Flowcell="FC10182" Lane="6" Filename="Flowcells/FC10182/070925_FC10182_s6_jurkat_1_hpa_SL58.align_25.hg18.txt" Count=" 1162424" Date="070925" />
+<Track Flowcell="FC11977" Lane="5" Filename="Flowcells/FC11977/070928_FC11977_s5_jurkat_1_hpa_SL58.align_25.hg18.txt" Count="  904205" Date="070928" />
+</Library>
+<Library Name="SL80">
+<Track Flowcell="FC10170" Lane="3" Filename="Flowcells/FC10170/071010_FC10170_s3_WOL3_cervical_RNA_human_SL80.align_25.hg18.txt" Count="  189349" Date="071010" />
+</Library>
+<Library Name="SL81">
+<Track Flowcell="FC10170" Lane="4" Filename="Flowcells/FC10170/071010_FC10170_s4_WOL4_cervical_RNA_human_SL81.align_25.hg18.txt" Count="  208690" Date="071010" />
+</Library>
+<Library Name="SL82">
+<Track Flowcell="FC12673" Lane="4" Filename="Flowcells/FC12673/071023_FC12673_s4_WOL6_cervical_RNA_human_SL82.align_25.hg18.txt" Count="    8190" Date="071023" />
+</Library>
+<Library Name="SL83">
+<Track Flowcell="30DAGAAXX" Lane="1" Filename="Flowcells/30DAGAAXX/080826_30DAGAAXX_s1_HTB-11_NRSF_ChIP_Rep1_SL83.align_25.hg18.txt" Count=" 3016467" Date="080826" />
+<Track Flowcell="FC10182" Lane="5" Filename="Flowcells/FC10182/070925_FC10182_s5_NRSF_monoclonal_HTB11_SL83.align_25.hg18.txt" Count="  918491" Date="070925" />
+<Track Flowcell="FC11977" Lane="1" Filename="Flowcells/FC11977/070928_FC11977_s1_NRSF_monoclonal_HTB11_SL83.align_25.hg18.txt" Count="  932151" Date="070928" />
+<Track Flowcell="FC14428" Lane="3" Filename="Flowcells/FC14428/071102_FC14428_s3_NRSF_monoclonal_HTB11_SL83.align_25.hgt18.txt" Count="  987885" Date="071102" />
+<Track Flowcell="FC14432" Lane="2" Filename="Flowcells/FC14432/071026_FC14432_s2_NRSF_monoclonal_HTB11_SL83.align_25.hg18.txt" Count=" 1052331" Date="071026" />
+<Track Flowcell="FC6420" Lane="678" Filename="Flowcells/FC6420/080212_FC6420_s678_NRSF_ChIP_HTB-11_SL83.align_25.hg18.txt" Count=" 3489053" Date="080212" />
+</Library>
+<Library Name="SL84">
+<Track Flowcell="FC10135" Lane="1" Filename="Flowcells/FC10135/071005_FC10135_s1_BG02_ES_A_hpa_SL84.align_25.hg18.txt" Count=" 1908138" Date="071005" />
+<Track Flowcell="FC12563" Lane="1" Filename="Flowcells/FC12563/071016_FC12563_s1_BG02_ES_A_hpa_SL84.align_25.hg18.txt" Count=" 1319454" Date="071016" />
+</Library>
+<Library Name="SL85">
+<Track Flowcell="FC10135" Lane="2" Filename="Flowcells/FC10135/071005_FC10135_s2_BG02_ES_B_hpa_SL85.align_25.hg18.txt" Count=" 2046898" Date="071005" />
+<Track Flowcell="FC12563" Lane="2" Filename="Flowcells/FC12563/071016_FC12563_s2_BG02_ES_B_hpa_SL85.align_25.hg18.txt" Count=" 1576213" Date="071016" />
+</Library>
+<Library Name="SL86">
+<Track Flowcell="FC10135" Lane="7" Filename="Flowcells/FC10135/071005_FC10135_s7_H9_hepato_B_hpa_SL86.align_25.hg18.txt" Count=" 1645814" Date="071005" />
+<Track Flowcell="FC12673" Lane="6" Filename="Flowcells/FC12673/071023_FC12673_s6_H9_hepato_B_hpa_SL86.align_25.hg18.txt" Count="  581257" Date="071023" />
+</Library>
+<Library Name="SL87">
+<Track Flowcell="FC10135" Lane="8" Filename="Flowcells/FC10135/071005_FC10135_s8_H9_hepato_D_hpa_SL87.align_25.hg18.txt" Count="  894069" Date="071005" />
+<Track Flowcell="FC12673" Lane="7" Filename="Flowcells/FC12673/071023_FC12673_s7_H9_hepato_D_hpa_SL87.align_25.hg18.txt" Count="  410824" Date="071023" />
+</Library>
+<Library Name="SL88">
+<Track Flowcell="30DAGAAXX" Lane="3" Filename="Flowcells/30DAGAAXX/080826_30DAGAAXX_s3_U87_monoclonal_NRSF_ChIP_Rep1_SL88.align_25.hg18.txt" Count=" 2910545" Date="080826" />
+<Track Flowcell="FC10135" Lane="3" Filename="Flowcells/FC10135/071005_FC10135_s3_NRSF_monoclonal_U87_SL88.align_25.hg18.txt" Count=" 1240914" Date="071005" />
+<Track Flowcell="FC12565" Lane="5" Filename="Flowcells/FC12565/071019_FC12565_s5_NRSF_monoclonal_U87_SL88.align_25.hg18.txt" Count="  571952" Date="071019" />
+<Track Flowcell="FC14428" Lane="4" Filename="Flowcells/FC14428/071102_FC14428_s4_NRSF_monoclonal_U87_SL88.align_25.hg18.txt" Count=" 1114155" Date="071102" />
+<Track Flowcell="FC14432" Lane="4" Filename="Flowcells/FC14432/071026_FC14432_s4_NRSF_monoclonal_U87_SL88.align_25.hg18.txt" Count="  914576" Date="071026" />
+<Track Flowcell="FC2057R" Lane="6" Filename="Flowcells/FC2057R/071211_FC2057R_s6_U87_monoclonal_NRSF_ChIP_SL88.align_25.hg18.txt" Count="  172579" Date="071211" />
+<Track Flowcell="FC6192" Lane="345" Filename="Flowcells/FC6192/080212_FC6192_s345_NRSF_ChIP_UB7_SL88.align_25.hg18.txt" Count=" 3307816" Date="080212" />
+</Library>
+<Library Name="SL89">
+<Track Flowcell="FC10135" Lane="5" Filename="Flowcells/FC10135/071005_FC10135_s5_FoxP2_polyclonal_pfsk1_SL89.align_25.hg18.txt" Count=" 1992055" Date="071005" />
+<Track Flowcell="FC10170" Lane="56" Filename="Flowcells/FC10170/071010_FC10170_s56_FoxP2_polyclonal_pfsk1_SL89.align_25.hg18.txt" Count=" 2332472" Date="071010" />
+<Track Flowcell="FC12673" Lane="5" Filename="Flowcells/FC12673/071023_FC12673_s5_FoxP2_polyclonal_pfsk1_SL89.align_25.hg18.txt" Count="  754441" Date="071023" />
+<Track Flowcell="FC14432" Lane="5678" Filename="Flowcells/FC14432/071026_FC14432_s5678_FoxP2_polyclonal_pfsk1_SL89.align_25.hg18.txt" Count=" 4136620" Date="071026" />
+</Library>
+<Library Name="SL90">
+<Track Flowcell="FC10135" Lane="6" Filename="Flowcells/FC10135/071005_FC10135_s6_NlaIII_18_mouse_SL90.align_25.hg18.txt" Count="  925114" Date="071005" />
+</Library>
+<Library Name="SL91">
+<Track Flowcell="FC10170" Lane="1" Filename="Flowcells/FC10170/071010_FC10170_s1_BG02_hepato_B_hpa_SL91.align_25.hg18.txt" Count="   53593" Date="071010" />
+<Track Flowcell="FC12673" Lane="3" Filename="Flowcells/FC12673/071023_FC12673_s3_BG02_hepato_B_hpa_SL91.align_25.hg18.txt" Count="   34104" Date="071023" />
+</Library>
+<Library Name="SL92">
+<Track Flowcell="FC10170" Lane="2" Filename="Flowcells/FC10170/071010_FC10170_s2_BG02_hepato_C_hpa_SL92.align_25.hg18.txt" Count="  101462" Date="071010" />
+</Library>
+<Library Name="SL93">
+<Track Flowcell="FC10170" Lane="7" Filename="Flowcells/FC10170/071010_FC10170_s7_HCT_mCIP_1_SL93.align_25.hg18.txt" Count=" 1110620" Date="071010" />
+<Track Flowcell="FC12673" Lane="8" Filename="Flowcells/FC12673/071023_FC12673_s8_HCT_mCIP_1_SL93.align_25.hg18.txt" Count="  348238" Date="071023" />
+<Track Flowcell="FC14430" Lane="78" Filename="Flowcells/FC14430/071030_FC14430_s78_HCT_mCIP_1_SL93.align_25.hg18.txt" Count=" 1271501" Date="071030" />
+</Library>
+<Library Name="SL94">
+<Track Flowcell="FC10170" Lane="8" Filename="Flowcells/FC10170/071010_FC10170_s8_HCT_unbound_mCIP_1_SL94.align_25.hg18.txt" Count="  926056" Date="071010" />
+</Library>
+<Library Name="SL98">
+<Track Flowcell="FC12563" Lane="3" Filename="Flowcells/FC12563/071016_FC12563_s3_fetal_liver_11weeks_1_hpa_SL98.align_25.hg18.txt" Count="  199565" Date="071016" />
+<Track Flowcell="FC12565" Lane="12" Filename="Flowcells/FC12565/071019_FC12565_s12_fetal_liver_11weeks_1_hpa_SL98.align_25.hg18.txt" Count="  764623" Date="071019" />
+<Track Flowcell="FC12673" Lane="1" Filename="Flowcells/FC12673/071023_FC12673_s1_fetal_liver_11weeks_1_hpa_SL98.align_25.hg18.txt" Count="   97009" Date="071023" />
+</Library>
+<Library Name="SL99">
+<Track Flowcell="FC12563" Lane="4" Filename="Flowcells/FC12563/071016_FC12563_s4_fetal_liver_24weeks_1_hpa_SL99.align_25.hg18.txt" Count="  101572" Date="071016" />
+<Track Flowcell="FC12565" Lane="34" Filename="Flowcells/FC12565/071019_FC12565_s34_fetal_liver_24weeks_1_hpa_SL99.align_25.hg18.txt" Count="  315747" Date="071019" />
+<Track Flowcell="FC12673" Lane="2" Filename="Flowcells/FC12673/071023_FC12673_s2_fetal_liver_24weeks_1_hpa_SL99.align_25.hg18.txt" Count="   64593" Date="071023" />
+</Library>
+<Library Name="SL100">
+<Track Flowcell="FC12563" Lane="5" Filename="Flowcells/FC12563/071016_FC12563_s5_Y157SL_genomic_yeast_SL100.align_25.hg18.txt" Count="  316612" Date="071016" />
+</Library>
+<Library Name="SL101">
+<Track Flowcell="FC12565" Lane="78" Filename="Flowcells/FC12565/071019_FC12565_s78_FoxP2_polyclonal_SK_N_MC_SL101.align_25.hg18.txt" Count=" 2344547" Date="071019" />
+</Library>
+<Library Name="SL102">
+<Track Flowcell="FC20162" Lane="4" Filename="Flowcells/FC20162/080111_FC20162_s4_SK-N-MC_TotalChromatin_SL102.align_25.hg18.txt" Count=" 3103385" Date="080111" />
+<Track Flowcell="FC202W1" Lane="1234" Filename="Flowcells/FC202W1/080116_FC202W1_s1234_SK-N-MC_total_chromatin_SL102.align_25.hg18.txt" Count=" 6381095" Date="080116" />
+<Track Flowcell="FC2057R" Lane="4" Filename="Flowcells/FC2057R/071211_FC2057R_s4_SK_N_MC_control_SL102.align_25.hg18.txt" Count="  247103" Date="071211" />
+<Track Flowcell="FC2057R" Lane="5" Filename="Flowcells/FC2057R/071211_FC2057R_s5_SK_N_MC_control_SL102.align_25.hg18.txt" Count="  285363" Date="071211" />
+</Library>
+<Library Name="SL103">
+<Track Flowcell="30DAGAAXX" Lane="4" Filename="Flowcells/30DAGAAXX/080826_30DAGAAXX_s4_U87_Control_SL103.align_25.hg18.txt" Count=" 5805925" Date="080826" />
+<Track Flowcell="FC12565" Lane="6" Filename="Flowcells/FC12565/071019_FC12565_s6_mock_IP_U87_SL103.align_25.hg18.txt" Count=" 1642558" Date="071019" />
+<Track Flowcell="FC14428" Lane="5" Filename="Flowcells/FC14428/071102_FC14428_s5_mock_IP_U87_SL103.align_25.hg18.txt" Count="   44271" Date="071102" />
+<Track Flowcell="FC14432" Lane="3" Filename="Flowcells/FC14432/071026_FC14432_s3_mock_IP_U87_SL103.align_25.hg18.txt" Count=" 2246689" Date="071026" />
+<Track Flowcell="FC6420" Lane="45" Filename="Flowcells/FC6420/080212_FC6420_s45_UB7_Control_SL103.align_25.hg18.txt" Count=" 7268724" Date="080212" />
+</Library>
+<Library Name="SL104">
+<Track Flowcell="FC14426" Lane="3" Filename="Flowcells/FC14426/071106_FC14426_s3_WOL9_small_RNA_human_SL104.align_25.hg18.txt" Count="  104372" Date="071106" />
+</Library>
+<Library Name="SL105">
+<Track Flowcell="FC14426" Lane="4" Filename="Flowcells/FC14426/071106_FC14426_s4_WOL8_small_RNA_human_SL105.align_25.hg18.txt" Count="  433070" Date="071106" />
+</Library>
+<Library Name="SL108">
+<Track Flowcell="30DAGAAXX" Lane="2" Filename="Flowcells/30DAGAAXX/080826_30DAGAAXX_s2_HTB-11_Control_SL108.align_25.hg18.txt" Count=" 6550943" Date="080826" />
+<Track Flowcell="FC14428" Lane="6" Filename="Flowcells/FC14428/071102_FC14428_s6_mock_IP_HTB11_SL108.align_25.hg18.txt" Count="  201318" Date="071102" />
+<Track Flowcell="FC14432" Lane="1" Filename="Flowcells/FC14432/071026_FC14432_s1_mock_IP_HTB11_SL108.align_25.hg18.txt" Count=" 2525735" Date="071026" />
+<Track Flowcell="FC6420" Lane="123" Filename="Flowcells/FC6420/080212_FC6420_s123_HTB-11_Control_SL108.align_25.hg18.txt" Count=" 9532996" Date="080212" />
+</Library>
+<Library Name="SL109">
+<Track Flowcell="FC14428" Lane="7" Filename="Flowcells/FC14428/071102_FC14428_s7_RNAseq_liver_human_rep1_SL109.align_25.hg18.txt" Count="  755566" Date="071102" />
+</Library>
+<Library Name="SL110">
+<Track Flowcell="FC14428" Lane="8" Filename="Flowcells/FC14428/071102_FC14428_s8_RNAseq_liver_human_rep2_SL110.align_25.hg18.txt" Count="  515935" Date="071102" />
+<Track Flowcell="FC14763" Lane="4" Filename="Flowcells/FC14763/071129_FC14763_s4_RNAseq_liver_human_rep2_SL110.align_25.hg18.txt" Count="  501659" Date="071129" />
+</Library>
+<Library Name="SL111">
+<Track Flowcell="FC14426" Lane="1" Filename="Flowcells/FC14426/071106_FC14426_s1_BG02_hepato_B2_hpa_SL111.align_25.hg18.txt" Count=" 1878793" Date="071106" />
+<Track Flowcell="FC14430" Lane="1" Filename="Flowcells/FC14430/071030_FC14430_s1_BG02_hepato_B2_hpa_SL111.align_25.hg18.txt" Count=" 1172349" Date="071030" />
+</Library>
+<Library Name="SL112">
+<Track Flowcell="FC14426" Lane="2" Filename="Flowcells/FC14426/071106_FC14426_s2_BG02_hepato_C2_hpa_SL112.align_25.hg18.txt" Count=" 2107429" Date="071106" />
+<Track Flowcell="FC14430" Lane="2" Filename="Flowcells/FC14430/071030_FC14430_s2_BG02_hepato_C2_hpa_SL112.align_25.hg18.txt" Count=" 1447587" Date="071030" />
+</Library>
+<Library Name="SL113">
+<Track Flowcell="FC14420" Lane="1" Filename="Flowcells/FC14420/071113_FC14420_s1_fetal_liver_11weeks2_hpa_SL113.align_25.hg18.txt" Count=" 1250091" Date="071113" />
+<Track Flowcell="FC14428" Lane="1" Filename="Flowcells/FC14428/071102_FC14428_s1_fetal_liver_11weeks2_hpa_SL113.align_25.hgt18.txt" Count=" 1172349" Date="071102" />
+<Track Flowcell="FC14430" Lane="3" Filename="Flowcells/FC14430/071030_FC14430_s3_fetal_liver_11weeks2_hpa_SL113.align_25.hg18.txt" Count="  987885" Date="071030" />
+</Library>
+<Library Name="SL114">
+<Track Flowcell="FC14420" Lane="2" Filename="Flowcells/FC14420/071113_FC14420_s2_fetal_liver_24weeks2_hpa_SL114.align_25.hg18.txt" Count=" 1805836" Date="071113" />
+<Track Flowcell="FC14428" Lane="2" Filename="Flowcells/FC14428/071102_FC14428_s2_fetal_liver_24weeks2_hpa_SL114.align_25.hgt18.txt" Count=" 1447587" Date="071102" />
+<Track Flowcell="FC14430" Lane="4" Filename="Flowcells/FC14430/071030_FC14430_s4_fetal_liver_24weeks2_hpa_SL114.align_25.hg18.txt" Count=" 1114155" Date="071030" />
+</Library>
+<Library Name="SL115">
+<Track Flowcell="FC202VW" Lane="1234" Filename="Flowcells/FC202VW/071218_FC202VW_s1234_cat_x_chromosome_SL115.align_25.catGar12.txt" Count=" 5488573" Date="071218" />
+</Library>
+<Library Name="SL116">
+<Track Flowcell="FC12472" Lane="12" Filename="Flowcells/FC12472/071116_FC12472_s12_NRSF_monoclonal_PANC1_SL116.align_25.hg18.txt" Count=" 1924940" Date="071116" />
+<Track Flowcell="FC14420" Lane="5" Filename="Flowcells/FC14420/071113_FC14420_s5_NRSF_monoclonal_PANC1_SL116.align_25.hg18.txt" Count=" 1037723" Date="071113" />
+<Track Flowcell="FC14426" Lane="5" Filename="Flowcells/FC14426/071106_FC14426_s5_NRSF_monoclonal_PANC1_SL116.align_25.hg18.txt" Count="  961751" Date="071106" />
+<Track Flowcell="FC6192" Lane="678" Filename="Flowcells/FC6192/080212_FC6192_s678_NRSF_ChIP_PANC1_SL116.align_25.hg18.txt" Count=" 4075003" Date="080212" />
+</Library>
+<Library Name="SL117">
+<Track Flowcell="FC14420" Lane="6" Filename="Flowcells/FC14420/071113_FC14420_s6_mock_IP_PANC1_SL117.align_25.hg18.txt" Count=" 2462518" Date="071113" />
+<Track Flowcell="FC14426" Lane="6" Filename="Flowcells/FC14426/071106_FC14426_s6_mock_IP_PANC1_SL117.align_25.hg18.txt" Count=" 2214338" Date="071106" />
+<Track Flowcell="FC6192" Lane="12" Filename="Flowcells/FC6192/080212_FC6192_s12_PANC1_Control_SL117.align_25.hg18.txt" Count=" 6046619" Date="080212" />
+</Library>
+<Library Name="SL118">
+<Track Flowcell="FC12472" Lane="34" Filename="Flowcells/FC12472/071116_FC12472_s34_NRSF_monoclonal_HTB187_SL118.align_25.hg18.txt" Count=" 2520211" Date="071116" />
+<Track Flowcell="FC14420" Lane="7" Filename="Flowcells/FC14420/071113_FC14420_s7_NRSF_monoclonal_HTB187_SL118.align_25.hg18.txt" Count=" 1183349" Date="071113" />
+<Track Flowcell="FC14426" Lane="7" Filename="Flowcells/FC14426/071106_FC14426_s7_NRSF_monoclonal_HTB187_SL118.align_25.hg18.txt" Count="  998104" Date="071106" />
+</Library>
+<Library Name="SL119">
+<Track Flowcell="FC14420" Lane="8" Filename="Flowcells/FC14420/071113_FC14420_s8_mock_IP_HTB187_SL119.align_25.hg18.txt" Count=" 2376268" Date="071113" />
+<Track Flowcell="FC14426" Lane="8" Filename="Flowcells/FC14426/071106_FC14426_s8_mock_IP_HTB187_SL119.align_25.hg18.txt" Count=" 1606271" Date="071106" />
+</Library>
+<Library Name="SL120">
+<Track Flowcell="FC13512" Lane="1" Filename="Flowcells/FC13512/071109_FC13512_s1_NRSF_monoclonal_Jurkat_SL120.align_25.hg18.txt" Count=" 1285225" Date="071109" />
+<Track Flowcell="FC14420" Lane="3" Filename="Flowcells/FC14420/071113_FC14420_s3_NRSF_monoclonal_Jurkat_SL120.align_25.hg18.txt" Count=" 2130283" Date="071113" />
+</Library>
+<Library Name="SL121">
+<Track Flowcell="FC13512" Lane="2" Filename="Flowcells/FC13512/071109_FC13512_s2_GAPB_monoclonal_Jurkat_SL121.align_25.hg18.txt" Count=" 1512986" Date="071109" />
+<Track Flowcell="FC14420" Lane="4" Filename="Flowcells/FC14420/071113_FC14420_s4_GAPB_monoclonal_Jurkat_SL121.align_25.hg18.txt" Count=" 2069705" Date="071113" />
+<Track Flowcell="FC207CF" Lane="4" Filename="Flowcells/FC207CF/080207_FC207CF_s4_Jurkat_GABP_ChIP_SL121.align_25.hg18.txt" Count=" 11127549" Date="080207" />
+<Track Flowcell="FC20AJ4" Lane="2" Filename="Flowcells/FC20AJ4/080312_FC20AJ4_s2_Jurkat_GABP_ChIP_SL121.align_25.hg18.txt" Count=" 3288504" Date="080312" />
+</Library>
+<Library Name="SL127">
+<Track Flowcell="FC12472" Lane="7" Filename="Flowcells/FC12472/071116_FC12472_s7_RNA_Pol_Jurkat_SL127.align_25.hg18.txt" Count=" 1744821" Date="071116" />
+<Track Flowcell="FC2057R" Lane="7" Filename="Flowcells/FC2057R/071211_FC2057R_s7_RNA_PoI_II_ChIP_Jurkat_SL127.align_25.hg18.txt" Count="  245677" Date="071211" />
+<Track Flowcell="FC2057R" Lane="8" Filename="Flowcells/FC2057R/071211_FC2057R_s8_RNA_PoI_II_ChIP_Jurkat_SL127.align_25.hg18.txt" Count="  124585" Date="071211" />
+<Track Flowcell="FC2057R" Lane="4" Filename="Flowcells/FC2057R/080118_FC2057R_s4_RNApol2_ChIP_Jurkat_SL127.align_25.hg18.txt" Count=" 2876921" Date="080118" />
+<Track Flowcell="FC20686" Lane="4" Filename="Flowcells/FC20686/080221_FC20686_s4_RNApol2_ChIP_Jurkat_SL127.align_25.hg18.txt" Count="   15232" Date="080221" />
+<Track Flowcell="FC207CF" Lane="5" Filename="Flowcells/FC207CF/080207_FC207CF_s5_RNApol2_ChIP_Jurkat_SL127.align_25.hg18.txt" Count=" 14282376" Date="080207" />
+<Track Flowcell="FC20AN8" Lane="4" Filename="Flowcells/FC20AN8/080310_FC20AN8_s4_RNApol2_ChIP_Jurkat_SL127.align_25.hg18.txt" Count="  804871" Date="080310" />
+</Library>
+<Library Name="SL128">
+<Track Flowcell="FC12472" Lane="8" Filename="Flowcells/FC12472/071116_FC12472_s8_MsMeCP2_Jurkat_SL128.align_25.hg18.txt" Count=" 1441721" Date="071116" />
+<Track Flowcell="FC204PR" Lane="8" Filename="Flowcells/FC204PR/080520_FC204PR_s8_MeCP2_ChIP_Jurkat_SL128.align_25.hg18.txt" Count=" 3300096" Date="080520" />
+<Track Flowcell="FC2057R" Lane="5" Filename="Flowcells/FC2057R/080118_FC2057R_s5_MeCP2_ChIP_Jurkat_SL128.align_25.hg18.txt" Count=" 2246279" Date="080118" />
+</Library>
+<Library Name="SL129">
+<Track Flowcell="FC14763" Lane="1" Filename="Flowcells/FC14763/071129_FC14763_s1_dko_1_hpa_SL129.align_25.hg18.txt" Count=" 1039877" Date="071129" />
+<Track Flowcell="FC20162" Lane="16" Filename="Flowcells/FC20162/080111_FC20162_s16_dko_1_hpa_SL129.align_25.hg18.txt" Count=" 3270592" Date="080111" />
+<Track Flowcell="FC2057R" Lane="1" Filename="Flowcells/FC2057R/071211_FC2057R_s1_dko_1_hpa_SL129.align_25.hg18.txt" Count="  246639" Date="071211" />
+<Track Flowcell="FC2057R" Lane="12" Filename="Flowcells/FC2057R/080118_FC2057R_s12_dko_1_hpa_SL129.align_25.hg18.txt" Count=" 5238922" Date="080118" />
+</Library>
+<Library Name="SL130">
+<Track Flowcell="FC14763" Lane="2" Filename="Flowcells/FC14763/071129_FC14763_s2_dnmt1_1_hpa_SL130.align_25.hg18.txt" Count=" 1224247" Date="071129" />
+<Track Flowcell="FC20162" Lane="27" Filename="Flowcells/FC20162/080111_FC20162_s27_dnmt1_1_hpa_SL130.align_25.hg18.txt" Count=" 3592097" Date="080111" />
+<Track Flowcell="FC2057R" Lane="2" Filename="Flowcells/FC2057R/071211_FC2057R_s2_dnmt1_1_hpa_SL130.align_25.hg18.txt" Count="  417799" Date="071211" />
+</Library>
+<Library Name="SL131">
+<Track Flowcell="FC14763" Lane="3" Filename="Flowcells/FC14763/071129_FC14763_s3_dnmt3b_1_hpa_SL131.align_25.hg18.txt" Count=" 1328448" Date="071129" />
+<Track Flowcell="FC20162" Lane="38" Filename="Flowcells/FC20162/080111_FC20162_s38_dmnt3b_1_hpa_SL131.align_25.hg18.txt" Count=" 3486008" Date="080111" />
+<Track Flowcell="FC2057R" Lane="3" Filename="Flowcells/FC2057R/071211_FC2057R_s3_dnmt3b_1_hpa_SL131.align_25.hg18.txt" Count="  501842" Date="071211" />
+</Library>
+<Library Name="SL138">
+<Track Flowcell="FC2057U" Lane="1" Filename="Flowcells/FC2057U/080220_FC2057U_s1_e_7_75_Embryo_hpa_1_SL138.align_25.hg18.txt" Count=" 3235636" Date="080220" />
+<Track Flowcell="FC205DP" Lane="3" Filename="Flowcells/FC205DP/080118_FC205DP_s3_e_7_75_Embryo_hpa_SL138.align_25.mm9.txt" Count=" 3251998" Date="080118" />
+</Library>
+<Library Name="SL139">
+<Track Flowcell="FC2057U" Lane="2" Filename="Flowcells/FC2057U/080220_FC2057U_s2_e_7_75_Extra_Em_hpa_1_SL139.align_25.hg18.txt" Count=" 3408585" Date="080220" />
+<Track Flowcell="FC205DP" Lane="6" Filename="Flowcells/FC205DP/080118_FC205DP_s6_e_7_75_Extra_Em_hpa_1_SL139.align_25.mm9.txt" Count=" 3017471" Date="080118" />
+</Library>
+<Library Name="SL140">
+<Track Flowcell="FC2057U" Lane="7" Filename="Flowcells/FC2057U/080220_FC2057U_s7_e_8_5_Unturned_hpa_1_SL140.align_25.hg18.txt" Count=" 4152706" Date="080220" />
+<Track Flowcell="FC205DP" Lane="7" Filename="Flowcells/FC205DP/080118_FC205DP_s7_e_8_5_Unturned_hpa_SL140.align_25.mm9.txt" Count=" 2924215" Date="080118" />
+</Library>
+<Library Name="SL141">
+<Track Flowcell="FC20678" Lane="1" Filename="Flowcells/FC20678/080214_FC20678_s1_e11_5_SL141.align_25.hg18.txt" Count="  288210" Date="080214" />
+<Track Flowcell="FC20AJ6" Lane="7" Filename="Flowcells/FC20AJ6/080318_FC20AJ6_s7_e11.5_Fet-Plac_hpa_SL141.align_25.mm9.txt" Count=" 3531295" Date="080318" />
+</Library>
+<Library Name="SL142">
+<Track Flowcell="FC20678" Lane="2" Filename="Flowcells/FC20678/080214_FC20678_s2_e15_5_SL142.align_25.hg18.txt" Count="  610226" Date="080214" />
+<Track Flowcell="FC20AJ6" Lane="8" Filename="Flowcells/FC20AJ6/080318_FC20AJ6_s8_e1.5_Fet-Plac_hpa_SL142.align_25.mm9.txt" Count=" 3896266" Date="080318" />
+</Library>
+<Library Name="SL143">
+<Track Flowcell="FC2057U" Lane="8" Filename="Flowcells/FC2057U/080220_FC2057U_s8_e_15_5_Fet_Plac_msp_1_SL143.align_25.hg18.txt" Count=" 2901012" Date="080220" />
+<Track Flowcell="FC205DP" Lane="8" Filename="Flowcells/FC205DP/080118_FC205DP_s8_e_15_5_Fet_Plac_msp_1_SL143.align_25.mm9.txt" Count=" 2661896" Date="080118" />
+</Library>
+<Library Name="SL144">
+<Track Flowcell="FC20162" Lane="5" Filename="Flowcells/FC20162/080111_FC20162_s5_SK-N-MC_FOXP2_ChIP_SL144.align_25.hg18.txt" Count=" 2578704" Date="080111" />
+<Track Flowcell="FC202W1" Lane="5678" Filename="Flowcells/FC202W1/080116_FC202W1_s5678_SK-N-MC_FOXP2_ChIP_SL144.align_25.hg18.txt" Count=" 10867872" Date="080116" />
+</Library>
+<Library Name="SL147">
+<Track Flowcell="FC2065U" Lane="12" Filename="Flowcells/FC2065U/080226_FC2065U_s12_BE2_C_NRSF_ChIP_SL147.align_25.hg18.txt" Count=" 5768434" Date="080226" />
+<Track Flowcell="FC20686" Lane="5" Filename="Flowcells/FC20686/080221_FC20686_s5_BE2_C_NRSF_ChIP_SL147.align_25.hg18.txt" Count="     327" Date="080221" />
+<Track Flowcell="FC20AJ4" Lane="3" Filename="Flowcells/FC20AJ4/080312_FC20AJ4_s3_BE_2_-C_NRSF_ChIP_SL147.align_25.hg18.txt" Count=" 3022727" Date="080312" />
+</Library>
+<Library Name="SL148">
+<Track Flowcell="FC2065U" Lane="3" Filename="Flowcells/FC2065U/080226_FC2065U_s3_Input_BE2_C_SL148.align_25.hg18.txt" Count=" 4076715" Date="080226" />
+<Track Flowcell="FC2079B" Lane="5" Filename="Flowcells/FC2079B/080129_FC2079B_s5_Input_BE2-C_SL148.align_25.hg18.txt" Count=" 3917265" Date="080129" />
+<Track Flowcell="FC20AJ6" Lane="4" Filename="Flowcells/FC20AJ6/080318_FC20AJ6_s4_Input_control_BE2-C_SL148.align_25.hg18.txt" Count=" 4103876" Date="080318" />
+</Library>
+<Library Name="SL149">
+<Track Flowcell="FC2065U" Lane="45" Filename="Flowcells/FC2065U/080226_FC2065U_s45_PFSK1_NRSF_ChIP_SL149.align_25.hg18.txt" Count=" 5690999" Date="080226" />
+<Track Flowcell="FC20678" Lane="6" Filename="Flowcells/FC20678/080214_FC20678_s6_NRSF_ChIP_PFSK-1_SL149.align_25.hg18.txt" Count="  976436" Date="080214" />
+<Track Flowcell="FC20686" Lane="6" Filename="Flowcells/FC20686/080221_FC20686_s6_PFSK1_NRSF_ChIP_SL149.align_25.hg18.txt" Count="   17027" Date="080221" />
+<Track Flowcell="FC20AJ4" Lane="4" Filename="Flowcells/FC20AJ4/080312_FC20AJ4_s4_PFSK1_NRSF_ChIP_SL149.align_25.hg18.txt" Count=" 2779349" Date="080312" />
+</Library>
+<Library Name="SL150">
+<Track Flowcell="FC2065U" Lane="6" Filename="Flowcells/FC2065U/080226_FC2065U_s6_Input_PFSK1_SL150.align_25.hg18.txt" Count=" 3558958" Date="080226" />
+<Track Flowcell="FC2079B" Lane="6" Filename="Flowcells/FC2079B/080129_FC2079B_s6_Input_PFSK-1_SL150.align_25.hg18.txt" Count=" 3864147" Date="080129" />
+<Track Flowcell="FC20AJ6" Lane="5" Filename="Flowcells/FC20AJ6/080318_FC20AJ6_s5_Input_control_PSF-1_SL150.align_25.hg18.txt" Count=" 4013975" Date="080318" />
+</Library>
+<Library Name="SL152">
+<Track Flowcell="FC2079B" Lane="4" Filename="Flowcells/FC2079B/080129_FC2079B_s4_scer_rnaseq_SL152.align_25.scer.txt" Count=" 3578645" Date="080129" />
+</Library>
+<Library Name="SL153">
+<Track Flowcell="FC207CF" Lane="2" Filename="Flowcells/FC207CF/080207_FC207CF_s2_InputControl_SL153.align_25.hg18.txt" Count="       0" Date="080207" />
+</Library>
+<Library Name="SL154">
+<Track Flowcell="FC207CF" Lane="3" Filename="Flowcells/FC207CF/080207_FC207CF_s3_PSTAT_ChIP_SL154.align_25.hg18.txt" Count="       0" Date="080207" />
+</Library>
+<Library Name="SL155">
+<Track Flowcell="FC20678" Lane="3" Filename="Flowcells/FC20678/080214_FC20678_s3_WL_GCT3_RNAseq_SL155.align_25.hg18.txt" Count="   90301" Date="080214" />
+</Library>
+<Library Name="SL156">
+<Track Flowcell="FC20678" Lane="4" Filename="Flowcells/FC20678/080214_FC20678_s4_WL_GCT4_RNAseq_SL156.align_25.hg18.txt" Count="    5791" Date="080214" />
+</Library>
+<Library Name="SL157">
+<Track Flowcell="FC2057U" Lane="3" Filename="Flowcells/FC2057U/080220_FC2057U_s3_Input_mouse_neurons_SL157.align_25.hg18.txt" Count=" 4030470" Date="080220" />
+</Library>
+<Library Name="SL158">
+<Track Flowcell="FC2057U" Lane="4" Filename="Flowcells/FC2057U/080220_FC2057U_s4_MeCP2_ChIP_SL158.align_25.hg18.txt" Count=" 2669313" Date="080220" />
+</Library>
+<Library Name="SL159">
+<Track Flowcell="FC2057U" Lane="5" Filename="Flowcells/FC2057U/080220_FC2057U_s5_Input_mouse_neurons_SL159.align_25.hg18.txt" Count=" 1776966" Date="080220" />
+</Library>
+<Library Name="SL160">
+<Track Flowcell="FC2057U" Lane="6" Filename="Flowcells/FC2057U/080220_FC2057U_s6_MEF2_ChIP_SL160.align_25.hg18.txt" Count=" 3568746" Date="080220" />
+</Library>
+<Library Name="SL161">
+<Track Flowcell="FC2064D" Lane="1" Filename="Flowcells/FC2064D/080208_FC2064D_s1_GABP_1x_K562_ChIP_SL161.align_25.hg18.txt" Count=" 1337451" Date="080208" />
+<Track Flowcell="FC20ANBA" Lane="4" Filename="Flowcells/FC20ANBA/080321_FC20ANBA_s4_GABP_ChIP_1x_K562_SL161.align_25.hg18.txt" Count=" 1933407" Date="080321" />
+</Library>
+<Library Name="SL162">
+<Track Flowcell="FC2064D" Lane="2" Filename="Flowcells/FC2064D/080208_FC2064D_s2_GABP_4x_K562_ChIP_SL162.align_25.hg18.txt" Count=" 1581402" Date="080208" />
+<Track Flowcell="FC20ANBA" Lane="5" Filename="Flowcells/FC20ANBA/080321_FC20ANBA_s5_GABP_ChIP_4x_K562_SL162.align_25.hg18.txt" Count=" 1981536" Date="080321" />
+</Library>
+<Library Name="SL163">
+<Track Flowcell="FC2064D" Lane="3" Filename="Flowcells/FC2064D/080208_FC2064D_s3_Input_K562_1_SL163.align_25.hg18.txt" Count=" 2506864" Date="080208" />
+</Library>
+<Library Name="SL164">
+<Track Flowcell="FC2064D" Lane="6" Filename="Flowcells/FC2064D/080208_FC2064D_s6_PolII_1x_K562_ChIP_SL164.align_25.hg18.txt" Count=" 2542107" Date="080208" />
+</Library>
+<Library Name="SL165">
+<Track Flowcell="FC2064D" Lane="7" Filename="Flowcells/FC2064D/080208_FC2064D_s7_PolII_4x_K562_ChIP_SL165.align_25.hg18.filter.txt" Count=" 2887076" Date="080208" />
+<Track Flowcell="FC2064D" Lane="7" Filename="Flowcells/FC2064D/080208_FC2064D_s7_PolII_4x_K562_ChIP_SL165.align_25.hg18.txt" Count=" 2890553" Date="080208" />
+<Track Flowcell="FC20ANBA" Lane="6" Filename="Flowcells/FC20ANBA/080321_FC20ANBA_s6_RNApol2_ChIP_4x_K562_SL165.align_25.hg18.txt" Count=" 2938622" Date="080321" />
+</Library>
+<Library Name="SL166">
+<Track Flowcell="FC2064D" Lane="8" Filename="Flowcells/FC2064D/080208_FC2064D_s8_Input_K562_2_SL166.align_25.hg18.txt" Count=" 2704047" Date="080208" />
+</Library>
+<Library Name="SL167">
+<Track Flowcell="FC2065U" Lane="78" Filename="Flowcells/FC2065U/080226_FC2065U_s78_FOXP2_ChIP_SKNMC_SL167.align_25.hg18.txt" Count=" 7161740" Date="080226" />
+<Track Flowcell="FC20678" Lane="78" Filename="Flowcells/FC20678/080214_FC20678_s78_FOXP2_ChIP_SK-N-MC_SL167.align_25.hg18.txt" Count=" 2037766" Date="080214" />
+<Track Flowcell="FC20686" Lane="78" Filename="Flowcells/FC20686/080221_FC20686_s78_FOXP2_ChIP_SKNMC_SL167.align_25.hg18.txt" Count="   35763" Date="080221" />
+<Track Flowcell="FC20AJ4" Lane="5" Filename="Flowcells/FC20AJ4/080312_FC20AJ4_s5_FOXP2_ChIP_SK-N-MC_SL167.align_25.hg18.txt" Count=" 3185960" Date="080312" />
+</Library>
+<Library Name="SL168">
+<Track Flowcell="FC207CF" Lane="6" Filename="Flowcells/FC207CF/080207_FC207CF_s6_RNASeq_muscle_1_SL168.align_25.hg18.txt" Count=" 1531101" Date="080207" />
+</Library>
+<Library Name="SL169">
+<Track Flowcell="FC207CF" Lane="7" Filename="Flowcells/FC207CF/080207_FC207CF_s7_RNASeq_muscle_2_SL169.align_25.hg18.txt" Count="   89434" Date="080207" />
+</Library>
+<Library Name="SL170">
+<Track Flowcell="FC20686" Lane="1" Filename="Flowcells/FC20686/080221_FC20686_s1_Methyl_ChIP_K562_SL170.align_25.hg18.txt" Count="    9738" Date="080221" />
+<Track Flowcell="FC209RM" Lane="12" Filename="Flowcells/FC209RM/080325_FC209RM_s12_Input_Control_K562_SL170.align_25.hg18.txt" Count=" 5950898" Date="080325" />
+<Track Flowcell="FC20AJB" Lane="7" Filename="Flowcells/FC20AJB/080411_FC20AJB_s7_Input_Control_K562_SL170.align_25.hg18.txt" Count=" 2808114" Date="080411" />
+<Track Flowcell="FC20AN8" Lane="1" Filename="Flowcells/FC20AN8/080310_FC20AN8_s1_Input_Control_K562_SL170.align_25.hg18.txt" Count=" 2366449" Date="080310" />
+</Library>
+<Library Name="SL171">
+<Track Flowcell="FC20686" Lane="2" Filename="Flowcells/FC20686/080221_FC20686_s2_Input_K562_SL171.align_25.hg18.txt" Count="    5745" Date="080221" />
+<Track Flowcell="FC209RM" Lane="34" Filename="Flowcells/FC209RM/080325_FC209RM_s34_MethylC_ChIP_K562_SL171.align_25.hg18.txt" Count=" 2019575" Date="080325" />
+<Track Flowcell="FC20AJB" Lane="8" Filename="Flowcells/FC20AJB/080411_FC20AJB_s8_MethylC_ChIP_K562_SL171.align_25.hg18.txt" Count=" 1109615" Date="080411" />
+<Track Flowcell="FC20AN8" Lane="2" Filename="Flowcells/FC20AN8/080310_FC20AN8_s2_MethylC_ChIP_K562_SL171.align_25.hg18.txt" Count=" 3470047" Date="080310" />
+<Track Flowcell="FC20AN8" Lane="4" Filename="Flowcells/FC20AN8/080310_FC20AN8_s4_MethylC_ChIP_K562_SL171.align_25.hg18.txt" Count="  804871" Date="080310" />
+</Library>
+<Library Name="SL172">
+<Track Flowcell="FC20686" Lane="3" Filename="Flowcells/FC20686/080221_FC20686_s3_Histone_Mod_ChIP_HESC_SL172.align_25.hg18.txt" Count="    6153" Date="080221" />
+<Track Flowcell="FC20AJB" Lane="23" Filename="Flowcells/FC20AJB/080411_FC20AJB_s23_Histone_Mod_ChIP_HESC_SL172.align_25.hg18.txt" Count=" 6025511" Date="080411" />
+<Track Flowcell="FC20AN8" Lane="3" Filename="Flowcells/FC20AN8/080310_FC20AN8_s3_Histone_Mod_ChIP_HESC_SL172.align_25.hg18.txt" Count=" 1662103" Date="080310" />
+</Library>
+<Library Name="SL173">
+<Track Flowcell="FC207CF" Lane="1" Filename="Flowcells/FC207CF/080207_FC207CF_s1_RNASeq_WOL_GCT6_SL173.align_25.hg18.txt" Count="  458511" Date="080207" />
+<Track Flowcell="FC20AUF" Lane="1" Filename="Flowcells/FC20AUF/080418_FC20AUF_s1_RNAseq_WOL_GCT6_SL173.align_25.hg18.txt" Count="  192208" Date="080418" />
+</Library>
+<Library Name="SL174">
+<Track Flowcell="FC207CF" Lane="8" Filename="Flowcells/FC207CF/080207_FC207CF_s8_RNAseq_WOL_GCT5_SL174.align_25.hg18.txt" Count="  419076" Date="080207" />
+<Track Flowcell="FC20AUF" Lane="2" Filename="Flowcells/FC20AUF/080418_FC20AUF_s2_RNAseq_WOL_GCT5_SL174.align_25.hg18.txt" Count="  277173" Date="080418" />
+</Library>
+<Library Name="SL180">
+<Track Flowcell="FC20AJ4" Lane="6" Filename="Flowcells/FC20AJ4/080312_FC20AJ4_s6_genomic_DNA_SL180.align_25.hg18.txt" Count=" 2947761" Date="080312" />
+</Library>
+<Library Name="SL182">
+<Track Flowcell="FC20AJ4" Lane="8" Filename="Flowcells/FC20AJ4/080312_FC20AJ4_s8_N_pound_124_SL182.align_25.hg18.txt" Count="  744786" Date="080312" />
+</Library>
+<Library Name="SL183">
+<Track Flowcell="FC20AN8" Lane="5" Filename="Flowcells/FC20AN8/080310_FC20AN8_s5_K27_ChIP_HESC_SL183.align_25.hg18.txt" Count=" 1323849" Date="080310" />
+</Library>
+<Library Name="SL184">
+<Track Flowcell="FC20AN8" Lane="6" Filename="Flowcells/FC20AN8/080310_FC20AN8_s6_K36_ChIP_HESC_SL184.align_25.hg18.txt" Count=" 1416960" Date="080310" />
+</Library>
+<Library Name="SL185">
+<Track Flowcell="FC20AN8" Lane="7" Filename="Flowcells/FC20AN8/080310_FC20AN8_s7_RNAPol2_ChIP_HESC_SL185.align_25.hg18.txt" Count="  303217" Date="080310" />
+</Library>
+<Library Name="SL186">
+<Track Flowcell="FC20AN8" Lane="8" Filename="Flowcells/FC20AN8/080310_FC20AN8_s8_Input_Control_HESC_SL186.align_25.hg18.txt" Count="  524314" Date="080310" />
+</Library>
+<Library Name="SL187">
+<Track Flowcell="FC204TE" Lane="4" Filename="Flowcells/FC204TE/080401_FC204TE_s4_RNApol2_ChIP_Jurkat_Rep2_SL187.align_25.hg18.txt" Count=" 3439954" Date="080401" />
+<Track Flowcell="FC209TN" Lane="4" Filename="Flowcells/FC209TN/080320_FC209TN_s4_RNApol2_ChIP_Jurkat_Rep2_SL187.align_25.hg18.txt" Count=" 2798720" Date="080320" />
+<Track Flowcell="FC20AJ6" Lane="3" Filename="Flowcells/FC20AJ6/080318_FC20AJ6_s3_RNApol2_ChIP_Jurkat_Rep2_SL187.align_25.hg18.txt" Count=" 2842778" Date="080318" />
+<Track Flowcell="FC20AJB" Lane="6" Filename="Flowcells/FC20AJB/080411_FC20AJB_s6_RNApol2_ChIP_Jurkat_Rep2_SL187.align_25.hg18.txt" Count=" 3636448" Date="080411" />
+</Library>
+<Library Name="SL189">
+<Track Flowcell="FC20AJ6" Lane="6" Filename="Flowcells/FC20AJ6/080318_FC20AJ6_s6_RNAseq_yeast_SL189.align_25.scer.txt" Count="  262855" Date="080318" />
+</Library>
+<Library Name="SL190">
+<Track Flowcell="FC20AJ6" Lane="1" Filename="Flowcells/FC20AJ6/080318_FC20AJ6_s1_YX271_nucleosomal_DNA_SL190.align_25.cel.txt" Count=" 3261307" Date="080318" />
+</Library>
+<Library Name="SL191">
+<Track Flowcell="FC20AJ6" Lane="2" Filename="Flowcells/FC20AJ6/080318_FC20AJ6_s2_YX213_nucleosomal_DNA_SL191.align_25.cel.txt" Count=" 1400954" Date="080318" />
+</Library>
+<Library Name="SL192">
+<Track Flowcell="FC20ANBA" Lane="1" Filename="Flowcells/FC20ANBA/080321_FC20ANBA_s1_DKO_2_hpa_SL192.align_25.hg18.txt" Count=" 2724639" Date="080321" />
+</Library>
+<Library Name="SL193">
+<Track Flowcell="FC20AJD" Lane="123" Filename="Flowcells/FC20AJD/080516_FC20AJD_s123_DKO_2_msp_SL193.align_25.hg18.txt" Count=" 10254337" Date="080516" />
+<Track Flowcell="FC20ANBA" Lane="2" Filename="Flowcells/FC20ANBA/080321_FC20ANBA_s2_DKO_2_msp_SL193.align_25.hg18.txt" Count=" 2748790" Date="080321" />
+</Library>
+<Library Name="SL194">
+<Track Flowcell="FC20ANBA" Lane="3" Filename="Flowcells/FC20ANBA/080321_FC20ANBA_s3_colon_A_hpa_SL194.align_25.hg18.txt" Count=" 3041571" Date="080321" />
+</Library>
+<Library Name="SL195">
+<Track Flowcell="FC204TE" Lane="1" Filename="Flowcells/FC204TE/080401_FC204TE_s1_colon_B_hpa_SL195.align_25.hg18.txt" Count=" 3349137" Date="080401" />
+<Track Flowcell="FC209TN" Lane="1" Filename="Flowcells/FC209TN/080320_FC209TN_s1_colon_B_hpa_SL195.align_25.hg18.txt" Count=" 3403447" Date="080320" />
+</Library>
+<Library Name="SL196">
+<Track Flowcell="FC204TE" Lane="2" Filename="Flowcells/FC204TE/080401_FC204TE_s2_colon_C_hpa_SL196.align_25.hg18.txt" Count=" 3827859" Date="080401" />
+<Track Flowcell="FC209TN" Lane="2" Filename="Flowcells/FC209TN/080320_FC209TN_s2_colon_C_hpa_SL196.align_25.hg18.txt" Count=" 3640251" Date="080320" />
+</Library>
+<Library Name="SL197">
+<Track Flowcell="FC204TE" Lane="3" Filename="Flowcells/FC204TE/080401_FC204TE_s3_colon_A_msp_SL197.align_25.hg18.txt" Count=" 2691934" Date="080401" />
+<Track Flowcell="FC209TN" Lane="3" Filename="Flowcells/FC209TN/080320_FC209TN_s3_colon_A_msp_SL197.align_25.hg18.txt" Count=" 2795747" Date="080320" />
+<Track Flowcell="FC20AJD" Lane="678" Filename="Flowcells/FC20AJD/080516_FC20AJD_s678_colon_A_msp_SL197.align_25.hg18.txt" Count=" 10383564" Date="080516" />
+</Library>
+<Library Name="SL199">
+<Track Flowcell="FC20AME" Lane="1" Filename="Flowcells/FC20AME/080327_FC20AME_s1_RIBO-XRLP_SL199.align_25.scer.txt" Count="  700987" Date="080327" />
+</Library>
+<Library Name="SL200">
+<Track Flowcell="FC20ANBA" Lane="7" Filename="Flowcells/FC20ANBA/080321_FC20ANBA_s7_K562_Rep2_Hpa_SL200.align_25.hg18.txt" Count=" 1655989" Date="080321" />
+</Library>
+<Library Name="SL201">
+<Track Flowcell="FC20ANBA" Lane="8" Filename="Flowcells/FC20ANBA/080321_FC20ANBA_s8_K562_Rep2_Msp_SL201.align_25.hg18.txt" Count=" 1438074" Date="080321" />
+</Library>
+<Library Name="SL202">
+<Track Flowcell="FC204PT" Lane="1234" Filename="Flowcells/FC204PT/080408_FC204PT_s1234_NRSF_ChIP_GM12878_Rep1_SL202.align_25.hg18.txt" Count=" 7274643" Date="080408" />
+<Track Flowcell="FC204TE" Lane="5" Filename="Flowcells/FC204TE/080401_FC204TE_s5_NRSF_ChIP_GM12878_Rep1_SL202.align_25.hg18.txt" Count=" 2247042" Date="080401" />
+<Track Flowcell="FC209TN" Lane="5" Filename="Flowcells/FC209TN/080320_FC209TN_s5_NRSF_ChIP_GM12878_Rep1_SL202.align_25.hg18.txt" Count=" 1576307" Date="080320" />
+</Library>
+<Library Name="SL203">
+<Track Flowcell="FC204PT" Lane="5678" Filename="Flowcells/FC204PT/080408_FC204PT_s5678_GABP_ChIP_GM12878_Rep1_SL203.align_25.hg18.txt" Count=" 7887093" Date="080408" />
+<Track Flowcell="FC204TE" Lane="6" Filename="Flowcells/FC204TE/080401_FC204TE_s6_GABP_ChIP_GM12878_Rep2_SL203.align_25.hg18.txt" Count=" 2373273" Date="080401" />
+<Track Flowcell="FC209TN" Lane="6" Filename="Flowcells/FC209TN/080320_FC209TN_s6_GABP_ChIP_GM12878_Rep1_SL203.align_25.hg18.txt" Count=" 1701737" Date="080320" />
+</Library>
+<Library Name="SL204">
+<Track Flowcell="FC204GV" Lane="123" Filename="Flowcells/FC204GV/080429_FC204GV_s123_NRSF_ChIP_GM12878_Rep2_SL204.align_25.hg18.txt" Count=" 3577884" Date="080429" />
+<Track Flowcell="FC209TN" Lane="7" Filename="Flowcells/FC209TN/080320_FC209TN_s7_NRSF_ChIP_GM12878_Rep2_SL204.align_25.hg18.txt" Count=" 1896929" Date="080320" />
+<Track Flowcell="FC20ATL" Lane="3" Filename="Flowcells/FC20ATL/080527_FC20ATL_s3_NRSF_ChIP_GM12878_Rep2_SL204.align_25.hg18.txt" Count=" 3115234" Date="080527" />
+<Track Flowcell="FC20ATL" Lane="4" Filename="Flowcells/FC20ATL/080527_FC20ATL_s4_NRSF_ChIP_GM12878_Rep2_SL204.align_25.hg18.txt" Count=" 3154935" Date="080527" />
+</Library>
+<Library Name="SL205">
+<Track Flowcell="FC204PR" Lane="1" Filename="Flowcells/FC204PR/080520_FC204PR_s1_GABP_ChIP_GM12878_Rep2_SL205.align_25.hg18.txt" Count=" 2655501" Date="080520" />
+<Track Flowcell="FC204TD" Lane="3" Filename="Flowcells/FC204TD/080509_FC204TD_s3_GABP_ChIP_GM12878_Rep2_SL205.align_25.hg18.txt" Count=" 3057775" Date="080509" />
+<Track Flowcell="FC209TN" Lane="8" Filename="Flowcells/FC209TN/080320_FC209TN_s8_GABP_ChIP_GM12878_Rep2_SL205.align_25.hg18.txt" Count=" 2362256" Date="080320" />
+<Track Flowcell="FC20ATL" Lane="1" Filename="Flowcells/FC20ATL/080527_FC20ATL_s1_GABP_ChIP_GM12878_Rep2_SL205.align_25.hg18.txt" Count=" 3517050" Date="080527" />
+</Library>
+<Library Name="SL206">
+<Track Flowcell="FC209RM" Lane="5" Filename="Flowcells/FC209RM/080325_FC209RM_s5_RNAPol2_ChIP_GM12878_Rep1_R1_SL206.align_25.hg18.txt" Count=" 2140588" Date="080325" />
+</Library>
+<Library Name="SL207">
+<Track Flowcell="FC204GW" Lane="12" Filename="Flowcells/FC204GW/080503_FC204GW_s12_RNAPol2_ChIP_GM12878_Rep1_R2_SL207.align_25.hg18.txt" Count=" 4626454" Date="080503" />
+<Track Flowcell="FC204TD" Lane="4" Filename="Flowcells/FC204TD/080509_FC204TD_s4_RNAPol2_ChIP_GM12878_Rep1_R2_SL207.align_25.hg18.txt" Count=" 2702184" Date="080509" />
+<Track Flowcell="FC209RM" Lane="6" Filename="Flowcells/FC209RM/080325_FC209RM_s6_RNAPol2_ChIP_GM12878_Rep1_R2_SL207.align_25.hg18.txt" Count=" 2332288" Date="080325" />
+<Track Flowcell="FC20AJB" Lane="4" Filename="Flowcells/FC20AJB/080411_FC20AJB_s4_RNAPol2_ChIP_GM12878_Rep1_R2_SL207.align_25.hg18.txt" Count=" 2615980" Date="080411" />
+</Library>
+<Library Name="SL208">
+<Track Flowcell="FC209RM" Lane="7" Filename="Flowcells/FC209RM/080325_FC209RM_s7_RNAPol2_ChIP_GM12878_Rep2_R1_SL208.align_25.hg18.txt" Count=" 2045107" Date="080325" />
+</Library>
+<Library Name="SL209">
+<Track Flowcell="FC204GW" Lane="34" Filename="Flowcells/FC204GW/080503_FC204GW_s34_RNAPol2_ChIP_GM12878_Rep2_R2_SL209.align_25.hg18.txt" Count=" 6350506" Date="080503" />
+<Track Flowcell="FC204TD" Lane="5" Filename="Flowcells/FC204TD/080509_FC204TD_s5_RNAPol2_ChIP_GM12878_Rep2_R2_SL209.align_25.hg18.txt" Count=" 2519907" Date="080509" />
+<Track Flowcell="FC209RM" Lane="8" Filename="Flowcells/FC209RM/080325_FC209RM_s8_RNAPol2_ChIP_GM12878_Rep2_R2_SL209.align_25.hg18.txt" Count=" 2375516" Date="080325" />
+<Track Flowcell="FC20AJB" Lane="5" Filename="Flowcells/FC20AJB/080411_FC20AJB_s5_RNAPol2_ChIP_GM12878_Rep2_R2_SL209.align_25.hg18.txt" Count=" 3527877" Date="080411" />
+</Library>
+<Library Name="SL210">
+<Track Flowcell="FC20AME" Lane="6" Filename="Flowcells/FC20AME/080327_FC20AME_s6_hESC_K27_New_SL210.align_25.hg18.txt" Count="  422900" Date="080327" />
+</Library>
+<Library Name="SL211">
+<Track Flowcell="FC20AME" Lane="7" Filename="Flowcells/FC20AME/080327_FC20AME_s7_hESC_Pol2_New_SL211.align_25.hg18.txt" Count="  805937" Date="080327" />
+</Library>
+<Library Name="SL212">
+<Track Flowcell="FC20AJB" Lane="1" Filename="Flowcells/FC20AJB/080411_FC20AJB_s1_hESC_input_New_SL212.align_25.hg18.txt" Count=" 1721512" Date="080411" />
+<Track Flowcell="FC20AME" Lane="8" Filename="Flowcells/FC20AME/080327_FC20AME_s8_hESC_input_New_SL212.align_25.hg18.txt" Count=" 2225791" Date="080327" />
+</Library>
+<Library Name="SL213">
+<Track Flowcell="FC204GW" Lane="7" Filename="Flowcells/FC204GW/080503_FC204GW_s7_day5_K4_SL213.align_25.hg18.txt" Count="  619689" Date="080503" />
+</Library>
+<Library Name="SL214">
+<Track Flowcell="FC204GW" Lane="8" Filename="Flowcells/FC204GW/080503_FC204GW_s8_day5_input_SL214.align_25.hg18.txt" Count=" 1212965" Date="080503" />
+</Library>
+<Library Name="SL217">
+<Track Flowcell="FC20ATK" Lane="1234" Filename="Flowcells/FC20ATK/080404_FC20ATK_s1234_GM12878_input_Ctrl_Rep1_SL217.align_25.hg18.txt" Count=" 14209973" Date="080404" />
+</Library>
+<Library Name="SL218">
+<Track Flowcell="FC20ATK" Lane="5678" Filename="Flowcells/FC20ATK/080404_FC20ATK_s5678_GM12878_input_Ctrl_Rep2_SL218.align_25.hg18.txt" Count=" 11888537" Date="080404" />
+</Library>
+<Library Name="SL219">
+<Track Flowcell="FC204TE" Lane="7" Filename="Flowcells/FC204TE/080401_FC204TE_s7_RNAPol2_ChIP_K562_Dex_rep1_SL219.align_25.hg18.txt" Count=" 3414718" Date="080401" />
+</Library>
+<Library Name="SL220">
+<Track Flowcell="FC204TE" Lane="8" Filename="Flowcells/FC204TE/080401_FC204TE_s8_RNAPol2_ChIP_K562_EtOH_rep1_SL220.align_25.hg18.txt" Count=" 3318493" Date="080401" />
+</Library>
+<Library Name="SL221">
+<Track Flowcell="FC209VL" Lane="12" Filename="Flowcells/FC209VL/080409_FC209VL_s12_NRSF_ChIP-K562_Rep1_SL221.align_25.hg18.txt" Count=" 5557080" Date="080409" />
+<Track Flowcell="FC20AJD" Lane="4" Filename="Flowcells/FC20AJD/080516_FC20AJD_s4_NRSF_ChIP-K562_Rep1_SL221.align_25.hg18.txt" Count=" 3599218" Date="080516" />
+<Track Flowcell="FC20ATL" Lane="5" Filename="Flowcells/FC20ATL/080527_FC20ATL_s5_NRSF_ChIP-K562_Rep1_SL221.align_25.hg18.txt" Count=" 4360336" Date="080527" />
+<Track Flowcell="FC20ATL" Lane="6" Filename="Flowcells/FC20ATL/080527_FC20ATL_s6_NRSF_ChIP-K562_Rep1_SL221.align_25.hg18.txt" Count=" 4414231" Date="080527" />
+</Library>
+<Library Name="SL222">
+<Track Flowcell="FC209VL" Lane="34" Filename="Flowcells/FC209VL/080409_FC209VL_s34_NRSF_ChIP_K562_Rep2_SL222.align_25.hg18.txt" Count=" 5104501" Date="080409" />
+<Track Flowcell="FC20AJD" Lane="5" Filename="Flowcells/FC20AJD/080516_FC20AJD_s5_NRSF_ChIP_K562_Rep2_SL222.align_25.hg18.txt" Count=" 3449963" Date="080516" />
+<Track Flowcell="FC20ATL" Lane="7" Filename="Flowcells/FC20ATL/080527_FC20ATL_s7_NRSF_ChIP_K562_Rep2_SL222.align_25.hg18.txt" Count=" 3722128" Date="080527" />
+<Track Flowcell="FC20ATL" Lane="8" Filename="Flowcells/FC20ATL/080527_FC20ATL_s8_NRSF_ChIP_K562_Rep2_SL222.align_25.hg18.txt" Count=" 3947293" Date="080527" />
+</Library>
+<Library Name="SL223">
+<Track Flowcell="FC204PR" Lane="2" Filename="Flowcells/FC204PR/080520_FC204PR_s2_GABP_ChIP_K562_Rep1_SL223.align_25.hg18.txt" Count=" 2860313" Date="080520" />
+<Track Flowcell="FC209VL" Lane="56" Filename="Flowcells/FC209VL/080409_FC209VL_s56_GABP_ChIP_K562_Rep1_SL223.align_25.hg18.txt" Count=" 4864011" Date="080409" />
+<Track Flowcell="FC20AJ8" Lane="4" Filename="Flowcells/FC20AJ8/080523_FC20AJ8_s4_GABP_ChIP_K562_Rep1_SL223.align_25.hg18.txt" Count=" 3540087" Date="080523" />
+</Library>
+<Library Name="SL224">
+<Track Flowcell="FC204PR" Lane="3" Filename="Flowcells/FC204PR/080520_FC204PR_s3_GABP_ChIP_K562_Rep2_SL224.align_25.hg18.txt" Count="      17" Date="080520" />
+<Track Flowcell="FC209VL" Lane="78" Filename="Flowcells/FC209VL/080409_FC209VL_s78_GABP_ChIP_K562_Rep2_SL224.align_25.hg18.txt" Count=" 4173297" Date="080409" />
+<Track Flowcell="FC20AJ8" Lane="1" Filename="Flowcells/FC20AJ8/080523_FC20AJ8_s1_GABP_ChIP_K562_Rep2_SL224.align_25.hg18.txt" Count=" 3048516" Date="080523" />
+<Track Flowcell="FC20AJ8" Lane="5" Filename="Flowcells/FC20AJ8/080523_FC20AJ8_s5_GABP_ChIP_K562_Rep2_SL224.align_25.hg18.txt" Count=" 3259099" Date="080523" />
+</Library>
+<Library Name="SL225">
+<Track Flowcell="FC209VY" Lane="1" Filename="Flowcells/FC209VY/080415_FC209VY_s1_MeC_DIP_GM12878_Rep1_SL225.align_25.hg18.txt" Count="  596256" Date="080415" />
+</Library>
+<Library Name="SL226">
+<Track Flowcell="FC209VY" Lane="2" Filename="Flowcells/FC209VY/080415_FC209VY_s2_MeC_DIP_GM12878_Rep2_SL226.align_25.hg18.txt" Count="  267004" Date="080415" />
+</Library>
+<Library Name="SL227">
+<Track Flowcell="FC209VY" Lane="3" Filename="Flowcells/FC209VY/080415_FC209VY_s3_MeC_DIP_Input_GM12878_Rep1_SL227.align_25.hg18.txt" Count=" 2207861" Date="080415" />
+</Library>
+<Library Name="SL228">
+<Track Flowcell="FC209VY" Lane="6" Filename="Flowcells/FC209VY/080415_FC209VY_s6_MeC_DIP_Input_GM12878_Rep2_SL228.align_25.hg18.txt" Count=" 2130348" Date="080415" />
+</Library>
+<Library Name="SL229">
+<Track Flowcell="306WNAAXX" Lane="1" Filename="Flowcells/306WNAAXX/080731_306WNAAXX_s1_Methylseq_K562_R1_hpa_SL229.align_25.hg18.txt" Count=" 2789078" Date="080731" />
+<Track Flowcell="30EGGAAXX" Lane="4" Filename="Flowcells/30EGGAAXX/080910_30EGGAAXX_s4_Methylseq_K562_R1_hpa_SL229.align_25.hg18.txt" Count="  340831" Date="080910" />
+<Track Flowcell="FC204TD" Lane="6" Filename="Flowcells/FC204TD/080509_FC204TD_s6_Methylseq_K562_R1_hpa_SL229.align_25.hg18.txt" Count=" 1710215" Date="080509" />
+</Library>
+<Library Name="SL230">
+<Track Flowcell="306WNAAXX" Lane="2" Filename="Flowcells/306WNAAXX/080731_306WNAAXX_s2_Methylseq_K562_R1_msp_SL230.align_25.hg18.txt" Count=" 3452680" Date="080731" />
+<Track Flowcell="FC204TD" Lane="7" Filename="Flowcells/FC204TD/080509_FC204TD_s7_Methylseq_K562_R1_msp_SL230.align_25.hg18.txt" Count=" 1645339" Date="080509" />
+</Library>
+<Library Name="SL231">
+<Track Flowcell="306WNAAXX" Lane="3" Filename="Flowcells/306WNAAXX/080731_306WNAAXX_s3_Methylseq_K562_R2_hpa_SL231.align_25.hg18.txt" Count=" 3339995" Date="080731" />
+<Track Flowcell="30EGGAAXX" Lane="5" Filename="Flowcells/30EGGAAXX/080910_30EGGAAXX_s5_Methylseq_K562_R2_hpa_SL231.align_25.hg18.txt" Count="  508537" Date="080910" />
+<Track Flowcell="FC204TD" Lane="8" Filename="Flowcells/FC204TD/080509_FC204TD_s8_Methylseq_K562_R2_hpa_SL231.align_25.hg18.txt" Count=" 1671255" Date="080509" />
+</Library>
+<Library Name="SL232">
+<Track Flowcell="306WNAAXX" Lane="4" Filename="Flowcells/306WNAAXX/080731_306WNAAXX_s4_Methylseq_K562_R2_msp_SL232.align_25.hg18.txt" Count=" 2608243" Date="080731" />
+<Track Flowcell="FC209VY" Lane="7" Filename="Flowcells/FC209VY/080415_FC209VY_s7_Methylseq_K562_msp_SL232.align_25.hg18.txt" Count=" 1996275" Date="080415" />
+</Library>
+<Library Name="SL233">
+<Track Flowcell="30DCTAAXX" Lane="1" Filename="Flowcells/30DCTAAXX/080904_30DCTAAXX_s1_Input_Control_K562_Rep1_SL233.align_25.hg18.txt" Count=" 2724511" Date="080904" />
+<Track Flowcell="FC209VY" Lane="4" Filename="Flowcells/FC209VY/080415_FC209VY_s4_Input_Control_K562_Rep1_SL233.align_25.hg18.txt" Count=" 2749243" Date="080415" />
+<Track Flowcell="FC20E3HAAXX" Lane="4" Filename="Flowcells/FC20E3HAAXX/080502_FC20E3HAAXX_s4_Input_Control_K562_Rep1_SL233.align_25.hg18.txt" Count=" 1825060" Date="080502" />
+<Track Flowcell="FC303PRAAXX" Lane="4" Filename="Flowcells/FC303PRAAXX/080523_FC303PRAAXX_s4_Input_Control_K562_Rep1_SL233.align_25.hg18.txt" Count=" 2792190" Date="080523" />
+</Library>
+<Library Name="SL234">
+<Track Flowcell="30DCTAAXX" Lane="2" Filename="Flowcells/30DCTAAXX/080904_30DCTAAXX_s2_Input_Control_K562_Rep2_SL234.align_25.hg18.txt" Count=" 3602099" Date="080904" />
+<Track Flowcell="30EFCAAXX" Lane="1" Filename="Flowcells/30EFCAAXX/080910_30EFCAAXX_s1_Input_Control_K562_Rep2_SL234.align_25.hg18.txt" Count="  569875" Date="080910" />
+<Track Flowcell="30EFCAAXX" Lane="2" Filename="Flowcells/30EFCAAXX/080910_30EFCAAXX_s2_Input_Control_K562_Rep2_SL234.align_25.hg18.txt" Count=" 1955971" Date="080910" />
+<Track Flowcell="FC209VY" Lane="5" Filename="Flowcells/FC209VY/080415_FC209VY_s5_Input_Control_K562_Rep2_SL234.align_25.hg18.txt" Count=" 2190280" Date="080415" />
+<Track Flowcell="FC20E3HAAXX" Lane="5" Filename="Flowcells/FC20E3HAAXX/080502_FC20E3HAAXX_s5_Input_Control_K562_Rep2_SL234.align_25.hg18.txt" Count=" 1592799" Date="080502" />
+</Library>
+<Library Name="SL235">
+<Track Flowcell="FC209VY" Lane="8" Filename="Flowcells/FC209VY/080415_FC209VY_s8_RNAseq_AF_sol_101_SL235.align_25.cel.txt" Count=" 2172789" Date="080415" />
+</Library>
+<Library Name="SL236">
+<Track Flowcell="FC20AUF" Lane="3" Filename="Flowcells/FC20AUF/080418_FC20AUF_s3_RNASeq_SL236.align_25.hg18.txt" Count="  334732" Date="080418" />
+</Library>
+<Library Name="SL237">
+<Track Flowcell="FC20AUF" Lane="6" Filename="Flowcells/FC20AUF/080418_FC20AUF_s6_DNA_nucleosomes_SL237.align_25.mm9.txt" Count=" 2337360" Date="080418" />
+</Library>
+<Library Name="SL238">
+<Track Flowcell="FC20AUF" Lane="7" Filename="Flowcells/FC20AUF/080418_FC20AUF_s7_AF_SOL_104_SL238.align_25.hg18.txt" Count=" 2960999" Date="080418" />
+</Library>
+<Library Name="SL239">
+<Track Flowcell="FC20AUF" Lane="8" Filename="Flowcells/FC20AUF/080418_FC20AUF_s8_AF_SOL_105_SL239.align_25.hg18.txt" Count=" 3601304" Date="080418" />
+</Library>
+<Library Name="SL240">
+<Track Flowcell="30DCTAAXX" Lane="3" Filename="Flowcells/30DCTAAXX/080904_30DCTAAXX_s3_K562_RNApol2_rep1_SL240.align_25.hg18.txt" Count=" 4391389" Date="080904" />
+<Track Flowcell="FC20E3HAAXX" Lane="1" Filename="Flowcells/FC20E3HAAXX/080502_FC20E3HAAXX_s1_K562_RNApol2_rep1_SL240.align_25.hg18.txt" Count=" 1666658" Date="080502" />
+<Track Flowcell="FC303PRAAXX" Lane="12" Filename="Flowcells/FC303PRAAXX/080523_FC303PRAAXX_s12_K562_RNApol2_rep1_SL240.align_25.hg18.txt" Count=" 6613625" Date="080523" />
+</Library>
+<Library Name="SL241">
+<Track Flowcell="30DCTAAXX" Lane="4" Filename="Flowcells/30DCTAAXX/080904_30DCTAAXX_s4_K562_RNApol2_rep2_SL241.align_25.hg18.txt" Count=" 3497136" Date="080904" />
+<Track Flowcell="FC20E3HAAXX" Lane="2" Filename="Flowcells/FC20E3HAAXX/080502_FC20E3HAAXX_s2_K562_RNApol2_rep2_SL241.align_25.hg18.txt" Count=" 2005718" Date="080502" />
+<Track Flowcell="FC303PRAAXX" Lane="36" Filename="Flowcells/FC303PRAAXX/080523_FC303PRAAXX_s36_K562_RNApol2_rep2_SL241.align_25.hg18.txt" Count=" 5690721" Date="080523" />
+</Library>
+<Library Name="SL242">
+<Track Flowcell="306WPAAXX" Lane="1" Filename="Flowcells/306WPAAXX/080731_306WPAAXX_s1_K562_TAF250_Rep1_SL242.align_25.hg18.txt" Count=" 2063352" Date="080731" />
+<Track Flowcell="30DCTAAXX" Lane="5" Filename="Flowcells/30DCTAAXX/080904_30DCTAAXX_s5_K562_TAF250_Rep1_SL242.align_25.hg18.txt" Count=" 1499911" Date="080904" />
+<Track Flowcell="30EFCAAXX" Lane="3" Filename="Flowcells/30EFCAAXX/080910_30EFCAAXX_s3_K562_TAF250_Rep1_SL242.align_25.hg18.txt" Count=" 1802228" Date="080910" />
+<Track Flowcell="FC20E3HAAXX" Lane="36" Filename="Flowcells/FC20E3HAAXX/080502_FC20E3HAAXX_s36_K562_TAF250_Rep1_SL242.align_25.hg18.txt" Count=" 3378725" Date="080502" />
+<Track Flowcell="FC303PRAAXX" Lane="7" Filename="Flowcells/FC303PRAAXX/080523_FC303PRAAXX_s7_K562_TAF250_Rep1_SL242.align_25.hg18.txt" Count=" 1171122" Date="080523" />
+</Library>
+<Library Name="SL243">
+<Track Flowcell="306WPAAXX" Lane="2" Filename="Flowcells/306WPAAXX/080731_306WPAAXX_s2_K562_TAF250_Rep2_SL243.align_25.hg18.txt" Count=" 4389115" Date="080731" />
+<Track Flowcell="FC20E3HAAXX" Lane="78" Filename="Flowcells/FC20E3HAAXX/080502_FC20E3HAAXX_s78_K562_TAF250_Rep2_SL243.align_25.hg18.txt" Count=" 4628303" Date="080502" />
+<Track Flowcell="FC303PRAAXX" Lane="8" Filename="Flowcells/FC303PRAAXX/080523_FC303PRAAXX_s8_K562_TAF250_Rep2_SL243.align_25.hg18.txt" Count=" 2228574" Date="080523" />
+</Library>
+<Library Name="SL244">
+<Track Flowcell="303y5aaxx" Lane="3" Filename="Flowcells/303y5aaxx/080715_303y5aaxx_s3_ChIPseq_A549_Pol2_Dex_SL244.align_25.hg18.txt" Count=" 4670412" Date="080715" />
+<Track Flowcell="FC204GV" Lane="4" Filename="Flowcells/FC204GV/080429_FC204GV_s4_ChIPseq_A549_Pol2_Dex_SL244.align_25.hg18.txt" Count=" 1143215" Date="080429" />
+<Track Flowcell="FC204GW" Lane="5" Filename="Flowcells/FC204GW/080503_FC204GW_s5_ChIPseq_A549_Pol2_Dex_SL244.align_25.hg18.txt" Count=" 2893570" Date="080503" />
+<Track Flowcell="FC204PR" Lane="4" Filename="Flowcells/FC204PR/080520_FC204PR_s4_ChIPseq_A549_Pol2_Dex_SL244.align_25.hg18.txt" Count=" 2984654" Date="080520" />
+</Library>
+<Library Name="SL245">
+<Track Flowcell="303y5aaxx" Lane="4" Filename="Flowcells/303y5aaxx/080715_303y5aaxx_s4_ChIPSeq_A549_Pol2_EtOH_SL245.align_25.hg18.txt" Count=" 5705811" Date="080715" />
+<Track Flowcell="FC204GV" Lane="5" Filename="Flowcells/FC204GV/080429_FC204GV_s5_ChIPSeq_A549_Pol2_EtOH_SL245.align_25.hg18.txt" Count=" 1331975" Date="080429" />
+<Track Flowcell="FC204GW" Lane="6" Filename="Flowcells/FC204GW/080503_FC204GW_s6_ChIPSeq_A549_Pol2_EtOH_SL245.align_25.hg18.txt" Count=" 3378626" Date="080503" />
+<Track Flowcell="FC204PR" Lane="5" Filename="Flowcells/FC204PR/080520_FC204PR_s5_ChIPSeq_A549_Pol2_EtOH_SL245.align_25.hg18.txt" Count=" 3137998" Date="080520" />
+</Library>
+<Library Name="SL246">
+<Track Flowcell="303y5aaxx" Lane="1" Filename="Flowcells/303y5aaxx/080715_303y5aaxx_s1_ChIPSeq_A549_M20_GR_Dex_SL246.align_25.hg18.txt" Count=" 2056910" Date="080715" />
+<Track Flowcell="FC204JB" Lane="2" Filename="Flowcells/FC204JB/080507_FC204JB_s2_ChIPSeq_A549_M20_GR_Dex_SL246.align_25.hg18.txt" Count=" 1321687" Date="080507" />
+<Track Flowcell="FC204PR" Lane="6" Filename="Flowcells/FC204PR/080520_FC204PR_s6_ChIPSeq_A549_M20_GR_Dex_SL246.align_25.hg18.txt" Count=" 1321643" Date="080520" />
+<Track Flowcell="FC20AUF" Lane="4" Filename="Flowcells/FC20AUF/080418_FC20AUF_s4_ChIPSeq_A549_M20_GR_Dex_SL246.align_25.hg18.txt" Count=" 1236139" Date="080418" />
+<Track Flowcell="FC20AUM" Lane="6" Filename="Flowcells/FC20AUM/080616_FC20AUM_s6_ChIPSeq_A549_M20_GR_Dex_SL246.align_25.hg18.txt" Count=" 1449936" Date="080616" />
+<Track Flowcell="FC20AUM" Lane="7" Filename="Flowcells/FC20AUM/080616_FC20AUM_s7_ChIPSeq_A549_M20_GR_Dex_SL246.align_25.hg18.txt" Count=" 1599501" Date="080616" />
+<Track Flowcell="FC20AUM" Lane="8" Filename="Flowcells/FC20AUM/080616_FC20AUM_s8_ChIPSeq_A549_M20_GR_Dex_SL246.align_25.hg18.txt" Count=" 1605226" Date="080616" />
+</Library>
+<Library Name="SL247">
+<Track Flowcell="303y5aaxx" Lane="2" Filename="Flowcells/303y5aaxx/080715_303y5aaxx_s2_ChIPSeq_A549_M20_GR_EtOH_SL247.align_25.hg18.txt" Count=" 3710876" Date="080715" />
+<Track Flowcell="FC204JB" Lane="4" Filename="Flowcells/FC204JB/080507_FC204JB_s4_ChIPSeq_A549_M20_GR_EtOH_SL247.align_25.hg18.txt" Count=" 2448458" Date="080507" />
+<Track Flowcell="FC204PR" Lane="7" Filename="Flowcells/FC204PR/080520_FC204PR_s7_ChIPSeq_A549_M20_GR_EtOH_SL247.align_25.hg18.txt" Count=" 2346726" Date="080520" />
+<Track Flowcell="FC20AUF" Lane="5" Filename="Flowcells/FC20AUF/080418_FC20AUF_s5_ChIPSeq_A549_M20_GR_EtOH_SL247.align_25.hg18.txt" Count=" 2148284" Date="080418" />
+</Library>
+<Library Name="SL248">
+<Track Flowcell="FC204GV" Lane="6" Filename="Flowcells/FC204GV/080429_FC204GV_s6_XRLP_041408_SL248.align_25.scer.txt" Count=" 2411445" Date="080429" />
+</Library>
+<Library Name="SL249">
+<Track Flowcell="FC204GV" Lane="7" Filename="Flowcells/FC204GV/080429_FC204GV_s7_NaCl_041408_SL249.align_25.scer.txt" Count=" 2433903" Date="080429" />
+</Library>
+<Library Name="SL250">
+<Track Flowcell="FC204GV" Lane="8" Filename="Flowcells/FC204GV/080429_FC204GV_s8_WT_041408_SL250.align_25.scer.txt" Count=" 2135397" Date="080429" />
+</Library>
+<Library Name="SL259">
+<Track Flowcell="FC204JB" Lane="3" Filename="Flowcells/FC204JB/080507_FC204JB_s3_Input_Control_Namwala_RX_Ch_SL259.align_25.hg18.txt" Count=" 3608279" Date="080507" />
+</Library>
+<Library Name="SL260">
+<Track Flowcell="FC204JB" Lane="5" Filename="Flowcells/FC204JB/080507_FC204JB_s5_Input_Control_MNase_UB_SL260.align_25.hg18.txt" Count=" 2239155" Date="080507" />
+</Library>
+<Library Name="SL261">
+<Track Flowcell="FC204JB" Lane="6" Filename="Flowcells/FC204JB/080507_FC204JB_s6_Input_Control_MNase_LB_SL261.align_25.hg18.txt" Count=" 4413501" Date="080507" />
+</Library>
+<Library Name="SL262">
+<Track Flowcell="FC204JB" Lane="7" Filename="Flowcells/FC204JB/080507_FC204JB_s7_ChIPSeq_Namwala_Pol2_MNase_UB_SL262.align_25.hg18.txt" Count=" 2135250" Date="080507" />
+</Library>
+<Library Name="SL263">
+<Track Flowcell="FC204JB" Lane="8" Filename="Flowcells/FC204JB/080507_FC204JB_s8_ChIPSeq_Namwala_Pol2_MNase_LB_SL263.align_25.hg18.txt" Count=" 4570064" Date="080507" />
+</Library>
+<Library Name="SL264">
+<Track Flowcell="FC204JB" Lane="1" Filename="Flowcells/FC204JB/080507_FC204JB_s1_RNASeq_GM12878_Rep1_SL264.align_25.hg18.txt" Count=" 3546913" Date="080507" />
+</Library>
+<Library Name="SL265">
+<Track Flowcell="FC204TD" Lane="1" Filename="Flowcells/FC204TD/080509_FC204TD_s1_RNASeq_K562_Rep1_SL265.align_25.hg18.txt" Count=" 3677080" Date="080509" />
+</Library>
+<Library Name="SL266">
+<Track Flowcell="FC204TD" Lane="2" Filename="Flowcells/FC204TD/080509_FC204TD_s2_RNASeq_K562_Rep2_SL266.align_25.hg18.txt" Count=" 3565092" Date="080509" />
+</Library>
+<Library Name="SL269">
+<Track Flowcell="FC204MT" Lane="1" Filename="Flowcells/FC204MT/080512_FC204MT_s1_MCF7_ChIPSeq_GR_Dex_SL269.align_25.hg18.txt" Count=" 2282494" Date="080512" />
+</Library>
+<Library Name="SL270">
+<Track Flowcell="FC204MT" Lane="2" Filename="Flowcells/FC204MT/080512_FC204MT_s2_MCF7_ChIPSeq_GR_EtOH_SL270.align_25.hg18.txt" Count=" 2399296" Date="080512" />
+</Library>
+<Library Name="SL271">
+<Track Flowcell="FC204MT" Lane="3" Filename="Flowcells/FC204MT/080512_FC204MT_s3_MCF7_ChIPSeq_Pol2_Dex_SL271.align_25.hg18.txt" Count=" 2884011" Date="080512" />
+</Library>
+<Library Name="SL272">
+<Track Flowcell="FC204MT" Lane="4" Filename="Flowcells/FC204MT/080512_FC204MT_s4_ChIPSeq_MCF7_Pol2_EtOH_SL272.align_25.hg18.txt" Count=" 3016679" Date="080512" />
+</Library>
+<Library Name="SL273">
+<Track Flowcell="209UKAAXX" Lane="1" Filename="Flowcells/209UKAAXX/080528_209UKAAXX_s1_ChIPSeq_HepG2_Rep1_NRSF_SL273.align_25.hg18.txt" Count=" 1096000" Date="080528" />
+<Track Flowcell="209UKAAXX" Lane="2" Filename="Flowcells/209UKAAXX/080528_209UKAAXX_s2_ChIPSeq_HepG2_Rep1_NRSF_SL273.align_25.hg18.txt" Count=" 1197533" Date="080528" />
+<Track Flowcell="209UKAAXX" Lane="3" Filename="Flowcells/209UKAAXX/080528_209UKAAXX_s3_ChIPSeq_HepG2_Rep1_NRSF_SL273.align_25.hg18.txt" Count=" 1437394" Date="080528" />
+<Track Flowcell="209UKAAXX" Lane="4" Filename="Flowcells/209UKAAXX/080528_209UKAAXX_s4_ChIPSeq_HepG2_Rep1_NRSF_SL273.align_25.hg18.txt" Count=" 1523562" Date="080528" />
+<Track Flowcell="306P2AAXX" Lane="4" Filename="Flowcells/306P2AAXX/080722_306P2AAXX_s4_ChIPSeq_HepG2_Rep1_NRSF_SL273.align_25.hg18.txt" Count=" 3111388" Date="080722" />
+<Track Flowcell="FC204MT" Lane="5" Filename="Flowcells/FC204MT/080512_FC204MT_s5_ChIPSeq_HepG2_Rep1_NRSF_SL273.align_25.hg18.txt" Count=" 2875972" Date="080512" />
+</Library>
+<Library Name="SL274">
+<Track Flowcell="209UKAAXX" Lane="5" Filename="Flowcells/209UKAAXX/080528_209UKAAXX_s5_ChIPSeq_HepG2_Rep2_NRSF_SL274.align_25.hg18.txt" Count=" 1726267" Date="080528" />
+<Track Flowcell="209UKAAXX" Lane="6" Filename="Flowcells/209UKAAXX/080528_209UKAAXX_s6_ChIPSeq_HepG2_Rep2_NRSF_SL274.align_25.hg18.txt" Count=" 1693140" Date="080528" />
+<Track Flowcell="209UKAAXX" Lane="7" Filename="Flowcells/209UKAAXX/080528_209UKAAXX_s7_ChIPSeq_HepG2_Rep2_NRSF_SL274.align_25.hg18.txt" Count=" 1537265" Date="080528" />
+<Track Flowcell="209UKAAXX" Lane="8" Filename="Flowcells/209UKAAXX/080528_209UKAAXX_s8_ChIPSeq_HepG2_Rep2_NRSF_SL274.align_25.hg18.txt" Count=" 1364998" Date="080528" />
+<Track Flowcell="306P2AAXX" Lane="3" Filename="Flowcells/306P2AAXX/080722_306P2AAXX_s3_ChIPSeq_HepG2_Rep2_NRSF_SL274.align_25.hg18.txt" Count=" 3945743" Date="080722" />
+<Track Flowcell="FC204MT" Lane="6" Filename="Flowcells/FC204MT/080512_FC204MT_s6_ChIPSeq_HepG2_Rep2_NRSF_SL274.align_25.hg18.txt" Count=" 2969609" Date="080512" />
+</Library>
+<Library Name="SL275">
+<Track Flowcell="306P2AAXX" Lane="2" Filename="Flowcells/306P2AAXX/080722_306P2AAXX_s2_ChIPSeq_HepG2_Rep1_GABP_SL275.align_25.hg18.txt" Count=" 4599829" Date="080722" />
+<Track Flowcell="30DCTAAXX" Lane="7" Filename="Flowcells/30DCTAAXX/080904_30DCTAAXX_s7_ChIPSeq_HepG2_Rep1_GABP_SL275.align_25.hg18.txt" Count="  677714" Date="080904" />
+<Track Flowcell="FC204MT" Lane="7" Filename="Flowcells/FC204MT/080512_FC204MT_s7_ChIPSeq_HepG2_Rep1_GABP_SL275.align_25.hg18.txt" Count=" 3373373" Date="080512" />
+</Library>
+<Library Name="SL276">
+<Track Flowcell="306P2AAXX" Lane="1" Filename="Flowcells/306P2AAXX/080722_306P2AAXX_s1_ChIPSeq_HepG2_Rep2_GABP_SL276.align_25.hg18.txt" Count=" 4297640" Date="080722" />
+<Track Flowcell="30DCTAAXX" Lane="8" Filename="Flowcells/30DCTAAXX/080904_30DCTAAXX_s8_ChIPSeq_HepG2_Rep2_GABP_SL276.align_25.hg18.txt" Count="  604582" Date="080904" />
+<Track Flowcell="FC204MT" Lane="8" Filename="Flowcells/FC204MT/080512_FC204MT_s8_ChIPSeq_HepG2_Rep2_GABP_SL276.align_25.hg18.txt" Count=" 3252004" Date="080512" />
+</Library>
+<Library Name="SL277">
+<Track Flowcell="FC20ATL" Lane="2" Filename="Flowcells/FC20ATL/080527_FC20ATL_s2_ChIPSeq_Jurkat_S_S_Rep1_SRF_SL277.align_25.hg18.txt" Count=" 3716401" Date="080527" />
+</Library>
+<Library Name="SL280">
+<Track Flowcell="FC20AJ8" Lane="2" Filename="Flowcells/FC20AJ8/080523_FC20AJ8_s2_RNASeq_HepG2_Rep1_SL280.align_25.hg18.txt" Count="  194175" Date="080523" />
+</Library>
+<Library Name="SL281">
+<Track Flowcell="FC20AJ8" Lane="3" Filename="Flowcells/FC20AJ8/080523_FC20AJ8_s3_RNASeq_HepG2_Rep2_SL281.align_25.hg18.txt" Count="  421392" Date="080523" />
+</Library>
+<Library Name="SL288">
+<Track Flowcell="306P2AAXX" Lane="8" Filename="Flowcells/306P2AAXX/080722_306P2AAXX_s8_ChIPSeq_GM12878_Rep1_TAF250_SL288.align_25.hg18.txt" Count=" 2153614" Date="080722" />
+<Track Flowcell="30DBLAAXX" Lane="1" Filename="Flowcells/30DBLAAXX/080815_30DBLAAXX_s1_ChIPSeq_GM12878_Rep1_TAF250_SL288.align_25.hg18.txt" Count=" 3958844" Date="080815" />
+<Track Flowcell="30DCTAAXX" Lane="6" Filename="Flowcells/30DCTAAXX/080904_30DCTAAXX_s6_ChIPSeq_GM12878_Rep1_TAF250_SL288.align_25.hg18.txt" Count=" 1715117" Date="080904" />
+<Track Flowcell="FC20AJ8" Lane="6" Filename="Flowcells/FC20AJ8/080523_FC20AJ8_s6_ChIPSeq_GM12878_Rep1_TAF250_SL288.align_25.hg18.txt" Count=" 3168587" Date="080523" />
+</Library>
+<Library Name="SL289">
+<Track Flowcell="30DBLAAXX" Lane="5" Filename="Flowcells/30DBLAAXX/080815_30DBLAAXX_s5_ChIPSeq_K562_Rep1_SRF_SL289.align_25.hg18.txt" Count=" 3892910" Date="080815" />
+<Track Flowcell="30DBLAAXX" Lane="6" Filename="Flowcells/30DBLAAXX/080815_30DBLAAXX_s6_ChIPSeq_K562_Rep1_SRF_SL289.align_25.hg18.txt" Count=" 3749795" Date="080815" />
+<Track Flowcell="FC20AJ8" Lane="7" Filename="Flowcells/FC20AJ8/080523_FC20AJ8_s7_ChIPSeq_K562_Rep1_SRF_SL289.align_25.hg18.txt" Count=" 2705050" Date="080523" />
+</Library>
+<Library Name="SL290">
+<Track Flowcell="30DBLAAXX" Lane="7" Filename="Flowcells/30DBLAAXX/080815_30DBLAAXX_s7_ChIPSeq_K562_Rep2_SRF_SL290.align_25.hg18.txt" Count=" 4165480" Date="080815" />
+<Track Flowcell="30DBLAAXX" Lane="8" Filename="Flowcells/30DBLAAXX/080815_30DBLAAXX_s8_ChIPSeq_K562_Rep2_SRF_SL290.align_25.hg18.txt" Count=" 2870741" Date="080815" />
+<Track Flowcell="FC20AJ8" Lane="8" Filename="Flowcells/FC20AJ8/080523_FC20AJ8_s8_ChIPSeq_K562_Rep2_SRF_SL290.align_25.hg18.txt" Count=" 2889602" Date="080523" />
+</Library>
+<Library Name="SL291">
+<Track Flowcell="2087VAAXX" Lane="1" Filename="Flowcells/2087VAAXX/080611_2087VAAXX_s1_ChIPSeq_GM12878_Rep1_SRF_SL291.align_25.hg18.txt" Count=" 2326626" Date="080611" />
+<Track Flowcell="2087VAAXX" Lane="5" Filename="Flowcells/2087VAAXX/080611_2087VAAXX_s5_ChIPSeq_GM12878_Rep1_SRF_SL291.align_25.hg18.txt" Count=" 2283483" Date="080611" />
+<Track Flowcell="30DBLAAXX" Lane="3" Filename="Flowcells/30DBLAAXX/080815_30DBLAAXX_s3_ChIP-seq_SRF_GM12878_Rep1_SL291.align_25.hg18.txt" Count=" 4312781" Date="080815" />
+<Track Flowcell="FC20ATH" Lane="1" Filename="Flowcells/FC20ATH/080602_FC20ATH_s1_ChIP-seq_SRF_GM12878_Rep1_SL291.align_25.hg18.txt" Count=" 1165384" Date="080602" />
+</Library>
+<Library Name="SL292">
+<Track Flowcell="2087VAAXX" Lane="2" Filename="Flowcells/2087VAAXX/080611_2087VAAXX_s2_ChIPSeq_GM12878_Rep2_SRF_SL292.align_25.hg18.txt" Count=" 2117859" Date="080611" />
+<Track Flowcell="2087VAAXX" Lane="6" Filename="Flowcells/2087VAAXX/080611_2087VAAXX_s6_ChIPSeq_GM12878_Rep2_SRF_SL292.align_25.hg18.txt" Count=" 2135165" Date="080611" />
+<Track Flowcell="30DBLAAXX" Lane="4" Filename="Flowcells/30DBLAAXX/080815_30DBLAAXX_s4_ChIP-seq_SRF_GM12878_Rep2_SL292.align_25.hg18.txt" Count=" 4481065" Date="080815" />
+<Track Flowcell="FC20ATH" Lane="2" Filename="Flowcells/FC20ATH/080602_FC20ATH_s2_ChIP-seq_SRF_GM12878_Rep2_SL292.align_25.hg18.txt" Count=" 2027947" Date="080602" />
+</Library>
+<Library Name="SL293">
+<Track Flowcell="2087VAAXX" Lane="3" Filename="Flowcells/2087VAAXX/080611_2087VAAXX_s3_ChIPSeq_GM12878_Rep2_TAF250_SL293.align_25.hg18.txt" Count=" 2598142" Date="080611" />
+<Track Flowcell="2087VAAXX" Lane="7" Filename="Flowcells/2087VAAXX/080611_2087VAAXX_s7_ChIPSeq_GM12878_Rep2_TAF250_SL293.align_25.hg18.txt" Count=" 2556642" Date="080611" />
+<Track Flowcell="30DBLAAXX" Lane="2" Filename="Flowcells/30DBLAAXX/080815_30DBLAAXX_s2_ChIP-seq_TAF250_GM12878_Rep2_SL293.align_25.hg18.txt" Count=" 4857889" Date="080815" />
+<Track Flowcell="FC20ATH" Lane="3" Filename="Flowcells/FC20ATH/080602_FC20ATH_s3_ChIP-seq_TAF250_GM12878_Rep2_SL293.align_25.hg18.txt" Count=" 2398112" Date="080602" />
+</Library>
+<Library Name="SL294">
+<Track Flowcell="2087VAAXX" Lane="8" Filename="Flowcells/2087VAAXX/080611_2087VAAXX_s8_ChIPSeq_HepG2_Rep1_InputControl_RXLCh_SL294.align_25.hg18.txt" Count=" 2190318" Date="080611" />
+<Track Flowcell="306P2AAXX" Lane="7" Filename="Flowcells/306P2AAXX/080722_306P2AAXX_s7_Input_Control_HepG2_Rep1_SL294.align_25.hg18.txt" Count=" 1918136" Date="080722" />
+<Track Flowcell="30CLAAXX" Lane="7" Filename="Flowcells/30CLAAXX/080815_30CLAAXX_s7_Input_Control_HepG2_Rep1_SL294.align_25.hg18.txt" Count=" 5027019" Date="080815" />
+<Track Flowcell="30EFCAAXX" Lane="4" Filename="Flowcells/30EFCAAXX/080910_30EFCAAXX_s4_Input_Control_HepG2_Rep1_SL294.align_25.hg18.txt" Count=" 1919675" Date="080910" />
+<Track Flowcell="30EFCAAXX" Lane="5" Filename="Flowcells/30EFCAAXX/080910_30EFCAAXX_s5_Input_Control_HepG2_Rep1_SL294.align_25.hg18.txt" Count=" 1999038" Date="080910" />
+<Track Flowcell="FC20ATH" Lane="4" Filename="Flowcells/FC20ATH/080602_FC20ATH_s4_Input_Control_HepG2_Rep1_SL294.align_25.hg18.txt" Count=" 2168270" Date="080602" />
+</Library>
+<Library Name="SL295">
+<Track Flowcell="306P2AAXX" Lane="5" Filename="Flowcells/306P2AAXX/080722_306P2AAXX_s5_Input_Control_HepG2_Rep2_SL295.align_25.hg18.txt" Count=" 2771168" Date="080722" />
+<Track Flowcell="306P2AAXX" Lane="6" Filename="Flowcells/306P2AAXX/080722_306P2AAXX_s6_Input_Control_HepG2_Rep2_SL295.align_25.hg18.txt" Count=" 1633531" Date="080722" />
+<Track Flowcell="30CLAAXX" Lane="8" Filename="Flowcells/30CLAAXX/080815_30CLAAXX_s8_Input_Control_HepG2_Rep2_SL295.align_25.hg18.txt" Count=" 3842145" Date="080815" />
+<Track Flowcell="30EFCAAXX" Lane="7" Filename="Flowcells/30EFCAAXX/080910_30EFCAAXX_s7_Input_Control_HepG2_Rep2_SL295.align_25.hg18.txt" Count=" 2989786" Date="080910" />
+<Track Flowcell="30EFCAAXX" Lane="8" Filename="Flowcells/30EFCAAXX/080910_30EFCAAXX_s8_Input_Control_HepG2_Rep2_SL295.align_25.hg18.txt" Count=" 2811384" Date="080910" />
+<Track Flowcell="FC20ATH" Lane="5" Filename="Flowcells/FC20ATH/080602_FC20ATH_s5_Input_Control_HepG2_Rep2_SL295.align_25.hg18.txt" Count=" 1970158" Date="080602" />
+</Library>
+<Library Name="SL296">
+<Track Flowcell="30DDDAAXX" Lane="4" Filename="Flowcells/30DDDAAXX/080826_30DDDAAXX_s4_Methylseq_GM12878_R1_hpa_1pcr_SL296.align_25.hg18.txt" Count=" 5963498" Date="080826" />
+<Track Flowcell="30EGGAAXX" Lane="1" Filename="Flowcells/30EGGAAXX/080910_30EGGAAXX_s1_Methylseq_GM12878_R1_hpa_1pcr_SL296.align_25.hg18.txt" Count="  977352" Date="080910" />
+<Track Flowcell="30EGGAAXX" Lane="2" Filename="Flowcells/30EGGAAXX/080910_30EGGAAXX_s2_Methylseq_GM12878_R1_hpa_1pcr_SL296.align_25.hg18.txt" Count="  687356" Date="080910" />
+<Track Flowcell="30EGGAAXX" Lane="3" Filename="Flowcells/30EGGAAXX/080910_30EGGAAXX_s3_Methylseq_GM12878_R1_hpa_1pcr_SL296.align_25.hg18.txt" Count="  488635" Date="080910" />
+<Track Flowcell="FC209TJ" Lane="5" Filename="Flowcells/FC209TJ/080606_FC209TJ_s5_Methylseq_GM12878_R1_hpa_1pcr_SL296.align_25.hg18.txt" Count=" 2940725" Date="080606" />
+</Library>
+<Library Name="SL297">
+<Track Flowcell="30DDDAAXX" Lane="3" Filename="Flowcells/30DDDAAXX/080826_30DDDAAXX_s3_Methylseq_GM12878_R2_hpa_2pcr_SL297.align_25.hg18.txt" Count=" 5868879" Date="080826" />
+<Track Flowcell="30EGGAAXX" Lane="6" Filename="Flowcells/30EGGAAXX/080910_30EGGAAXX_s6_Methylseq_GM12878_R2_hpa_2pcr_SL297.align_25.hg18.txt" Count="  326483" Date="080910" />
+<Track Flowcell="30EGGAAXX" Lane="7" Filename="Flowcells/30EGGAAXX/080910_30EGGAAXX_s7_Methylseq_GM12878_R2_hpa_2pcr_SL297.align_25.hg18.txt" Count="  397246" Date="080910" />
+<Track Flowcell="30EGGAAXX" Lane="8" Filename="Flowcells/30EGGAAXX/080910_30EGGAAXX_s8_Methylseq_GM12878_R2_hpa_2pcr_SL297.align_25.hg18.txt" Count="  646888" Date="080910" />
+<Track Flowcell="FC209TJ" Lane="6" Filename="Flowcells/FC209TJ/080606_FC209TJ_s6_Methylseq_GM12878_R2_hpa_2pcr_SL297.align_25.hg18.txt" Count=" 2634191" Date="080606" />
+</Library>
+<Library Name="SL298">
+<Track Flowcell="30DDDAAXX" Lane="2" Filename="Flowcells/30DDDAAXX/080826_30DDDAAXX_s2_Methylseq_GM12878_R1_msp_1pcr_SL298.align_25.hg18.txt" Count=" 4205665" Date="080826" />
+<Track Flowcell="FC209TJ" Lane="7" Filename="Flowcells/FC209TJ/080606_FC209TJ_s7_Methylseq_GM12878_R1_msp_1pcr_SL298.align_25.hg18.txt" Count=" 2516383" Date="080606" />
+</Library>
+<Library Name="SL299">
+<Track Flowcell="30DDDAAXX" Lane="1" Filename="Flowcells/30DDDAAXX/080826_30DDDAAXX_s1_Methylseq_GM12878_R2_msp_2pcr_SL299.align_25.hg18.txt" Count=" 3481486" Date="080826" />
+<Track Flowcell="FC209TJ" Lane="8" Filename="Flowcells/FC209TJ/080606_FC209TJ_s8_Methylseq_GM12878_R2_msp_2pcr_SL299.align_25.hg18.txt" Count=" 1951342" Date="080606" />
+</Library>
+<Library Name="SL300">
+<Track Flowcell="306WNAAXX" Lane="5" Filename="Flowcells/306WNAAXX/080731_306WNAAXX_s5_Methyl-seq_HepG2__rep1_hpa_1PCR_SL300.align_25.hg18.txt" Count=" 1404807" Date="080731" />
+<Track Flowcell="30DDDAAXX" Lane="5" Filename="Flowcells/30DDDAAXX/080826_30DDDAAXX_s5_Methyl-seq_HepG2__rep1_hpa_1PCR_SL300.align_25.hg18.txt" Count=" 5941690" Date="080826" />
+</Library>
+<Library Name="SL301">
+<Track Flowcell="306WNAAXX" Lane="6" Filename="Flowcells/306WNAAXX/080731_306WNAAXX_s6_Methyl-seq_HepG2__rep2_hpa_2PCR_SL301.align_25.hg18.txt" Count=" 1216872" Date="080731" />
+<Track Flowcell="30DDDAAXX" Lane="6" Filename="Flowcells/30DDDAAXX/080826_30DDDAAXX_s6_Methyl-seq_HepG2__rep2_hpa_2PCR_SL301.align_25.hg18.txt" Count=" 6122369" Date="080826" />
+</Library>
+<Library Name="SL302">
+<Track Flowcell="306WNAAXX" Lane="7" Filename="Flowcells/306WNAAXX/080731_306WNAAXX_s7_Methyl-seq_HepG2__rep2_msp_1PCR_SL302.align_25.hg18.txt" Count=" 1205138" Date="080731" />
+<Track Flowcell="30DDDAAXX" Lane="7" Filename="Flowcells/30DDDAAXX/080826_30DDDAAXX_s7_Methyl-seq_HepG2__rep2_msp_1PCR_SL302.align_25.hg18.txt" Count=" 4609878" Date="080826" />
+</Library>
+<Library Name="SL303">
+<Track Flowcell="306WNAAXX" Lane="8" Filename="Flowcells/306WNAAXX/080731_306WNAAXX_s8_Methyl-seq_HepG2__rep2_msp_2PCR_SL303.align_25.hg18.txt" Count="  856073" Date="080731" />
+<Track Flowcell="30DDDAAXX" Lane="8" Filename="Flowcells/30DDDAAXX/080826_30DDDAAXX_s8_Methyl-seq_HepG2__rep2_msp_2PCR_SL303.align_25.hg18.txt" Count=" 4337314" Date="080826" />
+</Library>
+<Library Name="SL304">
+<Track Flowcell="3072MAAXX" Lane="3" Filename="Flowcells/3072MAAXX/080722_3072MAAXX_s3_A549_Rep2_GR_Dex_ChIP_SL304.align_25.hg18.txt" Count=" 3036609" Date="080722" />
+<Track Flowcell="3072MAAXX" Lane="4" Filename="Flowcells/3072MAAXX/080722_3072MAAXX_s4_A549_Rep2_GR_Dex_ChIP_SL304.align_25.hg18.txt" Count=" 2826138" Date="080722" />
+<Track Flowcell="30DC1AAXX" Lane="1" Filename="Flowcells/30DC1AAXX/080826_30DC1AAXX_s1_A549_Rep2_GR_Dex_ChIP_SL304.align_25.hg18.txt" Count=" 2155855" Date="080826" />
+<Track Flowcell="FC20AUG" Lane="5" Filename="Flowcells/FC20AUG/080612_FC20AUG_s5_A549_Rep2_GR_Dex_ChIP_SL304.align_25.hg18.txt" Count=" 2357287" Date="080612" />
+</Library>
+<Library Name="SL305">
+<Track Flowcell="30DAGAAXX" Lane="7" Filename="Flowcells/30DAGAAXX/080826_30DAGAAXX_s7_A549_Rep2_GR_EtOH_ChIP_SL305.align_25.hg18.txt" Count=" 3119566" Date="080826" />
+<Track Flowcell="30DC1AAXX" Lane="2" Filename="Flowcells/30DC1AAXX/080826_30DC1AAXX_s2_A549_Rep2_GR_EtOH_ChIP_SL305.align_25.hg18.txt" Count=" 2437734" Date="080826" />
+<Track Flowcell="30DC1AAXX" Lane="3" Filename="Flowcells/30DC1AAXX/080826_30DC1AAXX_s3_A549_Rep2_GR_EtOH_ChIP_SL305.align_25.hg18.txt" Count=" 2429901" Date="080826" />
+<Track Flowcell="FC20AUG" Lane="6" Filename="Flowcells/FC20AUG/080612_FC20AUG_s6_A549_Rep2_GR_EtOH_ChIP_SL305.align_25.hg18.txt" Count=" 2182536" Date="080612" />
+</Library>
+<Library Name="SL306">
+<Track Flowcell="3072MAAXX" Lane="5" Filename="Flowcells/3072MAAXX/080722_3072MAAXX_s5_A549_Rep2_Pol2_Dex_ChIP_SL306.align_25.hg18.txt" Count=" 3787015" Date="080722" />
+<Track Flowcell="3072MAAXX" Lane="6" Filename="Flowcells/3072MAAXX/080722_3072MAAXX_s6_A549_Rep2_Pol2_Dex_ChIP_SL306.align_25.hg18.txt" Count=" 3262311" Date="080722" />
+<Track Flowcell="3072MAAXX" Lane="7" Filename="Flowcells/3072MAAXX/080722_3072MAAXX_s7_A549_Rep2_Pol2_Dex_ChIP_SL306.align_25.hg18.txt" Count=" 2664712" Date="080722" />
+<Track Flowcell="3072MAAXX" Lane="8" Filename="Flowcells/3072MAAXX/080722_3072MAAXX_s8_A549_Rep2_Pol2_Dex_ChIP_SL306.align_25.hg18.txt" Count=" 2082693" Date="080722" />
+<Track Flowcell="FC20AUG" Lane="7" Filename="Flowcells/FC20AUG/080612_FC20AUG_s7_A549_Rep2_Pol2_Dex_ChIP_SL306.align_25.hg18.txt" Count=" 4246636" Date="080612" />
+</Library>
+<Library Name="SL307">
+<Track Flowcell="3072MAAXX" Lane="1" Filename="Flowcells/3072MAAXX/080722_3072MAAXX_s1_A549_EtOH_RX_noIP_SL307.align_25.hg18.txt" Count=" 3727098" Date="080722" />
+<Track Flowcell="3072MAAXX" Lane="2" Filename="Flowcells/3072MAAXX/080722_3072MAAXX_s2_A549_EtOH_RX_noIP_SL307.align_25.hg18.txt" Count=" 4398655" Date="080722" />
+<Track Flowcell="FC20AUG" Lane="8" Filename="Flowcells/FC20AUG/080612_FC20AUG_s8_A549_EtOH_RX_noIP_SL307.align_25.hg18.txt" Count=" 3738550" Date="080612" />
+</Library>
+<Library Name="SL310">
+<Track Flowcell="FC20ATH" Lane="6" Filename="Flowcells/FC20ATH/080602_FC20ATH_s6_RNA-seq_GM12878_Rep2_SL310.align_25.hg18.txt" Count=" 3087245" Date="080602" />
+</Library>
+<Library Name="SL311">
+<Track Flowcell="FC20AUM" Lane="1" Filename="Flowcells/FC20AUM/080616_FC20AUM_s1_RNASeq_BJ_rep1_SL311.align_25.hg18.txt" Count=" 3688931" Date="080616" />
+</Library>
+<Library Name="SL312">
+<Track Flowcell="30EG4AAXX" Lane="8" Filename="Flowcells/30EG4AAXX/080916_30EG4AAXX_s8_RNA-seq_HepG2_Rep1_SL312.align_25.hg18.txt" Count=" 4776370" Date="080916" />
+<Track Flowcell="FC20ATH" Lane="7" Filename="Flowcells/FC20ATH/080602_FC20ATH_s7_RNA-seq_HepG2_Rep1_SL312.align_25.hg18.txt" Count=" 3535250" Date="080602" />
+</Library>
+<Library Name="SL313">
+<Track Flowcell="FC20ATH" Lane="8" Filename="Flowcells/FC20ATH/080602_FC20ATH_s8_RNA-seq_HepG2_Rep2_SL313.align_25.hg18.txt" Count=" 3462781" Date="080602" />
+</Library>
+<Library Name="SL314">
+<Track Flowcell="FC20AUM" Lane="2" Filename="Flowcells/FC20AUM/080616_FC20AUM_s2_RNA-seq_HMEC_rep1_SL314.align_25.hg18.txt" Count=" 4056466" Date="080616" />
+</Library>
+<Library Name="SL315">
+<Track Flowcell="FC20AUM" Lane="3" Filename="Flowcells/FC20AUM/080616_FC20AUM_s3_RNA-seq_HMEC_rep2_SL315.align_25.hg18.txt" Count=" 3958633" Date="080616" />
+</Library>
+<Library Name="SL316">
+<Track Flowcell="306WKAAXX" Lane="5" Filename="Flowcells/306WKAAXX/080728_306WKAAXX_s5_RNAseq_Jurkat_rep1_SL316.align_25.hg18.txt" Count=" 2116970" Date="080728" />
+<Track Flowcell="306WKAAXX" Lane="6" Filename="Flowcells/306WKAAXX/080728_306WKAAXX_s6_RNAseq_Jurkat_rep1_SL316.align_25.hg18.txt" Count=" 3213207" Date="080728" />
+<Track Flowcell="30DAGAAXX" Lane="8" Filename="Flowcells/30DAGAAXX/080826_30DAGAAXX_s8_RNAseq_Jurkat_rep1_SL316.align_25.hg18.txt" Count=" 4938781" Date="080826" />
+<Track Flowcell="FC20AUM" Lane="4" Filename="Flowcells/FC20AUM/080616_FC20AUM_s4_RNAseq_Jurkat_rep1_SL316.align_25.hg18.txt" Count=" 3926383" Date="080616" />
+</Library>
+<Library Name="SL317">
+<Track Flowcell="306WKAAXX" Lane="7" Filename="Flowcells/306WKAAXX/080728_306WKAAXX_s7_RNAseq_Jurkat_rep2_SL317.align_25.hg18.txt" Count=" 4116201" Date="080728" />
+<Track Flowcell="306WKAAXX" Lane="8" Filename="Flowcells/306WKAAXX/080728_306WKAAXX_s8_RNAseq_Jurkat_rep2_SL317.align_25.hg18.txt" Count=" 3689888" Date="080728" />
+<Track Flowcell="FC20AUM" Lane="5" Filename="Flowcells/FC20AUM/080616_FC20AUM_s5_RNAseq_Jurkat_rep2_SL317.align_25.hg18.txt" Count=" 4104444" Date="080616" />
+</Library>
+<Library Name="SL318">
+<Track Flowcell="FC20AMC" Lane="1" Filename="Flowcells/FC20AMC/080609_FC20AMC_s1_ChIP-seq_hESC_K36_repurified_SL318.align_25.hg18.txt" Count="  971624" Date="080609" />
+</Library>
+<Library Name="SL319">
+<Track Flowcell="FC20AMC" Lane="2" Filename="Flowcells/FC20AMC/080609_FC20AMC_s2_ChIP-seq_hESC_K27_repurified_SL319.align_25.hg18.txt" Count="  365209" Date="080609" />
+</Library>
+<Library Name="SL320">
+<Track Flowcell="FC20AMC" Lane="3" Filename="Flowcells/FC20AMC/080609_FC20AMC_s3_ChIP-seq_hESC_Pol2_repurified_SL320.align_25.hg18.txt" Count="  643582" Date="080609" />
+</Library>
+<Library Name="SL321">
+<Track Flowcell="FC20AMC" Lane="4" Filename="Flowcells/FC20AMC/080609_FC20AMC_s4_Input_Control_hESC_Repurified_SL321.align_25.hg18.txt" Count=" 1384822" Date="080609" />
+</Library>
+<Library Name="SL322">
+<Track Flowcell="FC209TJ" Lane="1" Filename="Flowcells/FC209TJ/080606_FC209TJ_s1_ChIpSeq_hESCday5_K4_SL322.align_25.hg18.txt" Count="  578452" Date="080606" />
+</Library>
+<Library Name="SL323">
+<Track Flowcell="FC209TJ" Lane="2" Filename="Flowcells/FC209TJ/080606_FC209TJ_s2_Input_Control_hESCday5_SL323.align_25.hg18.txt" Count=" 1756643" Date="080606" />
+</Library>
+<Library Name="SL324">
+<Track Flowcell="FC209TJ" Lane="3" Filename="Flowcells/FC209TJ/080606_FC209TJ_s3_ChIpSeq_hESCday5_K27_SL324.align_25.hg18.txt" Count=" 1306396" Date="080606" />
+</Library>
+<Library Name="SL325">
+<Track Flowcell="FC209TJ" Lane="4" Filename="Flowcells/FC209TJ/080606_FC209TJ_s4_ChIpSeq_hESCday5_K36_SL325.align_25.hg18.txt" Count="  478866" Date="080606" />
+</Library>
+<Library Name="SL326">
+<Track Flowcell="FC20AUG" Lane="1" Filename="Flowcells/FC20AUG/080612_FC20AUG_s1_RNA-seq_BJ_Rep2_75_SL326.align_25.hg18.txt" Count=" 3188417" Date="080612" />
+</Library>
+<Library Name="SL327">
+<Track Flowcell="FC20AUG" Lane="2" Filename="Flowcells/FC20AUG/080612_FC20AUG_s2_RNA-seq_K562_Rep3_75_SL327.align_25.hg18.txt" Count=" 3717306" Date="080612" />
+</Library>
+<Library Name="SL328">
+<Track Flowcell="FC20AUG" Lane="3" Filename="Flowcells/FC20AUG/080612_FC20AUG_s3_RNA-seq_BJ_Rep2_7_5_SL328.align_25.hg18.txt" Count=" 3864656" Date="080612" />
+</Library>
+<Library Name="SL329">
+<Track Flowcell="FC20AUG" Lane="4" Filename="Flowcells/FC20AUG/080612_FC20AUG_s4_RNA-seq_K562_Rep3_7_5_SL329.align_25.hg18.txt" Count=" 3915270" Date="080612" />
+</Library>
+<Library Name="SL330">
+<Track Flowcell="303n0aaxx" Lane="1" Filename="Flowcells/303n0aaxx/080715_303n0aaxx_s1_RNA-seq_A549_Rep1_Dex_SL330.align_25.hg18.txt" Count=" 1006721" Date="080715" />
+<Track Flowcell="303n0aaxx" Lane="2" Filename="Flowcells/303n0aaxx/080715_303n0aaxx_s2_RNA-seq_A549_Rep1_Dex_SL330.align_25.hg18.txt" Count=" 1140136" Date="080715" />
+<Track Flowcell="306WKAAXX" Lane="1" Filename="Flowcells/306WKAAXX/080728_306WKAAXX_s1_RNA-seq_A549_Rep1_Dex_SL330.align_25.hg18.txt" Count=" 3860069" Date="080728" />
+<Track Flowcell="30DC1AAXX" Lane="4" Filename="Flowcells/30DC1AAXX/080826_30DC1AAXX_s4_RNA-seq_A549_Rep1_Dex_SL330.align_25.hg18.txt" Count=" 5037938" Date="080826" />
+<Track Flowcell="FC20AMC" Lane="5" Filename="Flowcells/FC20AMC/080609_FC20AMC_s5_RNA-seq_A549_Rep1_Dex_SL330.align_25.hg18.txt" Count=" 2586689" Date="080609" />
+<Track Flowcell="FC306VCAAXX" Lane="1" Filename="Flowcells/FC306VCAAXX/080725_FC306VCAAXX_s1_RNA-seq_A549_Rep1_Dex_SL330.align_25.hg18.txt" Count=" 2607495" Date="080725" />
+<Track Flowcell="FC306VCAAXX" Lane="2" Filename="Flowcells/FC306VCAAXX/080725_FC306VCAAXX_s2_RNA-seq_A549_Rep1_Dex_SL330.align_25.hg18.txt" Count=" 2538777" Date="080725" />
+</Library>
+<Library Name="SL331">
+<Track Flowcell="303n0aaxx" Lane="5" Filename="Flowcells/303n0aaxx/080715_303n0aaxx_s5_RNA-seq_A549_Rep1_EtOH_SL331.align_25.hg18.txt" Count=" 2173243" Date="080715" />
+<Track Flowcell="303n0aaxx" Lane="6" Filename="Flowcells/303n0aaxx/080715_303n0aaxx_s6_RNA-seq_A549_Rep1_EtOH_SL331.align_25.hg18.txt" Count=" 2197491" Date="080715" />
+<Track Flowcell="306WKAAXX" Lane="2" Filename="Flowcells/306WKAAXX/080728_306WKAAXX_s2_RNA-seq_A549_Rep1_EtOH_SL331.align_25.hg18.txt" Count=" 3706943" Date="080728" />
+<Track Flowcell="30DC1AAXX" Lane="5" Filename="Flowcells/30DC1AAXX/080826_30DC1AAXX_s5_RNA-seq_A549_Rep1_EtOH_SL331.align_25.hg18.txt" Count=" 5087597" Date="080826" />
+<Track Flowcell="FC20AMC" Lane="6" Filename="Flowcells/FC20AMC/080609_FC20AMC_s6_RNA-seq_A549_Rep1_EtOH_SL331.align_25.hg18.txt" Count=" 2877770" Date="080609" />
+<Track Flowcell="FC306VCAAXX" Lane="5" Filename="Flowcells/FC306VCAAXX/080725_FC306VCAAXX_s5_RNA-seq_A549_Rep1_EtOH_SL331.align_25.hg18.txt" Count=" 1159780" Date="080725" />
+<Track Flowcell="FC306VCAAXX" Lane="6" Filename="Flowcells/FC306VCAAXX/080725_FC306VCAAXX_s6_RNA-seq_A549_Rep1_EtOH_SL331.align_25.hg18.txt" Count=" 1488352" Date="080725" />
+</Library>
+<Library Name="SL332">
+<Track Flowcell="303n0aaxx" Lane="3" Filename="Flowcells/303n0aaxx/080715_303n0aaxx_s3_RNA-seq_A549_Rep2_Dex_SL332.align_25.hg18.txt" Count=" 1087155" Date="080715" />
+<Track Flowcell="303n0aaxx" Lane="4" Filename="Flowcells/303n0aaxx/080715_303n0aaxx_s4_RNA-seq_A549_Rep2_Dex_SL332.align_25.hg18.txt" Count=" 1070083" Date="080715" />
+<Track Flowcell="306WKAAXX" Lane="3" Filename="Flowcells/306WKAAXX/080728_306WKAAXX_s3_RNA-seq_A549_Rep2_Dex_SL332.align_25.hg18.txt" Count=" 4289055" Date="080728" />
+<Track Flowcell="30DC1AAXX" Lane="6" Filename="Flowcells/30DC1AAXX/080826_30DC1AAXX_s6_RNA-seq_A549_Rep2_Dex_SL332.align_25.hg18.txt" Count=" 5053382" Date="080826" />
+<Track Flowcell="30DC1AAXX" Lane="7" Filename="Flowcells/30DC1AAXX/080826_30DC1AAXX_s7_RNA-seq_A549_Rep2_Dex_SL332.align_25.hg18.txt" Count=" 4921156" Date="080826" />
+<Track Flowcell="FC20AMC" Lane="7" Filename="Flowcells/FC20AMC/080609_FC20AMC_s7_RNA-seq_A549_Rep2_Dex_SL332.align_25.hg18.txt" Count=" 2950811" Date="080609" />
+<Track Flowcell="FC306VCAAXX" Lane="3" Filename="Flowcells/FC306VCAAXX/080725_FC306VCAAXX_s3_RNA-seq_A549_Rep2_Dex_SL332.align_25.hg18.txt" Count=" 1260681" Date="080725" />
+</Library>
+<Library Name="SL333">
+<Track Flowcell="303n0aaxx" Lane="7" Filename="Flowcells/303n0aaxx/080715_303n0aaxx_s7_RNA-seq_A549_Rep2_EtOH_SL333.align_25.hg18.txt" Count=" 1479883" Date="080715" />
+<Track Flowcell="303n0aaxx" Lane="8" Filename="Flowcells/303n0aaxx/080715_303n0aaxx_s8_RNA-seq_A549_Rep2_EtOH_SL333.align_25.hg18.txt" Count=" 1429681" Date="080715" />
+<Track Flowcell="306WKAAXX" Lane="4" Filename="Flowcells/306WKAAXX/080728_306WKAAXX_s4_RNA-seq_A549_Rep2_EtOH_SL333.align_25.hg18.txt" Count=" 3317843" Date="080728" />
+<Track Flowcell="30DC1AAXX" Lane="8" Filename="Flowcells/30DC1AAXX/080826_30DC1AAXX_s8_RNA-seq_A549_Rep2_EtOH_SL333.align_25.hg18.txt" Count=" 4215546" Date="080826" />
+<Track Flowcell="FC20AMC" Lane="8" Filename="Flowcells/FC20AMC/080609_FC20AMC_s8_RNA-seq_A549_Rep2_EtOH_SL333.align_25.hg18.txt" Count=" 2863677" Date="080609" />
+<Track Flowcell="FC306VCAAXX" Lane="7" Filename="Flowcells/FC306VCAAXX/080725_FC306VCAAXX_s7_RNA-seq_A549_Rep2_EtOH_SL333.align_25.hg18.txt" Count=" 3146212" Date="080725" />
+<Track Flowcell="FC306VCAAXX" Lane="8" Filename="Flowcells/FC306VCAAXX/080725_FC306VCAAXX_s8_RNA-seq_A549_Rep2_EtOH_SL333.align_25.hg18.txt" Count=" 2107413" Date="080725" />
+</Library>
+<Library Name="SL334">
+<Track Flowcell="303y5aaxx" Lane="5" Filename="Flowcells/303y5aaxx/080715_303y5aaxx_s5_Pol2_EtOH_chIP_Rep1_SL334.align_25.hg18.txt" Count=" 4221253" Date="080715" />
+<Track Flowcell="303y5aaxx" Lane="6" Filename="Flowcells/303y5aaxx/080715_303y5aaxx_s6_Pol2_EtOH_chIP_Rep1_SL334.align_25.hg18.txt" Count=" 4389966" Date="080715" />
+<Track Flowcell="30DAGAAXX" Lane="5" Filename="Flowcells/30DAGAAXX/080826_30DAGAAXX_s5_A549_Pol2_EtOH_chIP_Rep2_SL334.align_25.hg18.txt" Count=" 6048948" Date="080826" />
+</Library>
+<Library Name="SL335">
+<Track Flowcell="303y5aaxx" Lane="7" Filename="Flowcells/303y5aaxx/080715_303y5aaxx_s7_Input_RXLCh_+Dex_SL335.align_25.hg18.txt" Count=" 4320528" Date="080715" />
+<Track Flowcell="303y5aaxx" Lane="8" Filename="Flowcells/303y5aaxx/080715_303y5aaxx_s8_Input_RXLCh_+Dex_SL335.align_25.hg18.txt" Count=" 3212232" Date="080715" />
+<Track Flowcell="30DAGAAXX" Lane="6" Filename="Flowcells/30DAGAAXX/080826_30DAGAAXX_s6_Input_RXLCh_Dex_SL335.align_25.hg18.txt" Count=" 5399341" Date="080826" />
+</Library>
+<Library Name="SL336">
+<Track Flowcell="303PNAXX" Lane="1" Filename="Flowcells/303PNAXX/080718_303PNAXX_s1_Input_Control_RXLCh_Neuro2A_Rep1_SL336.align_25.mm9.txt" Count=" 4919007" Date="080718" />
+</Library>
+<Library Name="SL337">
+<Track Flowcell="303PNAXX" Lane="2" Filename="Flowcells/303PNAXX/080718_303PNAXX_s2_Input_Control_RXLCh_CAD_Rep1_SL337.align_25.mm9.txt" Count=" 5960847" Date="080718" />
+</Library>
+<Library Name="SL338">
+<Track Flowcell="303PNAXX" Lane="3" Filename="Flowcells/303PNAXX/080718_303PNAXX_s3_Neuro2A_CTCF_Rep1_SL338.align_25.mm9.txt" Count=" 5871158" Date="080718" />
+</Library>
+<Library Name="SL339">
+<Track Flowcell="303PNAXX" Lane="4" Filename="Flowcells/303PNAXX/080718_303PNAXX_s4_CAD_CTCF_Rep1_SL339.align_25.mm9.txt" Count=" 5226675" Date="080718" />
+</Library>
+<Library Name="SL340">
+<Track Flowcell="303PNAXX" Lane="5" Filename="Flowcells/303PNAXX/080718_303PNAXX_s5_Neuro2A_USF1_Rep1_SL340.align_25.mm9.txt" Count=" 5113687" Date="080718" />
+</Library>
+<Library Name="SL341">
+<Track Flowcell="303PNAXX" Lane="6" Filename="Flowcells/303PNAXX/080718_303PNAXX_s6_CAD_USF1_Rep1_SL341.align_25.mm9.txt" Count=" 5580547" Date="080718" />
+</Library>
+<Library Name="SL342">
+<Track Flowcell="303PNAXX" Lane="7" Filename="Flowcells/303PNAXX/080718_303PNAXX_s7_Neuro2A_USF2_Rep1_SL342.align_25.mm9.txt" Count=" 4845266" Date="080718" />
+</Library>
+<Library Name="SL343">
+<Track Flowcell="303PNAXX" Lane="8" Filename="Flowcells/303PNAXX/080718_303PNAXX_s8_CAD_USF2_Rep1_SL343.align_25.mm9.txt" Count=" 4722617" Date="080718" />
+</Library>
+<Library Name="SL344">
+<Track Flowcell="FC20AUL" Lane="5" Filename="Flowcells/FC20AUL/080621_FC20AUL_s5_Input_Control_HMEC_rep1_SL344.align_25.hg18.txt" Count=" 1854345" Date="080621" />
+<Track Flowcell="FC20AUL" Lane="6" Filename="Flowcells/FC20AUL/080621_FC20AUL_s6_Input_Control_HMEC_rep1_SL344.align_25.hg18.txt" Count=" 2139393" Date="080621" />
+</Library>
+<Library Name="SL345">
+<Track Flowcell="FC20AUL" Lane="7" Filename="Flowcells/FC20AUL/080621_FC20AUL_s7_Input_Control_HMEC_rep2_SL345.align_25.hg18.txt" Count=" 1981441" Date="080621" />
+<Track Flowcell="FC20AUL" Lane="8" Filename="Flowcells/FC20AUL/080621_FC20AUL_s8_Input_Control_HMEC_rep2_SL345.align_25.hg18.txt" Count=" 2279789" Date="080621" />
+</Library>
+<Library Name="SL346">
+<Track Flowcell="306WPAAXX" Lane="7" Filename="Flowcells/306WPAAXX/080731_306WPAAXX_s7_ChIP-seq_HepG2_Rep1_SRF_SL346.align_25.hg18.txt" Count=" 2030668" Date="080731" />
+<Track Flowcell="30EFCAAXX" Lane="6" Filename="Flowcells/30EFCAAXX/080910_30EFCAAXX_s6_ChIP-seq_HepG2_Rep1_SRF_SL346.align_25.hg18.txt" Count=" 1131395" Date="080910" />
+<Track Flowcell="30EH5AAXX" Lane="3" Filename="Flowcells/30EH5AAXX/080910_30EH5AAXX_s3_ChIP-seq_HepG2_Rep1_SRF_SL346.align_25.hg18.txt" Count="  656597" Date="080910" />
+</Library>
+<Library Name="SL347">
+<Track Flowcell="306WPAAXX" Lane="8" Filename="Flowcells/306WPAAXX/080731_306WPAAXX_s8_ChIP-seq_HepG2_Rep2_SRF_SL347.align_25.hg18.txt" Count=" 1645829" Date="080731" />
+</Library>
+<Library Name="SL348">
+<Track Flowcell="306WPAAXX" Lane="5" Filename="Flowcells/306WPAAXX/080731_306WPAAXX_s5_ChIP-seq_HepG2_Rep1_RNA_Pol2_SL348.align_25.hg18.txt" Count=" 3221651" Date="080731" />
+</Library>
+<Library Name="SL349">
+<Track Flowcell="306WPAAXX" Lane="6" Filename="Flowcells/306WPAAXX/080731_306WPAAXX_s6_ChIP-seq_HepG2_Rep2_RNA_Pol2_SL349.align_25.hg18.txt" Count=" 3150928" Date="080731" />
+</Library>
+<Library Name="SL350">
+<Track Flowcell="FC20AUL" Lane="1" Filename="Flowcells/FC20AUL/080621_FC20AUL_s1_ChIPSeq_HMEC_Rep1_RNApol2_SL350.align_25.hg18.txt" Count=" 1211303" Date="080621" />
+<Track Flowcell="FC20AUL" Lane="2" Filename="Flowcells/FC20AUL/080621_FC20AUL_s2_ChIPSeq_HMEC_Rep1_RNApol2_SL350.align_25.hg18.txt" Count=" 1646993" Date="080621" />
+</Library>
+<Library Name="SL351">
+<Track Flowcell="FC20AUL" Lane="3" Filename="Flowcells/FC20AUL/080621_FC20AUL_s3_chIPSeq_HMEC_Rep2_RNApol2_SL351.align_25.hg18.txt" Count=" 1810613" Date="080621" />
+<Track Flowcell="FC20AUL" Lane="4" Filename="Flowcells/FC20AUL/080621_FC20AUL_s4_chIPSeq_HMEC_Rep1_RNApol2_SL351.align_25.hg18.txt" Count=" 1866673" Date="080621" />
+</Library>
+<Library Name="SL352">
+<Track Flowcell="30CLAAXX" Lane="1" Filename="Flowcells/30CLAAXX/080815_30CLAAXX_s1_RNAseq_mouse_skin_gb1_SL352.align_25.mm9.txt" Count=" 3921939" Date="080815" />
+<Track Flowcell="30CLAAXX" Lane="1" Filename="Flowcells/DATA_FROM_HA/080815_30CLAAXX_s1_RNAseq_mouse_skin_gb1_SL352.align_25.mm9.txt" Count=" 1224253" Date="080815" />
+</Library>
+<Library Name="SL353">
+<Track Flowcell="30CLAAXX" Lane="2" Filename="Flowcells/30CLAAXX/080815_30CLAAXX_s2_RNAseq_mouse_skin_gb2_SL353.align_25.mm9.txt" Count=" 4617024" Date="080815" />
+</Library>
+<Library Name="SL354">
+<Track Flowcell="30CLAAXX" Lane="3" Filename="Flowcells/30CLAAXX/080815_30CLAAXX_s3_RNAseq_mouse_skin_gb3_SL354.align_25.mm9.txt" Count=" 4018744" Date="080815" />
+</Library>
+<Library Name="SL355">
+<Track Flowcell="30CLAAXX" Lane="4" Filename="Flowcells/30CLAAXX/080815_30CLAAXX_s4_RNAseq_mouse_skin_gb4_SL355.align_25.mm9.txt" Count=" 3719245" Date="080815" />
+</Library>
+<Library Name="SL356">
+<Track Flowcell="30CLAAXX" Lane="5" Filename="Flowcells/30CLAAXX/080815_30CLAAXX_s5_Input_Control_ChIP-seq_protocadherin_tagged_SL356.align_25.hg18.txt" Count=" 6608872" Date="080815" />
+</Library>
+<Library Name="SL357">
+<Track Flowcell="30CLAAXX" Lane="6" Filename="Flowcells/30CLAAXX/080815_30CLAAXX_s6_ChIP-seq_protocadherin_alpha6_tagged_SL357.align_25.hg18.txt" Count=" 6057062" Date="080815" />
+</Library>
+<Library Name="SL358">
+<Track Flowcell="306WDAAXX" Lane="1" Filename="Flowcells/306WDAAXX/080725_306WDAAXX_s1_ChIP-seq_HepG2_Rep1_TAF250_SL358.align_25.hg18.txt" Count=" 3950828" Date="080725" />
+<Track Flowcell="306WDAAXX" Lane="2" Filename="Flowcells/306WDAAXX/080725_306WDAAXX_s2_ChIP-seq_HepG2_Rep1_TAF250_SL358.align_25.hg18.txt" Count=" 4064007" Date="080725" />
+<Track Flowcell="306WPAAXX" Lane="3" Filename="Flowcells/306WPAAXX/080731_306WPAAXX_s3_ChIP-seq_HepG2_Rep1_TAF250_SL358.align_25.hg18.txt" Count=" 3307317" Date="080731" />
+</Library>
+<Library Name="SL359">
+<Track Flowcell="306WDAAXX" Lane="3" Filename="Flowcells/306WDAAXX/080725_306WDAAXX_s3_ChIP-seq_HepG2_Rep2_TAF250_SL359.align_25.hg18.txt" Count=" 2721181" Date="080725" />
+<Track Flowcell="306WDAAXX" Lane="4" Filename="Flowcells/306WDAAXX/080725_306WDAAXX_s4_ChIP-seq_HepG2_Rep2_TAF250_SL359.align_25.hg18.txt" Count=" 3705820" Date="080725" />
+<Track Flowcell="306WPAAXX" Lane="4" Filename="Flowcells/306WPAAXX/080731_306WPAAXX_s4_ChIP-seq_HepG2_Rep2_TAF250_SL359.align_25.hg18.txt" Count=" 3232318" Date="080731" />
+<Track Flowcell="30EG4AAXX" Lane="7" Filename="Flowcells/30EG4AAXX/080916_30EG4AAXX_s7_ChIP-seq_HepG2_Rep2_TAF250_SL359.align_25.hg18.txt" Count=" 4307991" Date="080916" />
+</Library>
+<Library Name="SL360">
+<Track Flowcell="306WDAAXX" Lane="5" Filename="Flowcells/306WDAAXX/080725_306WDAAXX_s5_ChIP-seq_BJ_Fibroblast_Rep1_RNAPol2_SL360.align_25.hg18.txt" Count=" 3104910" Date="080725" />
+</Library>
+<Library Name="SL361">
+<Track Flowcell="306WDAAXX" Lane="6" Filename="Flowcells/306WDAAXX/080725_306WDAAXX_s6_ChIP-seq_BJ_Fibroblast_Rep2_RNAPol2_SL361.align_25.hg18.txt" Count=" 2279682" Date="080725" />
+</Library>
+<Library Name="SL362">
+<Track Flowcell="306WDAAXX" Lane="7" Filename="Flowcells/306WDAAXX/080725_306WDAAXX_s7_Input_Control_BJ_Fibroblast_Rep1_SL362.align_25.hg18.txt" Count=" 1305197" Date="080725" />
+</Library>
+<Library Name="SL363">
+<Track Flowcell="306WDAAXX" Lane="8" Filename="Flowcells/306WDAAXX/080725_306WDAAXX_s8_Input_Control_BJ_Fibroblast_Rep2_SL363.align_25.hg18.txt" Count=" 1981571" Date="080725" />
+</Library>
+<Library Name="SL368">
+<Track Flowcell="30DRRAAXX" Lane="8" Filename="Flowcells/30DRRAAXX/080919_30DRRAAXX_s8_Input_Control_RXLCh_Neuro2A_Rep2_SL368.align_25.mm9.txt" Count=" 2783931" Date="080919" />
+</Library>
+<Library Name="SL369">
+<Track Flowcell="30DRRAAXX" Lane="7" Filename="Flowcells/30DRRAAXX/080919_30DRRAAXX_s7_Input_Control_RXLCh_CAD_Rep2_SL369.align_25.mm9.txt" Count=" 2520025" Date="080919" />
+</Library>
+<Library Name="SL370">
+<Track Flowcell="30DRRAAXX" Lane="6" Filename="Flowcells/30DRRAAXX/080919_30DRRAAXX_s6_Neuro2A_CTCF_Rep2_SL370.align_25.mm9.txt" Count=" 3046099" Date="080919" />
+</Library>
+<Library Name="SL371">
+<Track Flowcell="30DRRAAXX" Lane="5" Filename="Flowcells/30DRRAAXX/080919_30DRRAAXX_s5_CAD_CTCF_Rep2_SL371.align_25.mm9.txt" Count=" 3466060" Date="080919" />
+</Library>
+<Library Name="SL372">
+<Track Flowcell="30DRRAAXX" Lane="4" Filename="Flowcells/30DRRAAXX/080919_30DRRAAXX_s4_Neuro2A_USF1_Rep2_SL372.align_25.mm9.txt" Count=" 2857481" Date="080919" />
+</Library>
+<Library Name="SL373">
+<Track Flowcell="30DRRAAXX" Lane="3" Filename="Flowcells/30DRRAAXX/080919_30DRRAAXX_s3_CAD_USF1_Rep2_SL373.align_25.mm9.txt" Count=" 3741192" Date="080919" />
+</Library>
+<Library Name="SL374">
+<Track Flowcell="30DRRAAXX" Lane="2" Filename="Flowcells/30DRRAAXX/080919_30DRRAAXX_s2_Neuro2A_USF2_Rep2_SL374.align_25.mm9.txt" Count=" 4160807" Date="080919" />
+</Library>
+<Library Name="SL375">
+<Track Flowcell="30DRRAAXX" Lane="1" Filename="Flowcells/30DRRAAXX/080919_30DRRAAXX_s1_CAD_USF2_Rep2_SL375.align_25.mm9.txt" Count=" 2005344" Date="080919" />
+</Library>
+<Library Name="SL376">
+<Track Flowcell="30CUNAAXX" Lane="1" Filename="Flowcells/30CUNAAXX/080918_30CUNAAXX_s1_A549_P65_Dex_Rep2_SL376.align_25.hg18.txt" Count="  506090" Date="080918" />
+</Library>
+<Library Name="SL377">
+<Track Flowcell="30CUNAAXX" Lane="2" Filename="Flowcells/30CUNAAXX/080918_30CUNAAXX_s2_A549_P65_EtOH_Rep2_SL377.align_25.hg18.txt" Count=" 1618541" Date="080918" />
+</Library>
+<Library Name="SL378">
+<Track Flowcell="30CUNAAXX" Lane="3" Filename="Flowcells/30CUNAAXX/080918_30CUNAAXX_s3_A549_Pol2Ser2_Dex_Rep2_SL378.align_25.hg18.txt" Count=" 2773966" Date="080918" />
+<Track Flowcell="30CUNAAXX" Lane="7" Filename="Flowcells/30CUNAAXX/080918_30CUNAAXX_s7_A549_Pol2Ser2_Dex_Rep2_SL378.align_25.hg18.txt" Count=" 3050258" Date="080918" />
+</Library>
+<Library Name="SL379">
+<Track Flowcell="30CUNAAXX" Lane="4" Filename="Flowcells/30CUNAAXX/080918_30CUNAAXX_s4_A549_Pol2Ser2_EtOH_Rep2_SL379.align_25.hg18.txt" Count=" 1315068" Date="080918" />
+<Track Flowcell="30CUNAAXX" Lane="8" Filename="Flowcells/30CUNAAXX/080918_30CUNAAXX_s8_A549_Pol2Ser2_EtOH_Rep2_SL379.align_25.hg18.txt" Count=" 2957711" Date="080918" />
+</Library>
+<Library Name="SL388">
+<Track Flowcell="30EF9AAXX" Lane="8" Filename="Flowcells/30EF9AAXX/080916_30EF9AAXX_s8_RNAseq_mtN_mtG_7d_SL388.align_25.mm9.txt" Count=" 4555457" Date="080916" />
+</Library>
+<Library Name="SL389">
+<Track Flowcell="30EF9AAXX" Lane="7" Filename="Flowcells/30EF9AAXX/080916_30EF9AAXX_s7_RNAseq_mtN_WTG_7d_SL389.align_25.mm9.txt" Count="    2247" Date="080916" />
+</Library>
+<Library Name="SL390">
+<Track Flowcell="30EF9AAXX" Lane="6" Filename="Flowcells/30EF9AAXX/080916_30EF9AAXX_s6_RNAseq_WTN_mtG_7d_SL390.align_25.mm9.txt" Count=" 5472114" Date="080916" />
+</Library>
+<Library Name="SL391">
+<Track Flowcell="30EF9AAXX" Lane="5" Filename="Flowcells/30EF9AAXX/080916_30EF9AAXX_s5_RNAseq_WTN_WTG_7d_SL391.align_25.mm9.txt" Count=" 6010993" Date="080916" />
+</Library>
+<Library Name="SL392">
+<Track Flowcell="30EF9AAXX" Lane="4" Filename="Flowcells/30EF9AAXX/080916_30EF9AAXX_s4_RNAseq_mtG_mtN_7d_SL392.align_25.mm9.txt" Count=" 5573656" Date="080916" />
+</Library>
+<Library Name="SL393">
+<Track Flowcell="30EF9AAXX" Lane="3" Filename="Flowcells/30EF9AAXX/080916_30EF9AAXX_s3_RNAseq_WTG_mtN_7d_SL393.align_25.mm9.txt" Count=" 5993678" Date="080916" />
+</Library>
+<Library Name="SL394">
+<Track Flowcell="30EF9AAXX" Lane="2" Filename="Flowcells/30EF9AAXX/080916_30EF9AAXX_s2_RNAseq_mtG_WTN_7d_SL394.align_25.mm9.txt" Count=" 5988007" Date="080916" />
+</Library>
+<Library Name="SL395">
+<Track Flowcell="30EF9AAXX" Lane="1" Filename="Flowcells/30EF9AAXX/080916_30EF9AAXX_s1_RNAseq_WTG_WTN_7d_SL395.align_25.mm9.txt" Count=" 5716492" Date="080916" />
+</Library>
+<Library Name="SL396">
+<Track Flowcell="30EG4AAXX" Lane="6" Filename="Flowcells/30EG4AAXX/080916_30EG4AAXX_s6_RNAseq_4484-17_occipital_cortex_SL396.align_25.hg18.txt" Count=" 5908254" Date="080916" />
+</Library>
+<Library Name="SL397">
+<Track Flowcell="30EG4AAXX" Lane="5" Filename="Flowcells/30EG4AAXX/080916_30EG4AAXX_s5_RNAseq_4484-6_basal_ganglia_SL397.align_25.hg18.txt" Count=" 5949974" Date="080916" />
+</Library>
+<Library Name="SL398">
+<Track Flowcell="30CUNAAXX" Lane="5" Filename="Flowcells/30CUNAAXX/080918_30CUNAAXX_s5_A549_Pol2Ser2_Dex_Rep3_SL398.align_25.hg18.txt" Count=" 1352310" Date="080918" />
+<Track Flowcell="30EH5AAXX" Lane="1" Filename="Flowcells/30EH5AAXX/080910_30EH5AAXX_s1_A549_Pol2Ser2_Dex_Rep3_SL398.align_25.hg18.txt" Count=" 2249460" Date="080910" />
+</Library>
+<Library Name="SL399">
+<Track Flowcell="30CUNAAXX" Lane="6" Filename="Flowcells/30CUNAAXX/080918_30CUNAAXX_s6_A549_Pol2Ser2_EtOH_Rep3_SL399.align_25.hg18.txt" Count=" 1475564" Date="080918" />
+<Track Flowcell="30EH5AAXX" Lane="2" Filename="Flowcells/30EH5AAXX/080910_30EH5AAXX_s2_A549_Pol2Ser2_EtOH_Rep3_SL399.align_25.hg18.txt" Count=" 2020061" Date="080910" />
+</Library>
+<Library Name="SL415">
+<Track Flowcell="30EH5AAXX" Lane="4" Filename="Flowcells/30EH5AAXX/080910_30EH5AAXX_s4_A549_ChIP_NFkB_Dex_R3_SL415.align_25.hg18.txt" Count=" 1605808" Date="080910" />
+</Library>
+<Library Name="SL419">
+<Track Flowcell="30EG4AAXX" Lane="1" Filename="Flowcells/30EG4AAXX/080916_30EG4AAXX_s1_GM12878_Rep1_NFATc1_SL419.align_25.hg18.txt" Count=" 3203524" Date="080916" />
+</Library>
+<Library Name="SL420">
+<Track Flowcell="30EG4AAXX" Lane="2" Filename="Flowcells/30EG4AAXX/080916_30EG4AAXX_s2_K562_Rep1_NFATc1_SL420.align_25.hg18.txt" Count=" 4198383" Date="080916" />
+</Library>
+<Library Name="SL421">
+<Track Flowcell="30EG4AAXX" Lane="3" Filename="Flowcells/30EG4AAXX/080916_30EG4AAXX_s3_GM12878_Dex_Rep1_GR_ChIP_SL421.align_25.hg18.txt" Count=" 5259681" Date="080916" />
+</Library>
+<Library Name="SL422">
+<Track Flowcell="30EG4AAXX" Lane="4" Filename="Flowcells/30EG4AAXX/080916_30EG4AAXX_s4_GM12878_EtOH_Rep1_GR_ChIP_SL422.align_25.hg18.txt" Count=" 3451771" Date="080916" />
+</Library>
+</Libraries>
diff --git a/trunk/htsworkflow/frontend/reports/__init__.py b/trunk/htsworkflow/frontend/reports/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/trunk/htsworkflow/frontend/reports/admin.py b/trunk/htsworkflow/frontend/reports/admin.py
new file mode 100644 (file)
index 0000000..b5a0eb2
--- /dev/null
@@ -0,0 +1,10 @@
+from htsworkflow.frontend.reports.models import ProgressReport
+from django.contrib import admin
+from django.utils.translation import ugettext_lazy as _
+
+class ProgressReportOptions(admin.ModelAdmin):
+  list_display = ('Study','ab_batch','cell_line','library','sequencing','aligned_reads','QPCR','submit_to_DCC','submit_to_NCBI','interactome_complete')
+  ## list_filter = ('interactome_complete')
+
+admin.site.register(ProgressReport, ProgressReportOptions)
+
diff --git a/trunk/htsworkflow/frontend/reports/libinfopar.py b/trunk/htsworkflow/frontend/reports/libinfopar.py
new file mode 100644 (file)
index 0000000..954fce6
--- /dev/null
@@ -0,0 +1,103 @@
+from htsworkflow.frontend import settings
+from django.http import HttpResponse
+from datetime import datetime
+from string import *
+import re
+from xml.sax import make_parser
+from xml.sax.handler import ContentHandler
+import urllib
+import urllib2
+import os
+
+'''
+Example library node from LibraryInfo.xml:
+<Library Name="SL14">
+<Track Flowcell="FC10135" Lane="4" Filename="071005_FC10135_s4_FoxP2_polyclonal_pfsk1_SL14.align_25.hg18.txt" Count=" 2438679" Complexity="4.51989e-06"/>
+<Track Flowcell="FC11977" Lane="6" Filename="070928_FC11977_s6_FoxP2_polyclonal_pfsk1_SL14.align_25.hg18.txt" Count=" 2007880" Complexity="0"/>
+<Track Flowcell="FC13593" Lane="5" Filename="071002_FC13593_s5_FoxP2_polyclonal_pfsk1_SL14.align_25.hg18.txt" Count=" 2533720" Complexity="1.97771e-06"/>
+</Library>
+'''
+class LibInfoHandler(ContentHandler):
+
+  def __init__ (self, searchTerm):
+    self.searchTerm= searchTerm
+    self.currlibid = ''
+    self.LanesCount, self.ReadsCount = 0, 0
+    self.Msg = 'OK'
+       
+  def startElement(self, name, attrs):
+    try:
+      if name == 'Library':     
+        self.currlibid = attrs.get('Name',"")      
+      elif name == 'Track' and self.searchTerm == self.currlibid:
+        self.LanesCount += len(attrs.get('Lane',""))
+        self.ReadsCount += int(attrs.get('Count',""))
+      #else:
+      #  self.Msg += ' | name = '+name+', currlibid = '+ self.currlibid
+    except: 
+      self.Msg = 'failed parsing xml file'
+    return
+
+  #def characters (self, ch):
+    # return ..
+
+  #def endElement(self, name):
+    # return ..
+
+
+## TO DO: Change this to read the LibraryInfo.xml only ONCE per ReoprtRequest (do it in the models.py). + Read it directly from the analysis_server
+
+def getLibReads(libid):
+  searchTerm= libid
+  parser = make_parser()   
+  curHandler = LibInfoHandler(searchTerm)
+  parser.setContentHandler(curHandler)
+  reports_dir = os.path.split(__file__)[0]
+  library_info = os.path.join(reports_dir, 'LibraryInfo.xml')
+  parser.parse(open(library_info))
+  arRes = []
+  arRes.append(curHandler.LanesCount) 
+  arRes.append(curHandler.ReadsCount)
+  arRes.append(curHandler.Msg)
+
+  return arRes
+
+def getWebPage(url,params):
+  pdata = urllib.urlencode(params)
+  req = urllib2.Request(url,pdata)
+  wpage = urllib2.urlopen(req)
+  restext = wpage.read()
+  wpage.close()
+  return restext
+
+def refreshLibInfoFile(request): 
+ varStatus = 'getting conf file from exp trac server'
+ url = settings.TASKS_PROJS_SERVER+'/LibraryInfo.xml'
+ params = {}
+ readw = getWebPage(url,params)
+ # make sure file content starts as xml
+ match_str = re.compile('^<\?xml.+')
+ if match_str.search(readw): ##tempstr):
+   # Rename current file with timestamp
+   year = datetime.today().year.__str__()
+   year = replace(year,'20','')
+   month = datetime.today().month
+   if month < 10: month = "0"+month.__str__()
+   else: month = month.__str__()
+   day = datetime.today().day
+   if day < 10: day = "0"+day.__str__()
+   else: day = day.__str__()
+   mydate = year+month+day
+   folder_loc = '/htsworkflow/htswfrontend/htswfrontend'  # DEV                                                                                                                          
+   #folder_loc = '/Library/WebServer/gaworkflow/gaworkflow/frontend'  # PROD
+   folder = folder_loc+'/htsw_reports/LibInfo/'
+   os.rename(folder+'LibraryInfo.xml',folder+mydate+'_LibraryInfo.xml')
+   # create file in curret folder
+   file_path = os.path.join(folder,'LibraryInfo.xml')
+   f = open(file_path, 'w')
+   f.write(readw)
+   f.close()
+   varStatus = 'OK. LibraryInfo.xml refreshed at Web server.'
+ else:
+   varStatus = 'Failed reading valid LibraryInfo.xml server reply:\n'+readw
+ return HttpResponse(varStatus)  
diff --git a/trunk/htsworkflow/frontend/reports/models.py b/trunk/htsworkflow/frontend/reports/models.py
new file mode 100644 (file)
index 0000000..b898537
--- /dev/null
@@ -0,0 +1,238 @@
+from django.db import models
+from django.db.models import Q
+from django.core.exceptions import ObjectDoesNotExist
+from datetime import datetime
+from htsworkflow.frontend.samples.models import * 
+from htsworkflow.frontend.analysis.models import *
+from htsworkflow.frontend.experiments.models import *
+from string import *
+from htsworkflow.frontend.reports.utils import *
+import re
+##from p1 import LibInfo
+from libinfopar import *
+
+## This is a table based REPORT generator. The goal is to display a Progress Report for all the ENCODE projects, based on Study Name (e.g. NRSF, FOXP2, Methy-Seq on .. etc).
+  
+class ProgressReport(models.Model):
+  st_sbj = models.ForeignKey(Project,limit_choices_to = Q(project_name__startswith='ENCODE '),related_name='project',db_index=True,verbose_name="Studied Subject")
+  interactome_complete = models.BooleanField(default=False)
+
+  def Study(self):
+    str = self.st_sbj.__str__()
+    str += '<br/><br/>'
+    str += '<a title="open Project record" href="/admin/analys_track/project/'+self.st_sbj.id.__str__()+'/" target=_self style="font-size:85%">Edit Project</a>'
+    return str  
+  Study.allow_tags = True
+
+  def submit_to_DCC(self):
+    varText = ''
+    if self.note_about_DCC:
+      varText += '<br/><u>Note:</u><br/>'+self.note_about_DCC
+    return '%s<br/>%s' % (self.submitted_to_DCC,varText)
+  submit_to_DCC.allow_tags = True
+
+  def submit_to_NCBI(self):
+    varText = ''
+    if self.note_about_NCBI:
+      varText += '<br/><u>Note:</u><br/>'+self.note_about_NCBI 
+    return '%s<br/>%s' % (self.submitted_to_NCBI,varText)
+  submit_to_NCBI.allow_tags = True
+
+  ## -- Utility functions <-- This method was transfered to untils.py
+
+  ## --- LIBARAY PREPARATION SECTION 
+  def getLibIds(self):
+    ptasks = self.st_sbj.tasks.distinct()
+    arLibs = [] 
+    for t in ptasks:
+      if t.subject1 is not None:
+        arLibs.append(t.subject1.library_id)
+      if t.subject2 is not None:
+        arLibs.append(t.subject2.library_id)
+    arLibs = unique(arLibs)
+    return arLibs #.sort()
+
+  def getFCInfo(self,libid):   ## This is the haviest function 
+    arFCLanes = []
+    ##Test return arFCLanes
+    # can't get this to work: FC_L1 = FlowCell.objects.filter(lane_5_library__exact=libid)
+    allFCs = FlowCell.objects.all()
+    for f in allFCs:
+      entry = ''
+      lanes = []
+      #found = False
+#      for i in range(1,9):
+#        if eval('f.lane_'+i.__str__()+'_library.library_id==libid'):
+#          lanes.append(i.__str__())
+#          found = True
+
+# maybe a bit faster this way:
+      if f.lane_1_library.library_id==libid:
+          lanes.append('1')
+          #found = True
+      if f.lane_2_library.library_id==libid:
+          lanes.append('2')
+          #found = True
+      if f.lane_3_library.library_id==libid:
+          lanes.append('3')
+          #found = True
+      if f.lane_4_library.library_id==libid:
+          lanes.append('4')
+          #found = True
+      if f.lane_5_library.library_id==libid:
+          lanes.append('5')
+          #found = True
+      if f.lane_6_library.library_id==libid:
+          lanes.append('6')
+          #found = True
+      if f.lane_7_library.library_id==libid:
+          lanes.append('7')
+          #found = True
+      if f.lane_8_library.library_id==libid:
+          lanes.append('8')
+          #found = True
+
+
+      #if found:
+      if len(lanes)>0:
+        rundate = re.sub(pattern="\s.*$",repl="",string=f.run_date.__str__())
+        entry = '<b>'+f.flowcell_id + '</b> Lanes No.: '+','.join(lanes)+' ('+rundate+')' 
+        arFCLanes.append(entry)    
+    if len(arFCLanes)==0:
+      arFCLanes.append('<font color=red>Flowcell not found.</font>')
+    return arFCLanes
+
+  def ab_batch(self):
+    ##  To have the Company's lot number, apearing on the (source) tube, we need to add new Field in Library. 
+    arlibs = self.getLibIds()
+    tstr = '<ul>' ##<u><b>Ab</b> from '+len(arlibs).__str__()+' libs</u>: '
+    arRows = []
+    for l in arlibs:
+      try:
+        rec = Library.objects.get(library_id=l,antibody__isnull=False)
+        arRows.append('<li>'+rec.antibody.antibodies+' for <b>'+rec.antibody.antigene+'</b> (src:'+rec.antibody.source+', cat:'+rec.antibody.catalog+')</li>')
+      except ObjectDoesNotExist:
+        tstr += ""
+    tstr += "".join(unique(arRows))+'</ul>'
+    return tstr
+  ab_batch.allow_tags = True
+
+  def cell_line(self):                                                                                           
+    arlibs = self.getLibIds()
+    tstr = '<ul>'
+    arRows = []                                                                                                                                     
+    for l in arlibs:
+      try:
+        rec = Library.objects.get(library_id=l)
+        arRows.append('<li><b>'+rec.cell_line.cellline_name+'</b> ('+rec.condition.condition_name+')</li>')
+      except ObjectDoesNotExist:
+        tstr += ""                                                                                                                               
+    tstr += "".join(unique(arRows))+'</ul>'
+    return tstr
+  cell_line.allow_tags = True
+
+  def cell_harvest_batch(self): # <- data now displayed in "cell_line"
+    ## name + date  
+    arlibs = self.getLibIds()
+    tstr = '<ul>'
+    arRows = []
+    for l in arlibs:
+      try:
+        rec = Library.objects.get(library_id=l)
+        arRows.append('<li><b>'+rec.condition.condition_name+'</b></li>')
+      except ObjectDoesNotExist:
+        tstr += ""
+    tstr += "".join(unique(arRows))+'</ul>'
+    return tstr
+  cell_harvest_batch.allow_tags = True
+
+  def ChIP_made(self):
+    ## person + date                                                                                                                                                                                                             
+    return '...'
+
+  def library(self):
+    ## Lib Id + Date + Person
+    tstr = '<script>'
+    tstr += 'function togit(eid){'
+    tstr += 'f=document.getElementById(eid);'
+    tstr += 'if(f.style.display==\'none\'){'
+    tstr += 'f.style.display=\'block\';'
+    tstr += '}else{'
+    tstr += 'f.style.display=\'none\';'
+    tstr += '}'
+    tstr += '}'
+    tstr += '</script>'
+    arlibs = self.getLibIds() ##.sort()
+    arlibs = arlibs
+    tstr +='<a href=# onClick="togit(\'libInfo'+self.st_sbj.project_name+'\')">view /hide</a>'
+    tstr += '<div id="libInfo'+self.st_sbj.project_name+'" style="display:block;border:solid #cccccc 1px;width:200px;height:300px;overflow:auto"><ul>'
+    arRows = []
+    for l in arlibs:
+      try:
+        rec = Library.objects.get(library_id=l)
+        arRows.append('<li><b>'+rec.library_id+'</b>: '+rec.library_name+'.<br/>Made By: '+rec.made_by+', On: '+ rec.creation_date.__str__()+'</li>')
+      except ObjectDoesNotExist:
+        tstr += ""
+    tstr += "".join(unique(arRows))+'</ul></div>'
+    return tstr
+  library.allow_tags = True
+
+
+  ## -- SEQUENCING SECTION 
+  def sequencing(self):
+    ## FCId + Lane + Date
+    arlibs = self.getLibIds()
+    tstr ='<a href=# onClick="togit(\'seqInfo'+self.st_sbj.project_name+'\')">view /hide</a>'
+    tstr += '<div id="seqInfo'+self.st_sbj.project_name+'" style="display:block;border:solid #cccccc 1px;width:200px;height:300px;overflow:auto"><ul>'    
+    for l in arlibs:
+      tstr += '<li><b>'+l+'</b>:<br/>'+(' / '.join(self.getFCInfo(l)))+'</li>'
+    tstr += '</ul></div>'
+    return tstr
+  sequencing.allow_tags = True
+  
+  def aligned_reads(self):
+    ## Mega reads/lane                                              
+    arlibs = self.getLibIds()
+    tstr = '<a href=# onClick="togit(\'readsInfo'+self.st_sbj.project_name+'\')">view /hide</a>'
+    tstr += '<div id="readsInfo'+self.st_sbj.project_name+'" style="display:block;border:solid #cccccc 1px;width:200px;height:300px;overflow:auto">'
+    tstr += '<table><tr><td>Library Id</td><td>Total Lanes</td><td>M Reads</td></tr>'
+    LanesCnt, ReadsCnt = 0, 0
+    for l in arlibs:      
+      res = getLibReads(l)
+      LanesCnt += res[0]
+      ReadsCnt += res[1]
+      rc = "%1.2f" % (res[1]/1000000.0)
+      tstr += '<tr><td><b>'+l+'</b></td><td>'+res[0].__str__()+'</td></td><td>'+rc+'</td></tr>'
+    tstr += '</table>'
+    #tstr += '<a target=_blank href="'+settings.TASKS_PROJS_SERVER+'/projects/'+self.st_sbj.id.__str__()+'">Project results page</a>'
+    tstr += '</div>'
+    myNum = (ReadsCnt/1000000.0)
+    myNum  = "%1.2f" % (myNum) 
+    tstr += '<div>Total: <b>'+LanesCnt.__str__()+'</b> lanes and <b>'+myNum+'</b> M Reads</div>'
+    tstr += '<a target=_blank href="'+settings.TASKS_PROJS_SERVER+'/projects/'+self.st_sbj.id.__str__()+'">Project results page</a>'
+    return tstr
+  aligned_reads.allow_tags = True
+
+  def peak_calling(self):
+    # date + what etc..
+    return 'coming up ..'
+
+  QPCR = models.CharField(max_length=500,blank=True,null=True)    
+  submitted_to_DCC = models.DateTimeField(blank=True,null=True)
+  submitted_to_NCBI = models.DateTimeField(blank=True,null=True)
+  note_about_DCC =  models.TextField(blank=True)
+  note_about_NCBI = models.TextField(blank=True)
+  
+  def __str__(self):
+      return '"%s" - %s' % (self.st_sbj,self.interactome_complete)
+
+  class Meta:
+    #verbose_name_plural = "Reports"
+    ordering = ["id"]
+
+  class Admin:
+    list_display = ('Study','ab_batch','cell_line','library','sequencing','aligned_reads','QPCR','submit_to_DCC','submit_to_NCBI','interactome_complete')
+    ## list_filter = ('interactome_complete')
+    
+
+#############################################
diff --git a/trunk/htsworkflow/frontend/reports/reports.py b/trunk/htsworkflow/frontend/reports/reports.py
new file mode 100755 (executable)
index 0000000..30d9ad4
--- /dev/null
@@ -0,0 +1,308 @@
+from htsworkflow.frontend.experiments.models import *
+from django.http import HttpResponse
+from django.core.exceptions import ObjectDoesNotExist
+from django.shortcuts import render_to_response, get_object_or_404
+
+def getBgColor(reads_cnt,exp_type):
+  # Color Scheme: green is more than 12M, blue is more than 5M, orange is more than 3M and red is less. For RNAseq, all those thresholds are ~ double
+  bgcolor = '#ff3300'  # Red is the color for minimum read counts                                                                                                                            
+  rc_thr = [12000000,5000000,3000000] # Default for ChIP-Seq and Methyl-Seq
+  if exp_type == 'RNA-seq':
+    rc_thr = [20000000,10000000,6000000]
+
+  if reads_cnt > rc_thr[0]:
+    bgcolor = '#66ff66'  # Green                                                                                                                                                                                                                                               
+  else:
+    if reads_cnt > rc_thr[1]:
+      bgcolor ='#00ccff'  # Blue                                                                                                                                                                                                                                               
+    else:
+       if reads_cnt > rc_thr[2]:
+         bgcolor ='#ffcc33'  # Orange                                                                                                                                                                                                                                          
+  #tstr = '<div style="background-color:'+bgcolor+';color:black">'
+  #tstr += res[0].__str__()+' Lanes, '+rc+' M Reads'
+  #tstr += '</div>'
+
+  return bgcolor
+
+def report1(request):
+  EXP = 'ChIP-seq'
+
+  if request.GET.has_key('aflid'):
+    AFL_Id = request.GET['aflid']
+    try:
+      AFL = Affiliation.objects.get(id=AFL_Id).name
+      AFL_CNT = Affiliation.objects.get(id=AFL_Id).contact
+    except ObjectDoesNotExist:
+      return HttpResponse("ERROR: Affiliation Record Not Found for: '"+AFL_ID+"'")
+  else:
+    AFL = 'ENCODE_Tier1'
+    AFL_CNT = ''
+    try:
+      AFL_Id = Affiliation.objects.get(name=AFL,contact=AFL_CNT).id.__str__()
+    except ObjectDoesNotExist:
+      return HttpResponse("ERROR: Affiliation Record Not Found for: '"+AFL+"'")
+  
+  TFall = Library.objects.values('antibody').order_by('antibody').distinct()
+  CLLall = Library.objects.values('cell_line').order_by('cell_line').distinct()
+
+  TFs = TFall.filter(experiment_type=EXP,affiliations__name=AFL,affiliations__contact=AFL_CNT)
+  CLLs = CLLall.filter(experiment_type=EXP,affiliations__name=AFL,affiliations__contact=AFL_CNT)
+
+  # Check Replicate numbers
+  Reps = 1
+  RepRecs = Library.objects.filter(experiment_type=EXP,affiliations__name=AFL,affiliations__contact=AFL_CNT).order_by('-replicate')
+  if len(RepRecs) > 0: Reps = RepRecs[0].replicate
+  
+  ########
+  str = ''
+  str += '<span style="margin-right:20px"><a target=_self href="/admin" target=_self">Main Page</a></span>'
+  ##str += '<span style="margin-right:20px">Max Replicates: '+MaxRep.replicate.__str__()+'</span>'
+  str += '<span>Select another <b>'+EXP+'</b> Report:</span>  <select>'
+  for af in Affiliation.objects.distinct():
+    str += '<option value='+af.id.__str__()
+    if AFL_Id == af.id.__str__():
+      str += ' selected'
+    str += ' onclick="window.location=\'/reports/report?aflid='+af.id.__str__()+'\'; return false;">'+af.name+' '+af.contact+'</option>'  
+  str += '</select>'
+
+  str += '<span style="margin-left:20px;padding:1px;border:solid #cccccc 1px">color scheme: <span style="margin-left:5px;background-color:#66ff66"> > 12 M</span><span style="margin-left:5px;background-color:#00ccff"> >  5 M</span><span style="margin-left:5px;background-color:#ffcc33"> > 3 M</span><span style="margin-left:5px;background-color:#ff3300"> < 3 M</span></span>' 
+
+  str += '<span style="margin-left:20px">'
+  str += '<u>Switch to:</u> '+AFL+' '+AFL_CNT+' <a target=_self href="/reports/report_RM?exp=RNA-seq&aflid='+AFL_Id+'"><b>RNA-Seq</b> Report</a>'
+  str += '  | '
+  str += '<a target=_self href="/reports/report_RM?exp=Methyl-seq&aflid='+AFL_Id+'"><b>Methyl-Seq</b> Report</a>'
+
+  bgc = '#ffffff' 
+  pbgc = '#f7f7f7'
+  str += '<br/><br/><table border=1 cellspacing="2">'
+  str += '<tr><th style="text-align:right">PROJECT</th><th colspan='+(Reps*len(CLLs)).__str__()+' style="text-align:center">'+AFL+' '+AFL_CNT+' <span style="font-size:140%">'+EXP+'</span></th></tr>'
+  str += '<tr><th style="text-align:right">CELL LINE</th>'
+  for H in CLLs: 
+    str += '<th colspan='+Reps.__str__()+' style="text-align:center;background-color:'+bgc+'">'+Cellline.objects.get(id=H['cell_line']).cellline_name+'</th>'
+    tbgc = bgc
+    bgc = pbgc
+    pbgc = tbgc 
+  str += '</tr><tr><th style="text-align:left">TF</th>'
+  bgc = '#ffffff'
+  pbgc = '#f7f7f7'
+  for H in CLLs:
+    for r in range(1,Reps+1):
+      str += '<th style="text-align:center;background-color:'+bgc+'">Rep. '+r.__str__()+'</th>'
+    tbgc = bgc
+    bgc = pbgc
+    pbgc = tbgc
+  str += '</tr>'
+  str += '<tr><td align=right><a title="View Libraries Records" target=_self href=/admin/fctracker/library/?affiliations__id__exact='+AFL_Id+'&experiment_type__exact=INPUT_RXLCh>Total Chromatin</a></td>'
+  bgc = '#ffffff'
+  pbgc = '#f7f7f7'
+  for H in CLLs:
+    for r in range(1,Reps+1):
+      repReads = Library.objects.filter(experiment_type='INPUT_RXLCh',affiliations__name=AFL,affiliations__contact=AFL_CNT,cell_line=H['cell_line'].__str__(),replicate=r)
+      str += "<td align=center style='background-color:"+bgc+"'>"
+      if len(repReads) == 0:
+        str += 'No Libraries'
+      else:
+        cnt = 0
+        for R1 in repReads:
+          rres = R1.aligned_m_reads()
+          # Check data sanlty                                                                                                                                           
+          if rres[2] != 'OK':
+            str += '<div style="border:solid red 2px">'+rres[2]
+          else:
+            cnt = rres[1]
+            if cnt > 0:
+              str += "<div style='background-color:"+getBgColor(cnt,EXP)+";font-size:140%'>"
+              str += "%1.2f" % (cnt/1000000.0)+" M"
+            else:  str += "<div style='background-color:#ff3300;width:100%;font-size:140%'>0 Reads"
+          str += "<div style='font-size:70%'>"+R1.library_id+", "+R1.condition.nickname+"</div>"
+          str += "</div>"
+      str += '</td>'
+    tbgc = bgc
+    bgc = pbgc
+    pbgc = tbgc
+  str += '</tr>' 
+
+  for T in TFs:
+    str += '<tr>'
+    try:
+      if T['antibody']:
+        str += '<td><a title="View Libraries Records" target=_self href=/admin/fctracker/library/?affiliations__id__exact='+AFL_Id+'&antibody__id__exact='+T['antibody'].__str__()+'>'+Antibody.objects.get(id=T['antibody']).nickname+'</a></td>'
+    except Antibody.DoesNotExist:
+      str += '<td>n/a</td>'
+
+    bgc = '#ffffff'
+    pbgc = '#f7f7f7'
+    for H in CLLs:
+      for r in range(1,Reps+1):
+        repReads = Library.objects.filter(experiment_type=EXP,affiliations__name=AFL,affiliations__contact=AFL_CNT,cell_line=H['cell_line'].__str__(),antibody=T['antibody'].__str__(),replicate=r)
+        str += "<td align=center style='background-color:"+bgc+"'>"
+        if len(repReads) == 0: 
+          str += 'No Libraries'
+        else:
+          cnt = 0
+          for R1 in repReads:
+            rres = R1.aligned_m_reads()
+            # Check data sanlty
+            if rres[2] != 'OK':
+              str += '<div style="border:solid red 2px">'+rres[2]
+            else:
+              cnt = rres[1]
+              if cnt > 0:
+                str += "<div style='background-color:"+getBgColor(cnt,EXP)+";font-size:140%'>"
+                str += "%1.2f" % (cnt/1000000.0)+" M"
+              else:  str += "<div style='background-color:#ff3300;width:100%;font-size:140%'>0 Reads"
+            str += "<div style='font-size:70%'>"+R1.library_id+", "+R1.condition.nickname+"</div>"
+            str += "</div>"
+        str += '</td>'
+      tbgc = bgc
+      bgc = pbgc
+      pbgc = tbgc
+    str += '</tr>'
+  str += '</table>'
+
+  return render_to_response('reports/report.html',{'main': str})
+
+
+def report_RM(request): #for RNA-Seq and Methyl-Seq
+  EXP = 'RNA-seq'  
+
+  if request.GET.has_key('exp'):
+    EXP = request.GET['exp'] # Methyl-seq
+
+  if request.GET.has_key('aflid'):
+    AFL_Id = request.GET['aflid']
+    try:
+      AFL = Affiliation.objects.get(id=AFL_Id).name
+      AFL_CNT = Affiliation.objects.get(id=AFL_Id).contact
+    except ObjectDoesNotExist:
+      return HttpResponse("ERROR: Affiliation Record Not Found for: '"+AFL_ID+"'")
+  else:
+    AFL = 'ENCODE_Tier1'
+    AFL_CNT = ''
+    try:
+      AFL_Id = Affiliation.objects.get(name=AFL,contact=AFL_CNT).id.__str__()
+    except ObjectDoesNotExist:
+      return HttpResponse("ERROR: Affiliation Record Not Found for: '"+AFL+"'")
+
+  CLLall = Library.objects.values('cell_line').order_by('cell_line').distinct()
+  CLLs = CLLall.filter(experiment_type=EXP,affiliations__name=AFL,affiliations__contact=AFL_CNT)
+
+  ########
+  # Check Replicate numbers
+  Reps = 1
+  RepRecs = Library.objects.filter(experiment_type=EXP,affiliations__name=AFL,affiliations__contact=AFL_CNT).order_by('-replicate')
+  if len(RepRecs) > 0: Reps = RepRecs[0].replicate
+                                                                                                                                                                              
+  str = ''
+  str += '<span style="margin-right:20px"><a  target=_self href="/admin" target=_self">Main Page</a></span>'
+  str += '<span>Select another <b>'+EXP+'</b> Report:</span> <select>'
+  for af in Affiliation.objects.distinct():
+    str += '<option value='+af.id.__str__()
+    if AFL_Id == af.id.__str__():
+      str += ' selected'
+    str += ' onclick="window.location=\'/reports/report_RM?exp='+EXP+'&aflid='+af.id.__str__()+'\'; return false;">'+af.name+' '+af.contact+'</option>'
+  str += '</select>'
+
+  if EXP == 'RNA-seq':
+    str += '<span style="margin-left:20px;padding:1px;border:solid #cccccc 1px">color scheme: <span style="margin-left:5px;background-color:#66ff66"> > 20 M</span><span style="margin-left:5px;background-color:#00ccff"> >  10 M</span><span style="margin-left:5px;background-color:#ffcc33"> > 6 M</span><span style="margin-left:5px;background-color:#ff3300"> < 6 M</span></span>'
+    str += '<span style="margin-left:20px">'
+    str += '<u>Switch to:</u> '+AFL+' '+AFL_CNT+' <a target=_self href="/reports/report?exp=RNA-seq&aflid='+AFL_Id+'"><b>ChIP-Seq</b> Report</a>'
+    str += '  | '
+    str += '<a target=_self href="/reports/report_RM?exp=Methyl-seq&aflid='+AFL_Id+'"><b>Methyl-Seq</b> Report</a>'
+  else:
+    str += '<span style="margin-left:20px;padding:1px;border:solid #cccccc 1px">color scheme: <span style="margin-left:5px;background-color:#66ff66"> > 12 M</span><span style="margin-left:5px;background-color:#00ccff"> >  5 M</span><span style="margin-left:5px;background-color:#ffcc33"> > 3 M</span><span style="margin-left:5px;background-color:#ff3300"> < 3 M</span></span>'
+    str += '<span style="margin-left:20px">'
+    str += '<u>Switch to:</u> '+AFL+' '+AFL_CNT+' <a target=_self href="/reports/report?exp=RNA-seq&aflid='+AFL_Id+'"><b>ChIP-Seq</b> Report</a>'
+    str += '  | '
+    str += '<a target=_self href="/reports/report_RM?exp=RNA-seq&aflid='+AFL_Id+'"><b>RNA-Seq</b> Report</a>'
+
+  str += '<br/><br/><table border=1 cellspacing="2">'
+  str += '<tr><th colspan='+(Reps*len(CLLs)).__str__()+' style="text-align:center">'+AFL+' '+AFL_CNT+'  <span style="font-size:140%">'+EXP+'</span></th></tr>'
+  str += '<tr>'
+  bgc = '#ffffff'
+  pbgc = '#f7f7f7'
+  for H in CLLs:
+    str += '<th colspan='+Reps.__str__()+' style="text-align:center;background-color:'+bgc+'">'+Cellline.objects.get(id=H['cell_line']).cellline_name+'</th>'
+    tbgc = bgc
+    bgc = pbgc
+    pbgc = tbgc
+  str += '</tr><tr>'
+  bgc = '#ffffff'
+  pbgc = '#f7f7f7'
+  for H in CLLs:
+    for r in range(1,Reps+1):
+      str += '<th style="text-align:center;background-color:'+bgc+'">Rep. '+r.__str__()+'</th>'
+    tbgc = bgc
+    bgc = pbgc
+    pbgc = tbgc
+  str += '</tr>'
+
+  str += '<tr>' 
+  bgc = '#ffffff'
+  pbgc = '#f7f7f7'
+  for H in CLLs:
+    for r in range(1,Reps+1):
+      repReads = Library.objects.filter(experiment_type=EXP,affiliations__name=AFL,affiliations__contact=AFL_CNT,cell_line=H['cell_line'],replicate=r)                                                                                                                    
+      str += "<td align=center valign=top style='background-color:"+bgc+"'>"
+      if len(repReads) == 0:
+        str += 'No Libraries'
+      else:
+        cnt = 0
+        for R1 in repReads:
+          rres = R1.aligned_m_reads()
+          # Check data sanlty   
+          if rres[2] != 'OK':
+            str += '<div style="border:solid red 2px">'+rres[2]
+          else:
+            cnt = rres[1]
+            if cnt > 0:
+              str += "<div style='background-color:"+getBgColor(cnt,EXP)+";border:solid #cccccc 1px;font-size:140%'>"
+              str += "%1.2f" % (cnt/1000000.0)+" M"
+            else:  str += "<div style='background-color:#ff3300;border:solid #cccccc 1px;width:100%;font-size:140%'>0 Reads"
+          str += "<div style='font-size:80%'><a title='View Record' target=_self href=/admin/fctracker/library/?q="+R1.library_id+">"+R1.library_id+"</a>, "+R1.condition.nickname+", "+R1.library_species.common_name+"</div>"
+          str += "<div style='font-size:70%'>\""+R1.library_name+"\"</div"
+          str += "</div>"
+      str += '</td>'
+    tbgc = bgc
+    bgc = pbgc
+    pbgc = tbgc
+  str += '</tr>'
+  str += '</table>'
+
+  return render_to_response('reports/report.html',{'main': str})
+
+def getNotRanFCs(request):
+  FCall = FlowCell.objects.order_by('-run_date').distinct()
+  str = '<table><tr><th>FlowCell</th><th>Lanes</th><th>Creation Date</th></tr>'
+  for f in FCall:
+    try:
+      t = DataRun.objects.get(fcid=f.id)
+    except ObjectDoesNotExist:
+      str += '<tr><td>'+f.flowcell_id+'</td><td>'+f.Lanes()+'</td><td>'+f.run_date.__str__()+'</td></tr>'
+  str += "</table>"
+  return render_to_response('reports/report.html',{'main':str})
+def test_Libs(request):
+  str = ''
+  str += '<table border=1><tr><th>Lib ID</th><th>Current Libaray Name (Free Text)</th><th>Auto-composed Libaray Name (antibody + celline + libid + species + [replicate])</th></tr>'
+  allLibs = Library.objects.all()
+  #allLibs = Library.objects.filter(antibody__isnull=False)
+  for L in allLibs:
+    str += '<tr>'
+    str += '<td>'+L.library_id+'</td><td>'+L.library_name+'</td>'   
+    str += '<td>'
+    str += L.experiment_type+'_'
+    if L.cell_line.cellline_name != 'Unknown':
+      str += L.cell_line.cellline_name+'_'
+
+    try:
+      if L.antibody is not None:
+        str += L.antibody.nickname + '_'
+    except Antibody.DoesNotExist:
+      pass
+  
+    str += 'Rep'+L.replicate.__str__()
+    str += '</td></tr>' 
+
+  str += '</table>'
+  return HttpResponse(str)  
diff --git a/trunk/htsworkflow/frontend/reports/urls.py b/trunk/htsworkflow/frontend/reports/urls.py
new file mode 100644 (file)
index 0000000..5a004f2
--- /dev/null
@@ -0,0 +1,9 @@
+from django.conf.urls.defaults import *
+
+urlpatterns = patterns('',                                               
+    (r'^updLibInfo$', 'htsworkflow.frontend.reports.libinfopar.refreshLibInfoFile'),
+    (r'^report$', 'htsworkflow.frontend.reports.reports.report1'),
+    (r'^report_RM$', 'htsworkflow.frontend.reports.reports.report_RM'),
+    (r'^report_FCs$', 'htsworkflow.frontend.reports.reports.getNotRanFCs'),
+    (r'^liblist$', 'htsworkflow.frontend.reports.reports.test_Libs')
+)
diff --git a/trunk/htsworkflow/frontend/reports/utils.py b/trunk/htsworkflow/frontend/reports/utils.py
new file mode 100644 (file)
index 0000000..7b2d1b8
--- /dev/null
@@ -0,0 +1,61 @@
+def unique(s):
+  """Return a list of the elements in s, but without duplicates.                                                                                                                                                                                             
+  For example, unique([1,2,3,1,2,3]) is some permutation of [1,2,3],                                                                                                                                                                                         
+  unique("abcabc") some permutation of ["a", "b", "c"], and                                                                                                                                                                                                  
+  unique(([1, 2], [2, 3], [1, 2])) some permutation of                                                                                                                                                                                                       
+  [[2, 3], [1, 2]].                                                                                                                                                                                                                                          
+  For best speed, all sequence elements should be hashable.  Then                                                                                                                                                                                            
+  unique() will usually work in linear time.                                                                                                                                                                                                                 
+  If not possible, the sequence elements should enjoy a total                                                                                                                                                                                                
+  ordering, and if list(s).sort() doesn't raise TypeError it's                                                                                                                                                                                               
+  assumed that they do enjoy a total ordering.  Then unique() will                                                                                                                                                                                           
+  usually work in O(N*log2(N)) time.                                                                                                                                                                                                                         
+  If that's not possible either, the sequence elements must support                                                                                                                                                                                          
+  equality-testing.  Then unique() will usually work in quadratic                                                                                                                                                                                            
+  time.                                                                                                                                                                                                                                                      
+  """
+
+  n = len(s)
+  if n == 0:
+      return []
+
+  # Try using a dict first, as that's the fastest and will usually                                                                                                                                                                                           
+  # work.  If it doesn't work, it will usually fail quickly, so it                                                                                                                                                                                           
+  # usually doesn't cost much to *try* it.  It requires that all the                                                                                                                                                                                         
+  # sequence elements be hashable, and support equality comparison.                                                                                                                                                                                          
+  u = {}
+  try:
+      for x in s:
+          u[x] = 1
+  except TypeError:
+      del u  # move on to the next method                                                                                                                                                                                                                    
+  else:
+      return u.keys()
+  # We can't hash all the elements.  Second fastest is to sort,                                                                                                                                                                                              
+  # which brings the equal elements together; then duplicates are                                                                                                                                                                                            
+  # easy to weed out in a single pass.                                                                                                                                                                                                                       
+  # NOTE:  Python's list.sort() was designed to be efficient in the                                                                                                                                                                                          
+  # presence of many duplicate elements.  This isn't true of all                                                                                                                                                                                             
+  # sort functions in all languages or libraries, so this approach                                                                                                                                                                                           
+  # is more effective in Python than it may be elsewhere.                                                                                                                                                                                                    
+  try:
+      t = list(s)
+      t.sort()
+  except TypeError:
+      del t  # move on to the next method                                                                                                                                                                                                                    
+  else:
+      assert n > 0
+      last = t[0]
+      lasti = i = 1
+      while i < n:
+          if t[i] != last:
+              t[lasti] = last = t[i]
+              lasti += 1
+          i += 1
+      return t[:lasti]
+  # Brute force is all that's left.                                                                                                                                                                                                                          
+  u = []
+  for x in s:
+      if x not in u:
+          u.append(x)
+  return u                                                                                                         
diff --git a/trunk/htsworkflow/frontend/samples/__init__.py b/trunk/htsworkflow/frontend/samples/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/trunk/htsworkflow/frontend/samples/admin.py b/trunk/htsworkflow/frontend/samples/admin.py
new file mode 100644 (file)
index 0000000..643413f
--- /dev/null
@@ -0,0 +1,148 @@
+from django.contrib import admin
+from django.contrib.admin import widgets
+from django.db import models
+from django.utils.translation import ugettext_lazy as _
+
+from htsworkflow.frontend.samples.models import Antibody, Cellline, Condition, ExperimentType, Species, Affiliation, Library, Tag
+
+class Library_Inline(admin.TabularInline):
+  model = Library
+
+class CelllineOptions(admin.ModelAdmin):
+    list_display = ('cellline_name', 'nickname', 'notes')
+    search_fields = ('cellline_name', 'nickname', 'notes')
+    fieldsets = (
+      (None, {
+          'fields': (('cellline_name'),('notes'),)
+      }),
+     )
+
+class ExperimentTypeOptions(admin.ModelAdmin):
+  model = ExperimentType
+  #list_display = ('name',)
+  #fieldsets = ( (None, { 'fields': ('name',) }), )
+
+class LibraryOptions(admin.ModelAdmin):
+    date_hierarchy = "creation_date"
+    save_as = True
+    save_on_top = True
+    search_fields = (
+        'library_id',
+        'library_name',
+        'cell_line__cellline_name',
+        'library_species__scientific_name',
+        'library_species__common_name',
+    )
+    list_display = (
+        'library_id',
+        #'aligned_reads',
+        #'DataRun',
+        'library_name',
+        'public',
+        #'experiment_type',
+        #'organism',
+        #'antibody_name',
+        #'cell_line',
+        #'libtags',
+        #'made_for',
+        'affiliation',
+        #'made_by',
+        'undiluted_concentration',
+        'creation_date',
+        'stopping_point',
+        #'condition',
+
+    )
+    list_filter = (
+        'experiment_type', 
+        'library_species', 
+        'tags',
+        #'made_for',
+        'affiliations',
+        'made_by', 
+        'antibody',
+        'cell_line',
+        'condition',
+        'stopping_point',
+        'hidden')
+    list_display_links = ('library_id', 'library_name',)
+    fieldsets = (
+      (None, {
+        'fields': (
+          ('library_id','library_name','hidden'),
+          ('library_species'),
+          ('experiment_type', 'replicate'),
+          ('cell_line','condition','antibody'),)
+         }),
+         ('Creation Information:', {
+             'fields' : (('made_for', 'made_by', 'creation_date'), ('stopping_point', 'amplified_from_sample'), ('avg_lib_size','undiluted_concentration', 'ten_nM_dilution', 'successful_pM'), 'notes',)
+         }),
+         ('Library/Project Affiliation:', {
+             'fields' : (('affiliations'), ('tags'),)
+         }),
+         )
+
+    # some post 1.0.2 version of django has formfield_overrides 
+    # which would replace this code with:
+    # formfield_overrids = {
+    #   models.ManyToMany: { 'widget': widgets.FilteredSelectMultiple }
+    # }
+    def formfield_for_dbfield(self, db_field, **kwargs):
+      if db_field.name == 'affiliations':
+        kwargs['widget'] = widgets.FilteredSelectMultiple(db_field.verbose_name, (db_field.name in self.filter_vertical))
+      rv = super(LibraryOptions, self).formfield_for_dbfield(db_field, **kwargs)
+      print db_field.name, kwargs
+      return rv
+
+class AffiliationOptions(admin.ModelAdmin):
+    list_display = ('name','contact','email')
+    fieldsets = (
+      (None, {
+          'fields': (('name','contact','email'))
+      }),
+    )
+
+# class UserOptions(admin.ModelAdmin):
+#   inlines = [Library_Inline]
+
+class AntibodyOptions(admin.ModelAdmin):
+    search_fields = ('antigene','nickname','catalog','antibodies','source','biology','notes')
+    list_display = ('antigene','nickname','antibodies','catalog','source','biology','notes')
+    list_filter = ('antibodies','source')
+    fieldsets = (
+      (None, {
+          'fields': (('antigene','nickname','antibodies'),('catalog','source'),('biology'),('notes'))
+      }),
+     )
+
+class SpeciesOptions(admin.ModelAdmin):
+    fieldsets = (
+      (None, {
+          'fields': (('scientific_name', 'common_name'),)
+      }),
+    )
+
+class ConditionOptions(admin.ModelAdmin):
+    list_display = (('condition_name'), ('notes'),)
+    fieldsets = (
+      (None, {
+          'fields': (('condition_name'),('nickname'),('notes'),)
+      }),
+     )
+
+class TagOptions(admin.ModelAdmin):
+    list_display = ('tag_name', 'context')
+    fieldsets = ( 
+        (None, {
+          'fields': ('tag_name', 'context')
+          }),
+        )
+
+admin.site.register(Affiliation, AffiliationOptions)
+admin.site.register(Antibody, AntibodyOptions)
+admin.site.register(Cellline, CelllineOptions)
+admin.site.register(Condition, ConditionOptions)
+admin.site.register(ExperimentType, ExperimentTypeOptions)
+admin.site.register(Library, LibraryOptions)
+admin.site.register(Species, SpeciesOptions)
+admin.site.register(Tag, TagOptions)
diff --git a/trunk/htsworkflow/frontend/samples/changelist.py b/trunk/htsworkflow/frontend/samples/changelist.py
new file mode 100644 (file)
index 0000000..dccba27
--- /dev/null
@@ -0,0 +1,239 @@
+"""
+Slightly modified version of the django admin component that handles filters and searches
+"""
+from django.contrib.admin.filterspecs import FilterSpec
+from django.contrib.admin.options import IncorrectLookupParameters
+from django.core.paginator import Paginator, InvalidPage, EmptyPage
+from django.db import models
+from django.db.models.query import QuerySet
+from django.utils.encoding import smart_str
+from django.utils.http import urlencode
+
+import operator
+
+MAX_SHOW_ALL_ALLOWED = 20000
+
+#change list settings
+ALL_VAR = 'all'
+ORDER_VAR = 'o'
+ORDER_TYPE_VAR = 'ot'
+PAGE_VAR = 'p'
+SEARCH_VAR = 'q'
+IS_POPUP_VAR = 'pop'
+ERROR_FLAG = 'e'
+
+class ChangeList(object):
+    def __init__(self, request, model, list_filter, search_fields, list_per_page, queryset=None):
+        self.model = model
+        self.opts = model._meta
+        self.lookup_opts = self.opts
+        if queryset is None:
+            self.root_query_set = model.objects
+        else:
+            self.root_query_set = queryset
+        self.list_display =  []
+        self.list_display_links = None
+        self.list_filter = list_filter
+
+        self.search_fields = search_fields
+        self.list_select_related = None
+        self.list_per_page = list_per_page
+        self.model_admin = None
+
+        try:
+            self.page_num = int(request.GET.get(PAGE_VAR,'0'))
+        except ValueError:
+            self.page_num = 0
+        self.show_all = 'all' in request.GET
+        self.params = dict(request.GET.items())
+        if PAGE_VAR in self.params:
+            del self.params[PAGE_VAR]
+        if ERROR_FLAG in self.params:
+            del self.params[ERROR_FLAG]
+
+        self.multi_page = True
+        self.can_show_all = False
+      
+        self.order_field, self.order_type = self.get_ordering()
+        self.query = request.GET.get(SEARCH_VAR, '')
+        self.query_set = self.get_query_set()
+        self.get_results(request)
+        self.filter_specs, self.has_filters = self.get_filters(request)
+
+        #self.result_count = 'result count'
+        #self.full_result_count = 'full result count'
+    def get_filters(self, request):
+        filter_specs = []
+        if self.list_filter:
+            filter_fields = [self.lookup_opts.get_field(field_name) for field_name in self.list_filter]
+            for f in filter_fields:
+                spec = FilterSpec.create(f, request, self.params, self.model, self.model_admin)
+                if spec and spec.has_output():
+                    filter_specs.append(spec)
+        return filter_specs, bool(filter_specs)
+
+    def get_query_string(self, new_params=None, remove=None):
+        if new_params is None: new_params = {}
+        if remove is None: remove = []
+        p = self.params.copy()
+        for r in remove:
+            for k in p.keys():
+                if k.startswith(r):
+                    del p[k]
+        for k, v in new_params.items():
+            if v is None:
+                if k in p:
+                    del p[k]
+            else:
+                p[k] = v
+        return '?%s' % urlencode(p)
+
+    def get_results(self, request):
+        paginator = Paginator(self.query_set, self.list_per_page)
+        # Get the number of objects, with admin filters applied.
+        result_count = paginator.count
+
+        # Get the total number of objects, with no admin filters applied.
+        # Perform a slight optimization: Check to see whether any filters were
+        # given. If not, use paginator.hits to calculate the number of objects,
+        # because we've already done paginator.hits and the value is cached.
+        if not self.query_set.query.where:
+            full_result_count = result_count
+        else:
+            full_result_count = self.root_query_set.count()
+
+        can_show_all = result_count <= MAX_SHOW_ALL_ALLOWED
+        multi_page = result_count > self.list_per_page
+
+        # Get the list of objects to display on this page.
+        if (self.show_all and can_show_all) or not multi_page:
+            result_list = list(self.query_set)
+        else:
+            try:
+                result_list = paginator.page(self.page_num+1).object_list
+            except InvalidPage:
+                result_list = ()
+
+        self.result_count = result_count
+        self.full_result_count = full_result_count
+        self.result_list = result_list
+        self.can_show_all = can_show_all
+        self.multi_page = multi_page
+        self.paginator = paginator
+
+    def get_ordering(self):
+        lookup_opts, params = self.lookup_opts, self.params
+        # For ordering, first check the "ordering" parameter in the admin
+        # options, then check the object's default ordering. If neither of
+        # those exist, order descending by ID by default. Finally, look for
+        # manually-specified ordering from the query string.
+        ordering = lookup_opts.ordering or ['-' + lookup_opts.pk.name] 
+
+        if ordering[0].startswith('-'):
+            order_field, order_type = ordering[0][1:], 'desc'
+        else:
+            order_field, order_type = ordering[0], 'asc'
+        if ORDER_VAR in params:
+            try:
+                field_name = self.list_display[int(params[ORDER_VAR])]
+                try:
+                    f = lookup_opts.get_field(field_name)
+                except models.FieldDoesNotExist:
+                    # See whether field_name is a name of a non-field
+                    # that allows sorting.
+                    try:
+                        if callable(field_name):
+                            attr = field_name
+                        elif hasattr(self.model_admin, field_name):
+                            attr = getattr(self.model_admin, field_name)
+                        else:
+                            attr = getattr(self.model, field_name)
+                        order_field = attr.admin_order_field
+                    except AttributeError:
+                        pass
+                else:
+                    order_field = f.name
+            except (IndexError, ValueError):
+                pass # Invalid ordering specified. Just use the default.
+        if ORDER_TYPE_VAR in params and params[ORDER_TYPE_VAR] in ('asc', 'desc'):
+            order_type = params[ORDER_TYPE_VAR]
+        return order_field, order_type
+
+    def get_query_set(self):
+        qs = self.root_query_set
+        lookup_params = self.params.copy() # a dictionary of the query string
+        for i in (ALL_VAR, ORDER_VAR, ORDER_TYPE_VAR, SEARCH_VAR, IS_POPUP_VAR):
+            if i in lookup_params:
+                del lookup_params[i]
+        for key, value in lookup_params.items():
+            if not isinstance(key, str):
+                # 'key' will be used as a keyword argument later, so Python
+                # requires it to be a string.
+                del lookup_params[key]
+                lookup_params[smart_str(key)] = value
+
+            # if key ends with __in, split parameter into separate values
+            if key.endswith('__in'):
+                lookup_params[key] = value.split(',')
+
+        # Apply lookup parameters from the query string.
+        try:
+            qs = qs.filter(**lookup_params)
+        # Naked except! Because we don't have any other way of validating "params".
+        # They might be invalid if the keyword arguments are incorrect, or if the
+        # values are not in the correct type, so we might get FieldError, ValueError,
+        # ValicationError, or ? from a custom field that raises yet something else
+        # when handed impossible data.
+        except Exception, e:
+            print e
+            raise IncorrectLookupParameters
+
+        # Use select_related() if one of the list_display options is a field
+        # with a relationship.
+        if self.list_select_related:
+            qs = qs.select_related()
+        else:
+            for field_name in self.list_display:
+                try:
+                    f = self.lookup_opts.get_field(field_name)
+                except models.FieldDoesNotExist:
+                    pass
+                else:
+                    if isinstance(f.rel, models.ManyToOneRel):
+                        qs = qs.select_related()
+                        break
+
+        # Set ordering.
+        if self.order_field:
+            qs = qs.order_by('%s%s' % ((self.order_type == 'desc' and '-' or ''), self.order_field))
+
+        # Apply keyword searches.
+        def construct_search(field_name):
+            if field_name.startswith('^'):
+                return "%s__istartswith" % field_name[1:]
+            elif field_name.startswith('='):
+                return "%s__iexact" % field_name[1:]
+            elif field_name.startswith('@'):
+                return "%s__search" % field_name[1:]
+            else:
+                return "%s__icontains" % field_name
+
+        if self.search_fields and self.query:
+            for bit in self.query.split():
+                or_queries = [models.Q(**{construct_search(field_name): bit}) for field_name in self.search_fields]
+                other_qs = QuerySet(self.model)
+                other_qs.dup_select_related(qs)
+                other_qs = other_qs.filter(reduce(operator.or_, or_queries))
+                qs = qs & other_qs
+            for field_name in self.search_fields:
+                if '__' in field_name:
+                    qs = qs.distinct()
+                    break
+
+        if self.opts.one_to_one_field:
+            qs = qs.complex_filter(self.opts.one_to_one_field.rel.limit_choices_to)
+
+        return qs
+
+
diff --git a/trunk/htsworkflow/frontend/samples/models.py b/trunk/htsworkflow/frontend/samples/models.py
new file mode 100644 (file)
index 0000000..f51d250
--- /dev/null
@@ -0,0 +1,249 @@
+import urlparse
+from django.db import models
+from django.contrib.auth.models import User
+from htsworkflow.frontend import settings
+from htsworkflow.frontend.reports.libinfopar import *
+
+# Create your models here.
+
+class Antibody(models.Model):
+    antigene = models.CharField(max_length=500, db_index=True)
+    # New field Aug/20/08
+    # SQL to add column: 
+    # alter table fctracker_antibody add column "nickname" varchar(20) NULL;
+    nickname = models.CharField(
+        max_length=20,
+        blank=True,
+        null=True, 
+        db_index=True,
+        verbose_name = 'Short Name'
+    )
+    catalog = models.CharField(max_length=50, unique=True, db_index=True)
+    antibodies = models.CharField(max_length=500, db_index=True)
+    source = models.CharField(max_length=500, blank=True, db_index=True)
+    biology = models.TextField(blank=True)
+    notes = models.TextField(blank=True)
+    def __unicode__(self):
+        return u'%s - %s (%s)' % (self.antigene, self.antibodies, self.catalog)
+    class Meta:
+        verbose_name_plural = "antibodies"
+        ordering = ["antigene"]
+
+class Cellline(models.Model):
+    cellline_name = models.CharField(max_length=100, unique=True, db_index=True)
+    nickname = models.CharField(max_length=20,
+        blank=True,
+        null=True, 
+        db_index=True,
+        verbose_name = 'Short Name')
+    notes = models.TextField(blank=True)
+    def __unicode__(self):
+        return unicode(self.cellline_name)
+
+    class Meta:
+        ordering = ["cellline_name"]
+
+class Condition(models.Model):
+    condition_name = models.CharField(
+        max_length=2000, unique=True, db_index=True)
+    nickname = models.CharField(max_length=20,
+        blank=True,
+        null=True, 
+        db_index=True,
+        verbose_name = 'Short Name')
+    notes = models.TextField(blank=True)
+
+    def __unicode__(self):
+        return unicode(self.condition_name)
+
+    class Meta:
+        ordering = ["condition_name"]
+
+class ExperimentType(models.Model):
+  name = models.CharField(max_length=50, unique=True)
+
+  def __unicode__(self):
+    return unicode(self.name)
+
+class Tag(models.Model): 
+  tag_name = models.CharField(max_length=100, db_index=True,blank=False,null=False) 
+  TAG_CONTEXT = ( 
+      #('Antibody','Antibody'), 
+      #('Cellline', 'Cellline'), 
+      #('Condition', 'Condition'), 
+      ('Library', 'Library'), 
+      ('ANY','ANY'), 
+  ) 
+  context = models.CharField(max_length=50, 
+      choices=TAG_CONTEXT, default='Library') 
+  def __unicode__(self): 
+    return u'%s' % (self.tag_name) 
+  class Meta: 
+    ordering = ["context","tag_name"] 
+class Species(models.Model):
+  scientific_name = models.CharField(max_length=256, 
+      unique=False, 
+      db_index=True
+  )
+  common_name = models.CharField(max_length=256, blank=True)
+  #use_genome_build = models.CharField(max_length=100, blank=False, null=False)
+
+  def __unicode__(self):
+    return u'%s (%s)' % (self.scientific_name, self.common_name)
+  
+  class Meta:
+    verbose_name_plural = "species"
+    ordering = ["scientific_name"]
+  
+class Affiliation(models.Model):
+  name = models.CharField(max_length=256, db_index=True, verbose_name='Name')
+  contact = models.CharField(max_length=256, null=True, blank=True,verbose_name='Lab Name')  
+  email = models.EmailField(null=True,blank=True)
+  
+  def __unicode__(self):
+    str = unicode(self.name)
+    if self.contact is not None and len(self.contact) > 0:
+      str += u' ('+self.contact+u')' 
+    return str
+
+  class Meta:
+    ordering = ["name","contact"]
+    unique_together = (("name", "contact"),)
+
+class Library(models.Model):
+  id = models.AutoField(primary_key=True)
+  library_id = models.CharField(max_length=30, db_index=True, unique=True)
+  library_name = models.CharField(max_length=100, unique=True)
+  library_species = models.ForeignKey(Species)
+  # new field 2008 Mar 5, alter table samples_library add column "hidden" NOT NULL default 0;
+  hidden = models.BooleanField()
+  cell_line = models.ForeignKey(Cellline, null=True)
+  condition = models.ForeignKey(Condition, null=True)
+  antibody = models.ForeignKey(Antibody,blank=True,null=True)
+  # New field Aug/25/08. SQL: alter table fctracker_library add column "lib_affiliation" varchar(256)  NULL;
+  affiliations = models.ManyToManyField(Affiliation,related_name='library_affiliations',null=True)
+  # new field Nov/14/08
+  tags = models.ManyToManyField(Tag,related_name='library_tags',blank=True,null=True)
+  # New field Aug/19/08
+  # SQL to add column: alter table fctracker_library add column "replicate" smallint unsigned NULL;
+  REPLICATE_NUM = ((1,1),(2,2),(3,3),(4,4))
+  replicate =  models.PositiveSmallIntegerField(choices=REPLICATE_NUM,default=1) 
+  experiment_type = models.ForeignKey(ExperimentType)
+  creation_date = models.DateField(blank=True, null=True)
+  made_for = models.CharField(max_length=50, blank=True, 
+      verbose_name='ChIP/DNA/RNA Made By')
+  made_by = models.CharField(max_length=50, blank=True, default="Lorian")
+  
+  PROTOCOL_END_POINTS = (
+      ('?', 'Unknown'),
+      ('Sample', 'Raw sample'),
+      ('Progress', 'In progress'),
+      ('1A', 'Ligation, then gel'),
+      ('PCR', 'Ligation, then PCR'),
+      ('1Ab', 'Ligation, PCR, then gel'),
+      ('1Aa', 'Ligation, gel, then PCR'),
+      ('2A', 'Ligation, PCR, gel, PCR'),
+      ('Done', 'Completed'),
+    )
+  stopping_point = models.CharField(max_length=25, choices=PROTOCOL_END_POINTS, default='Done')
+  amplified_from_sample = models.ForeignKey('self', blank=True, null=True, related_name='amplified_into_sample')  
+  
+  undiluted_concentration = models.DecimalField("Concentration", 
+      max_digits=5, decimal_places=2, blank=True, null=True,
+      help_text=u"Undiluted concentration (ng/\u00b5l)") 
+      # note \u00b5 is the micro symbol in unicode
+  successful_pM = models.DecimalField(max_digits=9, decimal_places=1, blank=True, null=True)
+  ten_nM_dilution = models.BooleanField()
+  avg_lib_size = models.IntegerField(default=225, blank=True, null=True)
+  notes = models.TextField(blank=True)
+  
+  def __unicode__(self):
+    return u'#%s: %s' % (self.library_id, self.library_name)
+  
+  class Meta:
+    verbose_name_plural = "libraries"
+    #ordering = ["-creation_date"] 
+    ordering = ["-library_id"]
+  
+  def antibody_name(self):
+    str ='<a target=_self href="/admin/samples/antibody/'+self.antibody.id.__str__()+'/" title="'+self.antibody.__str__()+'">'+self.antibody.nickname+'</a>' 
+    return str
+  antibody_name.allow_tags = True
+
+  def organism(self):
+    return self.library_species.common_name
+
+  def affiliation(self):
+    affs = self.affiliations.all().order_by('name')
+    tstr = ''
+    ar = []
+    for t in affs:
+        ar.append(t.__unicode__())
+    return '%s' % (", ".join(ar))
+    
+  def is_archived(self):
+    """
+    returns True if archived else False
+    """
+    if self.longtermstorage_set.count() > 0:
+        return True
+    else:
+        return False
+
+  def libtags(self):
+    affs = self.tags.all().order_by('tag_name')
+    ar = []
+    for t in affs:
+      ar.append(t.__unicode__())
+    return u'%s' % ( ", ".join(ar))
+
+  def DataRun(self):
+    str ='<a target=_self href="/admin/experiments/datarun/?q='+self.library_id+'" title="Check All Data Runs for This Specific Library ..." ">Data Run</a>' 
+    return str
+  DataRun.allow_tags = True
+
+  def aligned_m_reads(self):
+    return getLibReads(self.library_id)
+
+  def aligned_reads(self):
+    res = getLibReads(self.library_id)
+
+    # Check data sanity
+    if res[2] != "OK":
+      return u'<div style="border:solid red 2px">'+res[2]+'</div>'
+
+    rc = "%1.2f" % (res[1]/1000000.0)
+    # Color Scheme: green is more than 10M, blue is more than 5M, orange is more than 3M and red is less. For RNAseq, all those thresholds should be doubled
+    if res[0] > 0:
+      bgcolor = '#ff3300'  # Red
+      rc_thr = [10000000,5000000,3000000]
+      if self.experiment_type == 'RNA-seq':
+        rc_thr = [20000000,10000000,6000000]
+
+      if res[1] > rc_thr[0]:
+        bgcolor = '#66ff66'  # Green
+      else:
+        if res[1] > rc_thr[1]:
+          bgcolor ='#00ccff'  # Blue
+        else:
+           if res[1] > rc_thr[2]: 
+             bgcolor ='#ffcc33'  # Orange
+      tstr = '<div style="background-color:'+bgcolor+';color:black">'
+      tstr += res[0].__unicode__()+' Lanes, '+rc+' M Reads'
+      tstr += '</div>'
+    else: tstr = 'not processed yet' 
+    return tstr
+  aligned_reads.allow_tags = True
+
+  def public(self):
+    SITE_ROOT = '/'
+    summary_url = self.get_absolute_url()
+    return '<a href="%s">S</a>' % (summary_url,)
+  public.allow_tags = True
+
+  @models.permalink
+  def get_absolute_url(self):
+    return ('htsworkflow.frontend.samples.views.library_to_flowcells', [str(self.library_id)])
diff --git a/trunk/htsworkflow/frontend/samples/results.py b/trunk/htsworkflow/frontend/samples/results.py
new file mode 100644 (file)
index 0000000..c2419cd
--- /dev/null
@@ -0,0 +1,134 @@
+from htsworkflow.frontend import settings
+
+import glob
+import os
+import re
+
+s_paren = re.compile("^\w+")
+
+def get_flowcell_result_dict(flowcell_id):
+    """
+    returns a dictionary following the following pattern for
+    a given flowcell_id:
+    
+     
+    d['C1-33']['summary']           # Summary.htm file path
+    d['C1-33']['eland_results'][5]  # C1-33 lane 5 file eland results file path
+    d['C1-33']['run_xml']           # run_*.xml file path
+    d['C1-33']['scores']            # scores.tar.gz file path
+    """
+    flowcell_id = flowcell_id.strip()
+    
+    d = {}
+    
+    ################################
+    # Flowcell Directory
+    fc_dir = glob.glob(os.path.join(settings.RESULT_HOME_DIR, flowcell_id))
+    
+    # Not found
+    if len(fc_dir) == 0:
+        return None
+    
+    # No duplicates!
+    assert len(fc_dir) <= 1
+    
+    # Found fc dir
+    fc_dir = fc_dir[0]
+    
+    ################################
+    # C#-## dirs
+    c_dir_list = glob.glob(os.path.join(fc_dir, 'C*'))
+    
+    # Not found
+    if len(c_dir_list) == 0:
+        return d
+    
+    for c_dir_path in c_dir_list:
+        summary_file = glob.glob(os.path.join(c_dir_path, 'Summary.htm'))
+        pathdir, c_dir = os.path.split(c_dir_path)
+        
+        # Create sub-dictionary
+        d[c_dir] = {}
+        
+        
+        ###############################
+        # Summary.htm file
+        
+        # Not found
+        if len(summary_file) == 0:
+            d[c_dir]['summary'] = None
+            
+        # Found
+        else:
+            # No duplicates!
+            assert len(summary_file) == 1
+            
+            summary_file = summary_file[0]
+            d[c_dir]['summary'] = summary_file
+            
+        ###############################
+        # Result files
+        
+        d[c_dir]['eland_results'] = {}
+        
+        result_filepaths = glob.glob(os.path.join(c_dir_path, 's_*_eland_*'))
+        
+        for filepath in result_filepaths:
+            
+            junk, result_name = os.path.split(filepath)
+            
+            #lanes 1-8, single digit, therefore s_#; # == index 2
+            lane = int(result_name[2])
+            d[c_dir]['eland_results'][lane] = filepath
+            
+        ###############################
+        # run*.xml file
+        run_xml_filepath = glob.glob(os.path.join(c_dir_path, 'run_*.xml'))
+        
+        if len(run_xml_filepath) == 0:
+            d[c_dir]['run_xml'] = None
+        else:
+            # No duplicates
+            assert len(run_xml_filepath) == 1
+            
+            d[c_dir]['run_xml'] = run_xml_filepath[0]
+            
+        ###############################
+        # scores.tar.gz
+        scores_filepath = glob.glob(os.path.join(c_dir_path, 'scores*'))
+        
+        if len(scores_filepath) == 0:
+            d[c_dir]['scores'] = None
+        else:
+            # No duplicates
+            assert len(scores_filepath) == 1
+            
+            d[c_dir]['scores'] = scores_filepath[0]
+        
+    return d
+
+    
+def cn_mTobp(cn_m):
+    """
+    Converts CN-M (i.e. C1-33, C1-26, C4-28) cycle information into
+    number of base pairs.
+    """
+    pass
+
+
+def parse_flowcell_id(flowcell_id):
+    """
+    Return flowcell id and any status encoded in the id
+  
+    We stored the status information in the flowcell id name.
+    this was dumb, but database schemas are hard to update.
+    """
+    fields = flowcell_id.split()
+    fcid = None
+    status = None
+    if len(fields) > 0:
+        fcid = fields[0]
+    if len(fields) > 1:
+        status = fields[1]
+    return fcid, status
+    
diff --git a/trunk/htsworkflow/frontend/samples/tests.py b/trunk/htsworkflow/frontend/samples/tests.py
new file mode 100644 (file)
index 0000000..9e8a2a3
--- /dev/null
@@ -0,0 +1,102 @@
+import datetime
+import unittest
+from htsworkflow.frontend.samples.models import \
+        Affiliation, \
+        ExperimentType, \
+        Species, \
+        Library
+
+# The django test runner flushes the database between test suites not cases,
+# so to be more compatible with running via nose we flush the database tables
+# of interest before creating our sample data.
+def create_db(obj):
+    Species.objects.all().delete()
+    obj.species_human = Species(
+        scientific_name = 'Homo Sapeins',
+        common_name = 'human',
+    )
+    obj.species_human.save()
+    obj.species_worm = Species(
+        scientific_name = 'C. Elegans',
+        common_name = 'worm',
+    )
+    obj.species_worm.save()
+    obj.species_phix = Species(
+        scientific_name = 'PhiX',
+        common_name = 'PhiX'
+    )
+    obj.species_phix.save()
+
+    ExperimentType.objects.all().delete()
+    obj.experiment_de_novo = ExperimentType(
+        name = 'De Novo',
+    )
+    obj.experiment_de_novo.save()
+    obj.experiment_chip_seq = ExperimentType(
+        name = 'ChIP-Seq'
+    )
+    obj.experiment_chip_seq.save()
+    obj.experiment_rna_seq = ExperimentType(
+        name = 'RNA-Seq'
+    )
+    obj.experiment_rna_seq.save()
+
+    Affiliation.objects.all().delete()
+    obj.affiliation_alice = Affiliation(
+        name = 'Alice',
+        contact = 'Lab Boss',
+        email = 'alice@some.where.else.'
+    )
+    obj.affiliation_alice.save()
+    obj.affiliation_bob = Affiliation(
+        name = 'Bob',
+        contact = 'Other Lab Boss',
+        email = 'bob@some.where.else',
+    )
+    obj.affiliation_bob.save()
+
+    Library.objects.all().delete()
+    obj.library_10001 = Library(
+        library_id = 10001,
+        library_name = 'C2C12 named poorly',
+        library_species = obj.species_human,
+        experiment_type = obj.experiment_rna_seq,
+        creation_date = datetime.datetime.now(),
+        made_for = 'scientist unit 2007',
+        made_by = 'microfludics system 7321',
+        stopping_point = '2A',
+        undiluted_concentration = '5.01',
+    )
+    obj.library_10001.save()
+    obj.library_10002 = Library(
+        library_id = 10002,
+        library_name = 'Worm named poorly',
+        library_species = obj.species_human,
+        experiment_type = obj.experiment_rna_seq,
+        creation_date = datetime.datetime.now(),
+        made_for = 'scientist unit 2007',
+        made_by = 'microfludics system 7321',
+        stopping_point = '2A',
+        undiluted_concentration = '5.01',
+    )
+    obj.library_10002.save()
+class LibraryTestCase(unittest.TestCase):
+    def setUp(self):
+        create_db(self)
+               
+    def testOrganism(self):
+        self.assertEquals(self.library_10001.organism(), 'human')
+
+    def testAffiliations(self):
+        self.library_10001.affiliations.add(self.affiliation_alice)
+        self.library_10002.affiliations.add(
+                self.affiliation_alice, 
+                self.affiliation_bob
+        )
+        self.failUnless(len(self.library_10001.affiliations.all()), 1)
+        self.failUnless(self.library_10001.affiliation(), 'Alice')
+
+        self.failUnless(len(self.library_10002.affiliations.all()), 2)
+        self.failUnless(self.library_10001.affiliation(), 'Alice, Bob')
+
diff --git a/trunk/htsworkflow/frontend/samples/views.py b/trunk/htsworkflow/frontend/samples/views.py
new file mode 100644 (file)
index 0000000..b8956d5
--- /dev/null
@@ -0,0 +1,393 @@
+# Create your views here.
+from htsworkflow.frontend.experiments.models import FlowCell
+from htsworkflow.frontend.samples.changelist import ChangeList
+from htsworkflow.frontend.samples.models import Library
+from htsworkflow.frontend.samples.results import get_flowcell_result_dict, parse_flowcell_id
+from htsworkflow.pipelines.runfolder import load_pipeline_run_xml
+from htsworkflow.pipelines import runfolder
+from htsworkflow.frontend import settings
+from htsworkflow.util import makebed
+from htsworkflow.util import opener
+
+from django.core.exceptions import ObjectDoesNotExist
+from django.http import HttpResponse, HttpResponseRedirect
+from django.shortcuts import render_to_response
+from django.template import RequestContext
+from django.template.loader import get_template
+
+import StringIO
+import logging
+import os
+
+LANE_LIST = [1,2,3,4,5,6,7,8]
+
+def create_library_context(cl):
+    """
+    Create a list of libraries that includes how many lanes were run
+    """
+    records = []
+    #for lib in library_items.object_list:
+    for lib in cl.result_list:
+       summary = {}
+       summary['library_id'] = lib.library_id
+       summary['library_name'] = lib.library_name
+       summary['species_name' ] = lib.library_species.scientific_name
+       if lib.amplified_from_sample is not None:
+           summary['amplified_from'] = lib.amplified_from_sample.library_id
+       else:
+           summary['amplified_from'] = ''
+       lanes_run = 0
+       for lane_id in LANE_LIST:
+           lane = getattr(lib, 'lane_%d_library' % (lane_id,))
+           lanes_run += len( lane.all() )
+       summary['lanes_run'] = lanes_run
+       summary['is_archived'] = lib.is_archived()
+       records.append(summary)
+    cl.result_count = unicode(cl.paginator._count) + u" libraries"
+    return {'library_list': records }
+
+def library(request):
+   # build changelist
+    fcl = ChangeList(request, Library,
+        list_filter=['affiliations', 'library_species'],
+        search_fields=['library_id', 'library_name', 'amplified_from_sample__library_id'],
+        list_per_page=200,
+        queryset=Library.objects.filter(hidden__exact=0)
+    )
+
+    context = { 'cl': fcl, 'title': 'Library Index'}
+    context.update(create_library_context(fcl))
+    t = get_template('samples/library_index.html')
+    c = RequestContext(request, context)
+    return HttpResponse( t.render(c) )
+
+def library_to_flowcells(request, lib_id):
+    """
+    Display information about all the flowcells a library has been run on.
+    """
+    
+    try:
+      lib = Library.objects.get(library_id=lib_id)
+    except:
+      return HttpResponse("Library %s does not exist" % (lib_id))
+   
+    flowcell_list = []
+    interesting_flowcells = {} # aka flowcells we're looking at
+    for lane in LANE_LIST:
+        lane_library = getattr(lib, 'lane_%d_library' % (lane,))
+        for fc in lane_library.all():
+            flowcell_id, id = parse_flowcell_id(fc.flowcell_id)
+            if flowcell_id not in interesting_flowcells:
+                interesting_flowcells[flowcell_id] = get_flowcell_result_dict(flowcell_id)
+            flowcell_list.append((fc.flowcell_id, lane))
+
+    flowcell_list.sort()
+    
+    lane_summary_list = []
+    eland_results = []
+    for fc, lane in flowcell_list:
+        lane_summary, err_list = _summary_stats(fc, lane)
+
+        eland_results.extend(_make_eland_results(fc, lane, interesting_flowcells))
+        lane_summary_list.extend(lane_summary)
+
+    return render_to_response(
+        'samples/library_detail.html',
+        {'lib': lib,
+         'eland_results': eland_results,
+         'lane_summary_list': lane_summary_list,
+        },
+        context_instance = RequestContext(request))
+
+def summaryhtm_fc_cnm(request, flowcell_id, cnm):
+    """
+    returns a Summary.htm file if it exists.
+    """
+    fc_id, status = parse_flowcell_id(flowcell_id)
+    d = get_flowcell_result_dict(fc_id)
+    
+    if d is None:
+        return HttpResponse('<b>Results for Flowcell %s not found.</b>' % (fc_id))
+    
+    if cnm not in d:
+        return HttpResponse('<b>Results for Flowcell %s; %s not found.</b>' % (fc_id, cnm))
+    
+    summary_filepath = d[cnm]['summary']
+    
+    if summary_filepath is None:
+        return HttpResponse('<b>Summary.htm for Flowcell %s; %s not found.</b>' % (fc_id, cnm))
+    
+    f = open(summary_filepath, 'r')
+    
+    return HttpResponse(f)
+
+
+def result_fc_cnm_eland_lane(request, flowcell_id, cnm, lane):
+    """
+    returns an eland_file upon calling.
+    """
+    fc_id, status = parse_flowcell_id(flowcell_id)
+    d = get_flowcell_result_dict(fc_id)
+    
+    if d is None:
+        return HttpResponse('<b>Results for Flowcell %s not found.</b>' % (fc_id))
+    
+    if cnm not in d:
+        return HttpResponse('<b>Results for Flowcell %s; %s not found.</b>' % (fc_id, cnm))
+    
+    erd = d[cnm]['eland_results']
+    lane = int(lane)
+    
+    if lane not in erd:
+        return HttpResponse('<b>Results for Flowcell %s; %s; lane %s not found.</b>' % (fc_id, cnm, lane))
+    
+    filepath = erd[lane]
+    
+    #f = opener.autoopen(filepath, 'r')
+    # return HttpResponse(f, mimetype="application/x-elandresult")
+
+    f = open(filepath, 'r')
+    return HttpResponse(f, mimetype='application/x-bzip2')
+    
+
+
+def bedfile_fc_cnm_eland_lane_ucsc(request, fc_id, cnm, lane):
+    """
+    returns a bed file for a given flowcell, CN-M (i.e. C1-33), and lane (ucsc compatible)
+    """
+    return bedfile_fc_cnm_eland_lane(request, fc_id, cnm, lane, ucsc_compatible=True)
+
+
+def bedfile_fc_cnm_eland_lane(request, flowcell_id, cnm, lane, ucsc_compatible=False):
+    """
+    returns a bed file for a given flowcell, CN-M (i.e. C1-33), and lane
+    """
+    fc_id, status = parse_flowcell_id(flowcell_id)
+    d = get_flowcell_result_dict(fc_id)
+    
+    if d is None:
+        return HttpResponse('<b>Results for Flowcell %s not found.</b>' % (fc_id))
+    
+    if cnm not in d:
+        return HttpResponse('<b>Results for Flowcell %s; %s not found.</b>' % (fc_id, cnm))
+    
+    erd = d[cnm]['eland_results']
+    lane = int(lane)
+    
+    if lane not in erd:
+        return HttpResponse('<b>Results for Flowcell %s; %s; lane %s not found.</b>' % (fc_id, cnm, lane))
+    
+    filepath = erd[lane]
+    
+    # Eland result file
+    fi = opener.autoopen(filepath, 'r')
+    # output memory file
+    
+    name, description = makebed.make_description( fc_id, lane )
+    
+    bedgen = makebed.make_bed_from_eland_generator(fi, name, description)
+    
+    if ucsc_compatible:
+        return HttpResponse(bedgen)
+    else:
+        return HttpResponse(bedgen, mimetype="application/x-bedfile")
+
+
+def _summary_stats(flowcell_id, lane_id):
+    """
+    Return the summary statistics for a given flowcell, lane, and end.
+    """
+    fc_id, status = parse_flowcell_id(flowcell_id)
+    fc_result_dict = get_flowcell_result_dict(fc_id)
+
+    summary_list = []
+    err_list = []
+    
+    if fc_result_dict is None:
+        err_list.append('Results for Flowcell %s not found.' % (fc_id))
+        return (summary_list, err_list)
+
+    for cycle_width in fc_result_dict:
+        xmlpath = fc_result_dict[cycle_width]['run_xml']
+        
+        if xmlpath is None:
+            err_list.append('Run xml for Flowcell %s(%s) not found.' % (fc_id, cycle_width))
+            continue
+        
+        run = load_pipeline_run_xml(xmlpath)
+        gerald_summary = run.gerald.summary.lane_results
+        for end in range(len(gerald_summary)):
+            eland_summary = run.gerald.eland_results.results[end][lane_id]
+            # add information to lane_summary
+            eland_summary.flowcell_id = flowcell_id
+            eland_summary.clusters = gerald_summary[end][lane_id].cluster
+            eland_summary.cycle_width = cycle_width
+            if hasattr(eland_summary, 'genome_map'):
+                eland_summary.summarized_reads = runfolder.summarize_mapped_reads( 
+                                                   eland_summary.genome_map, 
+                                                   eland_summary.mapped_reads)
+
+            # grab some more information out of the flowcell db
+            flowcell = FlowCell.objects.get(flowcell_id=flowcell_id)
+            pm_field = 'lane_%d_pM' % (lane_id)
+            eland_summary.successful_pm = getattr(flowcell, pm_field)
+
+            summary_list.append(eland_summary)
+
+        #except Exception, e:
+        #    summary_list.append("Summary report needs to be updated.")
+        #    logging.error("Exception: " + str(e))
+    
+    return (summary_list, err_list)
+
+def _summary_stats_old(flowcell_id, lane):
+    """
+    return a dictionary of summary stats for a given flowcell_id & lane.
+    """
+    fc_id, status = parse_flowcell_id(flowcell_id)
+    fc_result_dict = get_flowcell_result_dict(fc_id)
+    
+    dict_list = []
+    err_list = []
+    summary_list = []
+    
+    if fc_result_dict is None:
+        err_list.append('Results for Flowcell %s not found.' % (fc_id))
+        return (dict_list, err_list, summary_list)
+    
+    for cnm in fc_result_dict:
+    
+        xmlpath = fc_result_dict[cnm]['run_xml']
+        
+        if xmlpath is None:
+            err_list.append('Run xml for Flowcell %s(%s) not found.' % (fc_id, cnm))
+            continue
+        
+        tree = ElementTree.parse(xmlpath).getroot()
+        results = runfolder.PipelineRun(pathname='', xml=tree)
+        try:
+            lane_report = runfolder.summarize_lane(results.gerald, lane)
+            summary_list.append(os.linesep.join(lane_report))
+        except Exception, e:
+            summary_list.append("Summary report needs to be updated.")
+            logging.error("Exception: " + str(e))
+       
+        print "----------------------------------"
+        print "-- DOES NOT SUPPORT PAIRED END ---"
+        print "----------------------------------"
+        lane_results = results.gerald.summary[0][lane]
+        lrs = lane_results
+        
+        d = {}
+        
+        d['average_alignment_score'] = lrs.average_alignment_score
+        d['average_first_cycle_intensity'] = lrs.average_first_cycle_intensity
+        d['cluster'] = lrs.cluster
+        d['lane'] = lrs.lane
+        d['flowcell'] = flowcell_id
+        d['cnm'] = cnm
+        d['percent_error_rate'] = lrs.percent_error_rate
+        d['percent_intensity_after_20_cycles'] = lrs.percent_intensity_after_20_cycles
+        d['percent_pass_filter_align'] = lrs.percent_pass_filter_align
+        d['percent_pass_filter_clusters'] = lrs.percent_pass_filter_clusters
+        
+        #FIXME: function finished, but need to take advantage of
+        #   may need to take in a list of lanes so we only have to
+        #   load the xml file once per flowcell rather than once
+        #   per lane.
+        dict_list.append(d)
+    
+    return (dict_list, err_list, summary_list)
+    
+    
+def get_eland_result_type(pathname):
+    """
+    Guess the eland result file type from the filename
+    """
+    path, filename = os.path.split(pathname)
+    if 'extended' in filename:
+        return 'extended'
+    elif 'multi' in filename:
+        return 'multi'
+    elif 'result' in filename:
+        return 'result'
+    else:
+        return 'unknown'
+
+def _make_eland_results(flowcell_id, lane, interesting_flowcells):
+    fc_id, status = parse_flowcell_id(flowcell_id)
+    cur_fc = interesting_flowcells.get(fc_id, None)
+    if cur_fc is None:
+      return []
+
+    results = []
+    for cycle in cur_fc.keys():
+        result_path = cur_fc[cycle]['eland_results'].get(lane, None)
+        result_link = make_result_link(fc_id, cycle, lane, result_path)
+        results.append({'flowcell_id': fc_id,
+                        'cycle': cycle, 
+                        'lane': lane, 
+                        'summary_url': make_summary_url(flowcell_id, cycle),
+                        'result_url': result_link[0],
+                        'result_label': result_link[1],
+                        'bed_url': result_link[2],
+        })
+    return results
+
+def make_summary_url(flowcell_id, cycle_name):
+    url = '/results/%s/%s/summary/' % (flowcell_id, cycle_name)
+    return url
+
+def make_result_link(flowcell_id, cycle_name, lane, eland_result_path):
+    if eland_result_path is None:
+        return ("", "", "")
+
+    result_type = get_eland_result_type(eland_result_path)
+    result_url = '/results/%s/%s/eland_result/%s' % (flowcell_id, cycle_name, lane)
+    result_label = 'eland %s' % (result_type,)
+    bed_url = None
+    if result_type == 'result':
+       bed_url_pattern = '/results/%s/%s/bedfile/%s'
+       bed_url = bed_url_pattern % (flowcell_id, cycle_name, lane)
+    
+    return (result_url, result_label, bed_url)
+
+def _files(flowcell_id, lane):
+    """
+    Sets up available files for download
+    """
+    lane = int(lane)
+
+    flowcell_id, id = parse_flowcell_id(flowcell_id)
+    d = get_flowcell_result_dict(flowcell_id)
+    
+    if d is None:
+        return ''
+    
+    output = []
+    
+    # c_name == 'CN-M' (i.e. C1-33)
+    for c_name in d:
+        
+        if d[c_name]['summary'] is not None:
+            output.append('<a href="/results/%s/%s/summary/">summary(%s)</a>' \
+                          % (flowcell_id, c_name, c_name))
+        
+        erd = d[c_name]['eland_results']
+        if lane in erd:
+            result_type = get_eland_result_type(erd[lane])
+            result_url_pattern = '<a href="/results/%s/%s/eland_result/%s">eland %s(%s)</a>'
+            output.append(result_url_pattern % (flowcell_id, c_name, lane, result_type, c_name))
+            if result_type == 'result':
+                bed_url_pattern = '<a href="/results/%s/%s/bedfile/%s">bedfile(%s)</a>'
+                output.append(bed_url_pattern % (flowcell_id, c_name, lane, c_name))
+    
+    if len(output) == 0:
+        return ''
+    
+    return '(' + '|'.join(output) + ')'
+
+def library_id_to_admin_url(request, lib_id):
+    lib = Library.objects.get(library_id=lib_id)
+    return HttpResponseRedirect('/admin/samples/library/%s' % (lib.id,))
+
diff --git a/trunk/htsworkflow/frontend/settings.py b/trunk/htsworkflow/frontend/settings.py
new file mode 100644 (file)
index 0000000..d971bbc
--- /dev/null
@@ -0,0 +1,175 @@
+"""
+Generate settings for the Django Application.
+
+To make it easier to customize the application the settings can be 
+defined in a configuration file read by ConfigParser.
+
+The options understood by this module are (with their defaults):
+
+  [frontend]
+  email_host=localhost
+  email_port=25
+  database_engine=sqlite3
+  database_name=/path/to/db
+
+  [admins]
+  #name1=email1
+
+  [allowed_hosts]
+  #name1=ip
+  localhost=127.0.0.1
+  
+  [allowed_analysis_hosts]
+  #name1=ip
+  localhost=127.0.0.1
+
+"""
+import ConfigParser
+import os
+
+# make epydoc happy
+__docformat__ = "restructuredtext en"
+
+def options_to_list(dest, section_name):
+  """
+  Load a options from section_name and store in a dictionary
+  """
+  if options.has_section(section_name):
+    for name in options.options(section_name):
+      dest.append( options.get(section_name, name) )
+      
+def options_to_dict(dest, section_name):
+  """
+  Load a options from section_name and store in a dictionary
+  """
+  if options.has_section(section_name):
+    for name in options.options(section_name):
+      dest[name] = options.get(section_name, name)
+
+# define your defaults here
+options = ConfigParser.SafeConfigParser(
+           { 'email_host': 'localhost',
+             'email_port': '25', 
+             'database_engine': 'sqlite3',
+             'database_name': 
+               os.path.abspath('/htsworkflow/htswfrontend/dev_fctracker.db'),
+             'time_zone': 'America/Los_Angeles',
+             'default_pm': '5',
+             'link_flowcell_storage_device_url': "http://localhost:8000/inventory/lts/link/"
+           })
+
+options.read([os.path.expanduser("~/.htsworkflow.ini"),
+              '/etc/htsworkflow.ini',])
+
+# Django settings for elandifier project.
+
+DEBUG = True
+TEMPLATE_DEBUG = DEBUG
+
+ADMINS = []
+options_to_list(ADMINS, 'admins')
+
+MANAGERS = ADMINS
+
+EMAIL_HOST = options.get('frontend', 'email_host')
+EMAIL_PORT = int(options.get('frontend', 'email_port'))
+
+# 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'ado_mssql'.
+DATABASE_ENGINE = options.get('frontend', 'database_engine')
+
+# Or path to database file if using sqlite3.
+DATABASE_NAME = options.get('frontend', 'database_name' )
+DATABASE_USER = ''             # Not used with sqlite3.
+DATABASE_PASSWORD = ''         # Not used with sqlite3.
+DATABASE_HOST = ''             # Set to empty string for localhost. Not used with sqlite3.
+DATABASE_PORT = ''             # Set to empty string for default. Not used with sqlite3.
+
+# Local time zone for this installation. Choices can be found here:
+# http://www.postgresql.org/docs/8.1/static/datetime-keywords.html#DATETIME-TIMEZONE-SET-TABLE
+# although not all variations may be possible on all operating systems.
+# If running in a Windows environment this must be set to the same as your
+# system time zone.
+TIME_ZONE = options.get('frontend', 'time_zone')
+
+# Language code for this installation. All choices can be found here:
+# http://www.w3.org/TR/REC-html40/struct/dirlang.html#langcodes
+# http://blogs.law.harvard.edu/tech/stories/storyReader$15
+LANGUAGE_CODE = 'en-us'
+
+SITE_ID = 1
+
+# If you set this to False, Django will make some optimizations so as not
+# to load the internationalization machinery.
+USE_I18N = True
+
+# Absolute path to the directory that holds media.
+# Example: "/home/media/media.lawrence.com/"
+MEDIA_ROOT = os.path.abspath(os.path.split(__file__)[0]) + '/static/'
+
+# URL that handles the media served from MEDIA_ROOT.
+# Example: "http://media.lawrence.com"
+MEDIA_URL = '/static/'
+
+# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
+# trailing slash.
+# Examples: "http://foo.com/media/", "/media/".
+ADMIN_MEDIA_PREFIX = '/media/'
+
+# Make this unique, and don't share it with anybody.
+SECRET_KEY = '(ekv^=gf(j9f(x25@a7r+8)hqlz%&_1!tw^75l%^041#vi=@4n'
+
+# List of callables that know how to import templates from various sources.
+TEMPLATE_LOADERS = (
+    'django.template.loaders.filesystem.load_template_source',
+    'django.template.loaders.app_directories.load_template_source',
+#     'django.template.loaders.eggs.load_template_source',
+)
+
+MIDDLEWARE_CLASSES = (
+    'django.middleware.common.CommonMiddleware',
+    'django.contrib.sessions.middleware.SessionMiddleware',
+    'django.contrib.auth.middleware.AuthenticationMiddleware',
+    'django.middleware.doc.XViewMiddleware',
+)
+
+ROOT_URLCONF = 'htsworkflow.frontend.urls'
+
+TEMPLATE_DIRS = (
+    # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
+    # Always use forward slashes, even on Windows.
+    # Don't forget to use absolute paths, not relative paths.
+    os.path.join(os.path.split(__file__)[0], 'templates'),
+)
+
+INSTALLED_APPS = (
+    'django.contrib.admin',
+    'django.contrib.auth',
+    'django.contrib.contenttypes',
+    'django.contrib.humanize',
+    'django.contrib.sessions',
+    'django.contrib.sites',
+    'htsworkflow.frontend.eland_config',
+    'htsworkflow.frontend.samples',
+    # modules from htsworkflow branch
+    'htsworkflow.frontend.experiments',
+    'htsworkflow.frontend.analysis', 
+    'htsworkflow.frontend.reports',
+    'htsworkflow.frontend.inventory',
+    'django.contrib.databrowse',
+)
+
+# Project specific settings
+
+ALLOWED_IPS={'127.0.0.1': '127.0.0.1'}
+options_to_dict(ALLOWED_IPS, 'allowed_hosts')
+
+ALLOWED_ANALYS_IPS = {'127.0.0.1': '127.0.0.1'}
+options_to_dict(ALLOWED_ANALYS_IPS, 'allowed_analysis_hosts')
+#UPLOADTO_HOME = os.path.abspath('../../uploads')
+#UPLOADTO_CONFIG_FILE = os.path.join(UPLOADTO_HOME, 'eland_config')
+#UPLOADTO_ELAND_RESULT_PACKS = os.path.join(UPLOADTO_HOME, 'eland_results')
+#UPLOADTO_BED_PACKS = os.path.join(UPLOADTO_HOME, 'bed_packs')
+RESULT_HOME_DIR='/Users/diane/proj/solexa/results/flowcells'
+
+LINK_FLOWCELL_STORAGE_DEVICE_URL = options.get('frontend', 'link_flowcell_storage_device_url')
+
diff --git a/trunk/htsworkflow/frontend/static/css/base.css b/trunk/htsworkflow/frontend/static/css/base.css
new file mode 100644 (file)
index 0000000..9760d67
--- /dev/null
@@ -0,0 +1,14 @@
+/*
+    DJANGO Admin
+    by Wilson Miner wilson@lawrence.com
+*/
+
+/* Block IE 5 */
+@import "null.css?\"\{";
+
+/* Import other styles */
+@import url('global.css');
+@import url('layout.css');
+
+/* Import patch for IE 6 Windows */
+/*\*/ @import "patch-iewin.css"; /**/
diff --git a/trunk/htsworkflow/frontend/static/css/changelists.css b/trunk/htsworkflow/frontend/static/css/changelists.css
new file mode 100644 (file)
index 0000000..a156c54
--- /dev/null
@@ -0,0 +1,50 @@
+@import url('base.css');
+
+/* CHANGELISTS */
+#changelist { position:relative; width:100%; }
+#changelist table { width:100%; }
+.change-list .filtered table { border-right:1px solid #ddd;  }
+.change-list .filtered { min-height:400px; }
+.change-list .filtered { background:white url(../img/changelist-bg.gif) top right repeat-y !important; }
+.change-list .filtered table, .change-list .filtered .paginator, .filtered #toolbar, .filtered div.xfull { margin-right:160px !important; width:auto !important; }
+.change-list .filtered table tbody th { padding-right:1em; }
+#changelist .toplinks { border-bottom:1px solid #ccc !important; }
+#changelist .paginator { color:#666; border-top:1px solid #eee; border-bottom:1px solid #eee; background:white url(../img/nav-bg.gif) 0 180% repeat-x; overflow:hidden; }
+.change-list .filtered .paginator { border-right:1px solid #ddd; }
+
+/*  CHANGELIST TABLES  */
+#changelist table thead th { white-space:nowrap; }
+#changelist table tbody td { border-left: 1px solid #ddd; }
+#changelist table tfoot { color: #666; }
+
+/*  TOOLBAR  */
+#changelist #toolbar { padding:3px; border-bottom:1px solid #ddd; background:#e1e1e1 url(../img/nav-bg.gif) top left repeat-x; color:#666; }
+#changelist #toolbar form input { font-size:11px; padding:1px 2px; }
+#changelist #toolbar form #searchbar { padding:2px; }
+#changelist #changelist-search img { vertical-align:middle; }
+
+/*  FILTER COLUMN  */
+#changelist-filter { position:absolute; top:0; right:0; z-index:1000; width:160px; border-left:1px solid #ddd; background:#efefef; margin:0; }
+#changelist-filter h2 { font-size:11px; padding:2px 5px; border-bottom:1px solid #ddd; }
+#changelist-filter h3 { font-size:12px; margin-bottom:0; }
+#changelist-filter ul { padding-left:0;margin-left:10px; }
+#changelist-filter li { list-style-type:none; margin-left:0; padding-left:0; }
+#changelist-filter a { color:#999; }
+#changelist-filter a:hover { color:#036; }
+#changelist-filter li.selected { border-left:5px solid #ccc; padding-left:5px;margin-left:-10px; }
+#changelist-filter li.selected a { color:#5b80b2 !important; }
+
+/*  DATE DRILLDOWN  */
+.change-list ul.toplinks { display:block; background:white url(../img/nav-bg-reverse.gif) 0 -10px repeat-x; border-top:1px solid white; float:left;  padding:0 !important;  margin:0 !important; width:100%; }
+.change-list ul.toplinks li { float: left; width: 9em; padding:3px 6px; font-weight: bold; list-style-type:none; }
+.change-list ul.toplinks .date-back a { color:#999; }
+.change-list ul.toplinks .date-back a:hover { color:#036; }
+
+/* PAGINATOR */
+.paginator { font-size:11px; padding-top:10px; padding-bottom:10px; line-height:22px; margin:0; border-top:1px solid #ddd; }
+.paginator a:link, .paginator a:visited { padding:2px 6px; border:solid 1px #ccc; background:white; text-decoration:none; }
+.paginator a.showall { padding:0 !important; border:none !important; }
+.paginator a.showall:hover { color:#036 !important; background:transparent !important; }
+.paginator .end { border-width:2px !important; margin-right:6px; }
+.paginator .this-page { padding:2px 6px; font-weight:bold; font-size:13px; vertical-align:top; }
+.paginator a:hover { color:white; background:#5b80b2; border-color:#036; }
diff --git a/trunk/htsworkflow/frontend/static/css/click-table.css b/trunk/htsworkflow/frontend/static/css/click-table.css
new file mode 100644 (file)
index 0000000..1d48412
--- /dev/null
@@ -0,0 +1,19 @@
+table, td {
+  border-style: solid;
+}
+table {
+  border-width: 0 0 1px 1px;
+  border-spacing: 0;
+  border-collapse: collapse;
+}
+thead {
+  text-align: center;
+}
+td {
+  margin: 0;
+  padding: 4px;
+  border-width: 1px 1px 0 0;
+}
+td a {
+  display: block;
+}
diff --git a/trunk/htsworkflow/frontend/static/css/data-browse-index.css b/trunk/htsworkflow/frontend/static/css/data-browse-index.css
new file mode 100644 (file)
index 0000000..59119bf
--- /dev/null
@@ -0,0 +1,2 @@
+@import url('changelists.css');
+@import url('click-table.css');
diff --git a/trunk/htsworkflow/frontend/static/css/forms.css b/trunk/htsworkflow/frontend/static/css/forms.css
new file mode 100644 (file)
index 0000000..a4b145f
--- /dev/null
@@ -0,0 +1,84 @@
+@import url('base.css');
+@import url('widgets.css');
+
+/* FORM ROWS */
+.form-row { overflow:hidden; padding:8px 12px; font-size:11px; border-bottom:1px solid #eee; }
+.form-row img, .form-row input { vertical-align:middle; }
+form .form-row p { padding-left:0; font-size:11px; }
+
+/* FORM LABELS */
+form h4 { margin:0 !important; padding:0 !important; border:none !important; }
+label { font-weight:normal !important; color:#666; font-size:12px; }
+.required label, label.required { font-weight:bold !important; color:#333 !important; }
+
+/* RADIO BUTTONS */
+form ul.radiolist li { list-style-type:none; }
+form ul.radiolist label { float:none; display:inline; }
+form ul.inline { margin-left:0; padding:0; }
+form ul.inline li { float:left; padding-right:7px; }
+
+/* ALIGNED FIELDSETS */
+.aligned label { display:block; padding:3px 10px 0 0; float:left; width:8em; }
+.colMS .aligned .vLargeTextField, .colMS .aligned .vXMLLargeTextField { width:350px; }
+form .aligned p, form .aligned ul { margin-left:7em; padding-left:30px; }
+form .aligned table p { margin-left:0; padding-left:0; }
+form .aligned p.help { padding-left:38px; }
+.aligned .vCheckboxLabel { float:none !important; display:inline; padding-left:4px; }
+.colM .aligned .vLargeTextField, .colM .aligned .vXMLLargeTextField { width:610px; }
+.checkbox-row p.help { margin-left:0; padding-left:0 !important; }
+fieldset .field-box { float:left; margin-right: 20px; }
+
+/* WIDE FIELDSETS */
+.wide label { width:15em !important; }
+form .wide p { margin-left:15em; }
+form .wide p.help { padding-left:38px; }
+.colM fieldset.wide .vLargeTextField, .colM fieldset.wide .vXMLLargeTextField { width:450px; }
+
+/* COLLAPSED FIELDSETS */
+fieldset.collapsed * { display:none; }
+fieldset.collapsed h2, fieldset.collapsed { display:block !important; }
+fieldset.collapsed h2 { background-image:url(../img/nav-bg.gif); background-position:bottom left; color:#999; }
+fieldset.collapsed .collapse-toggle { padding:3px 5px !important; background:transparent; display:inline !important;}
+
+/* MONOSPACE TEXTAREAS */
+fieldset.monospace textarea { font-family:"Bitstream Vera Sans Mono",Monaco,"Courier New",Courier,monospace; }
+
+/* SUBMIT ROW */
+.submit-row { padding:5px 7px; text-align:right; background:white url(../img/nav-bg.gif) 0 100% repeat-x; border:1px solid #ccc; margin:5px 0; overflow:hidden; }
+.submit-row input { margin:0 0 0 5px; }
+.submit-row p { margin:0.3em; }
+.submit-row p.deletelink-box { float: left; }
+.submit-row .deletelink { background:url(../img/icon_deletelink.gif) 0 50% no-repeat; padding-left:14px; }
+
+/* CUSTOM FORM FIELDS */
+.vSelectMultipleField { vertical-align:top !important; }
+.vCheckboxField { border:none; }
+.vDateField, .vTimeField { margin-right:2px; }
+.vURLField { width:30em; }
+.vLargeTextField, .vXMLLargeTextField { width:48em; }
+.flatpages-flatpage #id_content { height:40.2em; }
+.module table .vPositiveSmallIntegerField { width:2.2em; }
+.vTextField { width:20em; }
+.vIntegerField { width:5em; }
+.vForeignKeyRawIdAdminField { width: 5em; }
+
+/* INLINES */
+.inline-group {padding:0; border:1px solid #ccc; margin:10px 0;}
+.inline-group .aligned label { width: 8em; }
+
+.inline-related {position:relative;}
+.inline-related h3 {margin: 0; color:#666; padding:3px 5px; font-size:11px; background:#e1e1e1 url(../img/nav-bg.gif) top left repeat-x; border-bottom:1px solid #ddd;}
+.inline-related h3 span.delete {padding-left:20px; position:absolute; top:2px; right:10px;}
+.inline-related h3 span.delete label {margin-left:2px; font-size: 11px;}
+.inline-related fieldset {margin: 0; background:#fff; border: none; }
+.inline-related fieldset.module h3 { margin:0; padding:2px 5px 3px 5px; font-size:11px; text-align:left; font-weight:bold; background:#bcd; color:#fff; }
+.inline-related.tabular fieldset.module table {width:100%;}
+.last-related fieldset {border: none;}
+
+.inline-group .tabular tr.has_original td {padding-top:2em;}
+.inline-group .tabular tr td.original { padding:2px 0 0 0; width:0; _position:relative; }
+.inline-group .tabular th.original {width:0px; padding:0;}
+.inline-group .tabular td.original p {position:absolute; left:0; height:1.1em; padding:2px 7px; overflow:hidden; font-size:9px; font-weight:bold; color:#666; _width:700px;     }
+.inline-group ul.tools {padding:0; margin: 0; list-style:none;}
+.inline-group ul.tools li {display:inline; padding:0 5px;}
+.inline-group ul.tools a.add {background:url(../img/icon_addlink.gif) 0 50% no-repeat; padding-left:14px;}
diff --git a/trunk/htsworkflow/frontend/static/css/global.css b/trunk/htsworkflow/frontend/static/css/global.css
new file mode 100644 (file)
index 0000000..fd84060
--- /dev/null
@@ -0,0 +1,142 @@
+body { margin:0; padding:0; font-size:12px; font-family:"Lucida Grande","DejaVu Sans","Bitstream Vera Sans",Verdana,Arial,sans-serif; color:#333; background:#fff; }
+
+/* LINKS */
+a:link, a:visited { color: #5b80b2; text-decoration:none; }
+a:hover { color: #036; }
+a img { border:none; }
+a.section:link, a.section:visited { color: white; text-decoration:none; }
+
+/* GLOBAL DEFAULTS */
+p, ol, ul, dl { margin:.2em 0 .8em 0; }
+p { padding:0; line-height:140%; }
+
+h1,h2,h3,h4,h5 { font-weight:bold; }
+h1 { font-size:18px; color:#666; padding:0 6px 0 0; margin:0 0 .2em 0; }
+h2 { font-size:16px; margin:1em 0 .5em 0; }
+h2.subhead { font-weight:normal;margin-top:0; }
+h3 { font-size:14px; margin:.8em 0 .3em 0; color:#666; font-weight:bold; }
+h4 { font-size:12px; margin:1em 0 .8em 0; padding-bottom:3px; }
+h5 { font-size:10px; margin:1.5em 0 .5em 0; color:#666; text-transform:uppercase; letter-spacing:1px; }
+
+ul li { list-style-type:square; padding:1px 0; }
+ul.plainlist { margin-left:0 !important; }
+ul.plainlist li { list-style-type:none; }
+li ul { margin-bottom:0; }
+li, dt, dd { font-size:11px; line-height:14px; }
+dt { font-weight:bold; margin-top:4px; }
+dd { margin-left:0; }
+
+form { margin:0; padding:0; }
+fieldset { margin:0; padding:0; }
+
+blockquote { font-size:11px; color:#777; margin-left:2px; padding-left:10px; border-left:5px solid #ddd; }
+code, pre { font-family:"Bitstream Vera Sans Mono", Monaco, "Courier New", Courier, monospace; background:inherit; color:#666; font-size:11px; }
+pre.literal-block { margin:10px; background:#eee; padding:6px 8px; }
+code strong { color:#930; }
+hr { clear:both; color:#eee; background-color:#eee; height:1px; border:none; margin:0; padding:0; font-size:1px; line-height:1px; }
+
+/* TEXT STYLES & MODIFIERS */
+.small { font-size:11px; }
+.tiny { font-size:10px; }
+p.tiny { margin-top:-2px; }
+.mini { font-size:9px; }
+p.mini { margin-top:-3px; }
+.help, p.help { font-size:10px !important; color:#999; }
+p img, h1 img, h2 img, h3 img, h4 img, td img { vertical-align:middle; }
+.quiet, a.quiet:link, a.quiet:visited { color:#999 !important;font-weight:normal !important; }
+.quiet strong { font-weight:bold !important; }
+.float-right { float:right; }
+.float-left { float:left; }
+.clear { clear:both; }
+.align-left { text-align:left; }
+.align-right { text-align:right; }
+.example { margin:10px 0; padding:5px 10px; background:#efefef; }
+.nowrap { white-space:nowrap; }
+
+/* TABLES */
+table { border-collapse:collapse; border-color:#ccc; }
+td, th { font-size:11px; line-height:13px; border-bottom:1px solid #eee; vertical-align:top; padding:5px; font-family:"Lucida Grande", Verdana, Arial, sans-serif; }
+th { text-align:left; font-size:12px; font-weight:bold; }
+thead th, 
+tfoot td { color:#666; padding:2px 5px; font-size:11px; background:#e1e1e1 url(../img/nav-bg.gif) top left repeat-x; border-left:1px solid #ddd; border-bottom:1px solid #ddd; }
+tfoot td { border-bottom:none; border-top:1px solid #ddd; }
+thead th:first-child, 
+tfoot td:first-child { border-left:none !important; }
+thead th.optional { font-weight:normal !important; }
+fieldset table { border-right:1px solid #eee; }
+tr.row-label td { font-size:9px; padding-top:2px; padding-bottom:0; border-bottom:none; color:#666; margin-top:-1px; }
+tr.alt { background:#f6f6f6; }
+.row1 { background:#EDF3FE; }
+.row2 { background:white; }
+
+/* SORTABLE TABLES */
+thead th a:link, thead th a:visited { color:#666; display:block; }
+table thead th.sorted { background-position:bottom left !important; }
+table thead th.sorted a { padding-right:13px; }
+table thead th.ascending a { background:url(../img/arrow-down.gif) right .4em no-repeat; }
+table thead th.descending a { background:url(../img/arrow-up.gif) right .4em no-repeat; }
+
+/* ORDERABLE TABLES */
+table.orderable tbody tr td:hover { cursor:move; }
+table.orderable tbody tr td:first-child { padding-left:14px; background-image:url(../img/nav-bg-grabber.gif); background-repeat:repeat-y; }
+table.orderable-initalized .order-cell, body>tr>td.order-cell { display:none; }
+
+/* FORM DEFAULTS */
+input, textarea, select { margin:2px 0; padding:2px 3px; vertical-align:middle; font-family:"Lucida Grande", Verdana, Arial, sans-serif; font-weight:normal; font-size:11px; }
+textarea { vertical-align:top !important; }
+input[type=text], input[type=password], textarea, select, .vTextField { border:1px solid #ccc; }
+
+/*  FORM BUTTONS  */
+.button, input[type=submit], input[type=button], .submit-row input { background:white url(../img/nav-bg.gif) bottom repeat-x; padding:3px; color:black; border:1px solid #bbb; border-color:#ddd #aaa #aaa #ddd; }
+.button:active, input[type=submit]:active, input[type=button]:active { background-image:url(../img/nav-bg-reverse.gif); background-position:top; }
+.button.default, input[type=submit].default, .submit-row input.default { border:2px solid #5b80b2; background:#7CA0C7 url(../img/default-bg.gif) bottom repeat-x; font-weight:bold; color:white; float:right; }
+.button.default:active, input[type=submit].default:active { background-image:url(../img/default-bg-reverse.gif); background-position:top; }
+
+/* MODULES */
+.module { border:1px solid #ccc; margin-bottom:5px; background:white; }
+.module p, .module ul, .module h3, .module h4, .module dl, .module pre { padding-left:10px; padding-right:10px; }
+.module blockquote { margin-left:12px; }
+.module ul, .module ol { margin-left:1.5em; }
+.module h3 { margin-top:.6em; }
+.module h2, .module caption, .inline-group h2 { margin:0; padding:2px 5px 3px 5px; font-size:11px; text-align:left; font-weight:bold; background:#7CA0C7 url(../img/default-bg.gif) top left repeat-x; color:white; }
+.module table { border-collapse: collapse; }
+
+/* MESSAGES & ERRORS */
+ul.messagelist { padding:0 0 5px 0; margin:0; }
+ul.messagelist li { font-size:12px; display:block; padding:4px 5px 4px 25px; margin:0 0 3px 0; border-bottom:1px solid #ddd; color:#666; background:#ffc url(../img/icon_success.gif) 5px .3em no-repeat; }
+.errornote { font-size:12px !important; display:block; padding:4px 5px 4px 25px; margin:0 0 3px 0; border:1px solid red; color:red;background:#ffc url(../img/icon_error.gif) 5px .3em no-repeat; }
+ul.errorlist { margin:0 !important; padding:0 !important; }
+.errorlist li { font-size:12px !important; display:block; padding:4px 5px 4px 25px; margin:0 0 3px 0; border:1px solid red; color:white; background:red url(../img/icon_alert.gif) 5px .3em no-repeat; }
+td ul.errorlist { margin:0 !important; padding:0 !important; }
+td ul.errorlist li { margin:0 !important; }
+.errors { background:#ffc; }
+.errors input, .errors select { border:1px solid red; }
+div.system-message { background: #ffc; margin: 10px; padding: 6px 8px; font-size: .8em; }
+div.system-message p.system-message-title { padding:4px 5px 4px 25px; margin:0; color:red; background:#ffc url(../img/icon_error.gif) 5px .3em no-repeat; }
+.description { font-size:12px; padding:5px 0 0 12px; }
+
+/* BREADCRUMBS */
+div.breadcrumbs { background:white url(../img/nav-bg-reverse.gif) 0 -10px repeat-x; padding:2px 8px 3px 8px; font-size:11px;  color:#999;  border-top:1px solid white; border-bottom:1px solid #ccc; text-align:left; }
+
+/* ACTION ICONS */
+.addlink { padding-left:12px; background:url(../img/icon_addlink.gif) 0 .2em no-repeat; }
+.changelink { padding-left:12px; background:url(../img/icon_changelink.gif) 0 .2em no-repeat; }
+.deletelink { padding-left:12px; background:url(../img/icon_deletelink.gif) 0 .25em no-repeat; }
+a.deletelink:link, a.deletelink:visited { color:#CC3434; }
+a.deletelink:hover { color:#993333; }
+
+/* OBJECT TOOLS */
+.object-tools { font-size:10px; font-weight:bold; font-family:Arial,Helvetica,sans-serif; padding-left:0; float:right; position:relative; margin-top:-2.4em; margin-bottom:-2em; }
+.form-row .object-tools { margin-top:5px; margin-bottom:5px; float:none; height:2em; padding-left:3.5em; }
+.object-tools li { display:block; float:left; background:url(../img/tool-left.gif) 0 0 no-repeat; padding:0 0 0 8px; margin-left:2px; height:16px; }
+.object-tools li:hover { background:url(../img/tool-left_over.gif) 0 0 no-repeat; }
+.object-tools a:link, .object-tools a:visited { display:block; float:left; color:white; padding:.1em 14px .1em 8px; height:14px; background:#999 url(../img/tool-right.gif) 100% 0 no-repeat; }
+.object-tools a:hover, .object-tools li:hover a { background:#5b80b2 url(../img/tool-right_over.gif) 100% 0 no-repeat; }
+.object-tools a.viewsitelink, .object-tools a.golink { background:#999 url(../img/tooltag-arrowright.gif) top right no-repeat; padding-right:28px; }
+.object-tools a.viewsitelink:hover, .object-tools a.golink:hover { background:#5b80b2 url(../img/tooltag-arrowright_over.gif) top right no-repeat; }
+.object-tools a.addlink { background:#999 url(../img/tooltag-add.gif) top right no-repeat; padding-right:28px; }
+.object-tools a.addlink:hover { background:#5b80b2 url(../img/tooltag-add_over.gif) top right no-repeat; }
+
+/* OBJECT HISTORY */
+table#change-history { width:100%; }
+table#change-history tbody th { width:16em; }
diff --git a/trunk/htsworkflow/frontend/static/css/layout.css b/trunk/htsworkflow/frontend/static/css/layout.css
new file mode 100644 (file)
index 0000000..bab2121
--- /dev/null
@@ -0,0 +1,29 @@
+/* PAGE STRUCTURE */
+#container { position:relative; width:100%; min-width:760px; padding:0; }
+#content { margin:10px 15px; }
+#header { width:100%; }
+#content-main { float:left; width:100%; }
+#content-related { float:right; width:18em; position:relative; margin-right:-19em; }
+#footer { clear:both; padding:10px; }
+
+/*  COLUMN TYPES  */
+.colMS { margin-right:20em !important; }
+.colSM { margin-left:20em !important; }
+.colSM #content-related { float:left; margin-right:0; margin-left:-19em; }
+.colSM #content-main { float:right; }
+.popup .colM { width:95%; }
+.subcol { float:left; width:46%; margin-right:15px; }
+.dashboard #content { width:500px; }
+
+/*  HEADER  */
+#header { background:#417690; color:#ffc; overflow:hidden; }
+#header a:link, #header a:visited { color:white; }
+#header a:hover { text-decoration:underline; }
+#branding h1 { padding:0 10px; font-size:18px; margin:8px 0; font-weight:normal; color:#f4f379; }
+#branding h2 { padding:0 10px; font-size:14px; margin:-8px 0 8px 0; font-weight:normal; color:#ffc; }
+#user-tools { position:absolute; top:0; right:0; padding:1.2em 10px; font-size:11px; text-align:right; }
+
+/* SIDEBAR */
+#content-related h3 { font-size:12px; color:#666; margin-bottom:3px; }
+#content-related h4 { font-size:11px; }
+#content-related .module h2 { background:#eee url(../img/nav-bg.gif) bottom left repeat-x; color:#666; }
diff --git a/trunk/htsworkflow/frontend/static/css/null.css b/trunk/htsworkflow/frontend/static/css/null.css
new file mode 100644 (file)
index 0000000..1a93f22
--- /dev/null
@@ -0,0 +1 @@
+/* Nothing to see here. Dummy file to feed to the high pass filter which hides CSS from IE5/win. Details: http://tantek.com/CSS/Examples/highpass.html */
\ No newline at end of file
diff --git a/trunk/htsworkflow/frontend/static/css/patch-iewin.css b/trunk/htsworkflow/frontend/static/css/patch-iewin.css
new file mode 100644 (file)
index 0000000..2de1305
--- /dev/null
@@ -0,0 +1,8 @@
+* html #container { position:static; } /* keep header from flowing off the page */
+* html .colMS #content-related { margin-right:0; margin-left:10px; position:static; } /* put the right sidebars back on the page */
+* html .colSM #content-related { margin-right:10px; margin-left:-115px; position:static; } /* put the left sidebars back on the page */
+* html .form-row { height:1%; }
+* html .dashboard #content { width:768px; } /* proper fixed width for dashboard in IE6 */
+* html .dashboard #content-main { width:535px; } /* proper fixed width for dashboard in IE6 */
+* html #changelist-filter ul { margin-right:-10px; } /* fix right margin for changelist filters in IE6 */
+* html .change-list .filtered { height:400px; } /* IE ignores min-height, but treats height as if it were min-height */
\ No newline at end of file
diff --git a/trunk/htsworkflow/frontend/static/img/changelist-bg.gif b/trunk/htsworkflow/frontend/static/img/changelist-bg.gif
new file mode 100644 (file)
index 0000000..7f46994
Binary files /dev/null and b/trunk/htsworkflow/frontend/static/img/changelist-bg.gif differ
diff --git a/trunk/htsworkflow/frontend/static/img/default-bg.gif b/trunk/htsworkflow/frontend/static/img/default-bg.gif
new file mode 100644 (file)
index 0000000..003aeca
Binary files /dev/null and b/trunk/htsworkflow/frontend/static/img/default-bg.gif differ
diff --git a/trunk/htsworkflow/frontend/static/img/hdd_unmount.png b/trunk/htsworkflow/frontend/static/img/hdd_unmount.png
new file mode 100755 (executable)
index 0000000..4972e55
Binary files /dev/null and b/trunk/htsworkflow/frontend/static/img/hdd_unmount.png differ
diff --git a/trunk/htsworkflow/frontend/static/img/icon_searchbox.png b/trunk/htsworkflow/frontend/static/img/icon_searchbox.png
new file mode 100644 (file)
index 0000000..8ab579e
Binary files /dev/null and b/trunk/htsworkflow/frontend/static/img/icon_searchbox.png differ
diff --git a/trunk/htsworkflow/frontend/static/img/nav-bg-reverse.gif b/trunk/htsworkflow/frontend/static/img/nav-bg-reverse.gif
new file mode 100644 (file)
index 0000000..f11029f
Binary files /dev/null and b/trunk/htsworkflow/frontend/static/img/nav-bg-reverse.gif differ
diff --git a/trunk/htsworkflow/frontend/static/img/nav-bg.gif b/trunk/htsworkflow/frontend/static/img/nav-bg.gif
new file mode 100644 (file)
index 0000000..f8402b8
Binary files /dev/null and b/trunk/htsworkflow/frontend/static/img/nav-bg.gif differ
diff --git a/trunk/htsworkflow/frontend/static/img/readme.txt b/trunk/htsworkflow/frontend/static/img/readme.txt
new file mode 100644 (file)
index 0000000..81c803e
--- /dev/null
@@ -0,0 +1,11 @@
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+This copyright and license notice covers the following images:
+ * hdd_unmount.png
+************************************************************************
+
+TITLE: Crystal Project Icons
+AUTHOR:        Everaldo Coelho
+SITE:  http://www.everaldo.com
+CONTACT: everaldo@everaldo.com
+
+Copyright (c)  2006-2007  Everaldo Coelho.
diff --git a/trunk/htsworkflow/frontend/templates/admin/base_site.html b/trunk/htsworkflow/frontend/templates/admin/base_site.html
new file mode 100644 (file)
index 0000000..c212302
--- /dev/null
@@ -0,0 +1,16 @@
+{% extends "admin/base.html" %}
+{% load i18n %}
+
+{% block title %}
+{{ title }}|{%trans "dev site admin" %}
+{% endblock %}
+
+{% block branding %}
+<h1 id="site-name" style="background-color:#cccccc;color:black">
+  {%block sitename %}
+    {% trans 'HTS Workflow Dev Server' %}
+  {%endblock%}
+</h1>
+{% endblock %}
+
+{% block nav-global %}{% endblock %}
diff --git a/trunk/htsworkflow/frontend/templates/admin/index.html b/trunk/htsworkflow/frontend/templates/admin/index.html
new file mode 100644 (file)
index 0000000..e712275
--- /dev/null
@@ -0,0 +1,135 @@
+{% extends "admin/base_site.html" %}
+{% load i18n %}
+
+{% block stylesheet %}{% load adminmedia %}{% admin_media_prefix %}css/dashboard.css{% endblock %}
+
+{% block coltype %}colMS{% endblock %}
+
+{% block bodyclass %}dashboard{% endblock %}
+
+{% block breadcrumbs %}{% endblock %}
+
+{% block content %}
+<div id="content-main">
+
+{% if app_list %}
+    {% for app in app_list %}
+        <div class="module">
+        <table summary="{% blocktrans with app.name as name %}Models available in the {{ name }} application.{% endblocktrans %}">
+        <caption><a href="{{ app.app_url }}" class="section">{% blocktrans with app.name as name %}{{ name }}{% endblocktrans %}</a></caption>
+        {% for model in app.models %}
+            <tr>
+            {% if model.perms.change %}
+                <th scope="row"><a href="{{ model.admin_url }}">{{ model.name }}</a></th>
+            {% else %}
+                <th scope="row">{{ model.name }}</th>
+            {% endif %}
+
+            {% if model.perms.add %}
+                <td><a href="{{ model.admin_url }}add/" class="addlink">{% trans 'Add' %}</a></td>
+            {% else %}
+                <td>&nbsp;</td>
+            {% endif %}
+
+            {% if model.perms.change %}
+                <td><a href="{{ model.admin_url }}" class="changelink">{% trans 'Change' %}</a></td>
+            {% else %}
+                <td>&nbsp;</td>
+            {% endif %}
+            </tr>
+        {% endfor %}
+        </table>
+        </div>
+    {% endfor %}
+{% else %}
+    <p>{% trans "You don't have permission to edit anything." %}</p>
+{% endif %}
+</div>
+
+      <div class="module">
+        <table>
+          <caption><span style="color:red">New!</span> Reports <span style="color:black;font-size:80%">(Note: Some features currently work only on Firefox)</span></caption>
+            <tr>
+             <th scope="row"><a href="/reports/report" target=_self>ChIP-Seq Reports</a></th>
+             <th scope="row"></th><td></td><td>&nbsp;</td><td></td><td>&nbsp;</td>
+            </tr>
+            <tr>
+             <th scope="row"><a href="/reports/report_RM" target=_self>RNA-Seq Reports</a></th>
+             <th scope="row"></th><td></td><td>&nbsp;</td><td></td><td>&nbsp;</td>
+            </tr>
+            <tr>
+             <th scope="row"><a href="/reports/report_RM?exp=Methyl-seq" target=_self>Methyl-Seq Reports</a></th>
+             <th scope="row"></th><td></td><td>&nbsp;</td><td></td><td>&nbsp;</td>
+            </tr>
+       </table>
+       </div>
+       <div class="module">
+        <table>
+          <caption>Docs & Tools</caption>
+            <tr>
+             <th scope="row"><a href="http://illumina-mac.stanford.edu/SequencingSummaries/SequencingSummary.html" target=_blank>Sequencing Summary Page</a></th>
+             <th scope="row"></th>
+             <td></td>
+             <td>&nbsp;</td>
+             <td></td>
+             <td>&nbsp;</td>
+            </tr>
+            <tr>
+             <th scope="row"><a href="http://m304-apple-server.stanford.edu/PrimerDesign" target=_blank>ChIP QC Primer Design</a>&nbsp;&nbsp;
+             <a href="http://m304-apple-server.stanford.edu/ValidationDesign/" target=_blank>ChIP Validation Design</a>
+             </th>
+             <th scope="row"></th>
+             <td></td>
+             <td>&nbsp;</td>
+             <td></td>
+             <td>&nbsp;</td>
+            </tr>
+            <tr>
+             <th scope="row"><a href="http://myers.hudsonalpha.org/content/protocols.html" target=_blank>Protocols - Myers Lab</a></th>
+             <th scope="row"></th>
+             <td></td>
+             <td>&nbsp;</td>
+             <td></td>
+             <td>&nbsp;</td>
+            </tr>
+            <tr>
+             <th scope="row"><a href="https://woldlab.caltech.edu/html/protocols" target=_blank>Protocols - Wold Lab</a></th>             
+             <th scope="row"></th>
+             <td></td>
+             <td>&nbsp;</td>
+            <td></td>
+             <td>&nbsp;</td>
+            </tr>
+           <tr>
+             <th scope="row"><a href="http://www.ncbi.nlm.nih.gov/pubmed/18711362" target=_blank>QuEST</a></th>
+             <th scope="row"></th>
+             <td></td>
+             <td>&nbsp;</td>
+           <td></td>
+            <td>&nbsp;</td>
+            </tr>
+        </table>
+        </div>
+</div>
+{% endblock %}
+
+
+{% block sidebar %}
+<div id="content-related">
+    <div class="module" id="recent-actions-module">
+        <h2>{% trans 'Recent Actions' %}</h2>
+        <h3>{% trans 'My Actions' %}</h3>
+            {% load log %}
+            {% get_admin_log 10 as admin_log for_user user %}
+            {% if not admin_log %}
+            <p>{% trans 'None available' %}</p>
+            {% else %}
+            <ul class="actionlist">
+            {% for entry in admin_log %}
+            <li class="{% if entry.is_addition %}addlink{% endif %}{% if entry.is_change %}changelink{% endif %}{% if entry.is_deletion %}deletelink{% endif %}">{% if not entry.is_deletion %}<a href="{{ entry.get_admin_url }}">{% endif %}{{ entry.object_repr }}{% if not entry.is_deletion %}</a>{% endif %}<br /><span class="mini quiet">{% filter capfirst %}{% trans entry.content_type.name %}{% endfilter %}</span></li>
+            {% endfor %}
+            </ul>
+            {% endif %}
+    </div>
+</div>
+{% endblock %}
diff --git a/trunk/htsworkflow/frontend/templates/base.html b/trunk/htsworkflow/frontend/templates/base.html
new file mode 100644 (file)
index 0000000..5899e59
--- /dev/null
@@ -0,0 +1,56 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" lang="{{ LANGUAGE_CODE }}" xml:lang="{{ LANGUAGE_CODE }}" {% if LANGUAGE_BIDI %}dir="rtl"{% endif %}>
+<head>
+<title>{% block title %}{% endblock %}</title>
+<link rel="stylesheet" type="text/css" href="{% block stylesheet %}{% load adminmedia %}{% admin_media_prefix %}css/base.css{% endblock %}" />
+{% if LANGUAGE_BIDI %}<link rel="stylesheet" type="text/css" href="{% block stylesheet_rtl %}{% admin_media_prefix %}css/rtl.css{% endblock %}" />{% endif %}
+{% block extrastyle %}{% endblock %}
+{% block extrahead %}{% endblock %}
+{% block blockbots %}<meta name="robots" content="NONE,NOARCHIVE" />{% endblock %}
+</head>
+{% load i18n %}
+
+<body class="{% if is_popup %}popup {% endif %}{% block bodyclass %}{% endblock %}">
+
+<!-- Container -->
+<div id="container">
+
+    {% if not is_popup %}
+    <!-- Header -->
+    <div id="header">
+        <div id="branding">
+        {% block branding %}{% endblock %}
+        </div>
+        {% if user.is_authenticated and user.is_staff %}
+        <div id="user-tools">{% trans 'Welcome,' %} <strong>{% firstof user.first_name user.username %}</strong>. {% block userlinks %}{% url django-admindocs-docroot as docsroot %}{% if docsroot %}<a href="{{ docsroot }}">{% trans 'Documentation' %}</a> / {% endif %}<a href="{{ root_path }}password_change/">{% trans 'Change password' %}</a> / <a href="{{ root_path }}logout/">{% trans 'Log out' %}</a>{% endblock %} 
+        </div>
+        {% endif %}
+        {% block nav-global %}{% endblock %}
+    </div>
+    <!-- END Header -->
+    {% block breadcrumbs %}<div class="breadcrumbs"><a href="/">{% trans 'Home' %}</a>{% if title %} &rsaquo; {{ title }}{% endif %}</div>{% endblock %}
+    {% endif %}
+
+        {% if messages %}
+        <ul class="messagelist">{% for message in messages %}<li>{{ message }}</li>{% endfor %}</ul>
+        {% endif %}
+
+    <!-- Content -->
+    <div id="content" class="{% block coltype %}colM{% endblock %}">
+        {% block pretitle %}{% endblock %}
+        {% block content_title %}{% if title %}<h1>{{ title }}</h1>{% endif %}{% endblock %}
+        {% block content %}
+        {% block object-tools %}{% endblock %}
+        {{ content }}
+        {% endblock %}
+        {% block sidebar %}{% endblock %}
+        <br class="clear" />
+    </div>
+    <!-- END Content -->
+
+    {% block footer %}<div id="footer"></div>{% endblock %}
+</div>
+<!-- END Container -->
+
+</body>
+</html>
diff --git a/trunk/htsworkflow/frontend/templates/base_site.html b/trunk/htsworkflow/frontend/templates/base_site.html
new file mode 100644 (file)
index 0000000..3cf8738
--- /dev/null
@@ -0,0 +1,10 @@
+{% extends "admin/base.html" %}
+{% load i18n %}
+
+{% block title %}{{ sitename }}{% endblock %}
+
+{% block branding %}
+<h1 id="site-name">HTS Workflow</h1>
+{% endblock %}
+
+{% block nav-global %}{% endblock %}
diff --git a/trunk/htsworkflow/frontend/templates/experiments/detail.html b/trunk/htsworkflow/frontend/templates/experiments/detail.html
new file mode 100644 (file)
index 0000000..65d3c00
--- /dev/null
@@ -0,0 +1,7 @@
+{% if run_f %}
+    <ul>
+        RUN FOLDER:  <li>{{ run_f.run_folder }}</li>
+    </ul>
+{% else %}
+    <p>Run folder not found.</p>
+{% endif %}
diff --git a/trunk/htsworkflow/frontend/templates/experiments/flowcellSheet.html b/trunk/htsworkflow/frontend/templates/experiments/flowcellSheet.html
new file mode 100644 (file)
index 0000000..66b7b4d
--- /dev/null
@@ -0,0 +1,117 @@
+<html><head><title>{{ fc.flowcell_id }} - GA SEQUENCING (SOLEXA) LOG</title>
+<style type="text/css">
+<!--
+TD
+  {
+  color:black;
+  font-size:9pt;
+  font-face:Arial;
+  }
+-->
+</style>
+</head><body>
+{% if fc %}
+<table border="1" cellspacing="1">
+<tr><td colspan=2 nowrap><b>GA SEQUENCING (SOLEXA) LOG</b></td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td></tr>
+<tr><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td></tr>
+<tr><td>Date Run Started</td><td colspan=2>{{ fc.run_date }}</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td></tr>
+<tr><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td></tr>
+<tr><td>Cluster station used</td><td colspan=2 nowrap>{{ fc.cluster_mac_id }}</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td></tr>
+<tr><td>GA used</td><td colspan=2 nowrap>{{ fc.seq_mac_id }}</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td></tr>
+
+<tr><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td></tr>
+<tr><td>Flowcell number</td><td colspan=2 nowrap>{{ fc.flowcell_id }}</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td></tr>
+<tr><td>Number of Tiles per Lane</td><td>100</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td></tr>
+<tr><td>Number of Cycles</td><td>{{ fc.read_length  }}</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td></tr>
+<tr><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td></tr>
+<tr><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td></tr>
+<tr><td><font point-size="9" face="Arial"><b>SAMPLE INFORMATION</b><font></td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td></tr>
+<tr><td>&nbsp;</td><td>FC#</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td colspan=2 nowrap>FC bar code</td></tr>
+
+<tr><td valign=middle nowrap>Lane</td><td>1</td><td>2</td><td>3</td><td>4</td><td>5</td><td>6</td><td>7</td><td>8</td></tr>
+<tr><td valign=middle nowrap>Solexa Library Number</td>
+<td bgcolor=#CCFFCC>{{ fc.lane_1_library.library_id }}</td>
+<td bgcolor=#CCFFCC>{{ fc.lane_2_library.library_id }}</td>
+<td bgcolor=#CCFFCC>{{ fc.lane_3_library.library_id }}</td>
+<td bgcolor=#CCFFCC>{{ fc.lane_4_library.library_id }}</td>
+<td bgcolor=#CCFFCC>{{ fc.lane_5_library.library_id }}</td>
+<td bgcolor=#CCFFCC>{{ fc.lane_6_library.library_id }}</td>
+<td bgcolor=#CCFFCC>{{ fc.lane_7_library.library_id }}</td>
+<td bgcolor=#CCFFCC>{{ fc.lane_8_library.library_id }}</td></tr>
+
+<tr><td valign=middle nowrap>Sample Name</td>
+<td bgcolor=#CCFFCC>{{ fc.lane_1_library.library_name }}</td>
+<td bgcolor=#CCFFCC>{{ fc.lane_2_library.library_name }}</td>
+<td bgcolor=#CCFFCC>{{ fc.lane_3_library.library_name }}</td>
+<td bgcolor=#CCFFCC>{{ fc.lane_4_library.library_name }}</td>
+<td bgcolor=#CCFFCC>{{ fc.lane_5_library.library_name }}</td>
+<td bgcolor=#CCFFCC>{{ fc.lane_6_library.library_name }}</td>
+<td bgcolor=#CCFFCC>{{ fc.lane_7_library.library_name }}</td>
+<td bgcolor=#CCFFCC>{{ fc.lane_8_library.library_name }}</td>
+</tr>
+
+<tr><td valign=middle nowrap>Organism</td>
+<td bgcolor=#CCFFCC>{{ fc.lane_1_library.library_species.common_name }}</td>
+<td bgcolor=#CCFFCC>{{ fc.lane_2_library.library_species.common_name }}</td>
+<td bgcolor=#CCFFCC>{{ fc.lane_3_library.library_species.common_name }}</td>
+<td bgcolor=#CCFFCC>{{ fc.lane_4_library.library_species.common_name }}</td>
+<td bgcolor=#CCFFCC>{{ fc.lane_5_library.library_species.common_name }}</td>
+<td bgcolor=#CCFFCC>{{ fc.lane_6_library.library_species.common_name }}</td>
+<td bgcolor=#CCFFCC>{{ fc.lane_7_library.library_species.common_name }}</td>
+<td bgcolor=#CCFFCC>{{ fc.lane_8_library.library_species.common_name }}</td>
+</tr>
+
+<tr><td valign=middle nowrap>Submitter</td>
+<td bgcolor=#CCFFCC>{{ fc.lane_1_library.made_by }}</td>
+<td bgcolor=#CCFFCC>{{ fc.lane_2_library.made_by }}</td>
+<td bgcolor=#CCFFCC>{{ fc.lane_3_library.made_by }}</td>
+<td bgcolor=#CCFFCC>{{ fc.lane_4_library.made_by }}</td>
+<td bgcolor=#CCFFCC>{{ fc.lane_5_library.made_by }}</td>
+<td bgcolor=#CCFFCC>{{ fc.lane_6_library.made_by }}</td>
+<td bgcolor=#CCFFCC>{{ fc.lane_7_library.made_by }}</td>
+<td bgcolor=#CCFFCC>{{ fc.lane_8_library.made_by }}</td>
+</tr>
+
+
+<tr><td valign=middle nowrap>First time run?</td><td bgcolor=#CCFFCC></td><td bgcolor=#CCFFCC></td><td bgcolor=#CCFFCC></td><td bgcolor=#CCFFCC></td><td bgcolor=#CCFFCC></td><td bgcolor=#CCFFCC></td><td bgcolor=#CCFFCC></td><td bgcolor=#CCFFCC></td></tr>
+
+<tr><td valign=middle nowrap>Average Library Size (bp)</td>
+<td bgcolor=#CCFFCC>{{ fc.lane_1_library.avg_lib_size }}</td>
+<td bgcolor=#CCFFCC>{{ fc.lane_2_library.avg_lib_size }}</td>
+<td bgcolor=#CCFFCC>{{ fc.lane_3_library.avg_lib_size }}</td>
+<td bgcolor=#CCFFCC>{{ fc.lane_4_library.avg_lib_size }}</td>
+<td bgcolor=#CCFFCC>{{ fc.lane_5_library.avg_lib_size }}</td>
+<td bgcolor=#CCFFCC>{{ fc.lane_6_library.avg_lib_size }}</td>
+<td bgcolor=#CCFFCC>{{ fc.lane_7_library.avg_lib_size }}</td>
+<td bgcolor=#CCFFCC>{{ fc.lane_8_library.avg_lib_size }}</td>
+</tr>  
+
+<tr><td valign=middle nowrap>Template Concentration (ng/ul)</td>
+<td bgcolor=#CCFFCC>{{ fc.lane_1_library.undiluted_concentration }}</td>
+<td bgcolor=#CCFFCC>{{ fc.lane_2_library.undiluted_concentration }}</td>
+<td bgcolor=#CCFFCC>{{ fc.lane_3_library.undiluted_concentration }}</td>
+<td bgcolor=#CCFFCC>{{ fc.lane_4_library.undiluted_concentration }}</td>
+<td bgcolor=#CCFFCC>{{ fc.lane_5_library.undiluted_concentration }}</td>
+<td bgcolor=#CCFFCC>{{ fc.lane_6_library.undiluted_concentration }}</td>
+<td bgcolor=#CCFFCC>{{ fc.lane_7_library.undiluted_concentration }}</td>
+<td bgcolor=#CCFFCC>{{ fc.lane_8_library.undiluted_concentration }}</td>
+</tr>
+
+<tr><td valign=middle nowrap>Run Concentration (pM)</td>
+<td bgcolor=#CCFFCC>{{ fc.lane_1_pM }}</td>
+<td bgcolor=#CCFFCC>{{ fc.lane_2_pM }}</td>
+<td bgcolor=#CCFFCC>{{ fc.lane_3_pM }}</td>
+<td bgcolor=#CCFFCC>{{ fc.lane_4_pM }}</td>
+<td bgcolor=#CCFFCC>{{ fc.lane_5_pM }}</td>
+<td bgcolor=#CCFFCC>{{ fc.lane_6_pM }}</td>
+<td bgcolor=#CCFFCC>{{ fc.lane_7_pM }}</td>
+<td bgcolor=#CCFFCC>{{ fc.lane_8_pM }}</td>
+</tr>
+
+</table>
+
+{% else %}
+    <p>Flowcell object missing. Can't create sheet.</p>
+{% endif %}
+
+</body></html
diff --git a/trunk/htsworkflow/frontend/templates/experiments/index.html b/trunk/htsworkflow/frontend/templates/experiments/index.html
new file mode 100644 (file)
index 0000000..1105bec
--- /dev/null
@@ -0,0 +1,9 @@
+{% if data_run_list %}
+    <ul>
+    {% for run in data_run_list %}
+        <li>{{ run.run_folder }}</li>
+    {% endfor %}
+    </ul>
+{% else %}
+    <p>No data runs are available.</p>
+{% endif %}
diff --git a/trunk/htsworkflow/frontend/templates/reports/report.html b/trunk/htsworkflow/frontend/templates/reports/report.html
new file mode 100644 (file)
index 0000000..c134377
--- /dev/null
@@ -0,0 +1,11 @@
+{% extends "admin/base_site.html" %}
+
+{% if main %}
+{% block content %}
+ {{ main|safe }}
+
+{% endblock %}
+{% else %}
+    <p>No content. Can't create report.</p>
+{% endif %}
diff --git a/trunk/htsworkflow/frontend/templates/samples/library_detail.html b/trunk/htsworkflow/frontend/templates/samples/library_detail.html
new file mode 100644 (file)
index 0000000..4c5c2b8
--- /dev/null
@@ -0,0 +1,140 @@
+{% load humanize %}
+<style type="text/css">
+  /* <![CDATA[ */
+  table, td {
+    border-style: solid;
+  }
+  table {
+    border-width: 0 0 1px 1px;
+    border-spacing: 0;
+    border-collapse: collapse;
+  }
+  td {
+    margin: 0;
+    padding: 4px;
+    border-width: 1px 1px 0 0;
+  }
+  thead {
+    text-align: center;
+    }
+  tbody {
+    text-align: right;
+  }
+  /* ]]> */
+</style>
+
+<h2>About this library</h2>
+<b>Library ID</b>: {{ lib.library_id }}<br/>
+<b>Name</b>: {{ lib.library_name }}<br/>
+<b>Species</b>: {{ lib.library_species.scientific_name }}<br/>
+<b>Affiliations</b>:
+<ul>
+  {% for individual in lib.affiliations.all %}
+    <li>{{ individual.name }} ( {{ individual.contact }} )</li>
+  {% endfor %}
+</ul>
+
+<h2>Raw Result Files</h2>
+<table>
+<thead>
+  <tr>
+    <td>Cycle</td>
+    <td>Flowcell</td>
+    <td>Lane</td>
+    <td>Summary</td>
+    <td>Eland</td>
+    <td>Bed</td>
+  </tr>
+{% for result in eland_results %}
+<tr>
+  <td>{{ result.cycle }}</td>
+  <td>{{ result.flowcell_id }}</td>
+  <td>{{ result.lane }}</td>
+  <td><a href="{{ result.summary_url }}">Summary</a></td>
+  <td><a href="{{ result.result_url }}">{{ result.result_label }}</a></td>
+  <td>
+  {% if result.bed_url %}
+    <a href="{{ result.bed_url }}">Bed</a>
+  {% endif %}
+  </td>
+</tr>
+{% endfor %}
+</table>
+
+<h2>Lane Summary Statistics</h2>
+{% block summary_stats %}
+<table>
+  <thead>
+    <tr>
+      <td colspan="7"></td>
+      <td colspan="2">No Match</td>
+      <td colspan="2">QC Failed</td>
+      <td colspan="4">Unique</td>
+      <td colspan="4">Repeat</td>
+    </tr>
+    <tr>
+    <td>Cycles</td>
+    <td>Flowcell</td>
+    <td>Lane</td>
+    <td>End</td>
+    <td>Cluster / Tile</td>
+    <td>pM</td>
+    <td>Raw Reads</td>
+    <td>total</td>
+    <td>%</td>
+    <td>total</td>
+    <td>%</td>
+    <td>0 mismatch</td>
+    <td>1 mismatch</td>
+    <td>2 mismatch</td>
+    <td>Total</td>
+    <td>0 mismatch</td>
+    <td>1 mismatch</td>
+    <td>2 mismatch</td>
+    <td>Total</td>
+    </tr>
+  </thead>
+  <tbody>
+
+    {% for lane in lane_summary_list %}
+    <tr>
+      <td>{{ lane.cycle_width }}</td>
+      <td>{{ lane.flowcell_id }}</td>
+      <td>{{ lane.lane_id }}</td>
+      <td>{% if lane.end %}{{ lane.end }}{% endif %}</td>
+      <td>{{ lane.clusters.0|intcomma }}</td>
+      <td>{{ lane.successful_pm }}</td>
+      <td>{{ lane.reads|intcomma }}</td>
+      <td>{{ lane.no_match|intcomma }}</td>
+      <td>{{ lane.no_match_percent|stringformat:".2f" }}</td>
+      <td>{{ lane.qc_failed|intcomma }}</td>
+      <td>{{ lane.qc_failed_percent|stringformat:".2f" }}</td>
+      <td>{{ lane.match_codes.U0|intcomma }}</td>
+      <td>{{ lane.match_codes.U1|intcomma }}</td>
+      <td>{{ lane.match_codes.U2|intcomma }}</td>
+      <td>{{ lane.unique_reads|intcomma }}</td>
+      <td>{{ lane.match_codes.R0|intcomma }}</td>
+      <td>{{ lane.match_codes.R1|intcomma }}</td>
+      <td>{{ lane.match_codes.R2|intcomma }}</td>
+      <td>{{ lane.repeat_reads|intcomma }}</td>
+    </tr>
+    {% endfor %}
+  </tbody>
+</table>
+<br/>
+<hr/>
+<h2>Count of multi-reads</h2>
+{% for lane in lane_summary_list %}
+  {% if lane.summarized_reads %}
+  <h3>
+    {{lane.cycle_width}} {{ lane.flowcell_id }} lane {{ lane.lane_id }} 
+    {% if lane.end %} end {{ lane.end }}{% endif %}
+  </h3>
+  <ul>
+    {% for name, counts in lane.summarized_reads.items %}
+    <li><b>{{ name }}</b>: {{ counts|intcomma }}</li>
+    {% endfor %}
+  </ul>
+  {% endif %}
+{% endfor %}
+{% endblock %}
diff --git a/trunk/htsworkflow/frontend/templates/samples/library_index.html b/trunk/htsworkflow/frontend/templates/samples/library_index.html
new file mode 100644 (file)
index 0000000..c0a37e6
--- /dev/null
@@ -0,0 +1,59 @@
+{% extends "base_site.html" %}
+{% load adminmedia admin_list i18n %}
+
+{% block stylesheet %}{{ MEDIA_URL }}css/data-browse-index.css{% endblock %}
+
+{% block bodyclass %}change-list{% endblock %}
+{% block coltype %}flex{% endblock %}
+
+{% block content %}
+<div id="content-main">
+  <div class="module{% if cl.has_filters %} filtered{% endif %}" id="changelist">
+    {% block search %}{% search_form cl %}{% endblock %}
+
+    {% block filters %}
+    {% if cl.has_filters %}
+    <div id="changelist-filter">
+      <h2>{% trans 'Filter' %}</h2>
+      {% for spec in cl.filter_specs %}
+         {% admin_list_filter cl spec %}
+         {% endfor %}
+       </div>
+       {% endif %}
+       {% endblock %}
+
+  
+ {% block pagination %}{% pagination cl %}{% endblock %}
+
+{% block summary_stats %}
+<table>
+  <thead>
+    <tr>
+    <td>Parent</td>
+    <td>Library ID</td>
+    <td>Species</td>
+    <td>Library Name</td>
+    <td>Total Lanes</td>
+    <td>HD</td>
+    </tr>
+  </thead>
+  <tbody>
+    {% for lib in library_list %}
+    <tr>
+      <td><a href="/library/{{ lib.library_id }}">{{ lib.amplified_from }}</a></td>
+      <td><a href="/library/{{ lib.library_id }}">{{ lib.library_id }}</a></td>
+      <td><a href="/library/{{ lib.library_id }}">{{ lib.species_name }}</a></td>
+      <td><a href="/library/{{ lib.library_id }}">{{ lib.library_name }}</a></td>
+      <td>{{ lib.lanes_run }}</td>
+      {% if lib.is_archived %}
+        <td><img src="/static/img/hdd_unmount.png" alt="Archived" /></td>
+      {% else %}
+        <td></td>
+      {% endif %}
+    </tr>
+    {% endfor %}
+  </tbody>
+</table>
+</div>
+{% endblock %}
+{% endblock %}
diff --git a/trunk/htsworkflow/frontend/templates/search_form.html b/trunk/htsworkflow/frontend/templates/search_form.html
new file mode 100644 (file)
index 0000000..97ef8f1
--- /dev/null
@@ -0,0 +1,18 @@
+{% load adminmedia %}
+{% load i18n %}
+{% if cl.search_fields %}
+<div id="toolbar"><form id="changelist-search" action="" method="get">
+<div><!-- DIV needed for valid HTML -->
+<label for="searchbar"><img src="{% admin_media_prefix %}img/admin/icon_searchbox.png" alt="Search" /></label>
+<input type="text" size="40" name="{{ search_var }}" value="{{ cl.query }}" id="searchbar" />
+<input type="submit" value="{% trans 'Go' %}" />
+{% if show_result_count %}
+    <span class="small quiet">{% blocktrans count cl.result_count as counter %}1 result{% plural %}{{ counter }} results{% endblocktrans %} (<a href="?{% if cl.is_popup %}pop=1{% endif %}">{% blocktrans with cl.full_result_count as full_result_count %}{{ full_result_count }} total{% endblocktrans %}</a>)</span>
+{% endif %}
+{% for pair in cl.params.items %}
+    {% ifnotequal pair.0 search_var %}<input type="hidden" name="{{ pair.0 }}" value="{{ pair.1 }}"/>{% endifnotequal %}
+{% endfor %}
+</div>
+</form></div>
+<script type="text/javascript">document.getElementById("searchbar").focus();</script>
+{% endif %}
diff --git a/trunk/htsworkflow/frontend/urls.py b/trunk/htsworkflow/frontend/urls.py
new file mode 100644 (file)
index 0000000..8a558d1
--- /dev/null
@@ -0,0 +1,50 @@
+from django.conf.urls.defaults import *
+from django.contrib import admin
+admin.autodiscover()
+
+# Databrowser:
+#from django.contrib import databrowse
+#from htsworkflow.frontend.samples.models import Library
+#databrowse.site.register(Library)
+#databrowse.site.register(FlowCell)
+
+from htsworkflow.frontend import settings
+
+urlpatterns = patterns('',
+    # Base:
+    (r'^eland_config/', include('htsworkflow.frontend.eland_config.urls')),
+    # Admin:
+    (r'^admin/(.*)', admin.site.root),
+    # Experiments:
+    (r'^experiments/', include('htsworkflow.frontend.experiments.urls')),
+    # AnalysTrack:
+    #(r'^analysis/', include('htsworkflow.frontend.analysis.urls')),
+    # Report Views:
+    (r'^inventory/', include('htsworkflow.frontend.inventory.urls')),
+    (r'^reports/', include('htsworkflow.frontend.reports.urls')),
+    # Library browser
+    (r'^library/$', 'htsworkflow.frontend.samples.views.library'),
+    (r'^library/(?P<lib_id>\w+)/$', 
+      'htsworkflow.frontend.samples.views.library_to_flowcells'),
+    # library id to admin url
+    (r'^library_id_to_admin_url/(?P<lib_id>\w+)/$',
+     'htsworkflow.frontend.samples.views.library_id_to_admin_url'),
+    # Raw result files
+    (r'^results/(?P<fc_id>\w+)/(?P<cnm>C[1-9]-[0-9]+)/summary/',
+      'htsworkflow.frontend.samples.views.summaryhtm_fc_cnm'),
+    (r'^results/(?P<fc_id>\w+)/(?P<cnm>C[1-9]-[0-9]+)/eland_result/(?P<lane>[1-8])',
+      'htsworkflow.frontend.samples.views.result_fc_cnm_eland_lane'),
+    (r'^results/(?P<fc_id>\w+)/(?P<cnm>C[1-9]-[0-9]+)/bedfile/(?P<lane>[1-8])/ucsc',
+      'htsworkflow.frontend.samples.views.bedfile_fc_cnm_eland_lane_ucsc'),
+    (r'^results/(?P<fc_id>\w+)/(?P<cnm>C[1-9]-[0-9]+)/bedfile/(?P<lane>[1-8])',
+      'htsworkflow.frontend.samples.views.bedfile_fc_cnm_eland_lane'),
+    
+    # databrowser
+    #(r'^databrowse/(.*)', databrowse.site.root)
+)
+
+if settings.DEBUG:
+  urlpatterns += patterns('',
+      (r'^static/(?P<path>.*)$', 'django.views.static.serve', 
+        {'document_root': settings.MEDIA_ROOT}),
+  )
diff --git a/trunk/htsworkflow/pipelines/__init__.py b/trunk/htsworkflow/pipelines/__init__.py
new file mode 100644 (file)
index 0000000..beabfd1
--- /dev/null
@@ -0,0 +1,6 @@
+"""
+Provide code to interact with the vendor tools to produce useable "raw" data.
+
+the illumina sub-package contains components to interact with the Illumina provided
+GAPipeline
+"""
diff --git a/trunk/htsworkflow/pipelines/bustard.py b/trunk/htsworkflow/pipelines/bustard.py
new file mode 100644 (file)
index 0000000..f1d73eb
--- /dev/null
@@ -0,0 +1,331 @@
+"""
+Extract configuration from Illumina Bustard Directory.
+
+This includes the version number, run date, bustard executable parameters, and 
+phasing estimates. 
+"""
+from copy import copy
+from datetime import date
+from glob import glob
+import logging
+import os
+import re
+import sys
+import time
+
+from htsworkflow.pipelines.runfolder import \
+   ElementTree, \
+   VERSION_RE, \
+   EUROPEAN_STRPTIME
+
+# make epydoc happy
+__docformat__ = "restructuredtext en"
+
+LANE_LIST = range(1,9)
+
+class Phasing(object):
+    PHASING = 'Phasing'
+    PREPHASING = 'Prephasing'
+
+    def __init__(self, fromfile=None, xml=None):
+        self.lane = None
+        self.phasing = None
+        self.prephasing = None
+
+        if fromfile is not None:
+            self._initialize_from_file(fromfile)
+        elif xml is not None:
+            self.set_elements(xml)
+
+    def _initialize_from_file(self, pathname):
+        path, name = os.path.split(pathname)
+        basename, ext = os.path.splitext(name)
+        # the last character of the param base filename should be the
+        # lane number
+        tree = ElementTree.parse(pathname).getroot()
+        self.set_elements(tree)
+        self.lane = int(basename[-1])
+
+    def get_elements(self):
+        root = ElementTree.Element(Phasing.PHASING, {'lane': str(self.lane)})
+        root.tail = os.linesep
+        phasing = ElementTree.SubElement(root, Phasing.PHASING)
+        phasing.text = str(self.phasing)
+        phasing.tail = os.linesep
+        prephasing = ElementTree.SubElement(root, Phasing.PREPHASING)
+        prephasing.text = str(self.prephasing)
+        prephasing.tail = os.linesep
+        return root
+
+    def set_elements(self, tree):
+        if tree.tag not in ('Phasing', 'Parameters'):
+            raise ValueError('exptected Phasing or Parameters')
+        lane = tree.attrib.get('lane', None)
+        if lane is not None:
+            self.lane = int(lane)
+        for element in list(tree):
+            if element.tag == Phasing.PHASING:
+                self.phasing = float(element.text)
+            elif element.tag == Phasing.PREPHASING:
+                self.prephasing = float(element.text)
+
+class CrosstalkMatrix(object):
+    CROSSTALK = "MatrixElements"
+    BASE = 'Base'
+    ELEMENT = 'Element'
+
+    def __init__(self, fromfile=None, xml=None):
+        self.base = {}
+
+        if fromfile is not None:
+            self._initialize_from_file(fromfile)
+        elif xml is not None:
+            self.set_elements(xml)
+
+    def _initialize_from_file(self, pathname):
+        data = open(pathname).readlines()
+        auto_header = '# Auto-generated frequency response matrix'
+        if data[0].strip() != auto_header or len(data) != 9:
+            raise RuntimeError("matrix file %s is unusual" % (pathname,))
+        # skip over lines 1,2,3,4 which contain the 4 bases
+        self.base['A'] = [ float(v) for v in data[5].split() ]
+        self.base['C'] = [ float(v) for v in data[6].split() ]
+        self.base['G'] = [ float(v) for v in data[7].split() ]
+        self.base['T'] = [ float(v) for v in data[8].split() ]
+
+    def get_elements(self):
+        root = ElementTree.Element(CrosstalkMatrix.CROSSTALK)
+        root.tail = os.linesep
+        base_order = ['A','C','G','T']
+        for b in base_order:
+            base_element = ElementTree.SubElement(root, CrosstalkMatrix.BASE)
+            base_element.text = b
+            base_element.tail = os.linesep
+        for b in base_order:
+            for value in self.base[b]:
+                crosstalk_value = ElementTree.SubElement(root, CrosstalkMatrix.ELEMENT)
+                crosstalk_value.text = unicode(value)
+                crosstalk_value.tail = os.linesep
+
+        return root
+
+    def set_elements(self, tree):
+        if tree.tag != CrosstalkMatrix.CROSSTALK:
+            raise ValueError('Invalid run-xml exptected '+CrosstalkMatrix.CROSSTALK)
+        base_order = []
+        current_base = None
+        current_index = 0
+        for element in tree.getchildren():
+            # read in the order of the bases
+            if element.tag == 'Base':
+                base_order.append(element.text)
+            elif element.tag == 'Element':
+                # we're done reading bases, now its just the 4x4 matrix
+                # written out as a list of elements
+                # if this is the first element, make a copy of the list
+                # to play with and initialize an empty list for the current base
+                if current_base is None:
+                    current_base = copy(base_order)
+                    self.base[current_base[0]] = []
+                # we found (probably) 4 bases go to the next base
+                if current_index == len(base_order):
+                    current_base.pop(0)
+                    current_index = 0
+                    self.base[current_base[0]] = []
+                value = float(element.text)
+                self.base[current_base[0]].append(value)
+
+                current_index += 1
+            else:
+                raise RuntimeError("Unrecognized tag in run xml: %s" %(element.tag,))
+
+def crosstalk_matrix_from_bustard_config(bustard_path, bustard_config_tree):
+    """
+    Analyze the bustard config file and try to find the crosstalk matrix.
+    """
+    bustard_run = bustard_config_tree[0]
+    if bustard_run.tag != 'Run':
+        raise RuntimeError('Expected Run tag, got %s' % (bustard_run.tag,))
+
+    call_parameters = bustard_run.find('BaseCallParameters')
+    if call_parameters is None:
+        raise RuntimeError('Missing BaseCallParameters section')
+
+    matrix = call_parameters.find('Matrix')
+    if matrix is None:
+        raise RuntimeError('Expected to find Matrix in Bustard BaseCallParameters')
+
+    matrix_auto_flag = int(matrix.find('AutoFlag').text)
+    matrix_auto_lane = int(matrix.find('AutoLane').text)
+
+    if matrix_auto_flag:
+        # we estimated the matrix from something in this run.
+        # though we don't really care which lane it was
+        matrix_path = os.path.join(bustard_path, 'Matrix', 's_02_matrix.txt')
+        matrix = CrosstalkMatrix(matrix_path)
+    else:
+        # the matrix was provided
+        matrix_elements = call_parameters.find('MatrixElements')
+        if matrix_elements is None:
+            raise RuntimeError('Expected to find MatrixElements in Bustard BaseCallParameters')
+        matrix = CrosstalkMatrix(xml=matrix_elements)
+
+    return matrix
+
+class Bustard(object):
+    XML_VERSION = 2
+
+    # Xml Tags
+    BUSTARD = 'Bustard'
+    SOFTWARE_VERSION = 'version'
+    DATE = 'run_time'
+    USER = 'user'
+    PARAMETERS = 'Parameters'
+    BUSTARD_CONFIG = 'BaseCallAnalysis'
+
+    def __init__(self, xml=None):
+        self.version = None
+        self.date = date.today()
+        self.user = None
+        self.phasing = {}
+        self.crosstalk = None
+        self.pathname = None
+        self.bustard_config = None
+
+        if xml is not None:
+            self.set_elements(xml)
+
+    def _get_time(self):
+        return time.mktime(self.date.timetuple())
+    time = property(_get_time, doc='return run time as seconds since epoch')
+
+    def dump(self):
+        #print ElementTree.tostring(self.get_elements())
+        ElementTree.dump(self.get_elements())
+
+    def get_elements(self):
+        root = ElementTree.Element('Bustard', 
+                                   {'version': str(Bustard.XML_VERSION)})
+        version = ElementTree.SubElement(root, Bustard.SOFTWARE_VERSION)
+        version.text = self.version
+        run_date = ElementTree.SubElement(root, Bustard.DATE)
+        run_date.text = str(self.time)
+        user = ElementTree.SubElement(root, Bustard.USER)
+        user.text = self.user
+        params = ElementTree.SubElement(root, Bustard.PARAMETERS)
+
+        # add phasing parameters
+        for lane in LANE_LIST:
+            params.append(self.phasing[lane].get_elements())
+
+        # add crosstalk matrix if it exists
+        if self.crosstalk is not None:
+            root.append(self.crosstalk.get_elements())
+       
+        # add bustard config if it exists
+        if self.bustard_config is not None:
+            root.append(self.bustard_config)
+        return root
+
+    def set_elements(self, tree):
+        if tree.tag != Bustard.BUSTARD:
+            raise ValueError('Expected "Bustard" SubElements')
+        xml_version = int(tree.attrib.get('version', 0))
+        if xml_version > Bustard.XML_VERSION:
+            logging.warn('Bustard XML tree is a higher version than this class')
+        for element in list(tree):
+            if element.tag == Bustard.SOFTWARE_VERSION:
+                self.version = element.text
+            elif element.tag == Bustard.DATE:
+                self.date = date.fromtimestamp(float(element.text))
+            elif element.tag == Bustard.USER:
+                self.user = element.text
+            elif element.tag == Bustard.PARAMETERS:
+                for param in element:
+                    p = Phasing(xml=param)
+                    self.phasing[p.lane] = p
+            elif element.tag == CrosstalkMatrix.CROSSTALK:
+                self.crosstalk = CrosstalkMatrix(xml=element)
+            elif element.tag == Bustard.BUSTARD_CONFIG:
+                self.bustard_config = element
+            else:
+                raise ValueError("Unrecognized tag: %s" % (element.tag,))
+        
+def bustard(pathname):
+    """
+    Construct a Bustard object by analyzing an Illumina Bustard directory.
+
+    :Parameters:
+      - `pathname`: A bustard directory
+
+    :Return:
+      Fully initialized Bustard object.
+    """
+    b = Bustard()
+    pathname = os.path.abspath(pathname)
+    path, name = os.path.split(pathname)
+    groups = name.split("_")
+    version = re.search(VERSION_RE, groups[0])
+    b.version = version.group(1)
+    t = time.strptime(groups[1], EUROPEAN_STRPTIME)
+    b.date = date(*t[0:3])
+    b.user = groups[2]
+    b.pathname = pathname
+    bustard_config_filename = os.path.join(pathname, 'config.xml')
+    paramfiles = glob(os.path.join(pathname, "params?.xml"))
+    for paramfile in paramfiles:
+        phasing = Phasing(paramfile)
+        assert (phasing.lane >= 1 and phasing.lane <= 8)
+        b.phasing[phasing.lane] = phasing
+    # I only found these in Bustard1.9.5/1.9.6 directories
+    if b.version in ('1.9.5', '1.9.6'):
+        # at least for our runfolders for 1.9.5 and 1.9.6 matrix[1-8].txt are always the same
+        crosstalk_file = os.path.join(pathname, "matrix1.txt")
+        b.crosstalk = CrosstalkMatrix(crosstalk_file)
+    # for version 1.3.2 of the pipeline the bustard version number went down
+    # to match the rest of the pipeline. However there's now a nifty
+    # new (useful) bustard config file.
+    elif os.path.exists(bustard_config_filename):
+        bustard_config_root = ElementTree.parse(bustard_config_filename)
+        b.bustard_config = bustard_config_root.getroot()
+        b.crosstalk = crosstalk_matrix_from_bustard_config(b.pathname, b.bustard_config)
+
+    return b
+
+def fromxml(tree):
+    """
+    Reconstruct a htsworkflow.pipelines.Bustard object from an xml block
+    """
+    b = Bustard()
+    b.set_elements(tree)
+    return b
+
+def make_cmdline_parser():
+    from optparse import OptionParser
+    parser = OptionParser('%prog: bustard_directory')
+    return parser
+
+def main(cmdline):
+    parser = make_cmdline_parser()
+    opts, args = parser.parse_args(cmdline)
+
+    for bustard_dir in args:
+        print u'analyzing bustard directory: ' + unicode(bustard_dir)
+        bustard_object = bustard(bustard_dir)
+        bustard_object.dump() 
+
+        bustard_object2 = Bustard(xml=bustard_object.get_elements())
+        print ('-------------------------------------')
+        bustard_object2.dump()
+        print ('=====================================')
+        b1_tree = bustard_object.get_elements()
+        b1 = ElementTree.tostring(b1_tree).split(os.linesep)
+        b2_tree = bustard_object2.get_elements()
+        b2 = ElementTree.tostring(b2_tree).split(os.linesep)
+        for line1, line2 in zip(b1, b2):
+            if b1 != b2:
+                print "b1: ", b1
+                print "b2: ", b2
+
+if __name__ == "__main__":
+    main(sys.argv[1:])
diff --git a/trunk/htsworkflow/pipelines/configure_run.py b/trunk/htsworkflow/pipelines/configure_run.py
new file mode 100644 (file)
index 0000000..4d5cf86
--- /dev/null
@@ -0,0 +1,608 @@
+#!/usr/bin/python
+__docformat__ = "restructuredtext en"
+
+import subprocess
+import logging
+import time
+import re
+import os
+
+from htsworkflow.pipelines.retrieve_config import \
+     CONFIG_SYSTEM, CONFIG_USER, \
+     FlowCellNotFound, getCombinedOptions, saveConfigFile, WebError404
+from htsworkflow.pipelines.genome_mapper import DuplicateGenome, getAvailableGenomes, constructMapperDict
+from htsworkflow.pipelines.run_status import GARunStatus
+
+from pyinotify import WatchManager, ThreadedNotifier
+from pyinotify import EventsCodes, ProcessEvent
+
+class ConfigInfo:
+  
+  def __init__(self):
+    #run_path = firecrest analysis directory to run analysis from
+    self.run_path = None
+    self.bustard_path = None
+    self.config_filepath = None
+    self.status = None
+
+    #top level directory where all analyses are placed
+    self.base_analysis_dir = None
+    #analysis_dir, top level analysis dir...
+    # base_analysis_dir + '/070924_USI-EAS44_0022_FC12150'
+    self.analysis_dir = None
+
+
+  def createStatusObject(self):
+    """
+    Creates a status object which can be queried for
+    status of running the pipeline
+
+    returns True if object created
+    returns False if object cannot be created
+    """
+    if self.config_filepath is None:
+      return False
+
+    self.status = GARunStatus(self.config_filepath)
+    return True
+
+
+
+####################################
+# inotify event processor
+
+s_firecrest_finished = re.compile('Firecrest[0-9\._\-A-Za-z]+/finished.txt')
+s_bustard_finished = re.compile('Bustard[0-9\._\-A-Za-z]+/finished.txt')
+s_gerald_finished = re.compile('GERALD[0-9\._\-A-Za-z]+/finished.txt')
+
+s_gerald_all = re.compile('Firecrest[0-9\._\-A-Za-z]+/Bustard[0-9\._\-A-Za-z]+/GERALD[0-9\._\-A-Za-z]+/')
+s_bustard_all = re.compile('Firecrest[0-9\._\-A-Za-z]+/Bustard[0-9\._\-A-Za-z]+/')
+s_firecrest_all = re.compile('Firecrest[0-9\._\-A-Za-z]+/')
+
+class RunEvent(ProcessEvent):
+
+  def __init__(self, conf_info):
+
+    self.run_status_dict = {'firecrest': False,
+                            'bustard': False,
+                            'gerald': False}
+
+    self._ci = conf_info
+
+    ProcessEvent.__init__(self)
+    
+
+  def process_IN_CREATE(self, event):
+    fullpath = os.path.join(event.path, event.name)
+    if s_finished.search(fullpath):
+      logging.info("File Found: %s" % (fullpath))
+
+      if s_firecrest_finished.search(fullpath):
+        self.run_status_dict['firecrest'] = True
+        self._ci.status.updateFirecrest(event.name)
+      elif s_bustard_finished.search(fullpath):
+        self.run_status_dict['bustard'] = True
+        self._ci.status.updateBustard(event.name)
+      elif s_gerald_finished.search(fullpath):
+        self.run_status_dict['gerald'] = True
+        self._ci.status.updateGerald(event.name)
+
+    #WARNING: The following order is important!!
+    # Firecrest regex will catch all gerald, bustard, and firecrest
+    # Bustard regex will catch all gerald and bustard
+    # Gerald regex will catch all gerald
+    # So, order needs to be Gerald, Bustard, Firecrest, or this
+    #  won't work properly.
+    elif s_gerald_all.search(fullpath):
+      self._ci.status.updateGerald(event.name)
+    elif s_bustard_all.search(fullpath):
+      self._ci.status.updateBustard(event.name)
+    elif s_firecrest_all.search(fullpath):
+      self._ci.status.updateFirecrest(event.name)
+      
+    #print "Create: %s" % (os.path.join(event.path, event.name))
+
+  def process_IN_DELETE(self, event):
+    #print "Remove %s" % (os.path.join(event.path, event.name))
+    pass
+
+
+
+
+#FLAGS
+# Config Step Error
+RUN_ABORT = 'abort'
+# Run Step Error
+RUN_FAILED = 'failed'
+
+
+#####################################
+# Configure Step (goat_pipeline.py)
+#Info
+s_start = re.compile('Starting Genome Analyzer Pipeline')
+s_gerald = re.compile("[\S\s]+--GERALD[\S\s]+--make[\S\s]+")
+s_generating = re.compile('^Generating journals, Makefiles')
+s_seq_folder = re.compile('^Sequence folder: ')
+s_seq_folder_sub = re.compile('want to make ')
+s_stderr_taskcomplete = re.compile('^Task complete, exiting')
+
+#Errors
+s_invalid_cmdline = re.compile('Usage:[\S\s]*goat_pipeline.py')
+s_species_dir_err = re.compile('Error: Lane [1-8]:')
+s_goat_traceb = re.compile("^Traceback \(most recent call last\):")
+s_missing_cycles = re.compile('^Error: Tile s_[1-8]_[0-9]+: Different number of cycles: [0-9]+ instead of [0-9]+')
+
+SUPPRESS_MISSING_CYCLES = False
+
+
+##Ignore - Example of out above each ignore regex.
+#NOTE: Commenting out an ignore will cause it to be
+# logged as DEBUG with the logging module.
+#CF_STDERR_IGNORE_LIST = []
+s_skip = re.compile('s_[0-8]_[0-9]+')
+
+
+##########################################
+# Pipeline Run Step (make -j8 recursive)
+
+##Info
+s_finished = re.compile('finished')
+
+##Errors
+s_make_error = re.compile('^make[\S\s]+Error')
+s_no_gnuplot = re.compile('gnuplot: command not found')
+s_no_convert = re.compile('^Can\'t exec "convert"')
+s_no_ghostscript = re.compile('gs: command not found')
+
+##Ignore - Example of out above each ignore regex.
+#NOTE: Commenting out an ignore will cause it to be
+# logged as DEBUG with the logging module.
+#
+PL_STDERR_IGNORE_LIST = []
+# Info: PF 11802
+PL_STDERR_IGNORE_LIST.append( re.compile('^Info: PF') )
+# About to analyse intensity file s_4_0101_sig2.txt
+PL_STDERR_IGNORE_LIST.append( re.compile('^About to analyse intensity file') )
+# Will send output to standard output
+PL_STDERR_IGNORE_LIST.append( re.compile('^Will send output to standard output') )
+# Found 31877 clusters
+PL_STDERR_IGNORE_LIST.append( re.compile('^Found [0-9]+ clusters') )
+# Will use quality criterion ((CHASTITY>=0.6)
+PL_STDERR_IGNORE_LIST.append( re.compile('^Will use quality criterion') )
+# Quality criterion translated to (($F[5]>=0.6))
+PL_STDERR_IGNORE_LIST.append( re.compile('^Quality criterion translated to') )
+# opened /woldlab/trog/data1/king/070924_USI-EAS44_0022_FC12150/Data/C1-36_Firecrest1.9.1_14-11-2007_king.4/Bustard1.9.1_14-11-2007_king/s_4_0101_qhg.txt
+#  AND
+# opened s_4_0103_qhg.txt
+PL_STDERR_IGNORE_LIST.append( re.compile('^opened[\S\s]+qhg.txt') )
+# 81129 sequences out of 157651 passed filter criteria
+PL_STDERR_IGNORE_LIST.append( re.compile('^[0-9]+ sequences out of [0-9]+ passed filter criteria') )
+
+
+def pl_stderr_ignore(line):
+  """
+  Searches lines for lines to ignore (i.e. not to log)
+
+  returns True if line should be ignored
+  returns False if line should NOT be ignored
+  """
+  for s in PL_STDERR_IGNORE_LIST:
+    if s.search(line):
+      return True
+  return False
+
+
+def config_stdout_handler(line, conf_info):
+  """
+  Processes each line of output from GOAT
+  and stores useful information using the logging module
+
+  Loads useful information into conf_info as well, for future
+  use outside the function.
+
+  returns True if found condition that signifies success.
+  """
+
+  # Skip irrelevant line (without logging)
+  if s_skip.search(line):
+    pass
+
+  # Detect invalid command-line arguments
+  elif s_invalid_cmdline.search(line):
+    logging.error("Invalid commandline options!")
+
+  # Detect starting of configuration
+  elif s_start.search(line):
+    logging.info('START: Configuring pipeline')
+
+  # Detect it made it past invalid arguments
+  elif s_gerald.search(line):
+    logging.info('Running make now')
+
+  # Detect that make files have been generated (based on output)
+  elif s_generating.search(line):
+    logging.info('Make files generted')
+    return True
+
+  # Capture run directory
+  elif s_seq_folder.search(line):
+    mo = s_seq_folder_sub.search(line)
+    #Output changed when using --tiles=<tiles>
+    # at least in pipeline v0.3.0b2
+    if mo:
+      firecrest_bustard_gerald_makefile = line[mo.end():]
+      firecrest_bustard_gerald, junk = \
+                                os.path.split(firecrest_bustard_gerald_makefile)
+      firecrest_bustard, junk = os.path.split(firecrest_bustard_gerald)
+      firecrest, junk = os.path.split(firecrest_bustard)
+
+      conf_info.bustard_path = firecrest_bustard
+      conf_info.run_path = firecrest
+    
+    #Standard output handling
+    else:
+      print 'Sequence line:', line
+      mo = s_seq_folder.search(line)
+      conf_info.bustard_path = line[mo.end():]
+      conf_info.run_path, temp = os.path.split(conf_info.bustard_path)
+
+  # Log all other output for debugging purposes
+  else:
+    logging.warning('CONF:?: %s' % (line))
+
+  return False
+
+
+
+def config_stderr_handler(line, conf_info):
+  """
+  Processes each line of output from GOAT
+  and stores useful information using the logging module
+
+  Loads useful information into conf_info as well, for future
+  use outside the function.
+
+  returns RUN_ABORT upon detecting failure;
+          True on success message;
+          False if neutral message
+            (i.e. doesn't signify failure or success)
+  """
+  global SUPPRESS_MISSING_CYCLES
+
+  # Detect invalid species directory error
+  if s_species_dir_err.search(line):
+    logging.error(line)
+    return RUN_ABORT
+  # Detect goat_pipeline.py traceback
+  elif s_goat_traceb.search(line):
+    logging.error("Goat config script died, traceback in debug output")
+    return RUN_ABORT
+  # Detect indication of successful configuration (from stderr; odd, but ok)
+  elif s_stderr_taskcomplete.search(line):
+    logging.info('Configure step successful (from: stderr)')
+    return True
+  # Detect missing cycles
+  elif s_missing_cycles.search(line):
+
+    # Only display error once
+    if not SUPPRESS_MISSING_CYCLES:
+      logging.error("Missing cycles detected; Not all cycles copied?")
+      logging.debug("CONF:STDERR:MISSING_CYCLES: %s" % (line))
+      SUPPRESS_MISSING_CYCLES = True
+    return RUN_ABORT
+  
+  # Log all other output as debug output
+  else:
+    logging.debug('CONF:STDERR:?: %s' % (line))
+
+  # Neutral (not failure; nor success)
+  return False
+
+
+#def pipeline_stdout_handler(line, conf_info):
+#  """
+#  Processes each line of output from running the pipeline
+#  and stores useful information using the logging module
+#
+#  Loads useful information into conf_info as well, for future
+#  use outside the function.
+#
+#  returns True if found condition that signifies success.
+#  """
+#
+#  #f.write(line + '\n')
+#
+#  return True
+
+
+
+def pipeline_stderr_handler(line, conf_info):
+  """
+  Processes each line of stderr from pipelien run
+  and stores useful information using the logging module
+
+  ##FIXME: Future feature (doesn't actually do this yet)
+  #Loads useful information into conf_info as well, for future
+  #use outside the function.
+
+  returns RUN_FAILED upon detecting failure;
+          #True on success message; (no clear success state)
+          False if neutral message
+            (i.e. doesn't signify failure or success)
+  """
+
+  if pl_stderr_ignore(line):
+    pass
+  elif s_make_error.search(line):
+    logging.error("make error detected; run failed")
+    return RUN_FAILED
+  elif s_no_gnuplot.search(line):
+    logging.error("gnuplot not found")
+    return RUN_FAILED
+  elif s_no_convert.search(line):
+    logging.error("imagemagick's convert command not found")
+    return RUN_FAILED
+  elif s_no_ghostscript.search(line):
+    logging.error("ghostscript not found")
+    return RUN_FAILED
+  else:
+    logging.debug('PIPE:STDERR:?: %s' % (line))
+
+  return False
+
+
+def retrieve_config(conf_info, flowcell, cfg_filepath, genome_dir):
+  """
+  Gets the config file from server...
+  requires config file in:
+    /etc/ga_frontend/ga_frontend.conf
+   or
+    ~/.ga_frontend.conf
+
+  with:
+  [config_file_server]
+  base_host_url: http://host:port
+
+  return True if successful, False is failure
+  """
+  options = getCombinedOptions()
+
+  if options.url is None:
+    logging.error("%s or %s missing base_host_url option" % \
+                  (CONFIG_USER, CONFIG_SYSTEM))
+    return False
+
+  try:
+    saveConfigFile(flowcell, options.url, cfg_filepath)
+    conf_info.config_filepath = cfg_filepath
+  except FlowCellNotFound, e:
+    logging.error(e)
+    return False
+  except WebError404, e:
+    logging.error(e)
+    return False
+  except IOError, e:
+    logging.error(e)
+    return False
+  except Exception, e:
+    logging.error(e)
+    return False
+
+  f = open(cfg_filepath, 'r')
+  data = f.read()
+  f.close()
+
+  genome_dict = getAvailableGenomes(genome_dir)
+  mapper_dict = constructMapperDict(genome_dict)
+
+  logging.debug(data)
+
+  f = open(cfg_filepath, 'w')
+  f.write(data % (mapper_dict))
+  f.close()
+  
+  return True
+  
+
+
+def configure(conf_info):
+  """
+  Attempts to configure the GA pipeline using goat.
+
+  Uses logging module to store information about status.
+
+  returns True if configuration successful, otherwise False.
+  """
+  #ERROR Test:
+  #pipe = subprocess.Popen(['goat_pipeline.py',
+  #                         '--GERALD=config32bk.txt',
+  #                         '--make .',],
+  #                         #'.'],
+  #                        stdout=subprocess.PIPE,
+  #                        stderr=subprocess.PIPE)
+
+  #ERROR Test (2), causes goat_pipeline.py traceback
+  #pipe = subprocess.Popen(['goat_pipeline.py',
+  #                  '--GERALD=%s' % (conf_info.config_filepath),
+  #                         '--tiles=s_4_100,s_4_101,s_4_102,s_4_103,s_4_104',
+  #                         '--make',
+  #                         '.'],
+  #                        stdout=subprocess.PIPE,
+  #                        stderr=subprocess.PIPE)
+
+  ##########################
+  # Run configuration step
+  #   Not a test; actual configure attempt.
+  #pipe = subprocess.Popen(['goat_pipeline.py',
+  #                  '--GERALD=%s' % (conf_info.config_filepath),
+  #                         '--make',
+  #                         '.'],
+  #                        stdout=subprocess.PIPE,
+  #                        stderr=subprocess.PIPE)
+
+
+  stdout_filepath = os.path.join(conf_info.analysis_dir,
+                                 "pipeline_configure_stdout.txt")
+  stderr_filepath = os.path.join(conf_info.analysis_dir,
+                                 "pipeline_configure_stderr.txt")
+
+  fout = open(stdout_filepath, 'w')
+  ferr = open(stderr_filepath, 'w')
+  
+  pipe = subprocess.Popen(['goat_pipeline.py',
+                           '--GERALD=%s' % (conf_info.config_filepath),
+                           '--make',
+                           conf_info.analysis_dir],
+                           stdout=fout,
+                           stderr=ferr)
+
+  print "Configuring pipeline: %s" % (time.ctime())
+  error_code = pipe.wait()
+
+  # Clean up
+  fout.close()
+  ferr.close()
+  
+  
+  ##################
+  # Process stdout
+  fout = open(stdout_filepath, 'r')
+  
+  stdout_line = fout.readline()
+
+  complete = False
+  while stdout_line != '':
+    # Handle stdout
+    if config_stdout_handler(stdout_line, conf_info):
+      complete = True
+    stdout_line = fout.readline()
+
+  fout.close()
+
+
+  #error_code = pipe.wait()
+  if error_code:
+    logging.error('Recieved error_code: %s' % (error_code))
+  else:
+    logging.info('We are go for launch!')
+
+  #Process stderr
+  ferr = open(stderr_filepath, 'r')
+  stderr_line = ferr.readline()
+
+  abort = 'NO!'
+  stderr_success = False
+  while stderr_line != '':
+    stderr_status = config_stderr_handler(stderr_line, conf_info)
+    if stderr_status == RUN_ABORT:
+      abort = RUN_ABORT
+    elif stderr_status is True:
+      stderr_success = True
+    stderr_line = ferr.readline()
+
+  ferr.close()
+
+
+  #Success requirements:
+  # 1) The stdout completed without error
+  # 2) The program exited with status 0
+  # 3) No errors found in stdout
+  print '#Expect: True, False, True, True'
+  print complete, bool(error_code), abort != RUN_ABORT, stderr_success is True
+  status = complete is True and \
+           bool(error_code) is False and \
+           abort != RUN_ABORT and \
+           stderr_success is True
+
+  # If everything was successful, but for some reason
+  #  we didn't retrieve the path info, log it.
+  if status is True:
+    if conf_info.bustard_path is None or conf_info.run_path is None:
+      logging.error("Failed to retrieve run_path")
+      return False
+  
+  return status
+
+
+def run_pipeline(conf_info):
+  """
+  Run the pipeline and monitor status.
+  """
+  # Fail if the run_path doesn't actually exist
+  if not os.path.exists(conf_info.run_path):
+    logging.error('Run path does not exist: %s' \
+              % (conf_info.run_path))
+    return False
+
+  # Change cwd to run_path
+  stdout_filepath = os.path.join(conf_info.analysis_dir, 'pipeline_run_stdout.txt')
+  stderr_filepath = os.path.join(conf_info.analysis_dir, 'pipeline_run_stderr.txt')
+
+  # Create status object
+  conf_info.createStatusObject()
+
+  # Monitor file creation
+  wm = WatchManager()
+  mask = EventsCodes.IN_DELETE | EventsCodes.IN_CREATE
+  event = RunEvent(conf_info)
+  notifier = ThreadedNotifier(wm, event)
+  notifier.start()
+  wdd = wm.add_watch(conf_info.run_path, mask, rec=True)
+
+  # Log pipeline starting
+  logging.info('STARTING PIPELINE @ %s' % (time.ctime()))
+  
+  # Start the pipeline (and hide!)
+  #pipe = subprocess.Popen(['make',
+  #                         '-j8',
+  #                         'recursive'],
+  #                        stdout=subprocess.PIPE,
+  #                        stderr=subprocess.PIPE)
+
+  fout = open(stdout_filepath, 'w')
+  ferr = open(stderr_filepath, 'w')
+
+  pipe = subprocess.Popen(['make',
+                           '--directory=%s' % (conf_info.run_path),
+                           '-j8',
+                           'recursive'],
+                           stdout=fout,
+                           stderr=ferr)
+                           #shell=True)
+  # Wait for run to finish
+  retcode = pipe.wait()
+
+
+  # Clean up
+  notifier.stop()
+  fout.close()
+  ferr.close()
+
+  # Process stderr
+  ferr = open(stderr_filepath, 'r')
+
+  run_failed_stderr = False
+  for line in ferr:
+    err_status = pipeline_stderr_handler(line, conf_info)
+    if err_status == RUN_FAILED:
+      run_failed_stderr = True
+
+  ferr.close()
+
+  # Finished file check!
+  print 'RUN SUCCESS CHECK:'
+  for key, value in event.run_status_dict.items():
+    print '  %s: %s' % (key, value)
+
+  dstatus = event.run_status_dict
+
+  # Success or failure check
+  status = (retcode == 0) and \
+           run_failed_stderr is False and \
+           dstatus['firecrest'] is True and \
+           dstatus['bustard'] is True and \
+           dstatus['gerald'] is True
+
+  return status
+
+
diff --git a/trunk/htsworkflow/pipelines/eland.py b/trunk/htsworkflow/pipelines/eland.py
new file mode 100644 (file)
index 0000000..559a2a2
--- /dev/null
@@ -0,0 +1,605 @@
+"""
+Analyze ELAND files
+"""
+
+from glob import glob
+import logging
+import os
+import re
+import stat
+
+from htsworkflow.pipelines.runfolder import ElementTree
+from htsworkflow.util.ethelp import indent, flatten
+from htsworkflow.util.opener import autoopen
+
+SAMPLE_NAME = 'SampleName'
+LANE_ID = 'LaneID'
+END = 'End'
+READS = 'Reads'
+
+GENOME_MAP = 'GenomeMap'
+GENOME_ITEM = 'GenomeItem'
+MAPPED_READS = 'MappedReads'
+MAPPED_ITEM = 'MappedItem'
+MATCH_CODES = 'MatchCodes'
+MATCH_ITEM = 'Code'
+READS = 'Reads'
+
+ELAND_SINGLE = 0
+ELAND_MULTI = 1
+ELAND_EXTENDED = 2
+ELAND_EXPORT = 3
+
+
+class ResultLane(object):
+    """
+    Base class for result lanes
+    """
+    XML_VERSION = 2
+    LANE = 'ResultLane'
+
+    def __init__(self, pathname=None, lane_id=None, end=None, xml=None):
+        self.pathname = pathname
+        self._sample_name = None
+        self.lane_id = lane_id
+        self.end = end
+        self._reads = None
+        
+        if xml is not None:
+            self.set_elements(xml)
+
+    def _update(self):
+        """
+        Actually read the file and actually count the reads
+        """
+        raise NotImplementedError("Can't count abstract classes")
+
+    def _update_name(self):
+        # extract the sample name
+        if self.pathname is None:
+            return
+
+        path, name = os.path.split(self.pathname)
+        split_name = name.split('_')
+        self._sample_name = split_name[0]
+
+    def _get_sample_name(self):
+        if self._sample_name is None:
+            self._update_name()
+        return self._sample_name
+    sample_name = property(_get_sample_name)
+
+    def _get_reads(self):
+        if self._reads is None:
+            self._update()
+        return self._reads
+    reads = property(_get_reads)
+
+
+class ElandLane(ResultLane):
+    """
+    Process an eland result file
+    """
+    XML_VERSION = 2
+    LANE = "ElandLane"
+
+    def __init__(self, pathname=None, lane_id=None, end=None, genome_map=None, eland_type=None, xml=None):
+        super(ElandLane, self).__init__(pathname, lane_id, end)
+
+        self._mapped_reads = None
+        self._match_codes = None
+        if genome_map is None:
+            genome_map = {}
+        self.genome_map = genome_map
+        self.eland_type = None
+
+        if xml is not None:
+            self.set_elements(xml)
+
+    def _guess_eland_type(self, pathname):
+        if self.eland_type is None:
+          # attempt autodetect eland file type
+          pathn, name = os.path.split(pathname)
+          if re.search('result', name):
+            self.eland_type = ELAND_SINGLE
+          elif re.search('multi', name):
+            self.eland_type = ELAND_MULTI
+          elif re.search('extended', name):
+            self.eland_type = ELAND_EXTENDED
+          elif re.search('export', name):
+            self.eland_type = ELAND_EXPORT
+          else:
+            self.eland_type = ELAND_SINGLE
+
+    def _update(self):
+        """
+        Actually read the file and actually count the reads
+        """
+        # can't do anything if we don't have a file to process
+        if self.pathname is None:
+            return
+        self._guess_eland_type(self.pathname)
+
+        if os.stat(self.pathname)[stat.ST_SIZE] == 0:
+            raise RuntimeError("Eland isn't done, try again later.")
+
+        logging.info("summarizing results for %s" % (self.pathname))
+
+        if self.eland_type == ELAND_SINGLE:
+          result = self._update_eland_result(self.pathname)
+        elif self.eland_type == ELAND_MULTI or \
+             self.eland_type == ELAND_EXTENDED:
+          result = self._update_eland_multi(self.pathname)
+        else:
+          raise NotImplementedError("Only support single/multi/extended eland files")
+        self._match_codes, self._mapped_reads, self._reads = result
+
+    def _update_eland_result(self, pathname):
+        reads = 0
+        mapped_reads = {}
+
+        match_codes = {'NM':0, 'QC':0, 'RM':0,
+                       'U0':0, 'U1':0, 'U2':0,
+                       'R0':0, 'R1':0, 'R2':0,
+                      }
+        for line in autoopen(pathname,'r'):
+            reads += 1
+            fields = line.split()
+            # code = fields[2]
+            # match_codes[code] = match_codes.setdefault(code, 0) + 1
+            # the QC/NM etc codes are in the 3rd field and always present
+            match_codes[fields[2]] += 1
+            # ignore lines that don't have a fasta filename
+            if len(fields) < 7:
+                continue
+            fasta = self.genome_map.get(fields[6], fields[6])
+            mapped_reads[fasta] = mapped_reads.setdefault(fasta, 0) + 1
+        return match_codes, mapped_reads, reads
+
+    def _update_eland_multi(self, pathname):
+        reads = 0
+        mapped_reads = {}
+
+        match_codes = {'NM':0, 'QC':0, 'RM':0,
+                       'U0':0, 'U1':0, 'U2':0,
+                       'R0':0, 'R1':0, 'R2':0,
+                      }
+        match_counts_re = re.compile("([\d]+):([\d]+):([\d]+)")
+        for line in autoopen(pathname,'r'):
+            reads += 1
+            fields = line.split()
+            # fields[2] = QC/NM/or number of matches
+            groups = match_counts_re.match(fields[2])
+            if groups is None:
+                match_codes[fields[2]] += 1
+            else:
+                # when there are too many hit, eland  writes a - where
+                # it would have put the list of hits.
+                # or in a different version of eland, it just leaves
+                # that column blank, and only outputs 3 fields.     
+                if len(fields) < 4 or fields[3] == '-':
+                  continue
+                zero_mismatches = int(groups.group(1))
+                if zero_mismatches == 1:
+                  match_codes['U0'] += 1
+                elif zero_mismatches < 255:
+                  match_codes['R0'] += zero_mismatches
+
+                one_mismatches = int(groups.group(2))
+                if one_mismatches == 1:
+                  match_codes['U1'] += 1
+                elif one_mismatches < 255:
+                  match_codes['R1'] += one_mismatches
+
+                two_mismatches = int(groups.group(3))
+                if two_mismatches == 1:
+                  match_codes['U2'] += 1
+                elif two_mismatches < 255:
+                  match_codes['R2'] += two_mismatches
+
+                chromo = None
+                for match in fields[3].split(','):
+                  match_fragment = match.split(':')
+                  if len(match_fragment) == 2:
+                      chromo = match_fragment[0]
+                      pos = match_fragment[1]
+
+                  fasta = self.genome_map.get(chromo, chromo)
+                  assert fasta is not None
+                  mapped_reads[fasta] = mapped_reads.setdefault(fasta, 0) + 1
+        return match_codes, mapped_reads, reads
+
+    def _get_mapped_reads(self):
+        if self._mapped_reads is None:
+            self._update()
+        return self._mapped_reads
+    mapped_reads = property(_get_mapped_reads)
+
+    def _get_match_codes(self):
+        if self._match_codes is None:
+            self._update()
+        return self._match_codes
+    match_codes = property(_get_match_codes)
+
+    def _get_no_match(self):
+        if self._mapped_reads is None:
+            self._update()  
+        return self._match_codes['NM']
+    no_match = property(_get_no_match, 
+                        doc="total reads that didn't match the target genome.")
+
+    def _get_no_match_percent(self):
+        return float(self.no_match)/self.reads * 100 
+    no_match_percent = property(_get_no_match_percent,
+                                doc="no match reads as percent of total")
+
+    def _get_qc_failed(self):
+        if self._mapped_reads is None:
+            self._update()  
+        return self._match_codes['QC']
+    qc_failed = property(_get_qc_failed,
+                        doc="total reads that didn't match the target genome.")
+
+    def _get_qc_failed_percent(self):
+        return float(self.qc_failed)/self.reads * 100 
+    qc_failed_percent = property(_get_qc_failed_percent,
+                                 doc="QC failed reads as percent of total")
+
+    def _get_unique_reads(self):
+        if self._mapped_reads is None:
+           self._update()
+        sum = 0
+        for code in ['U0','U1','U2']:
+            sum += self._match_codes[code]
+        return sum
+    unique_reads = property(_get_unique_reads,
+                            doc="total unique reads")
+
+    def _get_repeat_reads(self):
+        if self._mapped_reads is None:
+           self._update()
+        sum = 0
+        for code in ['R0','R1','R2']:
+            sum += self._match_codes[code]
+        return sum
+    repeat_reads = property(_get_repeat_reads,
+                            doc="total repeat reads")
+    
+    def get_elements(self):
+        lane = ElementTree.Element(ElandLane.LANE,
+                                   {'version':
+                                    unicode(ElandLane.XML_VERSION)})
+        sample_tag = ElementTree.SubElement(lane, SAMPLE_NAME)
+        sample_tag.text = self.sample_name
+        lane_tag = ElementTree.SubElement(lane, LANE_ID)
+        lane_tag.text = str(self.lane_id)
+        if self.end is not None:
+            end_tag = ElementTree.SubElement(lane, END)
+            end_tag.text = str(self.end)
+        genome_map = ElementTree.SubElement(lane, GENOME_MAP)
+        for k, v in self.genome_map.items():
+            item = ElementTree.SubElement(
+                genome_map, GENOME_ITEM,
+                {'name':k, 'value':unicode(v)})
+        mapped_reads = ElementTree.SubElement(lane, MAPPED_READS)
+        for k, v in self.mapped_reads.items():
+            item = ElementTree.SubElement(
+                mapped_reads, MAPPED_ITEM,
+                {'name':k, 'value':unicode(v)})
+        match_codes = ElementTree.SubElement(lane, MATCH_CODES)
+        for k, v in self.match_codes.items():
+            item = ElementTree.SubElement(
+                match_codes, MATCH_ITEM,
+                {'name':k, 'value':unicode(v)})
+        reads = ElementTree.SubElement(lane, READS)
+        reads.text = unicode(self.reads)
+
+        return lane
+
+    def set_elements(self, tree):
+        if tree.tag != ElandLane.LANE:
+            raise ValueError('Exptecting %s' % (ElandLane.LANE,))
+
+        # reset dictionaries
+        self._mapped_reads = {}
+        self._match_codes = {}
+
+        for element in tree:
+            tag = element.tag.lower()
+            if tag == SAMPLE_NAME.lower():
+                self._sample_name = element.text
+            elif tag == LANE_ID.lower():
+                self.lane_id = int(element.text)
+            elif tag == END.lower():
+                self.end = int(element.text)
+            elif tag == GENOME_MAP.lower():
+                for child in element:
+                    name = child.attrib['name']
+                    value = child.attrib['value']
+                    self.genome_map[name] = value
+            elif tag == MAPPED_READS.lower():
+                for child in element:
+                    name = child.attrib['name']
+                    value = child.attrib['value']
+                    self._mapped_reads[name] = int(value)
+            elif tag == MATCH_CODES.lower():
+                for child in element:
+                    name = child.attrib['name']
+                    value = int(child.attrib['value'])
+                    self._match_codes[name] = value
+            elif tag == READS.lower():
+                self._reads = int(element.text)
+            else:
+                logging.warn("ElandLane unrecognized tag %s" % (element.tag,))
+
+class SequenceLane(ResultLane):
+    XML_VERSION=1
+    LANE = 'SequenceLane'
+    SEQUENCE_TYPE = 'SequenceType'
+
+    NONE_TYPE = None
+    SCARF_TYPE = 1
+    FASTQ_TYPE = 2
+    SEQUENCE_DESCRIPTION = { NONE_TYPE: 'None', SCARF_TYPE: 'SCARF', FASTQ_TYPE: 'FASTQ' }
+
+    def __init__(self, pathname=None, lane_id=None, end=None, xml=None):
+        self.sequence_type = None
+        super(SequenceLane, self).__init__(pathname, lane_id, end, xml)
+
+    def _guess_sequence_type(self, pathname):
+        """
+        Determine if we have a scarf or fastq sequence file
+        """
+        f = open(pathname,'r')
+        l = f.readline()
+        f.close()
+
+        if l[0] == '@':
+            # fastq starts with a @
+            self.sequence_type = SequenceLane.FASTQ_TYPE
+        else:
+            self.sequence_type = SequenceLane.SCARF_TYPE
+        return self.sequence_type
+
+    def _update(self):
+        """
+        Actually read the file and actually count the reads
+        """
+        # can't do anything if we don't have a file to process
+        if self.pathname is None:
+            return
+
+        if os.stat(self.pathname)[stat.ST_SIZE] == 0:
+            raise RuntimeError("Sequencing isn't done, try again later.")
+
+        self._guess_sequence_type(self.pathname)
+
+        logging.info("summarizing results for %s" % (self.pathname))
+        lines = 0
+        f = open(self.pathname)
+        for l in f.xreadlines():
+            lines += 1
+        f.close()
+
+        if self.sequence_type == SequenceLane.SCARF_TYPE:
+            self._reads = lines
+        elif self.sequence_type == SequenceLane.FASTQ_TYPE:
+            self._reads = lines / 4
+        else:
+          raise NotImplementedError("This only supports scarf or fastq squence files")
+
+    def get_elements(self):
+        lane = ElementTree.Element(SequenceLane.LANE,
+                                   {'version':
+                                    unicode(SequenceLane.XML_VERSION)})
+        sample_tag = ElementTree.SubElement(lane, SAMPLE_NAME)
+        sample_tag.text = self.sample_name
+        lane_tag = ElementTree.SubElement(lane, LANE_ID)
+        lane_tag.text = str(self.lane_id)
+        if self.end is not None:
+            end_tag = ElementTree.SubElement(lane, END)
+            end_tag.text = str(self.end)
+        reads = ElementTree.SubElement(lane, READS)
+        reads.text = unicode(self.reads)
+        sequence_type = ElementTree.SubElement(lane, SequenceLane.SEQUENCE_TYPE)
+        sequence_type.text = unicode(SequenceLane.SEQUENCE_DESCRIPTION[self.sequence_type])
+
+        return lane
+
+    def set_elements(self, tree):
+        if tree.tag != SequenceLane.LANE:
+            raise ValueError('Exptecting %s' % (SequenceLane.LANE,))
+        lookup_sequence_type = dict([ (v,k) for k,v in SequenceLane.SEQUENCE_DESCRIPTION.items()])
+
+        for element in tree:
+            tag = element.tag.lower()
+            if tag == SAMPLE_NAME.lower():
+                self._sample_name = element.text
+            elif tag == LANE_ID.lower():
+                self.lane_id = int(element.text)
+            elif tag == END.lower():
+                self.end = int(element.text)
+            elif tag == READS.lower():
+                self._reads = int(element.text)
+            elif tag == SequenceLane.SEQUENCE_TYPE.lower():
+                self.sequence_type = lookup_sequence_type.get(element.text, None)
+                print self.sequence_type
+            else:
+                logging.warn("SequenceLane unrecognized tag %s" % (element.tag,))
+
+class ELAND(object):
+    """
+    Summarize information from eland files
+    """
+    XML_VERSION = 3
+
+    ELAND = 'ElandCollection'
+    LANE = 'Lane'
+    LANE_ID = 'id'
+    END = 'end'
+
+    def __init__(self, xml=None):
+        # we need information from the gerald config.xml
+        self.results = [{},{}]
+
+        if xml is not None:
+            self.set_elements(xml)
+
+    def get_elements(self):
+        root = ElementTree.Element(ELAND.ELAND,
+                                   {'version': unicode(ELAND.XML_VERSION)})
+        for end in range(len(self.results)):
+           end_results = self.results[end]
+           for lane_id, lane in end_results.items():
+                eland_lane = lane.get_elements()
+                eland_lane.attrib[ELAND.END] = unicode (end)
+                eland_lane.attrib[ELAND.LANE_ID] = unicode(lane_id)
+                root.append(eland_lane)
+        return root
+
+    def set_elements(self, tree):
+        if tree.tag.lower() != ELAND.ELAND.lower():
+            raise ValueError('Expecting %s', ELAND.ELAND)
+        for element in list(tree):
+            lane_id = int(element.attrib[ELAND.LANE_ID])
+            end = int(element.attrib.get(ELAND.END, 0)) 
+            if element.tag.lower() == ElandLane.LANE.lower():
+                lane = ElandLane(xml=element)
+            elif element.tag.lower() == SequenceLane.LANE.lower():
+                lane = SequenceLane(xml=element)
+
+            self.results[end][lane_id] = lane
+
+def check_for_eland_file(basedir, pattern, lane_id, end):
+   if end is None:
+      full_lane_id = lane_id
+   else:
+      full_lane_id = "%d_%d" % ( lane_id, end )
+
+   basename = pattern % (full_lane_id,)
+   pathname = os.path.join(basedir, basename)
+   if os.path.exists(pathname):
+       logging.info('found eland file in %s' % (pathname,))
+       return pathname
+   else:
+       return None
+
+def update_result_with_eland(gerald, results, lane_id, end, pathname, genome_maps):
+    # yes the lane_id is also being computed in ElandLane._update
+    # I didn't want to clutter up my constructor
+    # but I needed to persist the sample_name/lane_id for
+    # runfolder summary_report
+    path, name = os.path.split(pathname)
+    logging.info("Adding eland file %s" %(name,))
+    # split_name = name.split('_')
+    # lane_id = int(split_name[1])
+
+    if genome_maps is not None:
+        genome_map = genome_maps[lane_id]
+    elif gerald is not None:
+        genome_dir = gerald.lanes[lane_id].eland_genome
+        genome_map = build_genome_fasta_map(genome_dir)
+    else:
+        genome_map = {}
+
+    lane = ElandLane(pathname, lane_id, end, genome_map)
+    
+    if end is None:
+        effective_end =  0
+    else:
+        effective_end = end - 1
+
+    results[effective_end][lane_id] = lane
+
+def update_result_with_sequence(gerald, results, lane_id, end, pathname):
+    result = SequenceLane(pathname, lane_id, end)
+
+    if end is None:
+        effective_end =  0
+    else:
+        effective_end = end - 1
+
+    results[effective_end][lane_id] = result
+
+
+def eland(gerald_dir, gerald=None, genome_maps=None):
+    e = ELAND()
+
+    lane_ids = range(1,9)
+    ends = [None, 1, 2]
+
+    basedirs = [gerald_dir]
+
+    # if there is a basedir/Temp change basedir to point to the temp
+    # directory, as 1.1rc1 moves most of the files we've historically
+    # cared about to that subdirectory.
+    # we should look into what the official 'result' files are.
+    # and 1.3 moves them back
+    basedir_temp = os.path.join(gerald_dir, 'Temp')
+    if os.path.isdir(basedir_temp):
+        basedirs.append(basedir_temp)
+
+   
+    # the order in patterns determines the preference for what
+    # will be found.
+    MAPPED_ELAND = 0
+    SEQUENCE = 1
+    patterns = [('s_%s_eland_result.txt', MAPPED_ELAND),
+                ('s_%s_eland_result.txt.bz2', MAPPED_ELAND),
+                ('s_%s_eland_result.txt.gz', MAPPED_ELAND),
+                ('s_%s_eland_extended.txt', MAPPED_ELAND),
+                ('s_%s_eland_extended.txt.bz2', MAPPED_ELAND),
+                ('s_%s_eland_extended.txt.gz', MAPPED_ELAND),
+                ('s_%s_eland_multi.txt', MAPPED_ELAND),
+                ('s_%s_eland_multi.txt.bz2', MAPPED_ELAND),
+                ('s_%s_eland_multi.txt.gz', MAPPED_ELAND),
+                ('s_%s_sequence.txt', SEQUENCE),]
+
+    for basedir in basedirs:
+        for end in ends:
+            for lane_id in lane_ids:
+                for p in patterns:
+                    pathname = check_for_eland_file(basedir, p[0], lane_id, end)
+                    if pathname is not None:
+                        if p[1] == MAPPED_ELAND:
+                            update_result_with_eland(gerald, e.results, lane_id, end, pathname, genome_maps)
+                        elif p[1] == SEQUENCE:
+                            update_result_with_sequence(gerald, e.results, lane_id, end, pathname)
+                        break
+                else:
+                    logging.debug("No eland file found in %s for lane %s and end %s" %(basedir, lane_id, end))
+                    continue
+
+    return e
+
+def build_genome_fasta_map(genome_dir):
+    # build fasta to fasta file map
+    logging.info("Building genome map")
+    genome = genome_dir.split(os.path.sep)[-1]
+    fasta_map = {}
+    for vld_file in glob(os.path.join(genome_dir, '*.vld')):
+        is_link = False
+        if os.path.islink(vld_file):
+            is_link = True
+        vld_file = os.path.realpath(vld_file)
+        path, vld_name = os.path.split(vld_file)
+        name, ext = os.path.splitext(vld_name)
+        if is_link:
+            fasta_map[name] = name
+        else:
+            fasta_map[name] = os.path.join(genome, name)
+    return fasta_map
+
+
+def extract_eland_sequence(instream, outstream, start, end):
+    """
+    Extract a chunk of sequence out of an eland file
+    """
+    for line in instream:
+        record = line.split()
+        if len(record) > 1:
+            result = [record[0], record[1][start:end]]
+        else:
+            result = [record[0][start:end]]
+        outstream.write("\t".join(result))
+        outstream.write(os.linesep)
diff --git a/trunk/htsworkflow/pipelines/firecrest.py b/trunk/htsworkflow/pipelines/firecrest.py
new file mode 100644 (file)
index 0000000..fe5d01a
--- /dev/null
@@ -0,0 +1,144 @@
+"""
+Extract information about the Firecrest run
+
+Firecrest 
+  class holding the properties we found
+firecrest 
+  Firecrest factory function initalized from a directory name
+fromxml 
+  Firecrest factory function initalized from an xml dump from
+  the Firecrest object.
+"""
+
+from datetime import date
+from glob import glob
+import os
+import re
+import time
+
+from htsworkflow.pipelines.runfolder import \
+   ElementTree, \
+   VERSION_RE, \
+   EUROPEAN_STRPTIME
+
+__docformat__ = "restructuredtext en"
+
+class Firecrest(object):
+    XML_VERSION=1
+
+    # xml tag names
+    FIRECREST = 'Firecrest'
+    SOFTWARE_VERSION = 'version'
+    START = 'FirstCycle'
+    STOP = 'LastCycle'
+    DATE = 'run_time'
+    USER = 'user'
+    MATRIX = 'matrix'
+
+    def __init__(self, xml=None):
+        self.start = None
+        self.stop = None
+        self.version = None
+        self.date = date.today()
+        self.user = None
+        self.matrix = None
+
+        if xml is not None:
+            self.set_elements(xml)
+        
+    def _get_time(self):
+        return time.mktime(self.date.timetuple())
+    time = property(_get_time, doc='return run time as seconds since epoch')
+
+    def dump(self):
+        print "Starting cycle:", self.start
+        print "Ending cycle:", self.stop
+        print "Firecrest version:", self.version
+        print "Run date:", self.date
+        print "user:", self.user
+
+    def get_elements(self):
+        attribs = {'version': str(Firecrest.XML_VERSION) }
+        root = ElementTree.Element(Firecrest.FIRECREST, attrib=attribs)
+        version = ElementTree.SubElement(root, Firecrest.SOFTWARE_VERSION)
+        version.text = self.version
+        start_cycle = ElementTree.SubElement(root, Firecrest.START)
+        start_cycle.text = str(self.start)
+        stop_cycle = ElementTree.SubElement(root, Firecrest.STOP)
+        stop_cycle.text = str(self.stop)
+        run_date = ElementTree.SubElement(root, Firecrest.DATE)
+        run_date.text = str(self.time)
+        user = ElementTree.SubElement(root, Firecrest.USER)
+        user.text = self.user
+        if self.matrix is not None:
+            matrix = ElementTree.SubElement(root, Firecrest.MATRIX)
+            matrix.text = self.matrix
+        return root
+
+    def set_elements(self, tree):
+        if tree.tag != Firecrest.FIRECREST:
+            raise ValueError('Expected "Firecrest" SubElements')
+        xml_version = int(tree.attrib.get('version', 0))
+        if xml_version > Firecrest.XML_VERSION:
+            logging.warn('Firecrest XML tree is a higher version than this class')
+        for element in list(tree):
+            if element.tag == Firecrest.SOFTWARE_VERSION:
+                self.version = element.text
+            elif element.tag == Firecrest.START:
+                self.start = int(element.text)
+            elif element.tag == Firecrest.STOP:
+                self.stop = int(element.text)
+            elif element.tag == Firecrest.DATE:
+                self.date = date.fromtimestamp(float(element.text))
+            elif element.tag == Firecrest.USER:
+                self.user = element.text
+            elif element.tag == Firecrest.MATRIX:
+                self.matrix = element.text
+            else:
+                raise ValueError("Unrecognized tag: %s" % (element.tag,))
+
+def firecrest(pathname):
+    """
+    Examine the directory at pathname and initalize a Firecrest object
+    """
+    f = Firecrest()
+    f.pathname = pathname
+
+    # parse firecrest directory name
+    path, name = os.path.split(pathname)
+    groups = name.split('_')
+    # grab the start/stop cycle information
+    cycle = re.match("C([0-9]+)-([0-9]+)", groups[0])
+    f.start = int(cycle.group(1))
+    f.stop = int(cycle.group(2))
+    # firecrest version
+    version = re.search(VERSION_RE, groups[1])
+    f.version = (version.group(1))
+    # datetime
+    t = time.strptime(groups[2], EUROPEAN_STRPTIME)
+    f.date = date(*t[0:3])
+    # username
+    f.user = groups[3]
+
+    bustard_pattern = os.path.join(pathname, 'Bustard*')
+    # should I parse this deeper than just stashing the 
+    # contents of the matrix file?
+    matrix_pathname = os.path.join(pathname, 'Matrix', 's_matrix.txt')
+    if os.path.exists(matrix_pathname):
+        # this is for firecrest < 1.3.2
+        f.matrix = open(matrix_pathname, 'r').read()
+    elif glob(bustard_pattern) > 0:
+        f.matrix = None
+        # there are runs here. Bustard should save the matrix.
+    else:
+        return None
+
+    return f
+
+def fromxml(tree):
+    """
+    Initialize a Firecrest object from an element tree node
+    """
+    f = Firecrest()
+    f.set_elements(tree)
+    return f
diff --git a/trunk/htsworkflow/pipelines/genome_mapper.py b/trunk/htsworkflow/pipelines/genome_mapper.py
new file mode 100644 (file)
index 0000000..d29e446
--- /dev/null
@@ -0,0 +1,141 @@
+#!/usr/bin/python
+import glob
+import sys
+import os
+import re
+
+import logging
+
+from htsworkflow.util.alphanum import alphanum
+
+class DuplicateGenome(Exception): pass
+
+
+def _has_metainfo(genome_dir):
+  metapath = os.path.join(genome_dir, '_metainfo_')
+  if os.path.isfile(metapath):
+    return True
+  else:
+    return False
+
+def getAvailableGenomes(genome_base_dir):
+  """
+  raises IOError (on genome_base_dir not found)
+  raises DuplicateGenome on duplicate genomes found.
+  
+  returns a double dictionary (i.e. d[species][build] = path)
+  """
+
+  # Need valid directory
+  if not os.path.exists(genome_base_dir):
+    msg = "Directory does not exist: %s" % (genome_base_dir)
+    raise IOError, msg
+
+  # Find all subdirectories
+  filepath_list = glob.glob(os.path.join(genome_base_dir, '*'))
+  potential_genome_dirs = \
+    [ filepath for filepath in filepath_list if os.path.isdir(filepath)]
+
+  # Get list of metadata files
+  genome_dir_list = \
+    [ dirpath \
+      for dirpath in potential_genome_dirs \
+      if _has_metainfo(dirpath) ]
+
+  # Genome double dictionary
+  d = {}
+
+  for genome_dir in genome_dir_list:
+    line = open(os.path.join(genome_dir, '_metainfo_'), 'r').readline().strip()
+
+    # Get species, build... log and skip on failure
+    try:
+      species, build = line.split('|')
+    except:
+      logging.warning('Skipping: Invalid metafile (%s) line: %s' \
+                      % (metafile, line))
+      continue
+
+    build_dict = d.setdefault(species, {})
+    if build in build_dict:
+      msg = "Duplicate genome for %s|%s" % (species, build)
+      raise DuplicateGenome, msg
+
+    build_dict[build] = genome_dir
+
+  return d
+  
+
+class constructMapperDict(object):
+    """
+    Emulate a dictionary to map genome|build names to paths.
+    
+    It uses the dictionary generated by getAvailableGenomes.
+    """
+    def __init__(self, genome_dict):
+        self.genome_dict = genome_dict
+        
+    def __getitem__(self, key):
+        """
+        Return the best match for key
+        """
+        elements = re.split("\|", key)
+        
+        try:  
+            if len(elements) == 1:
+                # we just the species name
+                # get the set of builds
+                builds = self.genome_dict[elements[0]]
+            
+                # sort build names the way humans would
+                keys = builds.keys()
+                keys.sort(cmp=alphanum)
+            
+                # return the path from the 'last' build name
+                return builds[keys[-1]]
+                        
+            elif len(elements) == 2:
+                # we have species, and build name
+                return self.genome_dict[elements[0]][elements[1]]
+            else:
+                raise KeyError("Unrecognized key")
+        except KeyError, e:
+            logging.error('Unrecognized genome identifier: %s' % str((elements),))
+            return "NoGenomeAvailable"
+        
+    def keys(self):
+        keys = []
+        for species in self.genome_dict.keys():
+            for build in self.genome_dict[species]:
+                keys.append([species+'|'+build])
+        return keys
+            
+    def values(self):
+        values = []
+        for species in self.genome_dict.keys():
+            for build in self.genome_dict[species]:
+                values.append(self.genome_dict[species][build])
+        return values
+       
+    def items(self):
+        items = []
+        for species in self.genome_dict.keys():
+            for build in self.genome_dict[species]:
+                key = [species+'|'+build]
+                value = self.genome_dict[species][build]
+                items.append((key, value))
+        return items
+            
+if __name__ == '__main__':
+
+  if len(sys.argv) != 2:
+    print 'useage: %s <base_genome_dir>' % (sys.argv[0])
+    sys.exit(1)
+
+  d = getAvailableGenomes(sys.argv[1])
+  d2 = constructMapperDict(d)
+
+  for k,v in d2.items():
+    print '%s: %s' % (k,v)
+  
+  
diff --git a/trunk/htsworkflow/pipelines/gerald.py b/trunk/htsworkflow/pipelines/gerald.py
new file mode 100644 (file)
index 0000000..cbc5fcb
--- /dev/null
@@ -0,0 +1,208 @@
+"""
+Provide access to information stored in the GERALD directory.
+"""
+from datetime import datetime, date
+import logging
+import os
+import time
+
+from htsworkflow.pipelines.summary import Summary
+from htsworkflow.pipelines.eland import eland, ELAND
+
+from htsworkflow.pipelines.runfolder import \
+   ElementTree, \
+   EUROPEAN_STRPTIME, \
+   LANES_PER_FLOWCELL, \
+   VERSION_RE
+from htsworkflow.util.ethelp import indent, flatten
+
+class Gerald(object):
+    """
+    Capture meaning out of the GERALD directory
+    """
+    XML_VERSION = 1
+    GERALD='Gerald'
+    RUN_PARAMETERS='RunParameters'
+    SUMMARY='Summary'
+
+    class LaneParameters(object):
+        """
+        Make it easy to access elements of LaneSpecificRunParameters from python
+        """
+        def __init__(self, gerald, lane_id):
+            self._gerald = gerald
+            self._lane_id = lane_id
+
+        def __get_attribute(self, xml_tag):
+            subtree = self._gerald.tree.find('LaneSpecificRunParameters')
+            container = subtree.find(xml_tag)
+            if container is None:
+                return None
+            if len(container.getchildren()) > LANES_PER_FLOWCELL:
+                raise RuntimeError('GERALD config.xml file changed')
+            lanes = [x.tag.split('_')[1] for x in container.getchildren()]
+            try:
+                index = lanes.index(self._lane_id)
+            except ValueError, e:
+                return None
+            element = container[index]
+            return element.text
+        def _get_analysis(self):
+            return self.__get_attribute('ANALYSIS')
+        analysis = property(_get_analysis)
+
+        def _get_eland_genome(self):
+            genome = self.__get_attribute('ELAND_GENOME')
+            # default to the chipwide parameters if there isn't an
+            # entry in the lane specific paramaters
+            if genome is None:
+                subtree = self._gerald.tree.find('ChipWideRunParameters')
+                container = subtree.find('ELAND_GENOME')
+                genome = container.text
+            return genome
+        eland_genome = property(_get_eland_genome)
+
+        def _get_read_length(self):
+            return self.__get_attribute('READ_LENGTH')
+        read_length = property(_get_read_length)
+
+        def _get_use_bases(self):
+            return self.__get_attribute('USE_BASES')
+        use_bases = property(_get_use_bases)
+
+    class LaneSpecificRunParameters(object):
+        """
+        Provide access to LaneSpecificRunParameters
+        """
+        def __init__(self, gerald):
+            self._gerald = gerald
+            self._lane = None
+
+        def _initalize_lanes(self):
+            """
+            build dictionary of LaneParameters
+            """
+            self._lanes = {}
+            tree = self._gerald.tree
+            analysis = tree.find('LaneSpecificRunParameters/ANALYSIS')
+            # according to the pipeline specs I think their fields
+            # are sampleName_laneID, with sampleName defaulting to s
+            # since laneIDs are constant lets just try using
+            # those consistently.
+            for element in analysis:
+                sample, lane_id = element.tag.split('_')
+                self._lanes[int(lane_id)] = Gerald.LaneParameters(
+                                              self._gerald, lane_id)
+
+        def __getitem__(self, key):
+            if self._lane is None:
+                self._initalize_lanes()
+            return self._lanes[key]
+        def keys(self):
+            if self._lane is None:
+                self._initalize_lanes()
+            return self._lanes.keys()
+        def values(self):
+            if self._lane is None:
+                self._initalize_lanes()
+            return self._lanes.values()
+        def items(self):
+            if self._lane is None:
+                self._initalize_lanes()
+            return self._lanes.items()
+        def __len__(self):
+            if self._lane is None:
+                self._initalize_lanes()
+            return len(self._lanes)
+
+    def __init__(self, xml=None):
+        self.pathname = None
+        self.tree = None
+
+        # parse lane parameters out of the config.xml file
+        self.lanes = Gerald.LaneSpecificRunParameters(self)
+
+        self.summary = None
+        self.eland_results = None
+
+        if xml is not None:
+            self.set_elements(xml)
+
+    def _get_date(self):
+        if self.tree is None:
+            return datetime.today()
+        timestamp = self.tree.findtext('ChipWideRunParameters/TIME_STAMP')
+        epochstamp = time.mktime(time.strptime(timestamp, '%c'))
+        return datetime.fromtimestamp(epochstamp)
+    date = property(_get_date)
+
+    def _get_time(self):
+        return time.mktime(self.date.timetuple())
+    time = property(_get_time, doc='return run time as seconds since epoch')
+
+    def _get_version(self):
+        if self.tree is None:
+            return None
+        return self.tree.findtext('ChipWideRunParameters/SOFTWARE_VERSION')
+    version = property(_get_version)
+
+    def dump(self):
+        """
+        Debugging function, report current object
+        """
+        print 'Gerald version:', self.version
+        print 'Gerald run date:', self.date
+        print 'Gerald config.xml:', self.tree
+        self.summary.dump()
+
+    def get_elements(self):
+        if self.tree is None or self.summary is None:
+            return None
+
+        gerald = ElementTree.Element(Gerald.GERALD,
+                                     {'version': unicode(Gerald.XML_VERSION)})
+        gerald.append(self.tree)
+        gerald.append(self.summary.get_elements())
+        if self.eland_results:
+            gerald.append(self.eland_results.get_elements())
+        return gerald
+
+    def set_elements(self, tree):
+        if tree.tag !=  Gerald.GERALD:
+            raise ValueError('exptected GERALD')
+        xml_version = int(tree.attrib.get('version', 0))
+        if xml_version > Gerald.XML_VERSION:
+            logging.warn('XML tree is a higher version than this class')
+        for element in list(tree):
+            tag = element.tag.lower()
+            if tag == Gerald.RUN_PARAMETERS.lower():
+                self.tree = element
+            elif tag == Gerald.SUMMARY.lower():
+                self.summary = Summary(xml=element)
+            elif tag == ELAND.ELAND.lower():
+                self.eland_results = ELAND(xml=element)
+            else:
+                logging.warn("Unrecognized tag %s" % (element.tag,))
+
+
+def gerald(pathname):
+    g = Gerald()
+    g.pathname = pathname
+    path, name = os.path.split(pathname)
+    logging.info("Parsing gerald config.xml")
+    config_pathname = os.path.join(pathname, 'config.xml')
+    g.tree = ElementTree.parse(config_pathname).getroot()
+
+    # parse Summary.htm file
+    logging.info("Parsing Summary.htm")
+    summary_pathname = os.path.join(pathname, 'Summary.htm')
+    g.summary = Summary(summary_pathname)
+    # parse eland files
+    g.eland_results = eland(g.pathname, g)
+    return g
+
+if __name__ == "__main__":
+  # quick test code
+  import sys
+  g = gerald(sys.argv[1])
+  #ElementTree.dump(g.get_elements())
diff --git a/trunk/htsworkflow/pipelines/ipar.py b/trunk/htsworkflow/pipelines/ipar.py
new file mode 100644 (file)
index 0000000..239239e
--- /dev/null
@@ -0,0 +1,239 @@
+"""
+Extract information about the IPAR run
+
+IPAR 
+    class holding the properties we found
+ipar
+    IPAR factory function initalized from a directory name
+fromxml
+    IPAR factory function initalized from an xml dump from
+    the IPAR object.
+"""
+__docformat__ = "restructuredtext en"
+
+import datetime
+from glob import glob
+import logging
+import os
+import re
+import stat
+import time
+
+from htsworkflow.pipelines.runfolder import \
+   ElementTree, \
+   VERSION_RE, \
+   EUROPEAN_STRPTIME
+
+class Tiles(object):
+  def __init__(self, tree):
+    self.tree = tree.find("TileSelection")
+
+  def keys(self):
+    key_list = []
+    for c in self.tree.getchildren():
+      k = c.attrib.get('Index', None)
+      if k is not None:
+        key_list.append(k)
+    return key_list
+
+  def values(self):
+    value_list = []
+    for lane in self.tree.getchildren():
+      attributes = {}
+      for child in lane.getchildren():
+        if child.tag == "Sample":
+          attributes['Sample'] = child.text
+        elif child.tag == 'TileRange':
+          attributes['TileRange'] = (int(child.attrib['Min']),int(child.attrib['Max']))
+      value_list.append(attributes)
+    return value_list
+
+  def items(self):
+    return zip(self.keys(), self.values())
+
+  def __getitem__(self, key):
+    # FIXME: this is inefficient. building the dictionary be rescanning the xml.
+    v = dict(self.items())
+    return v[key]
+
+class IPAR(object):
+    XML_VERSION=1
+
+    # xml tag names
+    IPAR = 'IPAR'
+    TIMESTAMP = 'timestamp'
+    MATRIX = 'matrix'
+    RUN = 'Run'
+
+    def __init__(self, xml=None):
+        self.tree = None
+        self.date = datetime.datetime.today()
+       self._tiles = None
+        if xml is not None:
+            self.set_elements(xml)
+
+    def _get_time(self):
+        return time.mktime(self.date.timetuple())
+    def _set_time(self, value):
+        mtime_tuple = time.localtime(value)
+        self.date = datetime.datetime(*(mtime_tuple[0:7]))
+    time = property(_get_time, _set_time,
+                    doc='run time as seconds since epoch')
+
+    def _get_cycles(self):
+        if self.tree is None:
+          return None
+        cycles = self.tree.find("Cycles")
+        if cycles is None:
+          return None
+        return cycles.attrib
+
+    def _get_start(self):
+        """
+        return cycle start
+        """
+        cycles = self._get_cycles()
+        if cycles is not None:
+          return int(cycles['First'])
+        else:
+          return None
+    start = property(_get_start, doc="get cycle start")
+
+    def _get_stop(self):
+        """
+        return cycle stop
+        """
+        cycles = self._get_cycles()
+        if cycles is not None:
+          return int(cycles['Last'])
+        else:
+          return None
+    stop = property(_get_stop, doc="get cycle stop")
+
+    def _get_tiles(self):
+      if self._tiles is None:
+        self._tiles = Tiles(self.tree)
+      return self._tiles
+    tiles = property(_get_tiles)
+
+    def _get_version(self):
+      software = self.tree.find('Software')
+      if software is not None:
+        return software.attrib['Version']
+    version = property(_get_version, "IPAR software version")
+
+
+    def file_list(self):
+        """
+        Generate list of all files that should be generated by the IPAR unit
+        """
+        suffix_node = self.tree.find('RunParameters/CompressionSuffix')
+        if suffix_node is None:
+          print "find compression suffix failed"
+          return None
+        suffix = suffix_node.text
+        files = []
+        format = "%s_%s_%04d_%s.txt%s"
+        for lane, attrib in self.tiles.items():
+          for file_type in ["int","nse"]:
+            start, stop = attrib['TileRange']
+            for tile in range(start, stop+1):
+              files.append(format % (attrib['Sample'], lane, tile, file_type, suffix))
+        return files
+
+    def dump(self):
+        print "Matrix:", self.matrix
+        print "Tree:", self.tree
+
+    def get_elements(self):
+        attribs = {'version': str(IPAR.XML_VERSION) }
+        root = ElementTree.Element(IPAR.IPAR, attrib=attribs)
+        timestamp = ElementTree.SubElement(root, IPAR.TIMESTAMP)
+        timestamp.text = str(int(self.time))
+        root.append(self.tree)
+        matrix = ElementTree.SubElement(root, IPAR.MATRIX)
+        matrix.text = self.matrix
+        return root
+
+    def set_elements(self, tree):
+        if tree.tag != IPAR.IPAR:
+            raise ValueError('Expected "IPAR" SubElements')
+        xml_version = int(tree.attrib.get('version', 0))
+        if xml_version > IPAR.XML_VERSION:
+            logging.warn('IPAR XML tree is a higher version than this class')
+        for element in list(tree):
+            if element.tag == IPAR.RUN:
+                self.tree = element
+            elif element.tag == IPAR.TIMESTAMP:
+               self.time = int(element.text)
+            elif element.tag == IPAR.MATRIX:
+                self.matrix = element.text
+            else:
+                raise ValueError("Unrecognized tag: %s" % (element.tag,))
+
+def load_ipar_param_tree(paramfile):
+    """
+    look for a .param file and load it if it is an IPAR tree
+    """
+
+    tree = ElementTree.parse(paramfile).getroot()
+    run = tree.find('Run')
+    if run.attrib.has_key('Name') and run.attrib['Name'].startswith("IPAR"):
+        return run
+
+    return None
+
+def ipar(pathname):
+    """
+    Examine the directory at pathname and initalize a IPAR object
+    """
+    logging.info("Searching IPAR directory")
+    i = IPAR()
+    i.pathname = pathname
+
+    # parse firecrest directory name
+    path, name = os.path.split(pathname)
+    groups = name.split('_')
+    if groups[0] != 'IPAR':
+      raise ValueError('ipar can only process IPAR directories')
+
+    bustard_pattern = os.path.join(pathname, 'Bustard*')
+    # contents of the matrix file?
+    matrix_pathname = os.path.join(pathname, 'Matrix', 's_matrix.txt')
+    if os.path.exists(matrix_pathname):
+        # this is IPAR_1.01
+        i.matrix = open(matrix_pathname, 'r').read()
+    elif glob(bustard_pattern) > 0:
+        i.matrix = None
+        # its still live.
+    else:
+        return None
+
+    # look for parameter xml file
+    paramfile = os.path.join(path, '.params')
+    if os.path.exists(paramfile):
+      i.tree = load_ipar_param_tree(paramfile)
+      mtime_local = os.stat(paramfile)[stat.ST_MTIME]
+      i.time = mtime_local
+    return i
+
+def fromxml(tree):
+    """
+    Initialize a IPAR object from an element tree node
+    """
+    f = IPAR()
+    f.set_elements(tree)
+    return f
+
+if __name__ == "__main__":
+  i = ipar(os.path.expanduser('~/gec/081021_HWI-EAS229_0063_30HKUAAXX/Data/IPAR_1.01'))
+  x = i.get_elements()
+  j = fromxml(x)
+  #ElementTree.dump(x)
+  print j.date
+  print j.start
+  print j.stop
+  print i.tiles.keys()
+  print j.tiles.keys()
+  print j.tiles.items()
+  print j.file_list()
diff --git a/trunk/htsworkflow/pipelines/recipe_parser.py b/trunk/htsworkflow/pipelines/recipe_parser.py
new file mode 100644 (file)
index 0000000..7f5ced6
--- /dev/null
@@ -0,0 +1,48 @@
+from xml import sax
+
+
+def get_cycles(recipe_xml_filepath):
+  """
+  returns the number of cycles found in Recipe*.xml
+  """
+  handler = CycleXmlHandler()
+  sax.parse(recipe_xml_filepath, handler)
+  return handler.cycle_count
+
+
+
+class CycleXmlHandler(sax.ContentHandler):
+
+  def __init__(self):
+    self.cycle_count = 0
+    self.in_protocol = False
+    sax.ContentHandler.__init__(self)
+
+
+  def startDocument(self):
+    self.cycle_count = 0
+    self.in_protocol = False
+
+
+  def startElement(self, name, attrs):
+
+    #Only count Incorporations as cycles if within
+    # the protocol section of the xml document.
+    if name == "Incorporation" and self.in_protocol:
+      #print 'Found a cycle!'
+      self.cycle_count += 1
+      return
+    
+    elif name == 'Protocol':
+      #print 'In protocol'
+      self.in_protocol = True
+      return
+
+    #print 'Skipping: %s' % (name)
+    
+
+  def endElement(self, name):
+    
+    if name == 'Protocol':
+      #print 'End protocol'
+      self.in_protocol = False
diff --git a/trunk/htsworkflow/pipelines/retrieve_config.py b/trunk/htsworkflow/pipelines/retrieve_config.py
new file mode 100644 (file)
index 0000000..13805c9
--- /dev/null
@@ -0,0 +1,186 @@
+#!/usr/bin/env python
+
+from optparse import OptionParser, IndentedHelpFormatter
+from ConfigParser import SafeConfigParser
+
+import logging
+import os
+import sys
+import urllib2
+
+__docformat__ = "restructredtext en"
+
+CONFIG_SYSTEM = '/etc/hts_frontend/hts_frontend.conf'
+CONFIG_USER = os.path.expanduser('~/.hts_frontend.conf')
+
+#Disable or enable commandline arg parsing; disabled by default.
+DISABLE_CMDLINE = True
+
+class FlowCellNotFound(Exception): pass
+class WebError404(Exception): pass
+
+class DummyOptions:
+  """
+  Used when command line parsing is disabled; default
+  """
+  def __init__(self):
+    self.url = None
+    self.output_filepath = None
+    self.flowcell = None
+    self.genome_dir = None
+
+class PreformattedDescriptionFormatter(IndentedHelpFormatter):
+  
+  #def format_description(self, description):
+  #  
+  #  if description:
+  #      return description + "\n"
+  #  else:
+  #     return ""
+      
+  def format_epilog(self, epilog):
+    """
+    It was removing my preformated epilog, so this should override
+    that behavior! Muhahaha!
+    """
+    if epilog:
+        return "\n" + epilog + "\n"
+    else:
+        return ""
+
+
+def constructOptionParser():
+  """
+  returns a pre-setup optparser
+  """
+  global DISABLE_CMDLINE
+  
+  if DISABLE_CMDLINE:
+    return None
+  
+  parser = OptionParser(formatter=PreformattedDescriptionFormatter())
+
+  parser.set_description('Retrieves eland config file from hts_frontend web frontend.')
+  
+  parser.epilog = """
+Config File:
+  * %s (System wide)
+  * %s (User specific; overrides system)
+  * command line overrides all config file options
+  
+  Example Config File:
+  
+    [config_file_server]
+    base_host_url=http://somewhere.domain:port
+""" % (CONFIG_SYSTEM, CONFIG_USER)
+  
+  #Special formatter for allowing preformatted description.
+  ##parser.format_epilog(PreformattedDescriptionFormatter())
+
+  parser.add_option("-u", "--url",
+                    action="store", type="string", dest="url")
+  
+  parser.add_option("-o", "--output",
+                    action="store", type="string", dest="output_filepath")
+  
+  parser.add_option("-f", "--flowcell",
+                    action="store", type="string", dest="flowcell")
+
+  parser.add_option("-g", "--genome_dir",
+                    action="store", type="string", dest="genome_dir")
+  
+  #parser.set_default("url", "default")
+  
+  return parser
+
+def constructConfigParser():
+  """
+  returns a pre-setup config parser
+  """
+  parser = SafeConfigParser()
+  parser.read([CONFIG_SYSTEM, CONFIG_USER])
+  if not parser.has_section('config_file_server'):
+    parser.add_section('config_file_server')
+  if not parser.has_section('local_setup'):
+    parser.add_section('local_setup')
+  
+  return parser
+
+
+def getCombinedOptions():
+  """
+  Returns optparse options after it has be updated with ConfigParser
+  config files and merged with parsed commandline options.
+  """
+  cl_parser = constructOptionParser()
+  conf_parser = constructConfigParser()
+  
+  if cl_parser is None:
+    options = DummyOptions()
+  else:
+    options, args = cl_parser.parse_args()
+  
+  if options.url is None:
+    if conf_parser.has_option('config_file_server', 'base_host_url'):
+      options.url = conf_parser.get('config_file_server', 'base_host_url')
+
+  if options.genome_dir is None:
+    if conf_parser.has_option('local_setup', 'genome_dir'):
+      options.genome_dir = conf_parser.get('local_setup', 'genome_dir')
+  
+  print 'USING OPTIONS:'
+  print ' URL:', options.url
+  print ' OUT:', options.output_filepath
+  print '  FC:', options.flowcell
+  print 'GDIR:', options.genome_dir
+  print ''
+  
+  return options
+
+
+def saveConfigFile(flowcell, base_host_url, output_filepath):
+  """
+  retrieves the flowcell eland config file, give the base_host_url
+  (i.e. http://sub.domain.edu:port)
+  """
+  url = base_host_url + '/eland_config/%s/' % (flowcell)
+  
+  f = open(output_filepath, 'w')
+  #try:
+  try:
+    web = urllib2.urlopen(url)
+  except urllib2.URLError, e:
+    errmsg = 'URLError: %s' % (e.reason,)
+    logging.error(errmsg)
+    logging.error('opened %s' % (url,))
+    raise IOError(errmsg)
+
+  #except IOError, msg:
+  #  if str(msg).find("Connection refused") >= 0:
+  #    print 'Error: Connection refused for: %s' % (url)
+  #    f.close()
+  #    sys.exit(1)
+  #  elif str(msg).find("Name or service not known") >= 0:
+  #    print 'Error: Invalid domain or ip address for: %s' % (url)
+  #    f.close()
+  #    sys.exit(2)
+  #  else:
+  #    raise IOError, msg
+
+  data = web.read()
+
+  if data.find('Hmm, config file for') >= 0:
+    msg = "Flowcell (%s) not found in DB; full url(%s)" % (flowcell, url)
+    raise FlowCellNotFound, msg
+
+  if data.find('404 - Not Found') >= 0:
+    msg = "404 - Not Found: Flowcell (%s); base_host_url (%s);\n full url(%s)\n " \
+          "Did you get right port #?" % (flowcell, base_host_url, url)
+    raise FlowCellNotFound, msg
+  
+  f.write(data)
+  web.close()
+  f.close()
+  logging.info('Wrote config file to %s' % (output_filepath,))
+
+  
diff --git a/trunk/htsworkflow/pipelines/run_status.py b/trunk/htsworkflow/pipelines/run_status.py
new file mode 100644 (file)
index 0000000..e6a3ed8
--- /dev/null
@@ -0,0 +1,454 @@
+__docformat__ = "restructuredtext en"
+
+import glob
+import re
+import os
+import sys
+import time
+import threading
+
+s_comment = re.compile('^#')
+s_general_read_len = re.compile('^READ_LENGTH ')
+s_read_len = re.compile('^[1-8]+:READ_LENGTH ')
+
+s_firecrest = None
+
+# FIRECREST PATTERNS
+# _p2f(<pattern>, lane, tile, cycle)
+PATTERN_FIRECREST_QCM = 's_%s_%s_%s_qcm.xml'
+
+# _p2f(<pattern>, lane, tile)
+PATTERN_FIRECREST_INT = 's_%s_%s_02_int.txt'
+PATTERN_FIRECREST_NSE = 's_%s_%s_nse.txt.gz'
+PATTERN_FIRECREST_POS = 's_%s_%s_pos.txt'
+PATTERN_FIRECREST_IDX = 's_%s_%s_idx.txt'
+PATTERN_FIRECREST_CLU1 = 's_%s_%s_01_1_clu.txt'
+PATTERN_FIRECREST_CLU2 = 's_%s_%s_01_2_clu.txt'
+PATTERN_FIRECREST_CLU3 = 's_%s_%s_01_3_clu.txt'
+PATTERN_FIRECREST_CLU4 = 's_%s_%s_01_4_clu.txt'
+
+
+# BUSTARD PATTERNS
+# _p2f(<pattern>, lane, tile)
+PATTERN_BUSTARD_SIG2 = 's_%s_%s_sig2.txt'
+PATTERN_BUSTARD_PRB = 's_%s_%s_prb.txt'
+
+
+
+# GERALD PATTERNS
+# _p2f(<pattern>, lane, tile)
+PATTERN_GERALD_ALLTMP = 's_%s_%s_all.txt.tmp'
+PATTERN_GERALD_QRAWTMP = 's_%s_%s_qraw.txt.tmp'
+PATTERN_GERALD_ALLPNGTMP = 's_%s_%s_all.tmp.png'
+PATTERN_GERALD_ALIGNTMP = 's_%s_%s_align.txt.tmp'
+PATTERN_GERALD_QVALTMP = 's_%s_%s_qval.txt.tmp'
+PATTERN_GERALD_SCORETMP = 's_%s_%s_score.txt.tmp'
+PATTERN_GERALD_PREALIGNTMP = 's_%s_%s_prealign.txt.tmp'
+PATTERN_GERALD_REALIGNTMP = 's_%s_%s_realign.txt.tmp'
+PATTERN_GERALD_RESCORETMP = 's_%s_%s_rescore.txt.tmp'
+PATTERN_GERALD_RESCOREPNG = 's_%s_%s_rescore.png'
+PATTERN_GERALD_ERRORSTMPPNG = 's_%s_%s_errors.tmp.png'
+PATTERN_GERALD_QCALTMP = 's_%s_%s_qcal.txt.tmp'
+PATTERN_GERALD_QVAL = 's_%s_%s_qval.txt'
+
+# _p2f(<pattern>, lane)
+PATTERN_GERALD_SEQPRETMP = 's_%s_seqpre.txt.tmp'
+PATTERN_GERALD_RESULTTMP = 's_%s_eland_result.txt.tmp'
+PATTERN_GERALD_SIGMEANSTMP = 's_%s_Signal_Means.txt.tmp'
+PATTERN_GERALD_CALLPNG = 's_%s_call.png'
+PATTERN_GERALD_ALLPNG = 's_%s_all.png'
+PATTERN_GERALD_PERCENTALLPNG = 's_%s_percent_all.png'
+PATTERN_GERALD_PERCENTCALLPNG = 's_%s_percent_call.png'
+PATTERN_GERALD_PERCENTBASEPNG = 's_%s_percent_base.png'
+PATTERN_GERALD_FILTTMP = 's_%s_filt.txt.tmp'
+PATTERN_GERALD_FRAGTMP = 's_%s_frag.txt.tmp'
+PATTERN_GERALD_QREPORTTMP = 's_%s_qreport.txt.tmp'
+PATTERN_GERALD_QTABLETMP = 's_%s_qtable.txt.tmp'
+PATTERN_GERALD_QCALREPORTTMP = 's_%s_qcalreport.txt.tmp'
+PATTERN_GERALD_SEQUENCETMP = 's_%s_sequence.txt.tmp'
+PATTERN_GERALD_LANEFINISHED = 's_%s_finished.txt'
+
+
+
+def _p2f(pattern, lane, tile=None, cycle=None):
+  """
+  Converts a pattern plus info into file names
+  """
+
+  # lane, and cycle provided (INVALID)
+  if tile is None and cycle is not None:
+    msg = "Handling of cycle without tile is not currently implemented."
+    raise ValueError, msg
+
+  # lane, tile, cycle provided
+  elif cycle:
+    return pattern % (lane,
+                      "%04d" % (tile,),
+                     "%02d" % (cycle,))
+  
+  # lane, tile provided
+  elif tile:
+    return pattern % (lane, "%04d" % (tile,))
+
+  # lane provided
+  else:
+    return pattern % (lane)
+    
+
+class GARunStatus(object):
+
+  def __init__(self, conf_filepath):
+    """
+    Given an eland config file in the top level directory
+    of a run, predicts the files that will be generated
+    during a run and provides methods for retrieving
+    (completed, total) for each step or entire run.
+    """
+    #print 'self._conf_filepath = %s' % (conf_filepath)
+    self._conf_filepath = conf_filepath
+    self._base_dir, junk = os.path.split(conf_filepath)
+    self._image_dir = os.path.join(self._base_dir, 'Images')
+    
+    self.lanes = []
+    self.lane_read_length = {}
+    self.tiles = None
+    self.cycles = None
+    
+    self.status = {}
+    self.status['firecrest'] = {}
+    self.status['bustard'] = {}
+    self.status['gerald'] = {}
+    
+    self._process_config()
+    self._count_tiles()
+    self._count_cycles()
+    self._generate_expected()
+
+
+  def _process_config(self):
+    """
+    Grabs info from self._conf_filepath
+    """
+    f = open(self._conf_filepath, 'r')
+
+    for line in f:
+
+      #Skip comment lines for now.
+      if s_comment.search(line):
+        continue
+
+      mo =  s_general_read_len.search(line)
+      if mo:
+        read_length = int(line[mo.end():])
+        #Handle general READ_LENGTH
+        for i in range(1,9):
+          self.lane_read_length[i] = read_length
+      
+      mo = s_read_len.search(line)
+      if mo:
+        read_length = int(line[mo.end():])
+        lanes, junk = line.split(':')
+
+        #Convert lanes from string of lanes to list of lane #s.
+        lanes = [ int(i) for i in lanes ]
+
+        
+        for lane in lanes:
+
+          #Keep track of which lanes are being run.
+          if lane not in self.lanes:
+            self.lanes.append(lane)
+
+          #Update with lane specific read lengths
+          self.lane_read_length[lane] = read_length
+
+        self.lanes.sort()
+
+
+  def _count_tiles(self):
+    """
+    Count the number of tiles being used
+    """
+    self.tiles = len(glob.glob(os.path.join(self._image_dir,
+                                            'L001',
+                                            'C1.1',
+                                            's_1_*_a.tif')))
+
+  def _count_cycles(self):
+    """
+    Figures out the number of cycles that are available
+    """
+    #print 'self._image_dir = %s' % (self._image_dir)
+    cycle_dirs = glob.glob(os.path.join(self._image_dir, 'L001', 'C*.1'))
+    #print 'cycle_dirs = %s' % (cycle_dirs)
+    cycle_list = []
+    for cycle_dir in cycle_dirs:
+      junk, c = os.path.split(cycle_dir)
+      cycle_list.append(int(c[1:c.find('.')]))
+
+    self.cycles = max(cycle_list)
+    
+
+
+
+  def _generate_expected(self):
+    """
+    generates a list of files we expect to find.
+    """
+
+    firecrest = self.status['firecrest']
+    bustard = self.status['bustard']
+    gerald = self.status['gerald']
+    
+    
+    for lane in self.lanes:
+      for tile in range(1,self.tiles+1):
+        for cycle in range(1, self.cycles+1):
+
+          ##########################
+          # LANE, TILE, CYCLE LAYER
+
+          # FIRECREST
+          firecrest[_p2f(PATTERN_FIRECREST_QCM, lane, tile, cycle)] = False
+
+
+        ###################
+        # LANE, TILE LAYER
+
+        # FIRECREST
+        firecrest[_p2f(PATTERN_FIRECREST_INT, lane, tile)] = False
+        firecrest[_p2f(PATTERN_FIRECREST_NSE, lane, tile)] = False
+        firecrest[_p2f(PATTERN_FIRECREST_POS, lane, tile)] = False
+        firecrest[_p2f(PATTERN_FIRECREST_IDX, lane, tile)] = False
+        firecrest[_p2f(PATTERN_FIRECREST_CLU1, lane, tile)] = False
+        firecrest[_p2f(PATTERN_FIRECREST_CLU2, lane, tile)] = False
+        firecrest[_p2f(PATTERN_FIRECREST_CLU3, lane, tile)] = False
+        firecrest[_p2f(PATTERN_FIRECREST_CLU4, lane, tile)] = False
+
+
+        # BUSTARD
+        bustard[_p2f(PATTERN_BUSTARD_SIG2, lane, tile)] = False
+        bustard[_p2f(PATTERN_BUSTARD_PRB, lane, tile)] = False
+
+
+        # GERALD
+        #gerald[_p2f(PATTERN_GERALD_ALLTMP, lane, tile)] = False
+        #gerald[_p2f(PATTERN_GERALD_QRAWTMP, lane, tile)] = False
+        #gerald[_p2f(PATTERN_GERALD_ALLPNGTMP, lane, tile)] = False
+        #gerald[_p2f(PATTERN_GERALD_ALIGNTMP, lane, tile)] = False
+        #gerald[_p2f(PATTERN_GERALD_QVALTMP, lane, tile)] = False
+        #gerald[_p2f(PATTERN_GERALD_SCORETMP, lane, tile)] = False
+        #gerald[_p2f(PATTERN_GERALD_PREALIGNTMP, lane, tile)] = False
+        #gerald[_p2f(PATTERN_GERALD_REALIGNTMP, lane, tile)] = False
+        #gerald[_p2f(PATTERN_GERALD_RESCORETMP, lane, tile)] = False
+        gerald[_p2f(PATTERN_GERALD_RESCOREPNG, lane, tile)] = False
+        #gerald[_p2f(PATTERN_GERALD_ERRORSTMPPNG, lane, tile)] = False
+        #gerald[_p2f(PATTERN_GERALD_QCALTMP, lane, tile)] = False
+        #gerald[_p2f(PATTERN_GERALD_QVAL, lane, tile)] = False
+
+      ###################
+      # LANE LAYER
+
+      # GERALD
+      #gerald[_p2f(PATTERN_GERALD_SEQPRETMP, lane)] = False
+      #gerald[_p2f(PATTERN_GERALD_RESULTTMP, lane)] = False
+      #gerald[_p2f(PATTERN_GERALD_SIGMEANSTMP, lane)] = False
+      gerald[_p2f(PATTERN_GERALD_CALLPNG, lane)] = False
+      gerald[_p2f(PATTERN_GERALD_ALLPNG, lane)] = False
+      gerald[_p2f(PATTERN_GERALD_PERCENTALLPNG, lane)] = False
+      gerald[_p2f(PATTERN_GERALD_PERCENTCALLPNG, lane)] = False
+      gerald[_p2f(PATTERN_GERALD_PERCENTBASEPNG, lane)] = False
+      #gerald[_p2f(PATTERN_GERALD_FILTTMP, lane)] = False
+      #gerald[_p2f(PATTERN_GERALD_FRAGTMP, lane)] = False
+      #gerald[_p2f(PATTERN_GERALD_QREPORTTMP, lane)] = False
+      #gerald[_p2f(PATTERN_GERALD_QTABLETMP, lane)] = False
+      #gerald[_p2f(PATTERN_GERALD_QCALREPORTTMP, lane)] = False
+      #gerald[_p2f(PATTERN_GERALD_SEQUENCETMP, lane)] = False
+      gerald[_p2f(PATTERN_GERALD_LANEFINISHED, lane)] = False
+      
+      
+
+    #################
+    # LOOPS FINISHED
+
+    # FIRECREST
+    firecrest['offsets_finished.txt'] = False
+    firecrest['finished.txt'] = False
+
+    # BUSTARD
+    bustard['finished.txt'] = False
+
+    # GERALD
+    gerald['tiles.txt'] = False
+    gerald['FullAll.htm'] = False
+    #gerald['All.htm.tmp'] = False
+    #gerald['Signal_Means.txt.tmp'] = False
+    #gerald['plotIntensity_for_IVC'] = False
+    #gerald['IVC.htm.tmp'] = False
+    gerald['FullError.htm'] = False
+    gerald['FullPerfect.htm'] = False
+    #gerald['Error.htm.tmp'] = False
+    #gerald['Perfect.htm.tmp'] = False
+    #gerald['Summary.htm.tmp'] = False
+    #gerald['Tile.htm.tmp'] = False
+    gerald['finished.txt'] = False
+    
+  def statusFirecrest(self):
+    """
+    returns (<completed>, <total>)
+    """
+    firecrest = self.status['firecrest']
+    total = len(firecrest)
+    completed = firecrest.values().count(True)
+
+    return (completed, total)
+
+
+  def statusBustard(self):
+    """
+    returns (<completed>, <total>)
+    """
+    bustard = self.status['bustard']
+    total = len(bustard)
+    completed = bustard.values().count(True)
+
+    return (completed, total)
+
+
+  def statusGerald(self):
+    """
+    returns (<completed>, <total>)
+    """
+    gerald = self.status['gerald']
+    total = len(gerald)
+    completed = gerald.values().count(True)
+
+    return (completed, total)
+
+
+  def statusTotal(self):
+    """
+    returns (<completed>, <total>)
+    """
+    #f = firecrest  c = completed
+    #b = bustard    t = total
+    #g = gerald
+    fc, ft = self.statusFirecrest()
+    bc, bt = self.statusBustard()
+    gc, gt = self.statusGerald()
+
+    return (fc+bc+gc, ft+bt+gt)
+
+
+  def statusReport(self):
+    """
+    Generate the basic percent complete report
+    """
+    def _percentCompleted(completed, total):
+      """
+      Returns precent completed as float
+      """
+      return (completed / float(total)) * 100
+
+    fc, ft = self.statusFirecrest()
+    bc, bt = self.statusBustard()
+    gc, gt = self.statusGerald()
+    tc, tt = self.statusTotal()
+    
+    fp = _percentCompleted(fc, ft)
+    bp = _percentCompleted(bc, bt)
+    gp = _percentCompleted(gc, gt)
+    tp = _percentCompleted(tc, tt)
+    
+    report = ['Firecrest: %s%% (%s/%s)' % (fp, fc, ft),
+              '  Bustard: %s%% (%s/%s)' % (bp, bc, bt),
+              '   Gerald: %s%% (%s/%s)' % (gp, gc, gt),
+              '-----------------------',
+              '    Total: %s%% (%s/%s)' % (tp, tc, tt),
+             ]
+    return report
+
+  def updateFirecrest(self, filename):
+    """
+    Marks firecrest filename as being completed.
+    """
+    self.status['firecrest'][filename] = True
+    
+
+  def updateBustard(self, filename):
+    """
+    Marks bustard filename as being completed.
+    """
+    self.status['bustard'][filename] = True
+
+
+  def updateGerald(self, filename):
+    """
+    Marks gerald filename as being completed.
+    """
+    self.status['gerald'][filename] = True
+
+
+
+##################################################
+# Functions to be called by Thread(target=<func>)
+def _cmdLineStatusMonitorFunc(conf_info):
+  """
+  Given a ConfigInfo object, provides status to stdout.
+
+  You should probably use startCmdLineStatusMonitor()
+  instead of ths function.
+
+  .. python:
+    def example_launch():
+        t = threading.Thread(target=_cmdLineStatusMonitorFunc,
+                             args=[conf_info])
+        t.setDaemon(True)
+        t.start()
+  """
+  SLEEP_AMOUNT = 30
+
+  while 1:
+    if conf_info.status is None:
+      print "No status object yet."
+      time.sleep(SLEEP_AMOUNT)
+      continue
+
+    report = conf_info.status.statusReport()
+    print os.linesep.join(report)
+    print
+
+    time.sleep(SLEEP_AMOUNT)
+
+
+#############################################
+# Start monitor thread convenience functions
+def startCmdLineStatusMonitor(conf_info):
+  """
+  Starts a command line status monitor given a conf_info object.
+  """
+  t = threading.Thread(target=_cmdLineStatusMonitorFunc, args=[conf_info])
+  t.setDaemon(True)
+  t.start()
+
+from optparse import OptionParser
+def make_parser():
+  usage = "%prog: config file"
+
+  parser = OptionParser()
+  return parser
+  
+def main(cmdline=None):
+  parser = make_parser()
+  opt, args = parser.parse_args(cmdline)
+
+  if len(args) != 1:
+    parser.error("need name of configuration file")
+    
+  status = GARunStatus(args[0])
+  print os.linesep.join(status.statusReport())
+  return 0
+
+if __name__ == "__main__":
+  sys.exit(main(sys.argv[1:]))
+                   
diff --git a/trunk/htsworkflow/pipelines/runfolder.py b/trunk/htsworkflow/pipelines/runfolder.py
new file mode 100644 (file)
index 0000000..11795c2
--- /dev/null
@@ -0,0 +1,499 @@
+"""
+Core information needed to inspect a runfolder.
+"""
+from glob import glob
+import logging
+import os
+import re
+import shutil
+import stat
+import subprocess
+import sys
+import time
+
+try:
+  from xml.etree import ElementTree
+except ImportError, e:
+  from elementtree import ElementTree
+
+EUROPEAN_STRPTIME = "%d-%m-%Y"
+EUROPEAN_DATE_RE = "([0-9]{1,2}-[0-9]{1,2}-[0-9]{4,4})"
+VERSION_RE = "([0-9\.]+)"
+USER_RE = "([a-zA-Z0-9]+)"
+LANES_PER_FLOWCELL = 8
+
+from htsworkflow.util.alphanum import alphanum
+from htsworkflow.util.ethelp import indent, flatten
+
+class PipelineRun(object):
+    """
+    Capture "interesting" information about a pipeline run
+    """
+    XML_VERSION = 1
+    PIPELINE_RUN = 'PipelineRun'
+    FLOWCELL_ID = 'FlowcellID'
+
+    def __init__(self, pathname=None, xml=None):
+        if pathname is not None:
+          self.pathname = os.path.normpath(pathname)
+        else:
+          self.pathname = None
+        self._name = None
+        self._flowcell_id = None
+        self.image_analysis = None
+        self.bustard = None
+        self.gerald = None
+
+        if xml is not None:
+          self.set_elements(xml)
+
+    def _get_flowcell_id(self):
+        # extract flowcell ID
+        if self._flowcell_id is None:
+          config_dir = os.path.join(self.pathname, 'Config')
+          flowcell_id_path = os.path.join(config_dir, 'FlowcellId.xml')
+         if os.path.exists(flowcell_id_path):
+            flowcell_id_tree = ElementTree.parse(flowcell_id_path)
+            self._flowcell_id = flowcell_id_tree.findtext('Text')
+         else:
+            path_fields = self.pathname.split('_')
+            if len(path_fields) > 0:
+              # guessing last element of filename
+              flowcell_id = path_fields[-1]
+            else:
+              flowcell_id = 'unknown'
+
+           logging.warning(
+             "Flowcell id was not found, guessing %s" % (
+                flowcell_id))
+           self._flowcell_id = flowcell_id
+        return self._flowcell_id
+    flowcell_id = property(_get_flowcell_id)
+
+    def get_elements(self):
+        """
+        make one master xml file from all of our sub-components.
+        """
+        root = ElementTree.Element(PipelineRun.PIPELINE_RUN)
+        flowcell = ElementTree.SubElement(root, PipelineRun.FLOWCELL_ID)
+        flowcell.text = self.flowcell_id
+        root.append(self.image_analysis.get_elements())
+        root.append(self.bustard.get_elements())
+        root.append(self.gerald.get_elements())
+        return root
+
+    def set_elements(self, tree):
+        # this file gets imported by all the others,
+        # so we need to hide the imports to avoid a cyclic imports
+        from htsworkflow.pipelines import firecrest
+        from htsworkflow.pipelines import ipar
+        from htsworkflow.pipelines import bustard
+        from htsworkflow.pipelines import gerald
+
+        tag = tree.tag.lower()
+        if tag != PipelineRun.PIPELINE_RUN.lower():
+          raise ValueError('Pipeline Run Expecting %s got %s' % (
+              PipelineRun.PIPELINE_RUN, tag))
+        for element in tree:
+          tag = element.tag.lower()
+          if tag == PipelineRun.FLOWCELL_ID.lower():
+            self._flowcell_id = element.text
+          #ok the xword.Xword.XWORD pattern for module.class.constant is lame
+          # you should only have Firecrest or IPAR, never both of them.
+          elif tag == firecrest.Firecrest.FIRECREST.lower():
+            self.image_analysis = firecrest.Firecrest(xml=element)
+          elif tag == ipar.IPAR.IPAR.lower():
+            self.image_analysis = ipar.IPAR(xml=element)
+          elif tag == bustard.Bustard.BUSTARD.lower():
+            self.bustard = bustard.Bustard(xml=element)
+          elif tag == gerald.Gerald.GERALD.lower():
+            self.gerald = gerald.Gerald(xml=element)
+          else:
+            logging.warn('PipelineRun unrecognized tag %s' % (tag,))
+
+    def _get_run_name(self):
+        """
+        Given a run tuple, find the latest date and use that as our name
+        """
+        if self._name is None:
+          tmax = max(self.image_analysis.time, self.bustard.time, self.gerald.time)
+          timestamp = time.strftime('%Y-%m-%d', time.localtime(tmax))
+          self._name = 'run_'+self.flowcell_id+"_"+timestamp+'.xml'
+        return self._name
+    name = property(_get_run_name)
+
+    def save(self, destdir=None):
+        if destdir is None:
+            destdir = ''
+        logging.info("Saving run report "+ self.name)
+        xml = self.get_elements()
+        indent(xml)
+        dest_pathname = os.path.join(destdir, self.name)
+        ElementTree.ElementTree(xml).write(dest_pathname)
+
+    def load(self, filename):
+        logging.info("Loading run report from " + filename)
+        tree = ElementTree.parse(filename).getroot()
+        self.set_elements(tree)
+
+def load_pipeline_run_xml(pathname):
+    """
+    Load and instantiate a Pipeline run from a run xml file
+
+    :Parameters: 
+      - `pathname` : location of an run xml file
+
+    :Returns: initialized PipelineRun object
+    """
+    tree = ElementTree.parse(pathname).getroot()
+    run = PipelineRun(xml=tree)
+    return run
+
+def get_runs(runfolder):
+    """
+    Search through a run folder for all the various sub component runs
+    and then return a PipelineRun for each different combination.
+
+    For example if there are two different GERALD runs, this will
+    generate two different PipelineRun objects, that differ
+    in there gerald component.
+    """
+    from htsworkflow.pipelines import firecrest
+    from htsworkflow.pipelines import ipar
+    from htsworkflow.pipelines import bustard
+    from htsworkflow.pipelines import gerald
+
+    def scan_post_image_analysis(runs, runfolder, image_analysis, pathname):
+        logging.info("Looking for bustard directories in %s" % (pathname,))
+        bustard_glob = os.path.join(pathname, "Bustard*")
+        for bustard_pathname in glob(bustard_glob):
+            logging.info("Found bustard directory %s" % (bustard_pathname,))
+            b = bustard.bustard(bustard_pathname)
+            gerald_glob = os.path.join(bustard_pathname, 'GERALD*')
+            logging.info("Looking for gerald directories in %s" % (pathname,))
+            for gerald_pathname in glob(gerald_glob):
+                logging.info("Found gerald directory %s" % (gerald_pathname,))
+                try:
+                    g = gerald.gerald(gerald_pathname)
+                    p = PipelineRun(runfolder)
+                    p.image_analysis = image_analysis
+                    p.bustard = b
+                    p.gerald = g
+                    runs.append(p)
+                except IOError, e:
+                    logging.error("Ignoring " + str(e))
+
+    datadir = os.path.join(runfolder, 'Data')
+
+    logging.info('Searching for runs in ' + datadir)
+    runs = []
+    # scan for firecrest directories
+    for firecrest_pathname in glob(os.path.join(datadir,"*Firecrest*")):
+        logging.info('Found firecrest in ' + datadir)
+        image_analysis = firecrest.firecrest(firecrest_pathname)
+        if image_analysis is None:
+           logging.warn(
+                "%s is an empty or invalid firecrest directory" % (firecrest_pathname,)
+            )
+       else:
+            scan_post_image_analysis(
+                runs, runfolder, image_analysis, firecrest_pathname
+            )
+    # scan for IPAR directories
+    for ipar_pathname in glob(os.path.join(datadir,"IPAR_*")):
+        logging.info('Found ipar directories in ' + datadir)
+        image_analysis = ipar.ipar(ipar_pathname)
+        if image_analysis is None:
+           logging.warn(
+                "%s is an empty or invalid IPAR directory" %(ipar_pathname,)
+            )
+       else:
+            scan_post_image_analysis(
+                runs, runfolder, image_analysis, ipar_pathname
+            )
+
+    return runs
+
+def get_specific_run(gerald_dir):
+    """
+    Given a gerald directory, construct a PipelineRun out of its parents
+
+    Basically this allows specifying a particular run instead of the previous
+    get_runs which scans a runfolder for various combinations of
+    firecrest/ipar/bustard/gerald runs.
+    """
+    from htsworkflow.pipelines import firecrest
+    from htsworkflow.pipelines import ipar
+    from htsworkflow.pipelines import bustard
+    from htsworkflow.pipelines import gerald
+
+    bustard_dir = os.path.abspath(os.path.join(gerald_dir, '..'))
+    image_dir = os.path.abspath(os.path.join(gerald_dir, '..', '..'))
+
+    runfolder_dir = os.path.abspath(os.path.join(image_dir, '..','..'))
+   
+    logging.info('--- use-run detected options ---')
+    logging.info('runfolder: %s' % (runfolder_dir,))
+    logging.info('image_dir: %s' % (image_dir,))
+    logging.info('bustard_dir: %s' % (bustard_dir,))
+    logging.info('gerald_dir: %s' % (gerald_dir,))
+
+    # find our processed image dir
+    image_run = None
+    # split into parent, and leaf directory
+    # leaf directory should be an IPAR or firecrest directory
+    data_dir, short_image_dir = os.path.split(image_dir)
+    logging.info('data_dir: %s' % (data_dir,))
+    logging.info('short_iamge_dir: %s' %(short_image_dir,))
+
+    # guess which type of image processing directory we have by looking
+    # in the leaf directory name
+    if re.search('Firecrest', short_image_dir, re.IGNORECASE) is not None:
+        image_run = firecrest.firecrest(image_dir)
+    elif re.search('IPAR', short_image_dir, re.IGNORECASE) is not None:
+        image_run = ipar.ipar(image_dir)
+    # if we din't find a run, report the error and return 
+    if image_run is None:
+        msg = '%s does not contain an image processing step' % (image_dir,)
+        logging.error(msg)
+        return None
+
+    # find our base calling
+    base_calling_run = bustard.bustard(bustard_dir)
+    if base_calling_run is None:
+        logging.error('%s does not contain a bustard run' % (bustard_dir,))
+        return None
+
+    # find alignments
+    gerald_run = gerald.gerald(gerald_dir)
+    if gerald_run is None:
+        logging.error('%s does not contain a gerald run' % (gerald_dir,))
+        return None
+
+    p = PipelineRun(runfolder_dir)
+    p.image_analysis = image_run
+    p.bustard = base_calling_run
+    p.gerald = gerald_run
+    
+    logging.info('Constructed PipelineRun from %s' % (gerald_dir,))
+    return p
+
+def extract_run_parameters(runs):
+    """
+    Search through runfolder_path for various runs and grab their parameters
+    """
+    for run in runs:
+      run.save()
+
+def summarize_mapped_reads(genome_map, mapped_reads):
+    """
+    Summarize per chromosome reads into a genome count
+    But handle spike-in/contamination symlinks seperately.
+    """
+    summarized_reads = {}
+    genome_reads = 0
+    genome = 'unknown'
+    for k, v in mapped_reads.items():
+        path, k = os.path.split(k)
+        if len(path) > 0 and not genome_map.has_key(path):
+            genome = path
+            genome_reads += v
+        else:
+            summarized_reads[k] = summarized_reads.setdefault(k, 0) + v
+    summarized_reads[genome] = genome_reads
+    return summarized_reads
+
+def summarize_lane(gerald, lane_id):
+    report = []
+    summary_results = gerald.summary.lane_results
+    for end in range(len(summary_results)):  
+      eland_result = gerald.eland_results.results[end][lane_id]
+      report.append("Sample name %s" % (eland_result.sample_name))
+      report.append("Lane id %s end %s" % (eland_result.lane_id, end))
+      cluster = summary_results[end][eland_result.lane_id].cluster
+      report.append("Clusters %d +/- %d" % (cluster[0], cluster[1]))
+      report.append("Total Reads: %d" % (eland_result.reads))
+
+      if hasattr(eland_result, 'match_codes'):
+          mc = eland_result.match_codes
+          nm = mc['NM']
+          nm_percent = float(nm)/eland_result.reads  * 100
+          qc = mc['QC']
+          qc_percent = float(qc)/eland_result.reads * 100
+
+          report.append("No Match: %d (%2.2g %%)" % (nm, nm_percent))
+          report.append("QC Failed: %d (%2.2g %%)" % (qc, qc_percent))
+          report.append('Unique (0,1,2 mismatches) %d %d %d' % \
+                        (mc['U0'], mc['U1'], mc['U2']))
+          report.append('Repeat (0,1,2 mismatches) %d %d %d' % \
+                        (mc['R0'], mc['R1'], mc['R2']))
+
+      if hasattr(eland_result, 'genome_map'):
+          report.append("Mapped Reads")
+          mapped_reads = summarize_mapped_reads(eland_result.genome_map, eland_result.mapped_reads)
+          for name, counts in mapped_reads.items():
+            report.append("  %s: %d" % (name, counts))
+
+      report.append('')
+    return report
+
+def summary_report(runs):
+    """
+    Summarize cluster numbers and mapped read counts for a runfolder
+    """
+    report = []
+    for run in runs:
+        # print a run name?
+        report.append('Summary for %s' % (run.name,))
+       # sort the report
+       eland_keys = run.gerald.eland_results.results[0].keys()
+       eland_keys.sort(alphanum)
+
+       for lane_id in eland_keys:
+            report.extend(summarize_lane(run.gerald, lane_id))
+            report.append('---')
+            report.append('')
+        return os.linesep.join(report)
+
+def is_compressed(filename):
+    if os.path.splitext(filename)[1] == ".gz":
+        return True
+    elif os.path.splitext(filename)[1] == '.bz2':
+        return True
+    else:
+        return False
+
+def extract_results(runs, output_base_dir=None):
+    if output_base_dir is None:
+        output_base_dir = os.getcwd()
+
+    for r in runs:
+      result_dir = os.path.join(output_base_dir, r.flowcell_id)
+      logging.info("Using %s as result directory" % (result_dir,))
+      if not os.path.exists(result_dir):
+        os.mkdir(result_dir)
+
+      # create cycle_dir
+      cycle = "C%d-%d" % (r.image_analysis.start, r.image_analysis.stop)
+      logging.info("Filling in %s" % (cycle,))
+      cycle_dir = os.path.join(result_dir, cycle)
+      if os.path.exists(cycle_dir):
+        logging.error("%s already exists, not overwriting" % (cycle_dir,))
+        continue
+      else:
+        os.mkdir(cycle_dir)
+
+      # copy stuff out of the main run
+      g = r.gerald
+
+      # save run file
+      r.save(cycle_dir)
+
+      # Copy Summary.htm
+      summary_path = os.path.join(r.gerald.pathname, 'Summary.htm')
+      if os.path.exists(summary_path):
+          logging.info('Copying %s to %s' % (summary_path, cycle_dir))
+          shutil.copy(summary_path, cycle_dir)
+      else:
+          logging.info('Summary file %s was not found' % (summary_path,))
+
+      # tar score files
+      score_files = []
+
+      # check for g.pathname/Temp a new feature of 1.1rc1
+      scores_path = g.pathname
+      scores_path_temp = os.path.join(scores_path, 'Temp')
+      if os.path.isdir(scores_path_temp):
+          scores_path = scores_path_temp
+
+      # hopefully we have a directory that contains s_*_score files
+      for f in os.listdir(scores_path):
+          if re.match('.*_score.txt', f):
+              score_files.append(f)
+
+      tar_cmd = ['/bin/tar', 'c'] + score_files
+      bzip_cmd = [ 'bzip2', '-9', '-c' ]
+      tar_dest_name =os.path.join(cycle_dir, 'scores.tar.bz2')
+      tar_dest = open(tar_dest_name, 'w')
+      logging.info("Compressing score files from %s" % (scores_path,))
+      logging.info("Running tar: " + " ".join(tar_cmd[:10]))
+      logging.info("Running bzip2: " + " ".join(bzip_cmd))
+      logging.info("Writing to %s" %(tar_dest_name))
+
+      env = {'BZIP': '-9'}
+      tar = subprocess.Popen(tar_cmd, stdout=subprocess.PIPE, shell=False, env=env,
+                             cwd=scores_path)
+      bzip = subprocess.Popen(bzip_cmd, stdin=tar.stdout, stdout=tar_dest)
+      tar.wait()
+
+      # copy & bzip eland files
+      for lanes_dictionary in g.eland_results.results:
+          for eland_lane in lanes_dictionary.values():
+              source_name = eland_lane.pathname
+              path, name = os.path.split(eland_lane.pathname)
+              dest_name = os.path.join(cycle_dir, name)
+             logging.info("Saving eland file %s to %s" % \
+                          (source_name, dest_name))
+
+              if is_compressed(name):
+                logging.info('Already compressed, Saving to %s' % (dest_name, ))
+                shutil.copy(source_name, dest_name)
+              else:
+                # not compressed
+                dest_name += '.bz2'
+                args = ['bzip2', '-9', '-c', source_name]
+                logging.info('Running: %s' % ( " ".join(args) ))
+                bzip_dest = open(dest_name, 'w')
+                bzip = subprocess.Popen(args, stdout=bzip_dest)
+                logging.info('Saving to %s' % (dest_name, ))
+                bzip.wait()
+
+def rm_list(files, dry_run=True):
+    for f in files:
+        if os.path.exists(f):
+            logging.info('deleting %s' % (f,))
+            if not dry_run:
+                if os.path.isdir(f):
+                    shutil.rmtree(f)
+                else:
+                    os.unlink(f)
+        else:
+            logging.warn("%s doesn't exist."% (f,))
+
+def clean_runs(runs, dry_run=True):
+    """
+    Clean up run folders to optimize for compression.
+    """
+    if dry_run:
+        logging.info('In dry-run mode')
+
+    for run in runs:
+        logging.info('Cleaninging %s' % (run.pathname,))
+        # rm RunLog*.xml
+        runlogs = glob(os.path.join(run.pathname, 'RunLog*xml'))
+        rm_list(runlogs, dry_run)
+        # rm pipeline_*.txt
+        pipeline_logs = glob(os.path.join(run.pathname, 'pipeline*.txt'))
+        rm_list(pipeline_logs, dry_run)
+        # rm gclog.txt?
+        # rm NetCopy.log? Isn't this robocopy?
+        logs = glob(os.path.join(run.pathname, '*.log'))
+        rm_list(logs, dry_run)
+        # rm nfn.log?
+        # Calibration
+        calibration_dir = glob(os.path.join(run.pathname, 'Calibration_*'))
+        rm_list(calibration_dir, dry_run)
+        # rm Images/L*
+        logging.info("Cleaning images")
+        image_dirs = glob(os.path.join(run.pathname, 'Images', 'L*'))
+        rm_list(image_dirs, dry_run)
+        # cd Data/C1-*_Firecrest*
+        logging.info("Cleaning intermediate files")
+        # make clean_intermediate
+        if os.path.exists(os.path.join(run.image_analysis.pathname, 'Makefile')):
+            clean_process = subprocess.Popen(['make', 'clean_intermediate'], 
+                                             cwd=run.image_analysis.pathname,)
+            clean_process.wait()
+
+
+
diff --git a/trunk/htsworkflow/pipelines/summary.py b/trunk/htsworkflow/pipelines/summary.py
new file mode 100644 (file)
index 0000000..dc323ff
--- /dev/null
@@ -0,0 +1,302 @@
+"""
+Analyze the Summary.htm file produced by GERALD
+"""
+import types
+from pprint import pprint
+
+from htsworkflow.pipelines.runfolder import ElementTree
+from htsworkflow.util.ethelp import indent, flatten
+
+nan = float('nan')
+
+class Summary(object):
+    """
+    Extract some useful information from the Summary.htm file
+    """
+    XML_VERSION = 3
+    SUMMARY = 'Summary'
+
+    class LaneResultSummary(object):
+        """
+        Parse the LaneResultSummary table out of Summary.htm
+        Mostly for the cluster number
+        """
+        LANE_RESULT_SUMMARY = 'LaneResultSummary'
+        TAGS = {
+          'LaneYield': 'lane_yield',
+          'Cluster': 'cluster', # Raw
+          'ClusterPF': 'cluster_pass_filter',
+          'AverageFirstCycleIntensity': 'average_first_cycle_intensity',
+          'PercentIntensityAfter20Cycles': 'percent_intensity_after_20_cycles',
+          'PercentPassFilterClusters': 'percent_pass_filter_clusters',
+          'PercentPassFilterAlign': 'percent_pass_filter_align',
+          'AverageAlignmentScore': 'average_alignment_score',
+          'PercentErrorRate': 'percent_error_rate'
+        }
+
+        def __init__(self, html=None, xml=None):
+            self.lane = None
+            self.end = 0
+            self.lane_yield = None
+            self.cluster = None
+            self.cluster_pass_filter = None
+            self.average_first_cycle_intensity = None
+            self.percent_intensity_after_20_cycles = None
+            self.percent_pass_filter_clusters = None
+            self.percent_pass_filter_align = None
+            self.average_alignment_score = None
+            self.percent_error_rate = None
+
+            if html is not None:
+                self.set_elements_from_html(html)
+            if xml is not None:
+                self.set_elements(xml)
+
+        def set_elements_from_html(self, data):
+            if not len(data) in (8,10):
+                raise RuntimeError("Summary.htm file format changed, len(data)=%d" % (len(data),))
+
+            # same in pre-0.3.0 Summary file and 0.3 summary file
+            self.lane = int(data[0])
+
+            if len(data) == 8:
+                parsed_data = [ parse_mean_range(x) for x in data[1:] ]
+                # this is the < 0.3 Pipeline version
+                self.cluster = parsed_data[0]
+                self.average_first_cycle_intensity = parsed_data[1]
+                self.percent_intensity_after_20_cycles = parsed_data[2]
+                self.percent_pass_filter_clusters = parsed_data[3]
+                self.percent_pass_filter_align = parsed_data[4]
+                self.average_alignment_score = parsed_data[5]
+                self.percent_error_rate = parsed_data[6]
+            elif len(data) == 10:
+                parsed_data = [ parse_mean_range(x) for x in data[2:] ]
+                # this is the >= 0.3 summary file
+                self.lane_yield = data[1]
+                self.cluster = parsed_data[0]
+                self.cluster_pass_filter = parsed_data[1]
+                self.average_first_cycle_intensity = parsed_data[2]
+                self.percent_intensity_after_20_cycles = parsed_data[3]
+                self.percent_pass_filter_clusters = parsed_data[4]
+                self.percent_pass_filter_align = parsed_data[5]
+                self.average_alignment_score = parsed_data[6]
+                self.percent_error_rate = parsed_data[7]
+
+        def get_elements(self):
+            lane_result = ElementTree.Element(
+                            Summary.LaneResultSummary.LANE_RESULT_SUMMARY,
+                            {'lane': str(self.lane), 'end': str(self.end)})
+            for tag, variable_name in Summary.LaneResultSummary.TAGS.items():
+                value = getattr(self, variable_name)
+                if value is None:
+                    continue
+                # it looks like a sequence
+                elif type(value) in (types.TupleType, types.ListType):
+                    element = make_mean_range_element(
+                      lane_result,
+                      tag,
+                      *value
+                    )
+                else:
+                    element = ElementTree.SubElement(lane_result, tag)
+                    element.text = value
+            return lane_result
+
+        def set_elements(self, tree):
+            if tree.tag != Summary.LaneResultSummary.LANE_RESULT_SUMMARY:
+                raise ValueError('Expected %s' % (
+                        Summary.LaneResultSummary.LANE_RESULT_SUMMARY))
+            self.lane = int(tree.attrib['lane'])
+            # default to the first end, for the older summary files
+            # that are single ended
+            self.end = int(tree.attrib.get('end', 0))
+            tags = Summary.LaneResultSummary.TAGS
+            for element in list(tree):
+                try:
+                    variable_name = tags[element.tag]
+                    setattr(self, variable_name,
+                            parse_summary_element(element))
+                except KeyError, e:
+                    logging.warn('Unrecognized tag %s' % (element.tag,))
+
+    def __init__(self, filename=None, xml=None):
+        # lane results is a list of 1 or 2 ends containing
+        # a dictionary of all the lanes reported in this
+        # summary file
+        self.lane_results = [{}]
+
+        if filename is not None:
+            self._extract_lane_results(filename)
+        if xml is not None:
+            self.set_elements(xml)
+
+    def __getitem__(self, key):
+        return self.lane_results[key]
+
+    def __len__(self):
+        return len(self.lane_results)
+
+    def _flattened_row(self, row):
+        """
+        flatten the children of a <tr>...</tr>
+        """
+        return [flatten(x) for x in row.getchildren() ]
+
+    def _parse_table(self, table):
+        """
+        assumes the first line is the header of a table,
+        and that the remaining rows are data
+        """
+        rows = table.getchildren()
+        data = []
+        for r in rows:
+            data.append(self._flattened_row(r))
+        return data
+
+    def _extract_named_tables(self, pathname):
+        """
+        extract all the 'named' tables from a Summary.htm file
+        and return as a dictionary
+
+        Named tables are <h2>...</h2><table>...</table> pairs
+        The contents of the h2 tag is considered to the name
+        of the table.
+        """
+        # tree = ElementTree.parse(pathname).getroot()
+        # hack for 1.1rc1, this should be removed when possible.
+        file_body = open(pathname).read()
+        file_body = file_body.replace('CHASTITY<=', 'CHASTITY&lt;=')
+        tree = ElementTree.fromstring(file_body)
+        body = tree.find('body')
+        tables = {}
+        for i in range(len(body)):
+            if body[i].tag == 'h2' and body[i+1].tag == 'table':
+                # we have an interesting table
+                name = flatten(body[i])
+                table = body[i+1]
+                data = self._parse_table(table)
+                tables[name] = data
+        return tables
+
+    def _extract_lane_results(self, pathname):
+        tables = self._extract_named_tables(pathname)
+        table_names = [ ('Lane Results Summary', 0),
+                        ('Lane Results Summary : Read 1', 0),
+                        ('Lane Results Summary : Read 2', 1),]
+        for name, end in table_names:
+          if tables.has_key(name):
+            self._extract_lane_results_for_end(tables, name, end)
+
+    def _extract_lane_results_for_end(self, tables, table_name, end):
+        """
+        extract the Lane Results Summary table
+        """
+        # parse lane result summary
+        lane_summary = tables[table_name]
+        # this is version 1 of the summary file
+        if len(lane_summary[-1]) == 8:
+            # strip header
+            headers = lane_summary[0]
+            # grab the lane by lane data
+            lane_summary = lane_summary[1:]
+
+        # len(lane_summary[-1] = 10 is version 2 of the summary file
+        #                      = 9  is version 3 of the Summary.htm file
+        elif len(lane_summary[-1]) in (9, 10):
+            # lane_summary[0] is a different less specific header row
+            headers = lane_summary[1]
+            lane_summary = lane_summary[2:10]
+            # after the last lane, there's a set of chip wide averages
+
+        # append an extra dictionary if needed
+        if len(self.lane_results) < (end + 1):
+          self.lane_results.append({})
+
+        for r in lane_summary:
+            lrs = Summary.LaneResultSummary(html=r)
+            lrs.end = end
+            self.lane_results[lrs.end][lrs.lane] = lrs
+
+    def get_elements(self):
+        summary = ElementTree.Element(Summary.SUMMARY,
+                                      {'version': unicode(Summary.XML_VERSION)})
+        for end in self.lane_results:
+            for lane in end.values():
+                summary.append(lane.get_elements())
+        return summary
+
+    def set_elements(self, tree):
+        if tree.tag != Summary.SUMMARY:
+            return ValueError("Expected %s" % (Summary.SUMMARY,))
+        xml_version = int(tree.attrib.get('version', 0))
+        if xml_version > Summary.XML_VERSION:
+            logging.warn('Summary XML tree is a higher version than this class')
+        for element in list(tree):
+            lrs = Summary.LaneResultSummary()
+            lrs.set_elements(element)
+            if len(self.lane_results) < (lrs.end + 1):
+              self.lane_results.append({})
+            self.lane_results[lrs.end][lrs.lane] = lrs
+
+    def is_paired_end(self):
+      return len(self.lane_results) == 2
+
+    def dump(self):
+        """
+        Debugging function, report current object
+        """
+        pass
+
+def tonumber(v):
+    """
+    Convert a value to int if its an int otherwise a float.
+    """
+    try:
+        v = int(v)
+    except ValueError, e:
+        v = float(v)
+    return v
+
+def parse_mean_range(value):
+    """
+    Parse values like 123 +/- 4.5
+    """
+    if value.strip() == 'unknown':
+        return nan, nan
+
+    values = value.split()
+    if len(values) == 1:
+        if values[0] == '+/-':
+            return nan,nan
+        else:
+            return tonumber(values[0])
+
+    average, pm, deviation = values
+    if pm != '+/-':
+        raise RuntimeError("Summary.htm file format changed")
+    return tonumber(average), tonumber(deviation)
+
+def make_mean_range_element(parent, name, mean, deviation):
+    """
+    Make an ElementTree subelement <Name mean='mean', deviation='deviation'/>
+    """
+    element = ElementTree.SubElement(parent, name,
+                                     { 'mean': unicode(mean),
+                                       'deviation': unicode(deviation)})
+    return element
+
+def parse_mean_range_element(element):
+    """
+    Grab mean/deviation out of element
+    """
+    return (tonumber(element.attrib['mean']),
+            tonumber(element.attrib['deviation']))
+
+def parse_summary_element(element):
+    """
+    Determine if we have a simple element or a mean/deviation element
+    """
+    if len(element.attrib) > 0:
+        return parse_mean_range_element(element)
+    else:
+        return element.text
diff --git a/trunk/htsworkflow/pipelines/test/__init__.py b/trunk/htsworkflow/pipelines/test/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/trunk/htsworkflow/pipelines/test/simulate_runfolder.py b/trunk/htsworkflow/pipelines/test/simulate_runfolder.py
new file mode 100644 (file)
index 0000000..2e340d2
--- /dev/null
@@ -0,0 +1,186 @@
+"""
+Create simulated solexa/illumina runfolders for testing
+"""
+
+import os
+import shutil
+
+TEST_CODE_DIR = os.path.split(__file__)[0]
+TESTDATA_DIR = os.path.join(TEST_CODE_DIR, 'testdata')
+LANE_LIST = range(1,9)
+
+def make_firecrest_dir(data_dir, version="1.9.2", start=1, stop=37):
+    firecrest_dir = os.path.join(data_dir, 
+                                 'C%d-%d_Firecrest%s_12-04-2008_diane' % (start, stop, version)
+                                 )
+    os.mkdir(firecrest_dir)
+    return firecrest_dir
+    
+def make_ipar_dir(data_dir, version='1.01'):
+    """
+    Construct an artificial ipar parameter file and directory
+    """
+    ipar1_01_file = os.path.join(TESTDATA_DIR, 'IPAR1.01.params')
+    shutil.copy(ipar1_01_file, os.path.join(data_dir, '.params'))
+
+    ipar_dir = os.path.join(data_dir, 'IPAR_%s' % (version,))
+    if not os.path.exists(ipar_dir):
+      os.mkdir(ipar_dir)
+    return ipar_dir
+
+def make_flowcell_id(runfolder_dir, flowcell_id=None):
+    if flowcell_id is None:
+        flowcell_id = '207BTAAXY'
+
+    config = """<?xml version="1.0"?>
+<FlowcellId>
+  <Text>%s</Text>
+</FlowcellId>""" % (flowcell_id,)
+    config_dir = os.path.join(runfolder_dir, 'Config')
+
+    if not os.path.exists(config_dir):
+        os.mkdir(config_dir)
+    pathname = os.path.join(config_dir, 'FlowcellId.xml')
+    f = open(pathname,'w')
+    f.write(config)
+    f.close()
+
+def make_bustard_config132(gerald_dir):
+    source = os.path.join(TESTDATA_DIR, 'bustard-config132.xml')
+    destination = os.path.join(gerald_dir, 'config.xml')
+    shutil.copy(source, destination)
+
+def make_matrix(matrix_filename):
+    contents = """# Auto-generated frequency response matrix
+> A
+> C
+> G
+> T
+0.77 0.15 -0.04 -0.04
+0.76 1.02 -0.05 -0.06
+-0.10 -0.10 1.17 -0.03
+-0.13 -0.12 0.80 1.27
+"""
+    f = open(matrix_filename, 'w')
+    f.write(contents)
+    f.close()
+
+def make_phasing_params(bustard_dir):
+    for lane in range(1,9):
+        pathname = os.path.join(bustard_dir, 'params%d.xml' % (lane))
+        f = open(pathname, 'w')
+        f.write("""<Parameters>
+  <Phasing>0.009900</Phasing>
+  <Prephasing>0.003500</Prephasing>
+</Parameters>
+""")
+        f.close()
+
+def make_gerald_config_026(gerald_dir):
+    source = os.path.join(TESTDATA_DIR, 'gerald_config_0.2.6.xml')
+    destination = os.path.join(gerald_dir, 'config.xml')
+    shutil.copy(source, destination)
+
+def make_gerald_config_100(gerald_dir):
+    source = os.path.join(TESTDATA_DIR, 'gerald_config_1.0.xml')
+    destination = os.path.join(gerald_dir, 'config.xml')
+    shutil.copy(source, destination)
+
+def make_summary_htm_100(gerald_dir):
+    source = os.path.join(TESTDATA_DIR, 'Summary-pipeline100.htm')
+    destination = os.path.join(gerald_dir, 'Summary.htm')
+    shutil.copy(source, destination)
+
+def make_summary_htm_110(gerald_dir):
+    source = os.path.join(TESTDATA_DIR, 'Summary-pipeline110.htm')
+    destination = os.path.join(gerald_dir, 'Summary.htm')
+    shutil.copy(source, destination)
+
+def make_summary_paired_htm(gerald_dir):
+    source = os.path.join(TESTDATA_DIR, 'Summary-paired-pipeline110.htm')
+    destination = os.path.join(gerald_dir, 'Summary.htm')
+    shutil.copy(source, destination)
+
+def make_summary_ipar130_htm(gerald_dir):
+    source = os.path.join(TESTDATA_DIR, 'Summary-ipar130.htm')
+    destination = os.path.join(gerald_dir, 'Summary.htm')
+    shutil.copy(source, destination)
+
+def make_eland_results(gerald_dir):
+    eland_result = """>HWI-EAS229_24_207BTAAXX:1:7:599:759    ACATAGNCACAGACATAAACATAGACATAGAC U0      1       1       3       chrUextra.fa    28189829        R       D.
+>HWI-EAS229_24_207BTAAXX:1:7:205:842    AAACAANNCTCCCAAACACGTAAACTGGAAAA  U1      0       1       0       chr2L.fa        8796855 R       DD      24T
+>HWI-EAS229_24_207BTAAXX:1:7:776:582    AGCTCANCCGATCGAAAACCTCNCCAAGCAAT        NM      0       0       0
+>HWI-EAS229_24_207BTAAXX:1:7:205:842    AAACAANNCTCCCAAACACGTAAACTGGAAAA        U1      0       1       0       Lambda.fa        8796855 R       DD      24T
+"""
+    for i in range(1,9):
+        pathname = os.path.join(gerald_dir,
+                                's_%d_eland_result.txt' % (i,))
+        f = open(pathname, 'w')
+        f.write(eland_result)
+        f.close()
+
+def make_eland_multi(gerald_dir, paired=False, lane_list=LANE_LIST):
+    eland_multi = [""">HWI-EAS229_60_30DP9AAXX:1:1:1221:788   AAGATATCTACGACGTGGTATGGCGGTGTCTGGTCGT      NM
+>HWI-EAS229_60_30DP9AAXX:1:1:931:747    AAAAAAGCAAATTTCATTCACATGTTCTGTGTTCATA   1:0:2   chr5.fa:55269838R0
+>HWI-EAS229_60_30DP9AAXX:1:1:1121:379   AGAAGAGACATTAAGAGTTCCTGAAATTTATATCTGG   2:1:0   chr16.fa:46189180R1,chr7.fa:122968519R0,chr8.fa:48197174F0
+>HWI-EAS229_60_30DP9AAXX:1:1:892:1155   ACATTCTCCTTTCCTTCTGAAGTTTTTACGATTCTTT   0:9:10  chr10.fa:114298201F1,chr12.fa:8125072F1,19500297F2,42341293R2,chr13.fa:27688155R2,95069772R1,chr15.fa:51016475F2,chr16.fa:27052155F2,chr1.fa:192426217R2,chr21.fa:23685310R2,chr2.fa:106680068F1,chr3.fa:185226695F2,chr4.fa:106626808R2,chr5.fa:14704894F1,43530779F1,126543189F2,chr6.fa:74284101F1,chr7.fa:22516603F1,chr9.fa:134886204R
+>HWI-EAS229_60_30DP9AAXX:1:1:931:747    AAAAAAGCAAATTTCATTCACATGTTCTGTGTTCATA   1:0:0   spike.fa/sample1:55269838R0
+>HWI-EAS229_60_30DP9AAXX:1:1:931:747    AAAAAAGCAAATTTCATTCACATGTTCTGTGTTCATA   1:0:0   spike.fa/sample2:55269838R0
+""", """>HWI-EAS229_60_30DP9AAXX:1:1:1221:788   AAGATATCTACGACGTGGTATGGCGGTGTCTGGTCGT      NM
+>HWI-EAS229_60_30DP9AAXX:1:1:1221:788   NNNNNNNNNNNNNNGTGGTATGGCGGTGTCTGGTCGT     QC 
+>HWI-EAS229_60_30DP9AAXX:1:1:931:747    AAAAAAGCAAATTTCATTCACATGTTCTGTGTTCATA   1:0:2   chr5.fa:55269838R0
+>HWI-EAS229_60_30DP9AAXX:1:1:1121:379   AGAAGAGACATTAAGAGTTCCTGAAATTTATATCTGG   2:1:0   chr16.fa:46189180R1,chr7.fa:122968519R0,chr8.fa:48197174F0,chr7.fa:22516603F1,chr9.fa:134886204R
+>HWI-EAS229_60_30DP9AAXX:1:1:892:1155   ACATTCTCCTTTCCTTCTGAAGTTTTTACGATTCTTT   0:9:10  chr10.fa:114298201F1,chr12.fa:8125072F1,19500297F2,42341293R2,chr13.fa:27688155R2,95069772R1,chr15.fa:51016475F2,chr16.fa:27052155F2,chr1.fa:192426217R2,chr21.fa:23685310R2,chr2.fa:106680068F1,chr3.fa:185226695F2,chr4.fa:106626808R2,chr5.fa:14704894F1,43530779F1,126543189F2,chr6.fa:74284101F1
+>HWI-EAS229_60_30DP9AAXX:1:1:931:747    AAAAAAGCAAATTTCATTCACATGTTCTGTGTTCATA   1:0:0   spike.fa/sample1:55269838R0
+>HWI-EAS229_60_30DP9AAXX:1:1:931:747    AAAAAAGCAAATTTCATTCACATGTTCTGTGTTCATA   1:0:0   spike.fa/sample2:55269838R0
+"""]
+    if paired:
+        for e in [1,2]:
+            for i in lane_list:
+                pathname = os.path.join(gerald_dir,
+                                        's_%d_%d_eland_multi.txt' % (i,e))
+                f = open(pathname, 'w')
+                f.write(eland_multi[e-1])
+                f.close()
+    else:
+        for i in lane_list:
+            pathname = os.path.join(gerald_dir,
+                                    's_%d_eland_multi.txt' % (i,))
+            f = open(pathname, 'w')
+            f.write(eland_multi[0])
+            f.close()
+
+def make_scarf(gerald_dir, lane_list=LANE_LIST):
+    seq = """HWI-EAS229_92_30VNBAAXX:1:1:0:161:NCAATTACACGACGCTAGCCCTAAAGCTATTTCGAGG:E[aaaabb^a\a_^^a[S`ba_WZUXaaaaaaUKPER
+HWI-EAS229_92_30VNBAAXX:1:1:0:447:NAGATGCGCATTTGAAGTAGGAGCAAAAGATCAAGGT:EUabaab^baabaaaaaaaa^^Uaaaaa\aaaa__`a
+HWI-EAS229_92_30VNBAAXX:1:1:0:1210:NATAGCCTCTATAGAAGCCACTATTATTTTTTTCTTA:EUa`]`baaaaa^XQU^a`S``S_`J_aaaaaabb^V
+HWI-EAS229_92_30VNBAAXX:1:1:0:1867:NTGGAGCAGATATAAAAACAGATGGTGACGTTGAAGT:E[^UaaaUaba^aaa^aa^XV\baaLaLaaaaQVXV^
+HWI-EAS229_92_30VNBAAXX:1:1:0:1898:NAGCTCGTGTCGTGAGATGTTAGGTTAAGTCCTGCAA:EK_aaaaaaaaaaaUZaaZaXM[aaaXSM\aaZ]URE
+"""
+    for l in lane_list:
+        pathname = os.path.join(gerald_dir, 's_%d_sequence.txt' %(l,))
+        f = open(pathname,'w')
+        f.write(seq)
+        f.close()
+
+def make_fastq(gerald_dir, lane_list=LANE_LIST):
+    seq = """@HWI-EAS229:1:2:182:712#0/1
+AAAAAAAAAAAAAAAAAAAAANAAAAAAAAAAAAAAA
++HWI-EAS229:1:2:182:712#0/1
+\bab_bbaabbababbaaa]]D]bb_baabbab\baa
+@HWI-EAS229:1:2:198:621#0/1
+CCCCCCCCCCCCCCCCCCCCCNCCCCCCCCCCCCCCC
++HWI-EAS229:1:2:198:621#0/1
+[aaaaaaa`_`aaaaaaa[`ZDZaaaaaaaaaaaaaa
+@HWI-EAS229:1:2:209:1321#0/1
+AAAAAAAAAAAAAAAAAAAAANAAAAAAAAAAAAAAA
++HWI-EAS229:1:2:209:1321#0/1
+_bbbbbaaababaabbbbab]D]aaaaaaaaaaaaaa
+"""
+    for l in lane_list:
+        pathname = os.path.join(gerald_dir, 's_%d_sequence.txt' %(l,))
+        f = open(pathname,'w')
+        f.write(seq)
+        f.close()
+
+
diff --git a/trunk/htsworkflow/pipelines/test/test_genome_mapper.py b/trunk/htsworkflow/pipelines/test/test_genome_mapper.py
new file mode 100644 (file)
index 0000000..8ba1ba5
--- /dev/null
@@ -0,0 +1,33 @@
+import unittest
+
+from StringIO import StringIO
+from htsworkflow.pipelines import genome_mapper
+
+class testGenomeMapper(unittest.TestCase):
+    def test_construct_mapper(self):
+        genomes = {
+        'Arabidopsis thaliana': {'v01212004': '/arabidopsis'},
+        'Homo sapiens': {'hg18': '/hg18'},
+        'Mus musculus': {'mm8': '/mm8',
+                        'mm9': '/mm9',
+                        'mm10': '/mm10'},
+        'Phage': {'174': '/phi'},
+        }
+        genome_map = genome_mapper.constructMapperDict(genomes)
+        
+        self.failUnlessEqual("%(Mus musculus|mm8)s" % (genome_map), "/mm8")
+        self.failUnlessEqual("%(Phage|174)s" % (genome_map), "/phi")
+        self.failUnlessEqual("%(Mus musculus)s" % (genome_map), "/mm10")
+        self.failUnlessEqual("%(Mus musculus|mm8)s" % (genome_map), "/mm8")
+        self.failUnlessEqual("%(Mus musculus|mm10)s" % (genome_map), "/mm10")
+        
+        self.failUnlessEqual(len(genome_map.keys()), 6)
+        self.failUnlessEqual(len(genome_map.values()), 6)
+        self.failUnlessEqual(len(genome_map.items()), 6)
+        
+        
+def suite():
+    return unittest.makeSuite(testGenomeMapper,'test')
+
+if __name__ == "__main__":
+    unittest.main(defaultTest="suite")
diff --git a/trunk/htsworkflow/pipelines/test/test_runfolder026.py b/trunk/htsworkflow/pipelines/test/test_runfolder026.py
new file mode 100644 (file)
index 0000000..f8160ed
--- /dev/null
@@ -0,0 +1,474 @@
+#!/usr/bin/env python
+
+from datetime import datetime, date
+import os
+import tempfile
+import shutil
+import unittest
+
+from htsworkflow.pipelines import firecrest
+from htsworkflow.pipelines import bustard
+from htsworkflow.pipelines import gerald
+from htsworkflow.pipelines import runfolder
+from htsworkflow.pipelines.runfolder import ElementTree
+
+from htsworkflow.pipelines.test.simulate_runfolder import *
+
+
+def make_summary_htm(gerald_dir):
+    summary_htm = """<!--RUN_TIME Mon Apr 21 11:52:25 2008 -->
+<!--SOFTWARE_VERSION @(#) $Id: jerboa.pl,v 1.31 2007/03/05 17:52:15 km Exp $-->
+<html>
+<body>
+
+<a name="Top"><h2><title>080416_HWI-EAS229_0024_207BTAAXX Summary</title></h2></a>
+<h1>Summary Information For Experiment 080416_HWI-EAS229_0024_207BTAAXX on Machine HWI-EAS229</h1>
+<h2><br></br>Chip Summary<br></br></h2>
+<table border="1" cellpadding="5">
+<tr><td>Machine</td><td>HWI-EAS229</td></tr>
+<tr><td>Run Folder</td><td>080416_HWI-EAS229_0024_207BTAAXX</td></tr>
+<tr><td>Chip ID</td><td>unknown</td></tr>
+</table>
+<h2><br></br>Lane Parameter Summary<br></br></h2>
+<table border="1" cellpadding="5">
+<tr>
+<td>Lane</td>
+<td>Sample ID</td>
+<td>Sample Target</td>
+<td>Sample Type</td>
+<td>Length</td>
+<td>Filter</td>
+<td>Tiles</td>
+</tr>
+<tr>
+<td>1</td>
+<td>unknown</td>
+<td>dm3</td>
+<td>ELAND</td>
+<td>32</td>
+<td>'((CHASTITY>=0.6))'</td>
+<td><a href="#Lane1">Lane 1</a></td>
+</tr>
+<tr>
+<td>2</td>
+<td>unknown</td>
+<td>equcab1</td>
+<td>ELAND</td>
+<td>32</td>
+<td>'((CHASTITY>=0.6))'</td>
+<td><a href="#Lane2">Lane 2</a></td>
+</tr>
+<tr>
+<td>3</td>
+<td>unknown</td>
+<td>equcab1</td>
+<td>ELAND</td>
+<td>32</td>
+<td>'((CHASTITY>=0.6))'</td>
+<td><a href="#Lane3">Lane 3</a></td>
+</tr>
+<tr>
+<td>4</td>
+<td>unknown</td>
+<td>canfam2</td>
+<td>ELAND</td>
+<td>32</td>
+<td>'((CHASTITY>=0.6))'</td>
+<td><a href="#Lane4">Lane 4</a></td>
+</tr>
+<tr>
+<td>5</td>
+<td>unknown</td>
+<td>hg18</td>
+<td>ELAND</td>
+<td>32</td>
+<td>'((CHASTITY>=0.6))'</td>
+<td><a href="#Lane5">Lane 5</a></td>
+</tr>
+<tr>
+<td>6</td>
+<td>unknown</td>
+<td>hg18</td>
+<td>ELAND</td>
+<td>32</td>
+<td>'((CHASTITY>=0.6))'</td>
+<td><a href="#Lane6">Lane 6</a></td>
+</tr>
+<tr>
+<td>7</td>
+<td>unknown</td>
+<td>hg18</td>
+<td>ELAND</td>
+<td>32</td>
+<td>'((CHASTITY>=0.6))'</td>
+<td><a href="#Lane7">Lane 7</a></td>
+</tr>
+<tr>
+<td>8</td>
+<td>unknown</td>
+<td>hg18</td>
+<td>ELAND</td>
+<td>32</td>
+<td>'((CHASTITY>=0.6))'</td>
+<td><a href="#Lane8">Lane 8</a></td>
+</tr>
+</table>
+<h2><br></br>Lane Results Summary<br></br></h2>
+<table border="1" cellpadding="5">
+<tr>
+
+<td>Lane </td>
+<td>Clusters </td>
+<td>Av 1st Cycle Int </td>
+<td>% intensity after 20 cycles </td>
+<td>% PF Clusters </td>
+<td>% Align (PF) </td>
+<td>Av Alignment Score (PF) </td>
+<td> % Error Rate (PF) </td>
+</tr>
+<tr>
+<td>1</td>
+<td>17421 +/- 2139</td>
+<td>7230 +/- 801</td>
+<td>23.73 +/- 10.79</td>
+<td>13.00 +/- 22.91</td>
+<td>32.03 +/- 18.45</td>
+<td>6703.57 +/- 3753.85</td>
+<td>4.55 +/- 4.81</td>
+</tr>
+<tr>
+<td>2</td>
+<td>20311 +/- 2402</td>
+<td>7660 +/- 678</td>
+<td>17.03 +/- 4.40</td>
+<td>40.74 +/- 30.33</td>
+<td>29.54 +/- 9.03</td>
+<td>5184.02 +/- 1631.54</td>
+<td>3.27 +/- 3.94</td>
+</tr>
+<tr>
+<td>3</td>
+<td>20193 +/- 2399</td>
+<td>7700 +/- 797</td>
+<td>15.75 +/- 3.30</td>
+<td>56.56 +/- 17.16</td>
+<td>27.33 +/- 7.48</td>
+<td>4803.49 +/- 1313.31</td>
+<td>3.07 +/- 2.86</td>
+</tr>
+<tr>
+<td>4</td>
+<td>15537 +/- 2531</td>
+<td>7620 +/- 1392</td>
+<td>15.37 +/- 3.79</td>
+<td>63.05 +/- 18.30</td>
+<td>15.88 +/- 4.99</td>
+<td>3162.13 +/- 962.59</td>
+<td>3.11 +/- 2.22</td>
+</tr>
+<tr>
+<td>5</td>
+<td>32047 +/- 3356</td>
+<td>8093 +/- 831</td>
+<td>23.79 +/- 6.18</td>
+<td>53.36 +/- 18.06</td>
+<td>48.04 +/- 13.77</td>
+<td>9866.23 +/- 2877.30</td>
+<td>2.26 +/- 1.16</td>
+</tr>
+<tr>
+<td>6</td>
+<td>32946 +/- 4753</td>
+<td>8227 +/- 736</td>
+<td>24.07 +/- 4.69</td>
+<td>54.65 +/- 12.57</td>
+<td>50.98 +/- 10.54</td>
+<td>10468.86 +/- 2228.53</td>
+<td>2.21 +/- 2.33</td>
+</tr>
+<tr>
+<td>7</td>
+<td>39504 +/- 4171</td>
+<td>8401 +/- 785</td>
+<td>22.55 +/- 4.56</td>
+<td>45.22 +/- 10.34</td>
+<td>48.41 +/- 9.67</td>
+<td>9829.40 +/- 1993.20</td>
+<td>2.26 +/- 1.11</td>
+</tr>
+<tr>
+<td>8</td>
+<td>37998 +/- 3792</td>
+<td>8443 +/- 1211</td>
+<td>39.03 +/- 7.52</td>
+<td>42.16 +/- 12.35</td>
+<td>40.98 +/- 14.89</td>
+<td>8128.87 +/- 3055.34</td>
+<td>3.57 +/- 2.77</td>
+</tr>
+</table>
+</body>
+</html>
+"""
+    pathname = os.path.join(gerald_dir, 'Summary.htm')
+    f = open(pathname, 'w')
+    f.write(summary_htm)
+    f.close()
+
+def make_eland_results(gerald_dir):
+    eland_result = """>HWI-EAS229_24_207BTAAXX:1:7:599:759    ACATAGNCACAGACATAAACATAGACATAGAC U0      1       1       3       chrUextra.fa    28189829        R       D.
+>HWI-EAS229_24_207BTAAXX:1:7:205:842    AAACAANNCTCCCAAACACGTAAACTGGAAAA  U1      0       1       0       chr2L.fa        8796855 R       DD      24T
+>HWI-EAS229_24_207BTAAXX:1:7:776:582    AGCTCANCCGATCGAAAACCTCNCCAAGCAAT        NM      0       0       0
+>HWI-EAS229_24_207BTAAXX:1:7:205:842    AAACAANNCTCCCAAACACGTAAACTGGAAAA        U1      0       1       0       Lambda.fa        8796855 R       DD      24T
+"""
+    for i in range(1,9):
+        pathname = os.path.join(gerald_dir, 
+                                's_%d_eland_result.txt' % (i,))
+        f = open(pathname, 'w')
+        f.write(eland_result)
+        f.close()
+                     
+class RunfolderTests(unittest.TestCase):
+    """
+    Test components of the runfolder processing code
+    which includes firecrest, bustard, and gerald
+    """
+    def setUp(self):
+        # make a fake runfolder directory
+        self.temp_dir = tempfile.mkdtemp(prefix='tmp_runfolder_')
+
+        self.runfolder_dir = os.path.join(self.temp_dir, 
+                                          '080102_HWI-EAS229_0010_207BTAAXX')
+        os.mkdir(self.runfolder_dir)
+
+        self.data_dir = os.path.join(self.runfolder_dir, 'Data')
+        os.mkdir(self.data_dir)
+
+        self.firecrest_dir = os.path.join(self.data_dir, 
+                               'C1-33_Firecrest1.8.28_12-04-2008_diane'
+                             )
+        os.mkdir(self.firecrest_dir)
+        self.matrix_dir = os.path.join(self.firecrest_dir, 'Matrix')
+        os.mkdir(self.matrix_dir)
+        matrix_filename = os.path.join(self.matrix_dir, 's_matrix')
+        make_matrix(matrix_filename)
+
+        self.bustard_dir = os.path.join(self.firecrest_dir, 
+                                        'Bustard1.8.28_12-04-2008_diane')
+        os.mkdir(self.bustard_dir)
+        make_phasing_params(self.bustard_dir)
+        
+        self.gerald_dir = os.path.join(self.bustard_dir,
+                                       'GERALD_12-04-2008_diane')
+        os.mkdir(self.gerald_dir)
+        make_gerald_config_026(self.gerald_dir)
+        make_summary_htm(self.gerald_dir)
+        make_eland_results(self.gerald_dir)
+
+    def tearDown(self):
+        shutil.rmtree(self.temp_dir)
+
+    def test_firecrest(self):
+        """
+        Construct a firecrest object
+        """
+        f = firecrest.firecrest(self.firecrest_dir)
+        self.failUnlessEqual(f.version, '1.8.28')
+        self.failUnlessEqual(f.start, 1)
+        self.failUnlessEqual(f.stop, 33)
+        self.failUnlessEqual(f.user, 'diane')
+        self.failUnlessEqual(f.date, date(2008,4,12))
+
+        xml = f.get_elements()
+        # just make sure that element tree can serialize the tree
+        xml_str = ElementTree.tostring(xml)
+
+        f2 = firecrest.Firecrest(xml=xml)
+        self.failUnlessEqual(f.version, f2.version)
+        self.failUnlessEqual(f.start,   f2.start)
+        self.failUnlessEqual(f.stop,    f2.stop)
+        self.failUnlessEqual(f.user,    f2.user)
+        self.failUnlessEqual(f.date,    f2.date)
+
+    def test_bustard(self):
+        """
+        construct a bustard object
+        """
+        b = bustard.bustard(self.bustard_dir)
+        self.failUnlessEqual(b.version, '1.8.28')
+        self.failUnlessEqual(b.date,    date(2008,4,12))
+        self.failUnlessEqual(b.user,    'diane')
+        self.failUnlessEqual(len(b.phasing), 8)
+        self.failUnlessAlmostEqual(b.phasing[8].phasing, 0.0099)
+        
+        xml = b.get_elements()
+        b2 = bustard.Bustard(xml=xml)
+        self.failUnlessEqual(b.version, b2.version)
+        self.failUnlessEqual(b.date,    b2.date )
+        self.failUnlessEqual(b.user,    b2.user)
+        self.failUnlessEqual(len(b.phasing), len(b2.phasing))
+        for key in b.phasing.keys():
+            self.failUnlessEqual(b.phasing[key].lane, 
+                                 b2.phasing[key].lane)
+            self.failUnlessEqual(b.phasing[key].phasing, 
+                                 b2.phasing[key].phasing)
+            self.failUnlessEqual(b.phasing[key].prephasing, 
+                                 b2.phasing[key].prephasing)
+
+    def test_gerald(self):
+        # need to update gerald and make tests for it
+        g = gerald.gerald(self.gerald_dir) 
+
+        self.failUnlessEqual(g.version, 
+            '@(#) Id: GERALD.pl,v 1.68.2.2 2007/06/13 11:08:49 km Exp')
+        self.failUnlessEqual(g.date, datetime(2008,4,19,19,8,30))
+        self.failUnlessEqual(len(g.lanes), len(g.lanes.keys()))
+        self.failUnlessEqual(len(g.lanes), len(g.lanes.items()))
+
+        
+        # list of genomes, matches what was defined up in 
+        # make_gerald_config.
+        # the first None is to offset the genomes list to be 1..9
+        # instead of pythons default 0..8
+        genomes = [None, '/g/dm3', '/g/equcab1', '/g/equcab1', '/g/canfam2',
+                         '/g/hg18', '/g/hg18', '/g/hg18', '/g/hg18', ]
+
+        # test lane specific parameters from gerald config file
+        for i in range(1,9):
+            cur_lane = g.lanes[i]
+            self.failUnlessEqual(cur_lane.analysis, 'eland')
+            self.failUnlessEqual(cur_lane.eland_genome, genomes[i])
+            self.failUnlessEqual(cur_lane.read_length, '32')
+            self.failUnlessEqual(cur_lane.use_bases, 'Y'*32)
+
+        # test data extracted from summary file
+        clusters = [None, 
+                    (17421, 2139), (20311, 2402), (20193, 2399), (15537, 2531),
+                    (32047, 3356), (32946, 4753), (39504, 4171), (37998, 3792)]
+
+        self.failUnlessEqual(len(g.summary), 1)
+        for i in range(1,9):
+            summary_lane = g.summary[0][i]
+            self.failUnlessEqual(summary_lane.cluster, clusters[i])
+            self.failUnlessEqual(summary_lane.lane, i)
+
+        xml = g.get_elements()
+        # just make sure that element tree can serialize the tree
+        xml_str = ElementTree.tostring(xml)
+        g2 = gerald.Gerald(xml=xml)
+
+        # do it all again after extracting from the xml file
+        self.failUnlessEqual(g.version, g2.version)
+        self.failUnlessEqual(g.date, g2.date)
+        self.failUnlessEqual(len(g.lanes.keys()), len(g2.lanes.keys()))
+        self.failUnlessEqual(len(g.lanes.items()), len(g2.lanes.items()))
+
+        # test lane specific parameters from gerald config file
+        for i in range(1,9):
+            g_lane = g.lanes[i]
+            g2_lane = g2.lanes[i]
+            self.failUnlessEqual(g_lane.analysis, g2_lane.analysis)
+            self.failUnlessEqual(g_lane.eland_genome, g2_lane.eland_genome)
+            self.failUnlessEqual(g_lane.read_length, g2_lane.read_length)
+            self.failUnlessEqual(g_lane.use_bases, g2_lane.use_bases)
+
+        self.failUnlessEqual(len(g.summary), 1)
+        # test (some) summary elements
+        for i in range(1,9):
+            g_summary = g.summary[0][i]
+            g2_summary = g2.summary[0][i]
+            self.failUnlessEqual(g_summary.cluster, g2_summary.cluster)
+            self.failUnlessEqual(g_summary.lane, g2_summary.lane)
+
+            g_eland = g.eland_results
+            g2_eland = g2.eland_results
+            for lane in g_eland.results[0].keys():
+                g_results = g_eland.results[0][lane]
+                g2_results = g2_eland.results[0][lane]
+                self.failUnlessEqual(g_results.reads, 
+                                     g2_results.reads)
+                self.failUnlessEqual(len(g_results.mapped_reads), 
+                                     len(g2_results.mapped_reads))
+                for k in g_results.mapped_reads.keys():
+                    self.failUnlessEqual(g_results.mapped_reads[k],
+                                         g2_results.mapped_reads[k])
+
+                self.failUnlessEqual(len(g_results.match_codes), 
+                                     len(g2_results.match_codes))
+                for k in g_results.match_codes.keys():
+                    self.failUnlessEqual(g_results.match_codes[k],
+                                         g2_results.match_codes[k])
+
+
+    def test_eland(self):
+        dm3_map = { 'chrUextra.fa' : 'dm3/chrUextra.fa',
+                    'chr2L.fa': 'dm3/chr2L.fa',
+                    'Lambda.fa': 'Lambda.fa'}
+        genome_maps = { 1:dm3_map, 2:dm3_map, 3:dm3_map, 4:dm3_map,
+                        5:dm3_map, 6:dm3_map, 7:dm3_map, 8:dm3_map }
+        eland = gerald.eland(self.gerald_dir, genome_maps=genome_maps)
+        
+        for i in range(1,9):
+            lane = eland.results[0][i]
+            self.failUnlessEqual(lane.reads, 4)
+            self.failUnlessEqual(lane.sample_name, "s")
+            self.failUnlessEqual(lane.lane_id, i)
+            self.failUnlessEqual(len(lane.mapped_reads), 3)
+            self.failUnlessEqual(lane.mapped_reads['Lambda.fa'], 1)
+            self.failUnlessEqual(lane.mapped_reads['dm3/chr2L.fa'], 1)
+            self.failUnlessEqual(lane.match_codes['U1'], 2)
+            self.failUnlessEqual(lane.match_codes['NM'], 1)
+
+        xml = eland.get_elements()
+        # just make sure that element tree can serialize the tree
+        xml_str = ElementTree.tostring(xml)
+        e2 = gerald.ELAND(xml=xml)
+
+        for i in range(1,9):
+            l1 = eland.results[0][i]
+            l2 = e2.results[0][i]
+            self.failUnlessEqual(l1.reads, l2.reads)
+            self.failUnlessEqual(l1.sample_name, l2.sample_name)
+            self.failUnlessEqual(l1.lane_id, l2.lane_id)
+            self.failUnlessEqual(len(l1.mapped_reads), len(l2.mapped_reads))
+            self.failUnlessEqual(len(l1.mapped_reads), 3)
+            for k in l1.mapped_reads.keys():
+                self.failUnlessEqual(l1.mapped_reads[k],
+                                     l2.mapped_reads[k])
+
+            self.failUnlessEqual(len(l1.match_codes), 9)
+            self.failUnlessEqual(len(l1.match_codes), len(l2.match_codes))
+            for k in l1.match_codes.keys():
+                self.failUnlessEqual(l1.match_codes[k], 
+                                     l2.match_codes[k])
+
+    def test_runfolder(self):
+        runs = runfolder.get_runs(self.runfolder_dir)
+        
+        # do we get the flowcell id from the filename?
+        self.failUnlessEqual(len(runs), 1)
+        self.failUnlessEqual(runs[0].name, 'run_207BTAAXX_2008-04-19.xml')
+
+        # do we get the flowcell id from the FlowcellId.xml file
+        make_flowcell_id(self.runfolder_dir, '207BTAAXY')
+        runs = runfolder.get_runs(self.runfolder_dir)
+        self.failUnlessEqual(len(runs), 1)
+        self.failUnlessEqual(runs[0].name, 'run_207BTAAXY_2008-04-19.xml')
+        
+        r1 = runs[0]
+        xml = r1.get_elements()
+        xml_str = ElementTree.tostring(xml)
+
+        r2 = runfolder.PipelineRun(xml=xml)
+        self.failUnlessEqual(r1.name, r2.name)
+        self.failIfEqual(r2.image_analysis, None)
+        self.failIfEqual(r2.bustard, None)
+        self.failIfEqual(r2.gerald, None)
+        
+
+def suite():
+    return unittest.makeSuite(RunfolderTests,'test')
+
+if __name__ == "__main__":
+    unittest.main(defaultTest="suite")
+    
diff --git a/trunk/htsworkflow/pipelines/test/test_runfolder030.py b/trunk/htsworkflow/pipelines/test/test_runfolder030.py
new file mode 100644 (file)
index 0000000..0691308
--- /dev/null
@@ -0,0 +1,898 @@
+#!/usr/bin/env python
+
+from datetime import datetime, date
+import os
+import tempfile
+import shutil
+import unittest
+
+from htsworkflow.pipelines import firecrest
+from htsworkflow.pipelines import bustard
+from htsworkflow.pipelines import gerald
+from htsworkflow.pipelines import runfolder
+from htsworkflow.pipelines.runfolder import ElementTree
+
+from htsworkflow.pipelines.test.simulate_runfolder import *
+
+    
+def make_summary_htm(gerald_dir):
+    summary_htm="""<!--RUN_TIME Wed Jul  2 06:47:44 2008 -->
+<!--SOFTWARE_VERSION @(#) $Id: jerboa.pl,v 1.94 2007/12/04 09:59:07 rshaw Exp $-->
+<html>
+<body>
+
+<a name="Top"><h2><title>080627_HWI-EAS229_0036_3055HAXX Summary</title></h2></a>
+<h1>Summary Information For Experiment 080627_HWI-EAS229_0036_3055HAXX on Machine HWI-EAS229</h1>
+<h2><br></br>Chip Summary<br></br></h2>
+<table border="1" cellpadding="5">
+<tr><td>Machine</td><td>HWI-EAS229</td></tr>
+<tr><td>Run Folder</td><td>080627_HWI-EAS229_0036_3055HAXX</td></tr>
+<tr><td>Chip ID</td><td>unknown</td></tr>
+</table>
+<h2><br></br>Chip Results Summary<br></br></h2>
+<table border="1" cellpadding="5">
+<tr>
+<td>Clusters</td>
+<td>Clusters (PF)</td>
+<td>Yield (kbases)</td>
+</tr>
+<tr><td>80933224</td>
+<td>43577803</td>
+<td>1133022</td>
+</tr>
+</table>
+<h2><br></br>Lane Parameter Summary<br></br></h2>
+<table border="1" cellpadding="5">
+<tr>
+<td>Lane</td>
+<td>Sample ID</td>
+<td>Sample Target</td>
+<td>Sample Type</td>
+<td>Length</td>
+<td>Filter</td>
+<td>Num Tiles</td>
+<td>Tiles</td>
+</tr>
+<tr>
+<td>1</td>
+<td>unknown</td>
+<td>mm9</td>
+<td>ELAND</td>
+<td>26</td>
+<td>'((CHASTITY>=0.6))'</td>
+<td>100</td>
+<td><a href="#Lane1">Lane 1</a></td>
+</tr>
+<tr>
+<td>2</td>
+<td>unknown</td>
+<td>mm9</td>
+<td>ELAND</td>
+<td>26</td>
+<td>'((CHASTITY>=0.6))'</td>
+<td>100</td>
+<td><a href="#Lane2">Lane 2</a></td>
+</tr>
+<tr>
+<td>3</td>
+<td>unknown</td>
+<td>mm9</td>
+<td>ELAND</td>
+<td>26</td>
+<td>'((CHASTITY>=0.6))'</td>
+<td>100</td>
+<td><a href="#Lane3">Lane 3</a></td>
+</tr>
+<tr>
+<td>4</td>
+<td>unknown</td>
+<td>elegans170</td>
+<td>ELAND</td>
+<td>26</td>
+<td>'((CHASTITY>=0.6))'</td>
+<td>100</td>
+<td><a href="#Lane4">Lane 4</a></td>
+</tr>
+<tr>
+<td>5</td>
+<td>unknown</td>
+<td>elegans170</td>
+<td>ELAND</td>
+<td>26</td>
+<td>'((CHASTITY>=0.6))'</td>
+<td>100</td>
+<td><a href="#Lane5">Lane 5</a></td>
+</tr>
+<tr>
+<td>6</td>
+<td>unknown</td>
+<td>elegans170</td>
+<td>ELAND</td>
+<td>26</td>
+<td>'((CHASTITY>=0.6))'</td>
+<td>100</td>
+<td><a href="#Lane6">Lane 6</a></td>
+</tr>
+<tr>
+<td>7</td>
+<td>unknown</td>
+<td>elegans170</td>
+<td>ELAND</td>
+<td>26</td>
+<td>'((CHASTITY>=0.6))'</td>
+<td>100</td>
+<td><a href="#Lane7">Lane 7</a></td>
+</tr>
+<tr>
+<td>8</td>
+<td>unknown</td>
+<td>elegans170</td>
+<td>ELAND</td>
+<td>26</td>
+<td>'((CHASTITY>=0.6))'</td>
+<td>100</td>
+<td><a href="#Lane8">Lane 8</a></td>
+</tr>
+</table>
+<h2><br></br>Lane Results Summary<br></br></h2>
+<table border="1" cellpadding="5">
+<tr>
+<td colspan="2">Lane Info</td>
+<td colspan="8">Tile Mean +/- SD for Lane</td>
+</tr>
+<tr>
+<td>Lane </td>
+<td>Lane Yield (kbases) </td>
+<td>Clusters (raw)</td>
+<td>Clusters (PF) </td>
+<td>1st Cycle Int (PF) </td>
+<td>% intensity after 20 cycles (PF) </td>
+<td>% PF Clusters </td>
+<td>% Align (PF) </td>
+<td>Alignment Score (PF) </td>
+<td> % Error Rate (PF) </td>
+</tr>
+<tr>
+<td>1</td>
+<td>158046</td>
+<td>96483 +/- 9074</td>
+<td>60787 +/- 4240</td>
+<td>329 +/- 35</td>
+<td>101.88 +/- 6.03</td>
+<td>63.21 +/- 3.29</td>
+<td>70.33 +/- 0.24</td>
+<td>9054.08 +/- 59.16</td>
+<td>0.46 +/- 0.18</td>
+</tr>
+<tr>
+<td>2</td>
+<td>156564</td>
+<td>133738 +/- 7938</td>
+<td>60217 +/- 1926</td>
+<td>444 +/- 39</td>
+<td>92.62 +/- 7.58</td>
+<td>45.20 +/- 3.31</td>
+<td>51.98 +/- 0.74</td>
+<td>6692.04 +/- 92.49</td>
+<td>0.46 +/- 0.09</td>
+</tr>
+<tr>
+<td>3</td>
+<td>185818</td>
+<td>152142 +/- 10002</td>
+<td>71468 +/- 2827</td>
+<td>366 +/- 36</td>
+<td>91.53 +/- 8.66</td>
+<td>47.19 +/- 3.80</td>
+<td>82.24 +/- 0.44</td>
+<td>10598.68 +/- 64.13</td>
+<td>0.41 +/- 0.04</td>
+</tr>
+<tr>
+<td>4</td>
+<td>34953</td>
+<td>15784 +/- 2162</td>
+<td>13443 +/- 1728</td>
+<td>328 +/- 40</td>
+<td>97.53 +/- 9.87</td>
+<td>85.29 +/- 1.91</td>
+<td>80.02 +/- 0.53</td>
+<td>10368.82 +/- 71.08</td>
+<td>0.15 +/- 0.05</td>
+</tr>
+<tr>
+<td>5</td>
+<td>167936</td>
+<td>119735 +/- 8465</td>
+<td>64590 +/- 2529</td>
+<td>417 +/- 37</td>
+<td>88.69 +/- 14.79</td>
+<td>54.10 +/- 2.59</td>
+<td>76.95 +/- 0.32</td>
+<td>9936.47 +/- 65.75</td>
+<td>0.28 +/- 0.02</td>
+</tr>
+<tr>
+<td>6</td>
+<td>173463</td>
+<td>152177 +/- 8146</td>
+<td>66716 +/- 2493</td>
+<td>372 +/- 39</td>
+<td>87.06 +/- 9.86</td>
+<td>43.98 +/- 3.12</td>
+<td>78.80 +/- 0.43</td>
+<td>10162.28 +/- 49.65</td>
+<td>0.38 +/- 0.03</td>
+</tr>
+<tr>
+<td>7</td>
+<td>149287</td>
+<td>84649 +/- 7325</td>
+<td>57418 +/- 3617</td>
+<td>295 +/- 28</td>
+<td>89.40 +/- 8.23</td>
+<td>67.97 +/- 1.82</td>
+<td>33.38 +/- 0.25</td>
+<td>4247.92 +/- 32.37</td>
+<td>1.00 +/- 0.03</td>
+</tr>
+<tr>
+<td>8</td>
+<td>106953</td>
+<td>54622 +/- 4812</td>
+<td>41136 +/- 3309</td>
+<td>284 +/- 37</td>
+<td>90.21 +/- 9.10</td>
+<td>75.39 +/- 2.27</td>
+<td>48.33 +/- 0.29</td>
+<td>6169.21 +/- 169.50</td>
+<td>0.86 +/- 1.22</td>
+</tr>
+<tr><td colspan="13">Tile mean across chip</td></tr>
+<tr>
+<td>Av.</td>
+<td></td>
+<td>101166</td>
+<td>54472</td>
+<td>354</td>
+<td>92.36</td>
+<td>60.29</td>
+<td>65.25</td>
+<td>8403.69</td>
+<td>0.50</td>
+</tr>
+</table>
+<h2><br></br>Expanded Lane Summary<br></br></h2>
+<table border="1" cellpadding="5">
+<tr>
+
+<tr><td colspan="2">Lane Info</td>
+<td colspan="2">Phasing Info</td>
+<td colspan="2">Raw Data (tile mean)</td>
+<td colspan="7">Filtered Data (tile mean)</td></tr>
+<td>Lane </td>
+<td>Clusters (tile mean) (raw)</td>
+<td>% Phasing </td>
+<td>% Prephasing </td>
+<td>% Error Rate (raw) </td>
+<td> Equiv Perfect Clusters (raw) </td>
+<td>% retained </td>
+<td>Cycle 2-4 Av Int (PF) </td>
+<td>Cycle 2-10 Av % Loss (PF) </td>
+<td>Cycle 10-20 Av % Loss (PF) </td>
+<td>% Align (PF) </td>
+<td>% Error Rate (PF) </td>
+<td> Equiv Perfect Clusters (PF) </td>
+</tr>
+<tr>
+<td>1</td>
+<td>96483</td>
+<td>0.7700</td>
+<td>0.3100</td>
+<td>1.00</td>
+<td>49676</td>
+<td>63.21</td>
+<td>317 +/- 32</td>
+<td>0.13 +/- 0.44</td>
+<td>-1.14 +/- 0.34</td>
+<td>70.33</td>
+<td>0.46</td>
+<td>41758</td>
+</tr>
+<tr>
+<td>2</td>
+<td>133738</td>
+<td>0.7700</td>
+<td>0.3100</td>
+<td>1.22</td>
+<td>40467</td>
+<td>45.20</td>
+<td>415 +/- 33</td>
+<td>0.29 +/- 0.40</td>
+<td>-0.79 +/- 0.35</td>
+<td>51.98</td>
+<td>0.46</td>
+<td>30615</td>
+</tr>
+<tr>
+<td>3</td>
+<td>152142</td>
+<td>0.7700</td>
+<td>0.3100</td>
+<td>1.30</td>
+<td>78588</td>
+<td>47.19</td>
+<td>344 +/- 26</td>
+<td>0.68 +/- 0.51</td>
+<td>-0.77 +/- 0.42</td>
+<td>82.24</td>
+<td>0.41</td>
+<td>57552</td>
+</tr>
+<tr>
+<td>4</td>
+<td>15784</td>
+<td>0.7700</td>
+<td>0.3100</td>
+<td>0.29</td>
+<td>11095</td>
+<td>85.29</td>
+<td>306 +/- 34</td>
+<td>0.20 +/- 0.69</td>
+<td>-1.28 +/- 0.66</td>
+<td>80.02</td>
+<td>0.15</td>
+<td>10671</td>
+</tr>
+<tr>
+<td>5</td>
+<td>119735</td>
+<td>0.7700</td>
+<td>0.3100</td>
+<td>0.85</td>
+<td>60335</td>
+<td>54.10</td>
+<td>380 +/- 32</td>
+<td>0.34 +/- 0.49</td>
+<td>-1.55 +/- 4.69</td>
+<td>76.95</td>
+<td>0.28</td>
+<td>49015</td>
+</tr>
+<tr>
+<td>6</td>
+<td>152177</td>
+<td>0.7700</td>
+<td>0.3100</td>
+<td>1.21</td>
+<td>70905</td>
+<td>43.98</td>
+<td>333 +/- 27</td>
+<td>0.57 +/- 0.50</td>
+<td>-0.91 +/- 0.39</td>
+<td>78.80</td>
+<td>0.38</td>
+<td>51663</td>
+</tr>
+<tr>
+<td>7</td>
+<td>84649</td>
+<td>0.7700</td>
+<td>0.3100</td>
+<td>1.38</td>
+<td>21069</td>
+<td>67.97</td>
+<td>272 +/- 20</td>
+<td>1.15 +/- 0.52</td>
+<td>-0.84 +/- 0.58</td>
+<td>33.38</td>
+<td>1.00</td>
+<td>18265</td>
+</tr>
+<tr>
+<td>8</td>
+<td>54622</td>
+<td>0.7700</td>
+<td>0.3100</td>
+<td>1.17</td>
+<td>21335</td>
+<td>75.39</td>
+<td>262 +/- 31</td>
+<td>1.10 +/- 0.59</td>
+<td>-1.01 +/- 0.47</td>
+<td>48.33</td>
+<td>0.86</td>
+<td>19104</td>
+</tr>
+</table>
+<b><br></br>IVC Plots</b>
+<p> <a href='IVC.htm' target="_blank"> IVC.htm
+ </a></p>
+<b><br></br>All Intensity Plots</b>
+<p> <a href='All.htm' target="_blank"> All.htm
+ </a></p>
+<b><br></br>Error graphs: </b>
+<p> <a href='Error.htm' target="_blank"> Error.htm
+ </a></p>
+<td><a href="#Top">Back to top</a></td>
+<a name="Lane1"><h2><br></br>Lane 1<br></br></h2></a>
+<table border="1" cellpadding="5">
+<tr>
+<td>Lane </td>
+<td>Tile </td>
+<td>Clusters (raw)</td>
+<td>Av 1st Cycle Int (PF) </td>
+<td>Av % intensity after 20 cycles (PF) </td>
+<td>% PF Clusters </td>
+<td>% Align (PF) </td>
+<td>Av Alignment Score (PF) </td>
+<td>% Error Rate (PF) </td>
+</tr>
+<tr>
+<td>1</td>
+<td>0001</td>
+<td>114972</td>
+<td>326.48</td>
+<td>94.39</td>
+<td>57.44</td>
+<td>70.2</td>
+<td>9038.6</td>
+<td>0.44</td>
+</tr>
+</table>
+<td><a href="#Top">Back to top</a></td>
+<a name="Lane2"><h2><br></br>Lane 2<br></br></h2></a>
+<table border="1" cellpadding="5">
+<tr>
+<td>Lane </td>
+<td>Tile </td>
+<td>Clusters (raw)</td>
+<td>Av 1st Cycle Int (PF) </td>
+<td>Av % intensity after 20 cycles (PF) </td>
+<td>% PF Clusters </td>
+<td>% Align (PF) </td>
+<td>Av Alignment Score (PF) </td>
+<td>% Error Rate (PF) </td>
+</tr>
+<tr>
+<td>2</td>
+<td>0001</td>
+<td>147793</td>
+<td>448.12</td>
+<td>83.68</td>
+<td>38.57</td>
+<td>53.7</td>
+<td>6905.4</td>
+<td>0.54</td>
+</tr>
+</table>
+<td><a href="#Top">Back to top</a></td>
+<a name="Lane3"><h2><br></br>Lane 3<br></br></h2></a>
+<table border="1" cellpadding="5">
+<tr>
+<td>Lane </td>
+<td>Tile </td>
+<td>Clusters (raw)</td>
+<td>Av 1st Cycle Int (PF) </td>
+<td>Av % intensity after 20 cycles (PF) </td>
+<td>% PF Clusters </td>
+<td>% Align (PF) </td>
+<td>Av Alignment Score (PF) </td>
+<td>% Error Rate (PF) </td>
+</tr>
+<tr>
+<td>3</td>
+<td>0001</td>
+<td>167904</td>
+<td>374.05</td>
+<td>86.91</td>
+<td>40.36</td>
+<td>81.3</td>
+<td>10465.0</td>
+<td>0.47</td>
+</tr>
+</table>
+<td><a href="#Top">Back to top</a></td>
+<a name="Lane4"><h2><br></br>Lane 4<br></br></h2></a>
+<table border="1" cellpadding="5">
+<tr>
+<td>Lane </td>
+<td>Tile </td>
+<td>Clusters (raw)</td>
+<td>Av 1st Cycle Int (PF) </td>
+<td>Av % intensity after 20 cycles (PF) </td>
+<td>% PF Clusters </td>
+<td>% Align (PF) </td>
+<td>Av Alignment Score (PF) </td>
+<td>% Error Rate (PF) </td>
+</tr>
+<tr>
+<td>4</td>
+<td>0001</td>
+<td>20308</td>
+<td>276.85</td>
+<td>92.87</td>
+<td>84.26</td>
+<td>80.4</td>
+<td>10413.8</td>
+<td>0.16</td>
+</tr>
+</table>
+<td><a href="#Top">Back to top</a></td>
+<a name="Lane5"><h2><br></br>Lane 5<br></br></h2></a>
+<table border="1" cellpadding="5">
+<tr>
+<td>Lane </td>
+<td>Tile </td>
+<td>Clusters (raw)</td>
+<td>Av 1st Cycle Int (PF) </td>
+<td>Av % intensity after 20 cycles (PF) </td>
+<td>% PF Clusters </td>
+<td>% Align (PF) </td>
+<td>Av Alignment Score (PF) </td>
+<td>% Error Rate (PF) </td>
+</tr>
+</table>
+<td><a href="#Top">Back to top</a></td>
+<a name="Lane6"><h2><br></br>Lane 6<br></br></h2></a>
+<table border="1" cellpadding="5">
+<tr>
+<td>Lane </td>
+<td>Tile </td>
+<td>Clusters (raw)</td>
+<td>Av 1st Cycle Int (PF) </td>
+<td>Av % intensity after 20 cycles (PF) </td>
+<td>% PF Clusters </td>
+<td>% Align (PF) </td>
+<td>Av Alignment Score (PF) </td>
+<td>% Error Rate (PF) </td>
+</tr>
+<tr>
+<td>6</td>
+<td>0001</td>
+<td>166844</td>
+<td>348.12</td>
+<td>77.59</td>
+<td>38.13</td>
+<td>79.7</td>
+<td>10264.4</td>
+<td>0.44</td>
+</tr>
+</table>
+<td><a href="#Top">Back to top</a></td>
+<a name="Lane7"><h2><br></br>Lane 7<br></br></h2></a>
+<table border="1" cellpadding="5">
+<tr>
+<td>Lane </td>
+<td>Tile </td>
+<td>Clusters (raw)</td>
+<td>Av 1st Cycle Int (PF) </td>
+<td>Av % intensity after 20 cycles (PF) </td>
+<td>% PF Clusters </td>
+<td>% Align (PF) </td>
+<td>Av Alignment Score (PF) </td>
+<td>% Error Rate (PF) </td>
+</tr>
+<tr>
+<td>7</td>
+<td>0001</td>
+<td>98913</td>
+<td>269.90</td>
+<td>86.66</td>
+<td>64.55</td>
+<td>33.2</td>
+<td>4217.5</td>
+<td>1.02</td>
+</tr>
+</table>
+<td><a href="#Top">Back to top</a></td>
+<a name="Lane8"><h2><br></br>Lane 8<br></br></h2></a>
+<table border="1" cellpadding="5">
+<tr>
+<td>Lane </td>
+<td>Tile </td>
+<td>Clusters (raw)</td>
+<td>Av 1st Cycle Int (PF) </td>
+<td>Av % intensity after 20 cycles (PF) </td>
+<td>% PF Clusters </td>
+<td>% Align (PF) </td>
+<td>Av Alignment Score (PF) </td>
+<td>% Error Rate (PF) </td>
+</tr>
+<tr>
+<td>8</td>
+<td>0001</td>
+<td>64972</td>
+<td>243.60</td>
+<td>89.40</td>
+<td>73.17</td>
+<td>48.3</td>
+<td>6182.8</td>
+<td>0.71</td>
+</tr>
+</table>
+<td><a href="#Top">Back to top</a></td>
+</body>
+</html>
+"""
+    pathname = os.path.join(gerald_dir, 'Summary.htm')
+    f = open(pathname, 'w')
+    f.write(summary_htm)
+    f.close()
+
+def make_eland_results(gerald_dir):
+    eland_result = """>HWI-EAS229_24_207BTAAXX:1:7:599:759    ACATAGNCACAGACATAAACATAGACATAGAC U0      1       1       3       chrUextra.fa    28189829        R       D.
+>HWI-EAS229_24_207BTAAXX:1:7:205:842    AAACAANNCTCCCAAACACGTAAACTGGAAAA  U1      0       1       0       chr2L.fa        8796855 R       DD      24T
+>HWI-EAS229_24_207BTAAXX:1:7:776:582    AGCTCANCCGATCGAAAACCTCNCCAAGCAAT        NM      0       0       0
+>HWI-EAS229_24_207BTAAXX:1:7:205:842    AAACAANNCTCCCAAACACGTAAACTGGAAAA        U1      0       1       0       Lambda.fa        8796855 R       DD      24T
+"""
+    for i in range(1,9):
+        pathname = os.path.join(gerald_dir, 
+                                's_%d_eland_result.txt' % (i,))
+        f = open(pathname, 'w')
+        f.write(eland_result)
+        f.close()
+
+def make_runfolder(obj=None):
+    """
+    Make a fake runfolder, attach all the directories to obj if defined
+    """
+    # make a fake runfolder directory
+    temp_dir = tempfile.mkdtemp(prefix='tmp_runfolder_')
+
+    runfolder_dir = os.path.join(temp_dir, 
+                                 '080102_HWI-EAS229_0010_207BTAAXX')
+    os.mkdir(runfolder_dir)
+
+    data_dir = os.path.join(runfolder_dir, 'Data')
+    os.mkdir(data_dir)
+
+    firecrest_dir = os.path.join(data_dir, 
+                                 'C1-33_Firecrest1.8.28_12-04-2008_diane'
+                                 )
+    os.mkdir(firecrest_dir)
+    matrix_dir = os.path.join(firecrest_dir, 'Matrix')
+    os.mkdir(matrix_dir)
+    matrix_filename = os.path.join(matrix_dir, 's_matrix.txt')
+    make_matrix(matrix_filename)
+
+    bustard_dir = os.path.join(firecrest_dir, 
+                               'Bustard1.8.28_12-04-2008_diane')
+    os.mkdir(bustard_dir)
+    make_phasing_params(bustard_dir)
+
+    gerald_dir = os.path.join(bustard_dir,
+                              'GERALD_12-04-2008_diane')
+    os.mkdir(gerald_dir)
+    make_gerald_config_026(gerald_dir)
+    make_summary_htm(gerald_dir)
+    make_eland_results(gerald_dir)
+
+    if obj is not None:
+        obj.temp_dir = temp_dir
+        obj.runfolder_dir = runfolder_dir
+        obj.data_dir = data_dir
+        obj.firecrest_dir = firecrest_dir
+        obj.matrix_dir = matrix_dir
+        obj.bustard_dir = bustard_dir
+        obj.gerald_dir = gerald_dir
+        
+                     
+class RunfolderTests(unittest.TestCase):
+    """
+    Test components of the runfolder processing code
+    which includes firecrest, bustard, and gerald
+    """
+    def setUp(self):
+        # attaches all the directories to the object passed in
+        make_runfolder(self)
+
+    def tearDown(self):
+        shutil.rmtree(self.temp_dir)
+
+    def test_firecrest(self):
+        """
+        Construct a firecrest object
+        """
+        f = firecrest.firecrest(self.firecrest_dir)
+        self.failUnlessEqual(f.version, '1.8.28')
+        self.failUnlessEqual(f.start, 1)
+        self.failUnlessEqual(f.stop, 33)
+        self.failUnlessEqual(f.user, 'diane')
+        self.failUnlessEqual(f.date, date(2008,4,12))
+
+        xml = f.get_elements()
+        # just make sure that element tree can serialize the tree
+        xml_str = ElementTree.tostring(xml)
+
+        f2 = firecrest.Firecrest(xml=xml)
+        self.failUnlessEqual(f.version, f2.version)
+        self.failUnlessEqual(f.start,   f2.start)
+        self.failUnlessEqual(f.stop,    f2.stop)
+        self.failUnlessEqual(f.user,    f2.user)
+        self.failUnlessEqual(f.date,    f2.date)
+
+    def test_bustard(self):
+        """
+        construct a bustard object
+        """
+        b = bustard.bustard(self.bustard_dir)
+        self.failUnlessEqual(b.version, '1.8.28')
+        self.failUnlessEqual(b.date,    date(2008,4,12))
+        self.failUnlessEqual(b.user,    'diane')
+        self.failUnlessEqual(len(b.phasing), 8)
+        self.failUnlessAlmostEqual(b.phasing[8].phasing, 0.0099)
+        
+        xml = b.get_elements()
+        b2 = bustard.Bustard(xml=xml)
+        self.failUnlessEqual(b.version, b2.version)
+        self.failUnlessEqual(b.date,    b2.date )
+        self.failUnlessEqual(b.user,    b2.user)
+        self.failUnlessEqual(len(b.phasing), len(b2.phasing))
+        for key in b.phasing.keys():
+            self.failUnlessEqual(b.phasing[key].lane, 
+                                 b2.phasing[key].lane)
+            self.failUnlessEqual(b.phasing[key].phasing, 
+                                 b2.phasing[key].phasing)
+            self.failUnlessEqual(b.phasing[key].prephasing, 
+                                 b2.phasing[key].prephasing)
+
+    def test_gerald(self):
+        # need to update gerald and make tests for it
+        g = gerald.gerald(self.gerald_dir) 
+
+        self.failUnlessEqual(g.version, 
+            '@(#) Id: GERALD.pl,v 1.68.2.2 2007/06/13 11:08:49 km Exp')
+        self.failUnlessEqual(g.date, datetime(2008,4,19,19,8,30))
+        self.failUnlessEqual(len(g.lanes), len(g.lanes.keys()))
+        self.failUnlessEqual(len(g.lanes), len(g.lanes.items()))
+
+        
+        # list of genomes, matches what was defined up in 
+        # make_gerald_config.
+        # the first None is to offset the genomes list to be 1..9
+        # instead of pythons default 0..8
+        genomes = [None, '/g/dm3', '/g/equcab1', '/g/equcab1', '/g/canfam2',
+                         '/g/hg18', '/g/hg18', '/g/hg18', '/g/hg18', ]
+
+        # test lane specific parameters from gerald config file
+        for i in range(1,9):
+            cur_lane = g.lanes[i]
+            self.failUnlessEqual(cur_lane.analysis, 'eland')
+            self.failUnlessEqual(cur_lane.eland_genome, genomes[i])
+            self.failUnlessEqual(cur_lane.read_length, '32')
+            self.failUnlessEqual(cur_lane.use_bases, 'Y'*32)
+
+        # test data extracted from summary file
+        clusters = [None, 
+                    (96483, 9074), (133738, 7938), 
+                    (152142, 10002), (15784, 2162), 
+                    (119735, 8465), (152177, 8146),
+                    (84649, 7325), (54622, 4812),]
+
+        self.failUnlessEqual(len(g.summary), 1)
+        for i in range(1,9):
+            summary_lane = g.summary[0][i]
+            self.failUnlessEqual(summary_lane.cluster, clusters[i])
+            self.failUnlessEqual(summary_lane.lane, i)
+
+        xml = g.get_elements()
+        # just make sure that element tree can serialize the tree
+        xml_str = ElementTree.tostring(xml)
+        g2 = gerald.Gerald(xml=xml)
+
+        # do it all again after extracting from the xml file
+        self.failUnlessEqual(g.version, g2.version)
+        self.failUnlessEqual(g.date, g2.date)
+        self.failUnlessEqual(len(g.lanes.keys()), len(g2.lanes.keys()))
+        self.failUnlessEqual(len(g.lanes.items()), len(g2.lanes.items()))
+
+        # test lane specific parameters from gerald config file
+        for i in range(1,9):
+            g_lane = g.lanes[i]
+            g2_lane = g2.lanes[i]
+            self.failUnlessEqual(g_lane.analysis, g2_lane.analysis)
+            self.failUnlessEqual(g_lane.eland_genome, g2_lane.eland_genome)
+            self.failUnlessEqual(g_lane.read_length, g2_lane.read_length)
+            self.failUnlessEqual(g_lane.use_bases, g2_lane.use_bases)
+
+        # test (some) summary elements
+        self.failUnlessEqual(len(g.summary), 1)
+        for i in range(1,9):
+            g_summary = g.summary[0][i]
+            g2_summary = g2.summary[0][i]
+            self.failUnlessEqual(g_summary.cluster, g2_summary.cluster)
+            self.failUnlessEqual(g_summary.lane, g2_summary.lane)
+
+            g_eland = g.eland_results
+            g2_eland = g2.eland_results
+            for lane in g_eland.results[0].keys():
+                g_results = g_eland.results[0][lane]
+                g2_results = g2_eland.results[0][lane]
+                self.failUnlessEqual(g_results.reads, 
+                                     g2_results.reads)
+                self.failUnlessEqual(len(g_results.mapped_reads), 
+                                     len(g2_results.mapped_reads))
+                for k in g_results.mapped_reads.keys():
+                    self.failUnlessEqual(g_results.mapped_reads[k],
+                                         g2_results.mapped_reads[k])
+
+                self.failUnlessEqual(len(g_results.match_codes), 
+                                     len(g2_results.match_codes))
+                for k in g_results.match_codes.keys():
+                    self.failUnlessEqual(g_results.match_codes[k],
+                                         g2_results.match_codes[k])
+
+
+    def test_eland(self):
+        dm3_map = { 'chrUextra.fa' : 'dm3/chrUextra.fa',
+                    'chr2L.fa': 'dm3/chr2L.fa',
+                    'Lambda.fa': 'Lambda.fa'}
+        genome_maps = { 1:dm3_map, 2:dm3_map, 3:dm3_map, 4:dm3_map,
+                        5:dm3_map, 6:dm3_map, 7:dm3_map, 8:dm3_map }
+        eland = gerald.eland(self.gerald_dir, genome_maps=genome_maps)
+        
+        for i in range(1,9):
+            lane = eland.results[0][i]
+            self.failUnlessEqual(lane.reads, 4)
+            self.failUnlessEqual(lane.sample_name, "s")
+            self.failUnlessEqual(lane.lane_id, i)
+            self.failUnlessEqual(len(lane.mapped_reads), 3)
+            self.failUnlessEqual(lane.mapped_reads['Lambda.fa'], 1)
+            self.failUnlessEqual(lane.mapped_reads['dm3/chr2L.fa'], 1)
+            self.failUnlessEqual(lane.match_codes['U1'], 2)
+            self.failUnlessEqual(lane.match_codes['NM'], 1)
+
+        xml = eland.get_elements()
+        # just make sure that element tree can serialize the tree
+        xml_str = ElementTree.tostring(xml)
+        e2 = gerald.ELAND(xml=xml)
+
+        for i in range(1,9):
+            l1 = eland.results[0][i]
+            l2 = e2.results[0][i]
+            self.failUnlessEqual(l1.reads, l2.reads)
+            self.failUnlessEqual(l1.sample_name, l2.sample_name)
+            self.failUnlessEqual(l1.lane_id, l2.lane_id)
+            self.failUnlessEqual(len(l1.mapped_reads), len(l2.mapped_reads))
+            self.failUnlessEqual(len(l1.mapped_reads), 3)
+            for k in l1.mapped_reads.keys():
+                self.failUnlessEqual(l1.mapped_reads[k],
+                                     l2.mapped_reads[k])
+
+            self.failUnlessEqual(len(l1.match_codes), 9)
+            self.failUnlessEqual(len(l1.match_codes), len(l2.match_codes))
+            for k in l1.match_codes.keys():
+                self.failUnlessEqual(l1.match_codes[k], 
+                                     l2.match_codes[k])
+
+    def test_runfolder(self):
+        runs = runfolder.get_runs(self.runfolder_dir)
+        
+        # do we get the flowcell id from the filename?
+        self.failUnlessEqual(len(runs), 1)
+        self.failUnlessEqual(runs[0].name, 'run_207BTAAXX_2008-04-19.xml')
+
+        # do we get the flowcell id from the FlowcellId.xml file
+        make_flowcell_id(self.runfolder_dir, '207BTAAXY')
+        runs = runfolder.get_runs(self.runfolder_dir)
+        self.failUnlessEqual(len(runs), 1)
+        self.failUnlessEqual(runs[0].name, 'run_207BTAAXY_2008-04-19.xml')
+        
+        r1 = runs[0]
+        xml = r1.get_elements()
+        xml_str = ElementTree.tostring(xml)
+
+        r2 = runfolder.PipelineRun(xml=xml)
+        self.failUnlessEqual(r1.name, r2.name)
+        self.failIfEqual(r2.image_analysis, None)
+        self.failIfEqual(r2.bustard, None)
+        self.failIfEqual(r2.gerald, None)
+        
+
+def suite():
+    return unittest.makeSuite(RunfolderTests,'test')
+
+if __name__ == "__main__":
+    unittest.main(defaultTest="suite")
+    
diff --git a/trunk/htsworkflow/pipelines/test/test_runfolder110.py b/trunk/htsworkflow/pipelines/test/test_runfolder110.py
new file mode 100644 (file)
index 0000000..fc91ce4
--- /dev/null
@@ -0,0 +1,302 @@
+#!/usr/bin/env python
+
+from datetime import datetime, date
+import os
+import tempfile
+import shutil
+import unittest
+
+from htsworkflow.pipelines import firecrest
+from htsworkflow.pipelines import bustard
+from htsworkflow.pipelines import gerald
+from htsworkflow.pipelines import runfolder
+from htsworkflow.pipelines.runfolder import ElementTree
+
+from htsworkflow.pipelines.test.simulate_runfolder import *
+
+
+def make_runfolder(obj=None):
+    """
+    Make a fake runfolder, attach all the directories to obj if defined
+    """
+    # make a fake runfolder directory
+    temp_dir = tempfile.mkdtemp(prefix='tmp_runfolder_')
+
+    runfolder_dir = os.path.join(temp_dir,
+                                 '081017_HWI-EAS229_0062_30J55AAXX')
+    os.mkdir(runfolder_dir)
+
+    data_dir = os.path.join(runfolder_dir, 'Data')
+    os.mkdir(data_dir)
+
+    firecrest_dir = os.path.join(data_dir,
+                                 'C1-37_Firecrest1.9.6_20-10-2008_diane')
+    os.mkdir(firecrest_dir)
+
+    bustard_dir = os.path.join(firecrest_dir,
+                               'Bustard1.9.6_20-10-2008_diane')
+    os.mkdir(bustard_dir)
+    make_phasing_params(bustard_dir)
+
+    matrix_name = os.path.join(bustard_dir, 'matrix1.txt')
+    make_matrix(matrix_name)
+
+
+    gerald_dir = os.path.join(bustard_dir,
+                              'GERALD_20-10-2008_diane')
+    os.mkdir(gerald_dir)
+    make_gerald_config_100(gerald_dir)
+    make_summary_htm_110(gerald_dir)
+    make_eland_multi(gerald_dir)
+
+    if obj is not None:
+        obj.temp_dir = temp_dir
+        obj.runfolder_dir = runfolder_dir
+        obj.data_dir = data_dir
+        obj.image_analysis_dir = firecrest_dir
+        obj.bustard_dir = bustard_dir
+        obj.gerald_dir = gerald_dir
+
+
+class RunfolderTests(unittest.TestCase):
+    """
+    Test components of the runfolder processing code
+    which includes firecrest, bustard, and gerald
+    """
+    def setUp(self):
+        # attaches all the directories to the object passed in
+        make_runfolder(self)
+
+    def tearDown(self):
+        shutil.rmtree(self.temp_dir)
+
+    def test_firecrest(self):
+        """
+        Construct a firecrest object
+        """
+        f = firecrest.firecrest(self.image_analysis_dir)
+        self.failUnlessEqual(f.version, '1.9.6')
+        self.failUnlessEqual(f.start, 1)
+        self.failUnlessEqual(f.stop, 37)
+        self.failUnlessEqual(f.user, 'diane')
+        self.failUnlessEqual(f.date, date(2008,10,20))
+
+        xml = f.get_elements()
+        # just make sure that element tree can serialize the tree
+        xml_str = ElementTree.tostring(xml)
+
+        f2 = firecrest.Firecrest(xml=xml)
+        self.failUnlessEqual(f.version, f2.version)
+        self.failUnlessEqual(f.start,   f2.start)
+        self.failUnlessEqual(f.stop,    f2.stop)
+        self.failUnlessEqual(f.user,    f2.user)
+
+    def test_bustard(self):
+        """
+        construct a bustard object
+        """
+        b = bustard.bustard(self.bustard_dir)
+        self.failUnlessEqual(b.version, '1.9.6')
+        self.failUnlessEqual(b.date,    date(2008,10,20))
+        self.failUnlessEqual(b.user,    'diane')
+        self.failUnlessEqual(len(b.phasing), 8)
+        self.failUnlessAlmostEqual(b.phasing[8].phasing, 0.0099)
+
+        xml = b.get_elements()
+        b2 = bustard.Bustard(xml=xml)
+        self.failUnlessEqual(b.version, b2.version)
+        self.failUnlessEqual(b.date,    b2.date )
+        self.failUnlessEqual(b.user,    b2.user)
+        self.failUnlessEqual(len(b.phasing), len(b2.phasing))
+        for key in b.phasing.keys():
+            self.failUnlessEqual(b.phasing[key].lane,
+                                 b2.phasing[key].lane)
+            self.failUnlessEqual(b.phasing[key].phasing,
+                                 b2.phasing[key].phasing)
+            self.failUnlessEqual(b.phasing[key].prephasing,
+                                 b2.phasing[key].prephasing)
+
+    def test_gerald(self):
+        # need to update gerald and make tests for it
+        g = gerald.gerald(self.gerald_dir)
+
+        self.failUnlessEqual(g.version,
+            '@(#) Id: GERALD.pl,v 1.171 2008/05/19 17:36:14 mzerara Exp')
+        self.failUnlessEqual(g.date, datetime(2009,2,22,21,15,59))
+        self.failUnlessEqual(len(g.lanes), len(g.lanes.keys()))
+        self.failUnlessEqual(len(g.lanes), len(g.lanes.items()))
+
+
+        # list of genomes, matches what was defined up in
+        # make_gerald_config.
+        # the first None is to offset the genomes list to be 1..9
+        # instead of pythons default 0..8
+        genomes = [None, 
+                   '/g/mm9', 
+                   '/g/mm9', 
+                   '/g/elegans190', 
+                   '/g/arabidopsis01222004',
+                   '/g/mm9', 
+                   '/g/mm9', 
+                   '/g/mm9', 
+                   '/g/mm9', ]
+
+        # test lane specific parameters from gerald config file
+        for i in range(1,9):
+            cur_lane = g.lanes[i]
+            self.failUnlessEqual(cur_lane.analysis, 'eland_extended')
+            self.failUnlessEqual(cur_lane.eland_genome, genomes[i])
+            self.failUnlessEqual(cur_lane.read_length, '37')
+            self.failUnlessEqual(cur_lane.use_bases, 'Y'*37)
+
+        # I want to be able to use a simple iterator
+        for l in g.lanes.values():
+          self.failUnlessEqual(l.analysis, 'eland_extended')
+          self.failUnlessEqual(l.read_length, '37')
+          self.failUnlessEqual(l.use_bases, 'Y'*37)
+
+        # raw cluster numbers extracted from summary file
+        # its the first +/- value in the lane results summary
+        # section
+        clusters = [None,
+                    (190220, 15118), (190560, 14399),
+                    (187597, 12369), (204142, 16877),
+                    (247308, 11600), (204298, 15640),
+                    (202707, 15404), (198075, 14702),]
+
+        self.failUnlessEqual(len(g.summary), 1)
+        for i in range(1,9):
+            summary_lane = g.summary[0][i]
+            self.failUnlessEqual(summary_lane.cluster, clusters[i])
+            self.failUnlessEqual(summary_lane.lane, i)
+
+        xml = g.get_elements()
+        # just make sure that element tree can serialize the tree
+        xml_str = ElementTree.tostring(xml)
+        g2 = gerald.Gerald(xml=xml)
+
+        # do it all again after extracting from the xml file
+        self.failUnlessEqual(g.version, g2.version)
+        self.failUnlessEqual(g.date, g2.date)
+        self.failUnlessEqual(len(g.lanes.keys()), len(g2.lanes.keys()))
+        self.failUnlessEqual(len(g.lanes.items()), len(g2.lanes.items()))
+
+        # test lane specific parameters from gerald config file
+        for i in range(1,9):
+            g_lane = g.lanes[i]
+            g2_lane = g2.lanes[i]
+            self.failUnlessEqual(g_lane.analysis, g2_lane.analysis)
+            self.failUnlessEqual(g_lane.eland_genome, g2_lane.eland_genome)
+            self.failUnlessEqual(g_lane.read_length, g2_lane.read_length)
+            self.failUnlessEqual(g_lane.use_bases, g2_lane.use_bases)
+
+        self.failUnlessEqual(len(g.summary), 1)
+        # test (some) summary elements
+        for i in range(1,9):
+            g_summary = g.summary[0][i]
+            g2_summary = g2.summary[0][i]
+            self.failUnlessEqual(g_summary.cluster, g2_summary.cluster)
+            self.failUnlessEqual(g_summary.lane, g2_summary.lane)
+
+            g_eland = g.eland_results
+            g2_eland = g2.eland_results
+            for lane in g_eland.results[0].keys():
+                g_results = g_eland.results[0][lane]
+                g2_results = g2_eland.results[0][lane]
+                self.failUnlessEqual(g_results.reads,
+                                     g2_results.reads)
+                self.failUnlessEqual(len(g_results.mapped_reads),
+                                     len(g2_results.mapped_reads))
+                for k in g_results.mapped_reads.keys():
+                    self.failUnlessEqual(g_results.mapped_reads[k],
+                                         g2_results.mapped_reads[k])
+
+                self.failUnlessEqual(len(g_results.match_codes),
+                                     len(g2_results.match_codes))
+                for k in g_results.match_codes.keys():
+                    self.failUnlessEqual(g_results.match_codes[k],
+                                         g2_results.match_codes[k])
+
+
+    def test_eland(self):
+        hg_map = {'Lambda.fa': 'Lambda.fa'}
+        for i in range(1,22):
+          short_name = 'chr%d.fa' % (i,)
+          long_name = 'hg18/chr%d.fa' % (i,)
+          hg_map[short_name] = long_name
+
+        genome_maps = { 1:hg_map, 2:hg_map, 3:hg_map, 4:hg_map,
+                        5:hg_map, 6:hg_map, 7:hg_map, 8:hg_map }
+        eland = gerald.eland(self.gerald_dir, genome_maps=genome_maps)
+
+        for i in range(1,9):
+            lane = eland.results[0][i]
+            self.failUnlessEqual(lane.reads, 6)
+            self.failUnlessEqual(lane.sample_name, "s")
+            self.failUnlessEqual(lane.lane_id, i)
+            self.failUnlessEqual(len(lane.mapped_reads), 17)
+            self.failUnlessEqual(lane.mapped_reads['hg18/chr5.fa'], 4)
+            self.failUnlessEqual(lane.match_codes['U0'], 3)
+            self.failUnlessEqual(lane.match_codes['R0'], 2)
+            self.failUnlessEqual(lane.match_codes['U1'], 1)
+            self.failUnlessEqual(lane.match_codes['R1'], 9)
+            self.failUnlessEqual(lane.match_codes['U2'], 0)
+            self.failUnlessEqual(lane.match_codes['R2'], 12)
+            self.failUnlessEqual(lane.match_codes['NM'], 1)
+            self.failUnlessEqual(lane.match_codes['QC'], 0)
+
+        xml = eland.get_elements()
+        # just make sure that element tree can serialize the tree
+        xml_str = ElementTree.tostring(xml)
+        e2 = gerald.ELAND(xml=xml)
+
+        for i in range(1,9):
+            l1 = eland.results[0][i]
+            l2 = e2.results[0][i]
+            self.failUnlessEqual(l1.reads, l2.reads)
+            self.failUnlessEqual(l1.sample_name, l2.sample_name)
+            self.failUnlessEqual(l1.lane_id, l2.lane_id)
+            self.failUnlessEqual(len(l1.mapped_reads), len(l2.mapped_reads))
+            self.failUnlessEqual(len(l1.mapped_reads), 17)
+            for k in l1.mapped_reads.keys():
+                self.failUnlessEqual(l1.mapped_reads[k],
+                                     l2.mapped_reads[k])
+
+            self.failUnlessEqual(len(l1.match_codes), 9)
+            self.failUnlessEqual(len(l1.match_codes), len(l2.match_codes))
+            for k in l1.match_codes.keys():
+                self.failUnlessEqual(l1.match_codes[k],
+                                     l2.match_codes[k])
+
+    def test_runfolder(self):
+        runs = runfolder.get_runs(self.runfolder_dir)
+
+        # do we get the flowcell id from the filename?
+        self.failUnlessEqual(len(runs), 1)
+        name = 'run_30J55AAXX_2009-02-22.xml'
+        self.failUnlessEqual(runs[0].name, name)
+
+        # do we get the flowcell id from the FlowcellId.xml file
+        make_flowcell_id(self.runfolder_dir, '30J55AAXX')
+        runs = runfolder.get_runs(self.runfolder_dir)
+        self.failUnlessEqual(len(runs), 1)
+        name = 'run_30J55AAXX_2009-02-22.xml'
+        self.failUnlessEqual(runs[0].name, name)
+
+        r1 = runs[0]
+        xml = r1.get_elements()
+        xml_str = ElementTree.tostring(xml)
+
+        r2 = runfolder.PipelineRun(xml=xml)
+        self.failUnlessEqual(r1.name, r2.name)
+        self.failIfEqual(r2.image_analysis, None)
+        self.failIfEqual(r2.bustard, None)
+        self.failIfEqual(r2.gerald, None)
+
+
+def suite():
+    return unittest.makeSuite(RunfolderTests,'test')
+
+if __name__ == "__main__":
+    unittest.main(defaultTest="suite")
+
diff --git a/trunk/htsworkflow/pipelines/test/test_runfolder_ipar100.py b/trunk/htsworkflow/pipelines/test/test_runfolder_ipar100.py
new file mode 100644 (file)
index 0000000..d58a70d
--- /dev/null
@@ -0,0 +1,301 @@
+#!/usr/bin/env python
+
+from datetime import datetime, date
+import os
+import tempfile
+import shutil
+import unittest
+
+from htsworkflow.pipelines import ipar
+from htsworkflow.pipelines import bustard
+from htsworkflow.pipelines import gerald
+from htsworkflow.pipelines import runfolder
+from htsworkflow.pipelines.runfolder import ElementTree
+
+from htsworkflow.pipelines.test.simulate_runfolder import *
+
+
+def make_runfolder(obj=None):
+    """
+    Make a fake runfolder, attach all the directories to obj if defined
+    """
+    # make a fake runfolder directory
+    temp_dir = tempfile.mkdtemp(prefix='tmp_runfolder_')
+
+    runfolder_dir = os.path.join(temp_dir,
+                                 '080102_HWI-EAS229_0010_207BTAAXX')
+    os.mkdir(runfolder_dir)
+
+    data_dir = os.path.join(runfolder_dir, 'Data')
+    os.mkdir(data_dir)
+
+    ipar_dir = make_ipar_dir(data_dir)
+
+    matrix_dir = os.path.join(ipar_dir, 'Matrix')
+    os.mkdir(matrix_dir)
+    matrix_name = os.path.join(matrix_dir, 's_matrix.txt')
+    make_matrix(matrix_name)
+
+    bustard_dir = os.path.join(ipar_dir,
+                               'Bustard1.8.28_12-04-2008_diane')
+    os.mkdir(bustard_dir)
+    make_phasing_params(bustard_dir)
+
+    gerald_dir = os.path.join(bustard_dir,
+                              'GERALD_12-04-2008_diane')
+    os.mkdir(gerald_dir)
+    make_gerald_config_100(gerald_dir)
+    make_summary_htm_100(gerald_dir)
+    make_eland_multi(gerald_dir)
+
+    if obj is not None:
+        obj.temp_dir = temp_dir
+        obj.runfolder_dir = runfolder_dir
+        obj.data_dir = data_dir
+        obj.image_analysis_dir = ipar_dir
+        obj.matrix_dir = matrix_dir
+        obj.bustard_dir = bustard_dir
+        obj.gerald_dir = gerald_dir
+
+
+class RunfolderTests(unittest.TestCase):
+    """
+    Test components of the runfolder processing code
+    which includes firecrest, bustard, and gerald
+    """
+    def setUp(self):
+        # attaches all the directories to the object passed in
+        make_runfolder(self)
+
+    def tearDown(self):
+        shutil.rmtree(self.temp_dir)
+
+    def test_ipar(self):
+        """
+        Construct a firecrest object
+        """
+        i = ipar.ipar(self.image_analysis_dir)
+        self.failUnlessEqual(i.version, '2.01.192.0')
+        self.failUnlessEqual(i.start, 1)
+        self.failUnlessEqual(i.stop, 37)
+
+        xml = i.get_elements()
+        # just make sure that element tree can serialize the tree
+        xml_str = ElementTree.tostring(xml)
+
+        i2 = ipar.IPAR(xml=xml)
+        self.failUnlessEqual(i.version, i2.version)
+        self.failUnlessEqual(i.start,   i2.start)
+        self.failUnlessEqual(i.stop,    i2.stop)
+        self.failUnlessEqual(i.date,    i2.date)
+        self.failUnlessEqual(i.file_list(), i2.file_list())
+
+    def test_bustard(self):
+        """
+        construct a bustard object
+        """
+        b = bustard.bustard(self.bustard_dir)
+        self.failUnlessEqual(b.version, '1.8.28')
+        self.failUnlessEqual(b.date,    date(2008,4,12))
+        self.failUnlessEqual(b.user,    'diane')
+        self.failUnlessEqual(len(b.phasing), 8)
+        self.failUnlessAlmostEqual(b.phasing[8].phasing, 0.0099)
+
+        xml = b.get_elements()
+        b2 = bustard.Bustard(xml=xml)
+        self.failUnlessEqual(b.version, b2.version)
+        self.failUnlessEqual(b.date,    b2.date )
+        self.failUnlessEqual(b.user,    b2.user)
+        self.failUnlessEqual(len(b.phasing), len(b2.phasing))
+        for key in b.phasing.keys():
+            self.failUnlessEqual(b.phasing[key].lane,
+                                 b2.phasing[key].lane)
+            self.failUnlessEqual(b.phasing[key].phasing,
+                                 b2.phasing[key].phasing)
+            self.failUnlessEqual(b.phasing[key].prephasing,
+                                 b2.phasing[key].prephasing)
+
+    def test_gerald(self):
+        # need to update gerald and make tests for it
+        g = gerald.gerald(self.gerald_dir)
+
+        self.failUnlessEqual(g.version,
+            '@(#) Id: GERALD.pl,v 1.171 2008/05/19 17:36:14 mzerara Exp')
+        self.failUnlessEqual(g.date, datetime(2009,2,22,21,15,59))
+        self.failUnlessEqual(len(g.lanes), len(g.lanes.keys()))
+        self.failUnlessEqual(len(g.lanes), len(g.lanes.items()))
+
+
+        # list of genomes, matches what was defined up in
+        # make_gerald_config.
+        # the first None is to offset the genomes list to be 1..9
+        # instead of pythons default 0..8
+        genomes = [None, 
+                   '/g/mm9', 
+                   '/g/mm9', 
+                   '/g/elegans190', 
+                   '/g/arabidopsis01222004',
+                   '/g/mm9', 
+                   '/g/mm9', 
+                   '/g/mm9', 
+                   '/g/mm9', ]
+
+        # test lane specific parameters from gerald config file
+        for i in range(1,9):
+            cur_lane = g.lanes[i]
+            self.failUnlessEqual(cur_lane.analysis, 'eland_extended')
+            self.failUnlessEqual(cur_lane.eland_genome, genomes[i])
+            self.failUnlessEqual(cur_lane.read_length, '37')
+            self.failUnlessEqual(cur_lane.use_bases, 'Y'*37)
+
+        # I want to be able to use a simple iterator
+        for l in g.lanes.values():
+          self.failUnlessEqual(l.analysis, 'eland_extended')
+          self.failUnlessEqual(l.read_length, '37')
+          self.failUnlessEqual(l.use_bases, 'Y'*37)
+
+        # test data extracted from summary file
+        clusters = [None,
+                    (96483, 9074), (133738, 7938),
+                    (152142, 10002), (15784, 2162),
+                    (119735, 8465), (152177, 8146),
+                    (84649, 7325), (54622, 4812),]
+
+        self.failUnlessEqual(len(g.summary), 1)
+        for i in range(1,9):
+            summary_lane = g.summary[0][i]
+            self.failUnlessEqual(summary_lane.cluster, clusters[i])
+            self.failUnlessEqual(summary_lane.lane, i)
+
+        xml = g.get_elements()
+        # just make sure that element tree can serialize the tree
+        xml_str = ElementTree.tostring(xml)
+        g2 = gerald.Gerald(xml=xml)
+
+        # do it all again after extracting from the xml file
+        self.failUnlessEqual(g.version, g2.version)
+        self.failUnlessEqual(g.date, g2.date)
+        self.failUnlessEqual(len(g.lanes.keys()), len(g2.lanes.keys()))
+        self.failUnlessEqual(len(g.lanes.items()), len(g2.lanes.items()))
+
+        # test lane specific parameters from gerald config file
+        for i in range(1,9):
+            g_lane = g.lanes[i]
+            g2_lane = g2.lanes[i]
+            self.failUnlessEqual(g_lane.analysis, g2_lane.analysis)
+            self.failUnlessEqual(g_lane.eland_genome, g2_lane.eland_genome)
+            self.failUnlessEqual(g_lane.read_length, g2_lane.read_length)
+            self.failUnlessEqual(g_lane.use_bases, g2_lane.use_bases)
+
+        # test (some) summary elements
+        self.failUnlessEqual(len(g.summary), 1)
+        for i in range(1,9):
+            g_summary = g.summary[0][i]
+            g2_summary = g2.summary[0][i]
+            self.failUnlessEqual(g_summary.cluster, g2_summary.cluster)
+            self.failUnlessEqual(g_summary.lane, g2_summary.lane)
+
+            g_eland = g.eland_results
+            g2_eland = g2.eland_results
+            for lane in g_eland.results[0].keys():
+                g_results = g_eland.results[0][lane]
+                g2_results = g2_eland.results[0][lane]
+                self.failUnlessEqual(g_results.reads,
+                                     g2_results.reads)
+                self.failUnlessEqual(len(g_results.mapped_reads),
+                                     len(g2_results.mapped_reads))
+                for k in g_results.mapped_reads.keys():
+                    self.failUnlessEqual(g_results.mapped_reads[k],
+                                         g2_results.mapped_reads[k])
+
+                self.failUnlessEqual(len(g_results.match_codes),
+                                     len(g2_results.match_codes))
+                for k in g_results.match_codes.keys():
+                    self.failUnlessEqual(g_results.match_codes[k],
+                                         g2_results.match_codes[k])
+
+
+    def test_eland(self):
+        hg_map = {'Lambda.fa': 'Lambda.fa'}
+        for i in range(1,22):
+          short_name = 'chr%d.fa' % (i,)
+          long_name = 'hg18/chr%d.fa' % (i,)
+          hg_map[short_name] = long_name
+
+        genome_maps = { 1:hg_map, 2:hg_map, 3:hg_map, 4:hg_map,
+                        5:hg_map, 6:hg_map, 7:hg_map, 8:hg_map }
+        eland = gerald.eland(self.gerald_dir, genome_maps=genome_maps)
+
+        for i in range(1,9):
+            lane = eland.results[0][i]
+            self.failUnlessEqual(lane.reads, 6)
+            self.failUnlessEqual(lane.sample_name, "s")
+            self.failUnlessEqual(lane.lane_id, i)
+            self.failUnlessEqual(len(lane.mapped_reads), 17)
+            self.failUnlessEqual(lane.mapped_reads['hg18/chr5.fa'], 4)
+            self.failUnlessEqual(lane.mapped_reads['spike.fa/sample1'], 1)
+            self.failUnlessEqual(lane.mapped_reads['spike.fa/sample2'], 1)
+            self.failUnlessEqual(lane.match_codes['U0'], 3)
+            self.failUnlessEqual(lane.match_codes['R0'], 2)
+            self.failUnlessEqual(lane.match_codes['U1'], 1)
+            self.failUnlessEqual(lane.match_codes['R1'], 9)
+            self.failUnlessEqual(lane.match_codes['U2'], 0)
+            self.failUnlessEqual(lane.match_codes['R2'], 12)
+            self.failUnlessEqual(lane.match_codes['NM'], 1)
+            self.failUnlessEqual(lane.match_codes['QC'], 0)
+
+        xml = eland.get_elements()
+        # just make sure that element tree can serialize the tree
+        xml_str = ElementTree.tostring(xml)
+        e2 = gerald.ELAND(xml=xml)
+
+        for i in range(1,9):
+            l1 = eland.results[0][i]
+            l2 = e2.results[0][i]
+            self.failUnlessEqual(l1.reads, l2.reads)
+            self.failUnlessEqual(l1.sample_name, l2.sample_name)
+            self.failUnlessEqual(l1.lane_id, l2.lane_id)
+            self.failUnlessEqual(len(l1.mapped_reads), len(l2.mapped_reads))
+            self.failUnlessEqual(len(l1.mapped_reads), 17)
+            for k in l1.mapped_reads.keys():
+                self.failUnlessEqual(l1.mapped_reads[k],
+                                     l2.mapped_reads[k])
+
+            self.failUnlessEqual(len(l1.match_codes), 9)
+            self.failUnlessEqual(len(l1.match_codes), len(l2.match_codes))
+            for k in l1.match_codes.keys():
+                self.failUnlessEqual(l1.match_codes[k],
+                                     l2.match_codes[k])
+
+    def test_runfolder(self):
+        runs = runfolder.get_runs(self.runfolder_dir)
+
+        # do we get the flowcell id from the filename?
+        self.failUnlessEqual(len(runs), 1)
+        name = 'run_207BTAAXX_%s.xml' % ( date.today().strftime('%Y-%m-%d'),)
+        self.failUnlessEqual(runs[0].name, name)
+
+        # do we get the flowcell id from the FlowcellId.xml file
+        make_flowcell_id(self.runfolder_dir, '207BTAAXY')
+        runs = runfolder.get_runs(self.runfolder_dir)
+        self.failUnlessEqual(len(runs), 1)
+        name = 'run_207BTAAXY_%s.xml' % ( date.today().strftime('%Y-%m-%d'),)
+        self.failUnlessEqual(runs[0].name, name)
+
+        r1 = runs[0]
+        xml = r1.get_elements()
+        xml_str = ElementTree.tostring(xml)
+
+        r2 = runfolder.PipelineRun(xml=xml)
+        self.failUnlessEqual(r1.name, r2.name)
+        self.failIfEqual(r2.image_analysis, None)
+        self.failIfEqual(r2.bustard, None)
+        self.failIfEqual(r2.gerald, None)
+
+
+def suite():
+    return unittest.makeSuite(RunfolderTests,'test')
+
+if __name__ == "__main__":
+    unittest.main(defaultTest="suite")
+
diff --git a/trunk/htsworkflow/pipelines/test/test_runfolder_ipar130.py b/trunk/htsworkflow/pipelines/test/test_runfolder_ipar130.py
new file mode 100644 (file)
index 0000000..9e6ac22
--- /dev/null
@@ -0,0 +1,343 @@
+#!/usr/bin/env python
+
+from datetime import datetime, date
+import os
+import tempfile
+import shutil
+import unittest
+
+from htsworkflow.pipelines import eland
+from htsworkflow.pipelines import ipar
+from htsworkflow.pipelines import bustard
+from htsworkflow.pipelines import gerald
+from htsworkflow.pipelines import runfolder
+from htsworkflow.pipelines.runfolder import ElementTree
+
+from htsworkflow.pipelines.test.simulate_runfolder import *
+
+
+def make_runfolder(obj=None):
+    """
+    Make a fake runfolder, attach all the directories to obj if defined
+    """
+    # make a fake runfolder directory
+    temp_dir = tempfile.mkdtemp(prefix='tmp_runfolder_')
+
+    runfolder_dir = os.path.join(temp_dir,
+                                 '090313_HWI-EAS229_0101_3021JAAXX')
+    os.mkdir(runfolder_dir)
+
+    data_dir = os.path.join(runfolder_dir, 'Data')
+    os.mkdir(data_dir)
+
+    ipar_dir = make_ipar_dir(data_dir, '1.30')
+
+    bustard_dir = os.path.join(ipar_dir,
+                               'Bustard1.3.2_15-03-2008_diane')
+    os.mkdir(bustard_dir)
+    make_phasing_params(bustard_dir)
+    make_bustard_config132(bustard_dir)
+
+    gerald_dir = os.path.join(bustard_dir,
+                              'GERALD_15-03-2008_diane')
+    os.mkdir(gerald_dir)
+    make_gerald_config_100(gerald_dir)
+    make_summary_ipar130_htm(gerald_dir)
+    make_eland_multi(gerald_dir, lane_list=[1,2,3,4,5,6,])
+    make_scarf(gerald_dir, lane_list=[7,])
+    make_fastq(gerald_dir, lane_list=[8,])
+
+    if obj is not None:
+        obj.temp_dir = temp_dir
+        obj.runfolder_dir = runfolder_dir
+        obj.data_dir = data_dir
+        obj.image_analysis_dir = ipar_dir
+        obj.bustard_dir = bustard_dir
+        obj.gerald_dir = gerald_dir
+
+
+class RunfolderTests(unittest.TestCase):
+    """
+    Test components of the runfolder processing code
+    which includes firecrest, bustard, and gerald
+    """
+    def setUp(self):
+        # attaches all the directories to the object passed in
+        make_runfolder(self)
+
+    def tearDown(self):
+        shutil.rmtree(self.temp_dir)
+
+    def test_ipar(self):
+        """
+        Construct a firecrest object
+        """
+        i = ipar.ipar(self.image_analysis_dir)
+        self.failUnlessEqual(i.version, '2.01.192.0')
+        self.failUnlessEqual(i.start, 1)
+        self.failUnlessEqual(i.stop, 37)
+
+        xml = i.get_elements()
+        # just make sure that element tree can serialize the tree
+        xml_str = ElementTree.tostring(xml)
+
+        i2 = ipar.IPAR(xml=xml)
+        self.failUnlessEqual(i.version, i2.version)
+        self.failUnlessEqual(i.start,   i2.start)
+        self.failUnlessEqual(i.stop,    i2.stop)
+        self.failUnlessEqual(i.date,    i2.date)
+        self.failUnlessEqual(i.file_list(), i2.file_list())
+
+    def test_bustard(self):
+        """
+        construct a bustard object
+        """
+        def check_crosstalk(crosstalk):
+            self.failUnlessAlmostEqual(crosstalk.base['A'][0],  1.27)
+            self.failUnlessAlmostEqual(crosstalk.base['A'][1],  0.20999999999999)
+            self.failUnlessAlmostEqual(crosstalk.base['A'][2], -0.02)
+            self.failUnlessAlmostEqual(crosstalk.base['A'][3], -0.03)
+
+            self.failUnlessAlmostEqual(crosstalk.base['C'][0],  0.57)
+            self.failUnlessAlmostEqual(crosstalk.base['C'][1],  0.58)
+            self.failUnlessAlmostEqual(crosstalk.base['C'][2], -0.01)
+            self.failUnlessAlmostEqual(crosstalk.base['C'][3], -0.01)
+
+            self.failUnlessAlmostEqual(crosstalk.base['T'][0], -0.02)
+            self.failUnlessAlmostEqual(crosstalk.base['T'][1], -0.02)
+            self.failUnlessAlmostEqual(crosstalk.base['T'][2],  0.80)
+            self.failUnlessAlmostEqual(crosstalk.base['T'][3],  1.07)
+
+            self.failUnlessAlmostEqual(crosstalk.base['G'][0], -0.03)
+            self.failUnlessAlmostEqual(crosstalk.base['G'][1], -0.04)
+            self.failUnlessAlmostEqual(crosstalk.base['G'][2],  1.51)
+            self.failUnlessAlmostEqual(crosstalk.base['G'][3], -0.02)
+
+        b = bustard.bustard(self.bustard_dir)
+        self.failUnlessEqual(b.version, '1.3.2')
+        self.failUnlessEqual(b.date,    date(2008,3,15))
+        self.failUnlessEqual(b.user,    'diane')
+        self.failUnlessEqual(len(b.phasing), 8)
+        self.failUnlessAlmostEqual(b.phasing[8].phasing, 0.0099)
+        self.failUnlessEqual(b.crosstalk.base.keys(), ['A','C','T','G'])
+        check_crosstalk(b.crosstalk)
+
+        xml = b.get_elements()
+        print ElementTree.dump(xml)
+        b2 = bustard.Bustard(xml=xml)
+        self.failUnlessEqual(b.version, b2.version)
+        self.failUnlessEqual(b.date,    b2.date )
+        self.failUnlessEqual(b.user,    b2.user)
+        self.failUnlessEqual(len(b.phasing), len(b2.phasing))
+        for key in b.phasing.keys():
+            self.failUnlessEqual(b.phasing[key].lane,
+                                 b2.phasing[key].lane)
+            self.failUnlessEqual(b.phasing[key].phasing,
+                                 b2.phasing[key].phasing)
+            self.failUnlessEqual(b.phasing[key].prephasing,
+                                 b2.phasing[key].prephasing)
+        check_crosstalk(b2.crosstalk)
+
+    def test_gerald(self):
+        # need to update gerald and make tests for it
+        g = gerald.gerald(self.gerald_dir)
+
+        self.failUnlessEqual(g.version,
+            '@(#) Id: GERALD.pl,v 1.171 2008/05/19 17:36:14 mzerara Exp')
+        self.failUnlessEqual(g.date, datetime(2009,2,22,21,15,59))
+        self.failUnlessEqual(len(g.lanes), len(g.lanes.keys()))
+        self.failUnlessEqual(len(g.lanes), len(g.lanes.items()))
+
+
+        # list of genomes, matches what was defined up in
+        # make_gerald_config.
+        # the first None is to offset the genomes list to be 1..9
+        # instead of pythons default 0..8
+        genomes = [None, 
+                   '/g/mm9', 
+                   '/g/mm9', 
+                   '/g/elegans190', 
+                   '/g/arabidopsis01222004',
+                   '/g/mm9', 
+                   '/g/mm9', 
+                   '/g/mm9', 
+                   '/g/mm9', ]
+
+        # test lane specific parameters from gerald config file
+        for i in range(1,9):
+            cur_lane = g.lanes[i]
+            self.failUnlessEqual(cur_lane.analysis, 'eland_extended')
+            self.failUnlessEqual(cur_lane.eland_genome, genomes[i])
+            self.failUnlessEqual(cur_lane.read_length, '37')
+            self.failUnlessEqual(cur_lane.use_bases, 'Y'*37)
+
+        # I want to be able to use a simple iterator
+        for l in g.lanes.values():
+          self.failUnlessEqual(l.analysis, 'eland_extended')
+          self.failUnlessEqual(l.read_length, '37')
+          self.failUnlessEqual(l.use_bases, 'Y'*37)
+
+        # test data extracted from summary file
+        clusters = [None,
+                    (126910, 4300), (165739, 6792),
+                    (196565, 8216), (153897, 8501),
+                    (135536, 3908), (154083, 9315),
+                    (159991, 9292), (198479, 17671),]
+
+        self.failUnlessEqual(len(g.summary), 1)
+        for i in range(1,9):
+            summary_lane = g.summary[0][i]
+            self.failUnlessEqual(summary_lane.cluster, clusters[i])
+            self.failUnlessEqual(summary_lane.lane, i)
+
+        xml = g.get_elements()
+        # just make sure that element tree can serialize the tree
+        xml_str = ElementTree.tostring(xml)
+        g2 = gerald.Gerald(xml=xml)
+
+        # do it all again after extracting from the xml file
+        self.failUnlessEqual(g.version, g2.version)
+        self.failUnlessEqual(g.date, g2.date)
+        self.failUnlessEqual(len(g.lanes.keys()), len(g2.lanes.keys()))
+        self.failUnlessEqual(len(g.lanes.items()), len(g2.lanes.items()))
+
+        # test lane specific parameters from gerald config file
+        for i in range(1,9):
+            g_lane = g.lanes[i]
+            g2_lane = g2.lanes[i]
+            self.failUnlessEqual(g_lane.analysis, g2_lane.analysis)
+            self.failUnlessEqual(g_lane.eland_genome, g2_lane.eland_genome)
+            self.failUnlessEqual(g_lane.read_length, g2_lane.read_length)
+            self.failUnlessEqual(g_lane.use_bases, g2_lane.use_bases)
+
+        # test (some) summary elements
+        self.failUnlessEqual(len(g.summary), 1)
+        for i in range(1,9):
+            g_summary = g.summary[0][i]
+            g2_summary = g2.summary[0][i]
+            self.failUnlessEqual(g_summary.cluster, g2_summary.cluster)
+            self.failUnlessEqual(g_summary.lane, g2_summary.lane)
+
+            g_eland = g.eland_results
+            g2_eland = g2.eland_results
+            for lane in g_eland.results[0].keys():
+                g_results = g_eland.results[0][lane]
+                g2_results = g2_eland.results[0][lane]
+                self.failUnlessEqual(g_results.reads,
+                                     g2_results.reads)
+                if isinstance(g_results, eland.ElandLane):
+                  self.failUnlessEqual(len(g_results.mapped_reads),
+                                       len(g2_results.mapped_reads))
+                  for k in g_results.mapped_reads.keys():
+                      self.failUnlessEqual(g_results.mapped_reads[k],
+                                           g2_results.mapped_reads[k])
+
+                  self.failUnlessEqual(len(g_results.match_codes),
+                                       len(g2_results.match_codes))
+                  for k in g_results.match_codes.keys():
+                      self.failUnlessEqual(g_results.match_codes[k],
+                                           g2_results.match_codes[k])
+
+
+    def test_eland(self):
+        hg_map = {'Lambda.fa': 'Lambda.fa'}
+        for i in range(1,22):
+          short_name = 'chr%d.fa' % (i,)
+          long_name = 'hg18/chr%d.fa' % (i,)
+          hg_map[short_name] = long_name
+
+        genome_maps = { 1:hg_map, 2:hg_map, 3:hg_map, 4:hg_map,
+                        5:hg_map, 6:hg_map, 7:hg_map, 8:hg_map }
+        eland_container = gerald.eland(self.gerald_dir, genome_maps=genome_maps)
+
+        # I added sequence lanes to the last 2 lanes of this test case
+        for i in range(1,7):
+            lane = eland_container.results[0][i]
+            self.failUnlessEqual(lane.reads, 6)
+            self.failUnlessEqual(lane.sample_name, "s")
+            self.failUnlessEqual(lane.lane_id, i)
+            self.failUnlessEqual(len(lane.mapped_reads), 17)
+            self.failUnlessEqual(lane.mapped_reads['hg18/chr5.fa'], 4)
+            self.failUnlessEqual(lane.match_codes['U0'], 3)
+            self.failUnlessEqual(lane.match_codes['R0'], 2)
+            self.failUnlessEqual(lane.match_codes['U1'], 1)
+            self.failUnlessEqual(lane.match_codes['R1'], 9)
+            self.failUnlessEqual(lane.match_codes['U2'], 0)
+            self.failUnlessEqual(lane.match_codes['R2'], 12)
+            self.failUnlessEqual(lane.match_codes['NM'], 1)
+            self.failUnlessEqual(lane.match_codes['QC'], 0)
+
+        # test scarf
+        lane = eland_container.results[0][7]
+        self.failUnlessEqual(lane.reads, 5)
+        self.failUnlessEqual(lane.sample_name, 's')
+        self.failUnlessEqual(lane.lane_id, 7)
+        self.failUnlessEqual(lane.sequence_type, eland.SequenceLane.SCARF_TYPE)
+
+        # test fastq
+        lane = eland_container.results[0][8]
+        self.failUnlessEqual(lane.reads, 3)
+        self.failUnlessEqual(lane.sample_name, 's')
+        self.failUnlessEqual(lane.lane_id, 8)
+        self.failUnlessEqual(lane.sequence_type, eland.SequenceLane.FASTQ_TYPE)
+
+        xml = eland_container.get_elements()
+        # just make sure that element tree can serialize the tree
+        xml_str = ElementTree.tostring(xml)
+        e2 = gerald.ELAND(xml=xml)
+
+        for i in range(1,9):
+            l1 = eland_container.results[0][i]
+            l2 = e2.results[0][i]
+            self.failUnlessEqual(l1.reads, l2.reads)
+            self.failUnlessEqual(l1.sample_name, l2.sample_name)
+            self.failUnlessEqual(l1.lane_id, l2.lane_id)
+            if isinstance(l1, eland.ElandLane):
+              self.failUnlessEqual(len(l1.mapped_reads), len(l2.mapped_reads))
+              self.failUnlessEqual(len(l1.mapped_reads), 17)
+              for k in l1.mapped_reads.keys():
+                  self.failUnlessEqual(l1.mapped_reads[k],
+                                       l2.mapped_reads[k])
+
+              self.failUnlessEqual(len(l1.match_codes), 9)
+              self.failUnlessEqual(len(l1.match_codes), len(l2.match_codes))
+              for k in l1.match_codes.keys():
+                  self.failUnlessEqual(l1.match_codes[k],
+                                       l2.match_codes[k])
+            elif isinstance(l1, eland.SequenceLane):
+                print 'l1', l1.__dict__
+                print 'l2', l2.__dict__
+                self.failUnlessEqual(l1.sequence_type, l2.sequence_type)
+
+    def test_runfolder(self):
+        runs = runfolder.get_runs(self.runfolder_dir)
+
+        # do we get the flowcell id from the filename?
+        self.failUnlessEqual(len(runs), 1)
+        name = 'run_3021JAAXX_%s.xml' % ( date.today().strftime('%Y-%m-%d'),)
+        self.failUnlessEqual(runs[0].name, name)
+
+        # do we get the flowcell id from the FlowcellId.xml file
+        make_flowcell_id(self.runfolder_dir, '207BTAAXY')
+        runs = runfolder.get_runs(self.runfolder_dir)
+        self.failUnlessEqual(len(runs), 1)
+        name = 'run_207BTAAXY_%s.xml' % ( date.today().strftime('%Y-%m-%d'),)
+        self.failUnlessEqual(runs[0].name, name)
+
+        r1 = runs[0]
+        xml = r1.get_elements()
+        xml_str = ElementTree.tostring(xml)
+
+        r2 = runfolder.PipelineRun(xml=xml)
+        self.failUnlessEqual(r1.name, r2.name)
+        self.failIfEqual(r2.image_analysis, None)
+        self.failIfEqual(r2.bustard, None)
+        self.failIfEqual(r2.gerald, None)
+
+
+def suite():
+    return unittest.makeSuite(RunfolderTests,'test')
+
+if __name__ == "__main__":
+    unittest.main(defaultTest="suite")
+
diff --git a/trunk/htsworkflow/pipelines/test/test_runfolder_pair.py b/trunk/htsworkflow/pipelines/test/test_runfolder_pair.py
new file mode 100644 (file)
index 0000000..c0fb684
--- /dev/null
@@ -0,0 +1,327 @@
+#!/usr/bin/env python
+
+from datetime import datetime, date
+import os
+import tempfile
+import shutil
+import unittest
+
+from htsworkflow.pipelines import firecrest
+from htsworkflow.pipelines import bustard
+from htsworkflow.pipelines import gerald
+from htsworkflow.pipelines import runfolder
+from htsworkflow.pipelines.runfolder import ElementTree
+
+from htsworkflow.pipelines.test.simulate_runfolder import *
+
+
+def make_runfolder(obj=None):
+    """
+    Make a fake runfolder, attach all the directories to obj if defined
+    """
+    # make a fake runfolder directory
+    temp_dir = tempfile.mkdtemp(prefix='tmp_runfolder_')
+
+    runfolder_dir = os.path.join(temp_dir,
+                                 '080102_HWI-EAS229_0010_207BTAAXX')
+    os.mkdir(runfolder_dir)
+
+    data_dir = os.path.join(runfolder_dir, 'Data')
+    os.mkdir(data_dir)
+
+    ipar_dir = make_firecrest_dir(data_dir, "1.9.6", 1, 152)
+
+    matrix_dir = os.path.join(ipar_dir, 'Matrix')
+    os.mkdir(matrix_dir)
+    matrix_name = os.path.join(matrix_dir, 's_matrix.txt')
+    make_matrix(matrix_name)
+
+    bustard_dir = os.path.join(ipar_dir,
+                               'Bustard1.8.28_12-04-2008_diane')
+    os.mkdir(bustard_dir)
+    make_phasing_params(bustard_dir)
+
+    gerald_dir = os.path.join(bustard_dir,
+                              'GERALD_12-04-2008_diane')
+    os.mkdir(gerald_dir)
+    make_gerald_config_100(gerald_dir)
+    make_summary_paired_htm(gerald_dir)
+    make_eland_multi(gerald_dir, paired=True)
+
+    if obj is not None:
+        obj.temp_dir = temp_dir
+        obj.runfolder_dir = runfolder_dir
+        obj.data_dir = data_dir
+        obj.image_analysis_dir = ipar_dir
+        obj.matrix_dir = matrix_dir
+        obj.bustard_dir = bustard_dir
+        obj.gerald_dir = gerald_dir
+
+
+class RunfolderTests(unittest.TestCase):
+    """
+    Test components of the runfolder processing code
+    which includes firecrest, bustard, and gerald
+    """
+    def setUp(self):
+        # attaches all the directories to the object passed in
+        make_runfolder(self)
+
+    def tearDown(self):
+        shutil.rmtree(self.temp_dir)
+
+    def test_firecrest(self):
+        """
+        Construct a firecrest object
+        """
+        f = firecrest.firecrest(self.image_analysis_dir)
+        self.failUnlessEqual(f.version, '1.9.6')
+        self.failUnlessEqual(f.start, 1)
+        self.failUnlessEqual(f.stop, 152)
+        self.failUnlessEqual(f.user, 'diane')
+        # As of 2008-12-8, the date was being set in 
+        # simulate_runfolder.make_firecrest_dir
+        self.failUnlessEqual(f.date, date(2008,4,12))
+
+        xml = f.get_elements()
+        # just make sure that element tree can serialize the tree
+        xml_str = ElementTree.tostring(xml)
+
+        f2 = firecrest.Firecrest(xml=xml)
+        self.failUnlessEqual(f.version, f2.version)
+        self.failUnlessEqual(f.start,   f2.start)
+        self.failUnlessEqual(f.stop,    f2.stop)
+        self.failUnlessEqual(f.user,    f2.user)
+
+    def test_bustard(self):
+        """
+        construct a bustard object
+        """
+        b = bustard.bustard(self.bustard_dir)
+        self.failUnlessEqual(b.version, '1.8.28')
+        self.failUnlessEqual(b.date,    date(2008,4,12))
+        self.failUnlessEqual(b.user,    'diane')
+        self.failUnlessEqual(len(b.phasing), 8)
+        self.failUnlessAlmostEqual(b.phasing[8].phasing, 0.0099)
+
+        xml = b.get_elements()
+        b2 = bustard.Bustard(xml=xml)
+        self.failUnlessEqual(b.version, b2.version)
+        self.failUnlessEqual(b.date,    b2.date )
+        self.failUnlessEqual(b.user,    b2.user)
+        self.failUnlessEqual(len(b.phasing), len(b2.phasing))
+        for key in b.phasing.keys():
+            self.failUnlessEqual(b.phasing[key].lane,
+                                 b2.phasing[key].lane)
+            self.failUnlessEqual(b.phasing[key].phasing,
+                                 b2.phasing[key].phasing)
+            self.failUnlessEqual(b.phasing[key].prephasing,
+                                 b2.phasing[key].prephasing)
+
+    def test_gerald(self):
+        # need to update gerald and make tests for it
+        g = gerald.gerald(self.gerald_dir)
+
+        self.failUnlessEqual(g.version,
+            '@(#) Id: GERALD.pl,v 1.171 2008/05/19 17:36:14 mzerara Exp')
+        self.failUnlessEqual(g.date, datetime(2009,2,22,21,15,59))
+        self.failUnlessEqual(len(g.lanes), len(g.lanes.keys()))
+        self.failUnlessEqual(len(g.lanes), len(g.lanes.items()))
+
+
+        # list of genomes, matches what was defined up in
+        # make_gerald_config.
+        # the first None is to offset the genomes list to be 1..9
+        # instead of pythons default 0..8
+        genomes = [None, 
+                   '/g/mm9', 
+                   '/g/mm9', 
+                   '/g/elegans190', 
+                   '/g/arabidopsis01222004',
+                   '/g/mm9', 
+                   '/g/mm9', 
+                   '/g/mm9', 
+                   '/g/mm9', ]
+
+        # test lane specific parameters from gerald config file
+        for i in range(1,9):
+            cur_lane = g.lanes[i]
+            self.failUnlessEqual(cur_lane.analysis, 'eland_extended')
+            self.failUnlessEqual(cur_lane.eland_genome, genomes[i])
+            self.failUnlessEqual(cur_lane.read_length, '37')
+            self.failUnlessEqual(cur_lane.use_bases, 'Y'*37)
+
+        # I want to be able to use a simple iterator
+        for l in g.lanes.values():
+          self.failUnlessEqual(l.analysis, 'eland_extended')
+          self.failUnlessEqual(l.read_length, '37')
+          self.failUnlessEqual(l.use_bases, 'Y'*37)
+
+        # test data extracted from summary file
+        clusters = [[None,
+                    (103646, 4515), (106678, 4652),
+                    (84583, 5963), (68813, 4782),
+                    (104854, 4664), (43555, 1632),
+                    (54265, 1588), (64363, 2697),],
+                    [None,
+                    (103647, 4516), (106679, 4653),
+                    (84584, 5964), (68814, 4783),
+                    (104855, 4665), (43556, 1633),
+                    (54266, 1589), (64364, 2698),],]
+
+        for end in [0,1]:
+            for lane in range(1,9):
+                summary_lane = g.summary[end][lane]
+                self.failUnlessEqual(summary_lane.cluster, clusters[end][lane])
+                self.failUnlessEqual(summary_lane.lane, lane)
+
+        xml = g.get_elements()
+        # just make sure that element tree can serialize the tree
+        xml_str = ElementTree.tostring(xml)
+        g2 = gerald.Gerald(xml=xml)
+
+        # do it all again after extracting from the xml file
+        self.failUnlessEqual(g.version, g2.version)
+        self.failUnlessEqual(g.date, g2.date)
+        self.failUnlessEqual(len(g.lanes.keys()), len(g2.lanes.keys()))
+        self.failUnlessEqual(len(g.lanes.items()), len(g2.lanes.items()))
+
+        # test lane specific parameters from gerald config file
+        for i in range(1,9):
+            g_lane = g.lanes[i]
+            g2_lane = g2.lanes[i]
+            self.failUnlessEqual(g_lane.analysis, g2_lane.analysis)
+            self.failUnlessEqual(g_lane.eland_genome, g2_lane.eland_genome)
+            self.failUnlessEqual(g_lane.read_length, g2_lane.read_length)
+            self.failUnlessEqual(g_lane.use_bases, g2_lane.use_bases)
+
+        # test (some) summary elements
+        for end in [0,1]:
+            for i in range(1,9):
+                g_summary = g.summary[end][i]
+                g2_summary = g2.summary[end][i]
+                self.failUnlessEqual(g_summary.cluster, g2_summary.cluster)
+                self.failUnlessEqual(g_summary.lane, g2_summary.lane)
+
+                g_eland = g.eland_results
+                g2_eland = g2.eland_results
+                for lane in g_eland.results[end].keys():
+                    g_results = g_eland.results[end][lane]
+                    g2_results = g_eland.results[end][lane]
+                    self.failUnlessEqual(g_results.reads,
+                                         g2_results.reads)
+                    self.failUnlessEqual(len(g_results.mapped_reads),
+                                         len(g2_results.mapped_reads))
+                    for k in g_results.mapped_reads.keys():
+                        self.failUnlessEqual(g_results.mapped_reads[k],
+                                             g2_results.mapped_reads[k])
+
+                    self.failUnlessEqual(len(g_results.match_codes),
+                                         len(g2_results.match_codes))
+                    for k in g_results.match_codes.keys():
+                        self.failUnlessEqual(g_results.match_codes[k],
+                                             g2_results.match_codes[k])
+
+
+    def test_eland(self):
+        hg_map = {'Lambda.fa': 'Lambda.fa'}
+        for i in range(1,22):
+          short_name = 'chr%d.fa' % (i,)
+          long_name = 'hg18/chr%d.fa' % (i,)
+          hg_map[short_name] = long_name
+
+        genome_maps = { 1:hg_map, 2:hg_map, 3:hg_map, 4:hg_map,
+                        5:hg_map, 6:hg_map, 7:hg_map, 8:hg_map }
+        eland = gerald.eland(self.gerald_dir, genome_maps=genome_maps)
+
+        # check first end
+        for i in range(1,9):
+            lane = eland.results[0][i]
+            self.failUnlessEqual(lane.reads, 6)
+            self.failUnlessEqual(lane.sample_name, "s")
+            self.failUnlessEqual(lane.lane_id, i)
+            self.failUnlessEqual(len(lane.mapped_reads), 17)
+            self.failUnlessEqual(lane.mapped_reads['hg18/chr5.fa'], 4)
+            self.failUnlessEqual(lane.match_codes['U0'], 3)
+            self.failUnlessEqual(lane.match_codes['R0'], 2)
+            self.failUnlessEqual(lane.match_codes['U1'], 1)
+            self.failUnlessEqual(lane.match_codes['R1'], 9)
+            self.failUnlessEqual(lane.match_codes['U2'], 0)
+            self.failUnlessEqual(lane.match_codes['R2'], 12)
+            self.failUnlessEqual(lane.match_codes['NM'], 1)
+            self.failUnlessEqual(lane.match_codes['QC'], 0)
+
+        # check second end
+        for i in range(1,9):
+            lane = eland.results[1][i]
+            self.failUnlessEqual(lane.reads, 7)
+            self.failUnlessEqual(lane.sample_name, "s")
+            self.failUnlessEqual(lane.lane_id, i)
+            self.failUnlessEqual(len(lane.mapped_reads), 17)
+            self.failUnlessEqual(lane.mapped_reads['hg18/chr5.fa'], 4)
+            self.failUnlessEqual(lane.match_codes['U0'], 3)
+            self.failUnlessEqual(lane.match_codes['R0'], 2)
+            self.failUnlessEqual(lane.match_codes['U1'], 1)
+            self.failUnlessEqual(lane.match_codes['R1'], 9)
+            self.failUnlessEqual(lane.match_codes['U2'], 0)
+            self.failUnlessEqual(lane.match_codes['R2'], 12)
+            self.failUnlessEqual(lane.match_codes['NM'], 1)
+            self.failUnlessEqual(lane.match_codes['QC'], 1)
+
+        xml = eland.get_elements()
+        # just make sure that element tree can serialize the tree
+        xml_str = ElementTree.tostring(xml)
+        e2 = gerald.ELAND(xml=xml)
+
+        for end in [0, 1]:
+            for i in range(1,9):
+                l1 = eland.results[end][i]
+                l2 = e2.results[end][i]
+                self.failUnlessEqual(l1.reads, l2.reads)
+                self.failUnlessEqual(l1.sample_name, l2.sample_name)
+                self.failUnlessEqual(l1.lane_id, l2.lane_id)
+                self.failUnlessEqual(len(l1.mapped_reads), len(l2.mapped_reads))
+                self.failUnlessEqual(len(l1.mapped_reads), 17)
+                for k in l1.mapped_reads.keys():
+                    self.failUnlessEqual(l1.mapped_reads[k],
+                                         l2.mapped_reads[k])
+
+                self.failUnlessEqual(len(l1.match_codes), 9)
+                self.failUnlessEqual(len(l1.match_codes), len(l2.match_codes))
+                for k in l1.match_codes.keys():
+                    self.failUnlessEqual(l1.match_codes[k],
+                                         l2.match_codes[k])
+
+    def test_runfolder(self):
+        runs = runfolder.get_runs(self.runfolder_dir)
+
+        # do we get the flowcell id from the filename?
+        self.failUnlessEqual(len(runs), 1)
+        # firecrest's date depends on filename not the create time.
+        name = 'run_207BTAAXX_2009-02-22.xml'
+        self.failUnlessEqual(runs[0].name, name)
+
+        # do we get the flowcell id from the FlowcellId.xml file
+        make_flowcell_id(self.runfolder_dir, '207BTAAXY')
+        runs = runfolder.get_runs(self.runfolder_dir)
+        self.failUnlessEqual(len(runs), 1)
+        name = 'run_207BTAAXY_2009-02-22.xml'
+        self.failUnlessEqual(runs[0].name, name)
+
+        r1 = runs[0]
+        xml = r1.get_elements()
+        xml_str = ElementTree.tostring(xml)
+
+        r2 = runfolder.PipelineRun(xml=xml)
+        self.failUnlessEqual(r1.name, r2.name)
+        self.failIfEqual(r2.image_analysis, None)
+        self.failIfEqual(r2.bustard, None)
+        self.failIfEqual(r2.gerald, None)
+
+
+def suite():
+    return unittest.makeSuite(RunfolderTests,'test')
+
+if __name__ == "__main__":
+    unittest.main(defaultTest="suite")
+
diff --git a/trunk/htsworkflow/pipelines/test/testdata/IPAR1.01.params b/trunk/htsworkflow/pipelines/test/testdata/IPAR1.01.params
new file mode 100644 (file)
index 0000000..11bc608
--- /dev/null
@@ -0,0 +1,63 @@
+<?xml version="1.0"?>\r
+<ImageAnalysis>\r
+  <Run Name="IPAR_1.01">\r
+    <Software Name="IPAR" Version="2.01.192.0" />\r
+    <Cycles First="1" Last="37" Number="37" />\r
+    <RunParameters>\r
+      <ImagingReads Index="1">\r
+        <FirstCycle>1</FirstCycle>\r
+        <LastCycle>37</LastCycle>\r
+        <RunFolder>090220_HWI-EAS229_0093_30VR0AAXX</RunFolder>\r
+      </ImagingReads>\r
+      <Reads Index="1">\r
+        <FirstCycle>1</FirstCycle>\r
+        <LastCycle>37</LastCycle>\r
+        <RunFolder>090220_HWI-EAS229_0093_30VR0AAXX</RunFolder>\r
+      </Reads>\r
+      <Compression>gzip</Compression>\r
+      <CompressionSuffix>.p.gz</CompressionSuffix>\r
+      <Instrument>HWI-EAS229</Instrument>\r
+      <RunFolder>090220_HWI-EAS229_0093_30VR0AAXX</RunFolder>\r
+    </RunParameters>\r
+    <ImageParameters>\r
+      <AutoOffsetFlag>1</AutoOffsetFlag>\r
+      <Fwhm>2.7</Fwhm>\r
+      <RemappingDistance>1.5</RemappingDistance>\r
+      <Threshold>4</Threshold>\r
+    </ImageParameters>\r
+    <TileSelection>\r
+      <Lane Index="1">\r
+        <Sample>s</Sample>\r
+        <TileRange Max="100" Min="1" />\r
+      </Lane>\r
+      <Lane Index="2">\r
+        <Sample>s</Sample>\r
+        <TileRange Max="100" Min="1" />\r
+      </Lane>\r
+      <Lane Index="3">\r
+        <Sample>s</Sample>\r
+        <TileRange Max="100" Min="1" />\r
+      </Lane>\r
+      <Lane Index="4">\r
+        <Sample>s</Sample>\r
+        <TileRange Max="100" Min="1" />\r
+      </Lane>\r
+      <Lane Index="5">\r
+        <Sample>s</Sample>\r
+        <TileRange Max="100" Min="1" />\r
+      </Lane>\r
+      <Lane Index="6">\r
+        <Sample>s</Sample>\r
+        <TileRange Max="100" Min="1" />\r
+      </Lane>\r
+      <Lane Index="7">\r
+        <Sample>s</Sample>\r
+        <TileRange Max="100" Min="1" />\r
+      </Lane>\r
+      <Lane Index="8">\r
+        <Sample>s</Sample>\r
+        <TileRange Max="100" Min="1" />\r
+      </Lane>\r
+    </TileSelection>\r
+  </Run>\r
+</ImageAnalysis>
\ No newline at end of file
diff --git a/trunk/htsworkflow/pipelines/test/testdata/Summary-ipar130.htm b/trunk/htsworkflow/pipelines/test/testdata/Summary-ipar130.htm
new file mode 100644 (file)
index 0000000..c9eaca9
--- /dev/null
@@ -0,0 +1,9325 @@
+<html><body>
+<h1>Summary Information For Experiment 090313_HWI-EAS229_0101_3021JAAXX</h1>
+<h2>Chip Summary</h2>
+<table border="1" cellpadding="5">
+<tr>
+<td>Machine</td>
+<td>HWI-EAS229</td>
+</tr>
+<tr>
+<td>Run Folder</td>
+<td>090313_HWI-EAS229_0101_3021JAAXX</td>
+</tr>
+<tr>
+<td>Chip ID</td>
+<td>unknown</td>
+</tr>
+</table>
+<h2>Chip Results Summary</h2>
+<table border="1" cellpadding="5">
+<tr>
+<td>Clusters</td>
+<td>Clusters (PF)</td>
+<td>Yield (kbases)</td>
+</tr>
+<tr>
+<td>128921986</td>
+<td>21940781</td>
+<td>811809</td>
+</tr>
+</table>
+<h2>Lane Parameter Summary</h2>
+<table border="1" cellpadding="5">
+<tr>
+<td>Lane </td>
+<td>Sample ID</td>
+<td>Sample Target</td>
+<td>Sample Type</td>
+<td>Length</td>
+<td>Filter</td>
+<td>Chast. Thresh.</td>
+<td>Num Tiles</td>
+<td>Tiles</td>
+</tr>
+<tr>
+<td>1</td>
+<td>unknown</td>
+<td>mm9</td>
+<td>ELAND_EXTENDED</td>
+<td>37</td>
+<td>'((FAILED_CHASTITY&lt;=1.000000))'</td>
+<td>0.600000</td>
+<td>100</td>
+<td><a href="#Lane1">
+        Lane 1</a></td>
+</tr>
+<tr>
+<td>2</td>
+<td>unknown</td>
+<td>elegans190</td>
+<td>ELAND_EXTENDED</td>
+<td>37</td>
+<td>'((FAILED_CHASTITY&lt;=1.000000))'</td>
+<td>0.600000</td>
+<td>100</td>
+<td><a href="#Lane2">
+        Lane 2</a></td>
+</tr>
+<tr>
+<td>3</td>
+<td>unknown</td>
+<td>elegans190</td>
+<td>ELAND_EXTENDED</td>
+<td>37</td>
+<td>'((FAILED_CHASTITY&lt;=1.000000))'</td>
+<td>0.600000</td>
+<td>100</td>
+<td><a href="#Lane3">
+        Lane 3</a></td>
+</tr>
+<tr>
+<td>4</td>
+<td>unknown</td>
+<td>elegans190</td>
+<td>ELAND_EXTENDED</td>
+<td>37</td>
+<td>'((FAILED_CHASTITY&lt;=1.000000))'</td>
+<td>0.600000</td>
+<td>100</td>
+<td><a href="#Lane4">
+        Lane 4</a></td>
+</tr>
+<tr>
+<td>5</td>
+<td>unknown</td>
+<td>elegans190</td>
+<td>ELAND_EXTENDED</td>
+<td>37</td>
+<td>'((FAILED_CHASTITY&lt;=1.000000))'</td>
+<td>0.600000</td>
+<td>100</td>
+<td><a href="#Lane5">
+        Lane 5</a></td>
+</tr>
+<tr>
+<td>6</td>
+<td>unknown</td>
+<td>elegans190</td>
+<td>ELAND_EXTENDED</td>
+<td>37</td>
+<td>'((FAILED_CHASTITY&lt;=1.000000))'</td>
+<td>0.600000</td>
+<td>100</td>
+<td><a href="#Lane6">
+        Lane 6</a></td>
+</tr>
+<tr>
+<td>7</td>
+<td>unknown</td>
+<td>elegans190</td>
+<td>ELAND_EXTENDED</td>
+<td>37</td>
+<td>'((FAILED_CHASTITY&lt;=1.000000))'</td>
+<td>0.600000</td>
+<td>100</td>
+<td><a href="#Lane7">
+        Lane 7</a></td>
+</tr>
+<tr>
+<td>8</td>
+<td>unknown</td>
+<td>mm9</td>
+<td>ELAND_EXTENDED</td>
+<td>37</td>
+<td>'((FAILED_CHASTITY&lt;=1.000000))'</td>
+<td>0.600000</td>
+<td>100</td>
+<td><a href="#Lane8">
+        Lane 8</a></td>
+</tr>
+</table>
+<h2>Lane Results Summary</h2>
+<table border="1" cellpadding="5">
+<tr>
+<td colspan="2">Lane Info</td>
+<td colspan="8">Tile Mean +/- SD for Lane</td>
+</tr>
+<tr>
+<td>Lane </td>
+<td>Lane Yield (kbases) </td>
+<td>Clusters (raw)</td>
+<td>Clusters (PF) </td>
+<td>1st Cycle Int (PF) </td>
+<td>% intensity after 20 cycles (PF) </td>
+<td>% PF Clusters </td>
+<td>% Align (PF) </td>
+<td>Alignment Score (PF) </td>
+<td>% Error Rate (PF) </td>
+</tr>
+<tr>
+<td>1</td>
+<td>57684</td>
+<td>126910 +/- 4300</td>
+<td>15590 +/- 4874</td>
+<td>54 +/- 14</td>
+<td>124.36 +/- 27.05</td>
+<td>12.29 +/- 3.82</td>
+<td>7.36 +/- 1.84</td>
+<td>5.92 +/- 1.64</td>
+<td>4.52 +/- 0.58</td>
+</tr>
+<tr>
+<td>2</td>
+<td>78644</td>
+<td>165739 +/- 6792</td>
+<td>21255 +/- 5034</td>
+<td>76 +/- 16</td>
+<td>88.44 +/- 20.59</td>
+<td>12.94 +/- 3.52</td>
+<td>13.55 +/- 0.83</td>
+<td>12.33 +/- 0.84</td>
+<td>5.05 +/- 0.24</td>
+</tr>
+<tr>
+<td>3</td>
+<td>68671</td>
+<td>196565 +/- 8216</td>
+<td>18559 +/- 5413</td>
+<td>106 +/- 19</td>
+<td>78.51 +/- 19.65</td>
+<td>9.41 +/- 2.64</td>
+<td>1.07 +/- 0.10</td>
+<td>0.81 +/- 0.08</td>
+<td>6.27 +/- 0.46</td>
+</tr>
+<tr>
+<td>4</td>
+<td>126273</td>
+<td>153897 +/- 8501</td>
+<td>34128 +/- 7984</td>
+<td>75 +/- 12</td>
+<td>110.44 +/- 26.03</td>
+<td>22.13 +/- 4.82</td>
+<td>4.53 +/- 0.36</td>
+<td>4.51 +/- 0.38</td>
+<td>3.58 +/- 0.22</td>
+</tr>
+<tr>
+<td>5</td>
+<td>116257</td>
+<td>135536 +/- 3908</td>
+<td>31420 +/- 5039</td>
+<td>70 +/- 10</td>
+<td>116.68 +/- 24.46</td>
+<td>23.21 +/- 3.84</td>
+<td>4.25 +/- 0.39</td>
+<td>4.19 +/- 0.41</td>
+<td>3.62 +/- 0.26</td>
+</tr>
+<tr>
+<td>6</td>
+<td>159230</td>
+<td>154083 +/- 9315</td>
+<td>43035 +/- 10193</td>
+<td>79 +/- 14</td>
+<td>123.00 +/- 28.80</td>
+<td>27.76 +/- 5.59</td>
+<td>3.64 +/- 0.30</td>
+<td>3.53 +/- 0.31</td>
+<td>3.48 +/- 0.30</td>
+</tr>
+<tr>
+<td>7</td>
+<td>180779</td>
+<td>159991 +/- 9292</td>
+<td>48859 +/- 8420</td>
+<td>82 +/- 14</td>
+<td>121.76 +/- 24.75</td>
+<td>30.47 +/- 4.53</td>
+<td>0.86 +/- 0.10</td>
+<td>0.58 +/- 0.07</td>
+<td>3.11 +/- 0.36</td>
+</tr>
+<tr>
+<td>8</td>
+<td>24267</td>
+<td>198479 +/- 17671</td>
+<td>6625 +/- 1773</td>
+<td>122 +/- 14</td>
+<td>73.80 +/- 14.41</td>
+<td>3.30 +/- 0.68</td>
+<td>48.65 +/- 5.61</td>
+<td>43.07 +/- 6.50</td>
+<td>3.28 +/- 0.29</td>
+</tr>
+<tr><td colspan="13">Tile mean across chip</td></tr>
+<tr>
+<td colspan="2">Average</td>
+<td>161400</td>
+<td>27434</td>
+<td>83</td>
+<td>104.62</td>
+<td>17.69</td>
+<td>10.49</td>
+<td>9.37</td>
+<td>4.11</td>
+</tr>
+</table>
+<h2>Expanded Lane Summary</h2>
+<table border="1" cellpadding="5">
+<tr>
+<td colspan="2">Lane Info</td>
+<td colspan="2">Phasing Info</td>
+<td colspan="2">Raw Data (tile mean)</td>
+<td colspan="7">Filtered Data (tile mean)</td>
+</tr>
+<tr>
+<td>Lane </td>
+<td> Clusters (tile mean) (raw) </td>
+<td>% Phasing </td>
+<td>% Prephasing </td>
+<td>% Error Rate (raw) </td>
+<td> Equiv Perfect Clusters (raw) </td>
+<td>% retained </td>
+<td>Cycle 2-4 Av Int (PF) </td>
+<td>Cycle 2-10 Av % Loss (PF) </td>
+<td>Cycle 10-20 Av % Loss (PF) </td>
+<td>% Align (PF) </td>
+<td> % Error Rate (PF) </td>
+<td> Equiv Perfect Clusters (PF) </td>
+</tr>
+<tr>
+<td>1</td>
+<td>126911</td>
+<td>0.4800</td>
+<td>0.3000</td>
+<td>5.57</td>
+<td>3076</td>
+<td>12.29</td>
+<td>65.81 +/- 15.68</td>
+<td>-0.47 +/- 1.84</td>
+<td>2.12 +/- 1.13</td>
+<td>7.36</td>
+<td>4.52</td>
+<td>1075</td>
+</tr>
+<tr>
+<td>2</td>
+<td>165740</td>
+<td>0.4800</td>
+<td>0.3000</td>
+<td>5.96</td>
+<td>6076</td>
+<td>12.94</td>
+<td>76.73 +/- 12.15</td>
+<td>1.47 +/- 1.92</td>
+<td>1.21 +/- 1.90</td>
+<td>13.55</td>
+<td>5.05</td>
+<td>2541</td>
+</tr>
+<tr>
+<td>3</td>
+<td>196566</td>
+<td>0.4800</td>
+<td>0.3000</td>
+<td>7.47</td>
+<td>488</td>
+<td>9.41</td>
+<td>103.17 +/- 13.33</td>
+<td>2.71 +/- 1.99</td>
+<td>1.26 +/- 1.88</td>
+<td>1.07</td>
+<td>6.27</td>
+<td>173</td>
+</tr>
+<tr>
+<td>4</td>
+<td>153898</td>
+<td>0.4800</td>
+<td>0.3000</td>
+<td>4.79</td>
+<td>3322</td>
+<td>22.13</td>
+<td>95.07 +/- 11.42</td>
+<td>1.28 +/- 2.12</td>
+<td>2.69 +/- 1.55</td>
+<td>4.53</td>
+<td>3.58</td>
+<td>1402</td>
+</tr>
+<tr>
+<td>5</td>
+<td>135536</td>
+<td>0.4800</td>
+<td>0.3000</td>
+<td>4.75</td>
+<td>2582</td>
+<td>23.21</td>
+<td>87.51 +/- 10.87</td>
+<td>1.01 +/- 2.07</td>
+<td>2.00 +/- 1.53</td>
+<td>4.25</td>
+<td>3.62</td>
+<td>1221</td>
+</tr>
+<tr>
+<td>6</td>
+<td>154084</td>
+<td>0.4800</td>
+<td>0.3000</td>
+<td>4.76</td>
+<td>3270</td>
+<td>27.76</td>
+<td>109.23 +/- 14.97</td>
+<td>1.17 +/- 2.12</td>
+<td>3.01 +/- 1.73</td>
+<td>3.64</td>
+<td>3.48</td>
+<td>1422</td>
+</tr>
+<tr>
+<td>7</td>
+<td>159991</td>
+<td>0.4800</td>
+<td>0.3000</td>
+<td>4.36</td>
+<td>1001</td>
+<td>30.47</td>
+<td>109.94 +/- 12.23</td>
+<td>1.48 +/- 2.32</td>
+<td>2.30 +/- 2.47</td>
+<td>0.86</td>
+<td>3.11</td>
+<td>394</td>
+</tr>
+<tr>
+<td>8</td>
+<td>198479</td>
+<td>0.4800</td>
+<td>0.3000</td>
+<td>5.16</td>
+<td>10876</td>
+<td>3.30</td>
+<td>115.81 +/- 11.51</td>
+<td>2.09 +/- 1.67</td>
+<td>1.19 +/- 2.91</td>
+<td>48.65</td>
+<td>3.28</td>
+<td>2958</td>
+</tr>
+</table>
+<a name="Lane"><h2>Lane 1</h2></a><table border="1" cellpadding="5">
+<tr>
+<td colspan="1">Lane </td>
+<td colspan="1">Tile</td>
+<td colspan="1">Clusters (raw)</td>
+<td colspan="1">Av 1st Cycle Int (PF)</td>
+<td colspan="1">Av % intensity after 20 cycles (PF)</td>
+<td colspan="1">% PF Clusters </td>
+<td colspan="1">% Align (PF) </td>
+<td colspan="1">Av Alignment Score (PF)</td>
+<td colspan="1">% Error Rate (PF) </td>
+</tr>
+<tr>
+<td>1</td>
+<td>0001</td>
+<td>137187</td>
+<td>30.60</td>
+<td>217.24</td>
+<td>5.91</td>
+<td>4.17</td>
+<td>2.48</td>
+<td>5.11</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0002</td>
+<td>128576</td>
+<td>29.07</td>
+<td>106.36</td>
+<td>5.06</td>
+<td>3.23</td>
+<td>2.29</td>
+<td>6.25</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0003</td>
+<td>131374</td>
+<td>26.72</td>
+<td>120.11</td>
+<td>5.04</td>
+<td>3.34</td>
+<td>2.44</td>
+<td>5.43</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0004</td>
+<td>129258</td>
+<td>28.42</td>
+<td>121.90</td>
+<td>4.57</td>
+<td>3.10</td>
+<td>2.05</td>
+<td>6.42</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0005</td>
+<td>127183</td>
+<td>31.92</td>
+<td>128.19</td>
+<td>5.23</td>
+<td>3.66</td>
+<td>2.62</td>
+<td>5.68</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0006</td>
+<td>126810</td>
+<td>35.93</td>
+<td>107.59</td>
+<td>5.37</td>
+<td>3.54</td>
+<td>2.44</td>
+<td>5.83</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0007</td>
+<td>127600</td>
+<td>34.18</td>
+<td>128.16</td>
+<td>5.69</td>
+<td>3.68</td>
+<td>2.62</td>
+<td>5.90</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0008</td>
+<td>127404</td>
+<td>45.38</td>
+<td>81.27</td>
+<td>6.10</td>
+<td>3.47</td>
+<td>2.47</td>
+<td>5.65</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0009</td>
+<td>126616</td>
+<td>36.17</td>
+<td>126.68</td>
+<td>7.14</td>
+<td>4.21</td>
+<td>3.05</td>
+<td>5.36</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0010</td>
+<td>126528</td>
+<td>33.95</td>
+<td>143.96</td>
+<td>7.82</td>
+<td>4.62</td>
+<td>3.49</td>
+<td>5.31</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0011</td>
+<td>127241</td>
+<td>42.65</td>
+<td>104.63</td>
+<td>8.20</td>
+<td>4.81</td>
+<td>3.39</td>
+<td>5.50</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0012</td>
+<td>130637</td>
+<td>33.40</td>
+<td>151.65</td>
+<td>8.72</td>
+<td>4.43</td>
+<td>3.43</td>
+<td>4.97</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0013</td>
+<td>134557</td>
+<td>29.95</td>
+<td>152.84</td>
+<td>8.36</td>
+<td>4.69</td>
+<td>3.52</td>
+<td>5.34</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0014</td>
+<td>126519</td>
+<td>33.45</td>
+<td>124.07</td>
+<td>8.65</td>
+<td>5.17</td>
+<td>3.93</td>
+<td>5.14</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0015</td>
+<td>132462</td>
+<td>31.15</td>
+<td>167.26</td>
+<td>8.33</td>
+<td>5.70</td>
+<td>4.42</td>
+<td>4.71</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0016</td>
+<td>130705</td>
+<td>57.93</td>
+<td>75.96</td>
+<td>7.87</td>
+<td>6.65</td>
+<td>4.97</td>
+<td>5.12</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0017</td>
+<td>134599</td>
+<td>40.48</td>
+<td>139.53</td>
+<td>9.01</td>
+<td>6.53</td>
+<td>5.17</td>
+<td>4.46</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0018</td>
+<td>127550</td>
+<td>41.45</td>
+<td>130.16</td>
+<td>8.48</td>
+<td>6.34</td>
+<td>4.95</td>
+<td>4.89</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0019</td>
+<td>126880</td>
+<td>54.35</td>
+<td>108.00</td>
+<td>9.76</td>
+<td>6.03</td>
+<td>4.59</td>
+<td>4.71</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0020</td>
+<td>126811</td>
+<td>38.05</td>
+<td>122.40</td>
+<td>8.72</td>
+<td>6.23</td>
+<td>4.88</td>
+<td>4.88</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0021</td>
+<td>130562</td>
+<td>34.10</td>
+<td>140.98</td>
+<td>8.79</td>
+<td>5.43</td>
+<td>4.18</td>
+<td>5.19</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0022</td>
+<td>126586</td>
+<td>42.05</td>
+<td>99.64</td>
+<td>8.31</td>
+<td>5.63</td>
+<td>4.16</td>
+<td>5.41</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0023</td>
+<td>124746</td>
+<td>38.08</td>
+<td>108.08</td>
+<td>8.96</td>
+<td>5.85</td>
+<td>4.35</td>
+<td>5.07</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0024</td>
+<td>125450</td>
+<td>45.17</td>
+<td>109.24</td>
+<td>8.75</td>
+<td>5.39</td>
+<td>3.84</td>
+<td>5.37</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0025</td>
+<td>124739</td>
+<td>45.58</td>
+<td>109.05</td>
+<td>9.17</td>
+<td>6.51</td>
+<td>5.04</td>
+<td>4.72</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0026</td>
+<td>120845</td>
+<td>47.38</td>
+<td>104.96</td>
+<td>9.93</td>
+<td>5.87</td>
+<td>4.63</td>
+<td>4.63</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0027</td>
+<td>122292</td>
+<td>47.65</td>
+<td>104.56</td>
+<td>8.95</td>
+<td>6.27</td>
+<td>4.75</td>
+<td>4.91</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0028</td>
+<td>125133</td>
+<td>48.95</td>
+<td>128.50</td>
+<td>9.63</td>
+<td>7.69</td>
+<td>5.96</td>
+<td>4.74</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0029</td>
+<td>121876</td>
+<td>47.25</td>
+<td>123.44</td>
+<td>9.56</td>
+<td>7.61</td>
+<td>6.02</td>
+<td>4.53</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0030</td>
+<td>129067</td>
+<td>36.33</td>
+<td>140.61</td>
+<td>8.70</td>
+<td>7.21</td>
+<td>5.50</td>
+<td>4.83</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0031</td>
+<td>126997</td>
+<td>39.03</td>
+<td>148.37</td>
+<td>9.88</td>
+<td>7.62</td>
+<td>6.08</td>
+<td>4.44</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0032</td>
+<td>123097</td>
+<td>47.68</td>
+<td>129.52</td>
+<td>10.24</td>
+<td>8.10</td>
+<td>6.33</td>
+<td>4.61</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0033</td>
+<td>127916</td>
+<td>46.25</td>
+<td>171.78</td>
+<td>14.14</td>
+<td>8.11</td>
+<td>6.72</td>
+<td>4.29</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0034</td>
+<td>124936</td>
+<td>52.72</td>
+<td>129.07</td>
+<td>9.45</td>
+<td>9.24</td>
+<td>7.40</td>
+<td>4.65</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0035</td>
+<td>123332</td>
+<td>49.62</td>
+<td>135.67</td>
+<td>10.67</td>
+<td>8.91</td>
+<td>7.13</td>
+<td>4.50</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0036</td>
+<td>122410</td>
+<td>51.82</td>
+<td>98.94</td>
+<td>11.66</td>
+<td>8.95</td>
+<td>6.99</td>
+<td>4.93</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0037</td>
+<td>125820</td>
+<td>40.17</td>
+<td>143.31</td>
+<td>8.65</td>
+<td>8.68</td>
+<td>6.93</td>
+<td>4.59</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0038</td>
+<td>121654</td>
+<td>47.78</td>
+<td>111.88</td>
+<td>9.95</td>
+<td>9.17</td>
+<td>7.10</td>
+<td>4.67</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0039</td>
+<td>124171</td>
+<td>52.88</td>
+<td>102.84</td>
+<td>9.04</td>
+<td>10.15</td>
+<td>8.01</td>
+<td>4.70</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0040</td>
+<td>124584</td>
+<td>73.90</td>
+<td>98.38</td>
+<td>12.81</td>
+<td>10.30</td>
+<td>8.34</td>
+<td>4.29</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0041</td>
+<td>123949</td>
+<td>52.55</td>
+<td>172.79</td>
+<td>10.25</td>
+<td>10.60</td>
+<td>8.76</td>
+<td>4.27</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0042</td>
+<td>125220</td>
+<td>55.25</td>
+<td>129.95</td>
+<td>9.98</td>
+<td>10.18</td>
+<td>8.31</td>
+<td>4.41</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0043</td>
+<td>124591</td>
+<td>58.95</td>
+<td>126.25</td>
+<td>11.79</td>
+<td>10.23</td>
+<td>8.25</td>
+<td>4.60</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0044</td>
+<td>124204</td>
+<td>53.42</td>
+<td>104.35</td>
+<td>10.31</td>
+<td>9.75</td>
+<td>7.89</td>
+<td>4.62</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0045</td>
+<td>123608</td>
+<td>53.05</td>
+<td>116.35</td>
+<td>10.21</td>
+<td>9.73</td>
+<td>7.94</td>
+<td>4.52</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0046</td>
+<td>123860</td>
+<td>60.00</td>
+<td>135.88</td>
+<td>13.93</td>
+<td>8.24</td>
+<td>6.77</td>
+<td>4.71</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0047</td>
+<td>124395</td>
+<td>49.30</td>
+<td>156.09</td>
+<td>12.72</td>
+<td>8.75</td>
+<td>7.15</td>
+<td>4.58</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0048</td>
+<td>122450</td>
+<td>62.30</td>
+<td>115.21</td>
+<td>15.96</td>
+<td>8.22</td>
+<td>6.80</td>
+<td>4.36</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0049</td>
+<td>122445</td>
+<td>75.00</td>
+<td>96.43</td>
+<td>13.19</td>
+<td>10.02</td>
+<td>8.05</td>
+<td>4.36</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0050</td>
+<td>123765</td>
+<td>49.55</td>
+<td>141.68</td>
+<td>13.12</td>
+<td>9.01</td>
+<td>7.54</td>
+<td>4.32</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0051</td>
+<td>125692</td>
+<td>75.67</td>
+<td>124.12</td>
+<td>18.07</td>
+<td>8.34</td>
+<td>7.03</td>
+<td>3.94</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0052</td>
+<td>125104</td>
+<td>82.65</td>
+<td>120.02</td>
+<td>16.92</td>
+<td>8.82</td>
+<td>7.42</td>
+<td>4.02</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0053</td>
+<td>124636</td>
+<td>80.20</td>
+<td>112.31</td>
+<td>16.61</td>
+<td>9.96</td>
+<td>8.40</td>
+<td>3.94</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0054</td>
+<td>125641</td>
+<td>69.65</td>
+<td>143.90</td>
+<td>18.54</td>
+<td>8.60</td>
+<td>7.25</td>
+<td>4.05</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0055</td>
+<td>125905</td>
+<td>74.35</td>
+<td>116.48</td>
+<td>16.50</td>
+<td>8.73</td>
+<td>7.39</td>
+<td>4.04</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0056</td>
+<td>126935</td>
+<td>59.57</td>
+<td>125.05</td>
+<td>15.19</td>
+<td>9.10</td>
+<td>7.36</td>
+<td>4.51</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0057</td>
+<td>123510</td>
+<td>70.22</td>
+<td>101.57</td>
+<td>13.72</td>
+<td>9.66</td>
+<td>7.79</td>
+<td>4.39</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0058</td>
+<td>125968</td>
+<td>60.57</td>
+<td>144.66</td>
+<td>15.89</td>
+<td>7.99</td>
+<td>6.60</td>
+<td>4.28</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0059</td>
+<td>127385</td>
+<td>75.50</td>
+<td>100.33</td>
+<td>15.17</td>
+<td>9.69</td>
+<td>7.90</td>
+<td>4.33</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0060</td>
+<td>125188</td>
+<td>57.75</td>
+<td>122.21</td>
+<td>12.81</td>
+<td>8.87</td>
+<td>7.31</td>
+<td>4.40</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0061</td>
+<td>127952</td>
+<td>45.27</td>
+<td>124.96</td>
+<td>11.72</td>
+<td>7.77</td>
+<td>6.08</td>
+<td>4.73</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0062</td>
+<td>123964</td>
+<td>75.38</td>
+<td>72.80</td>
+<td>12.58</td>
+<td>8.48</td>
+<td>6.67</td>
+<td>4.47</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0063</td>
+<td>124803</td>
+<td>78.60</td>
+<td>114.57</td>
+<td>14.62</td>
+<td>7.92</td>
+<td>6.33</td>
+<td>4.48</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0064</td>
+<td>130185</td>
+<td>67.62</td>
+<td>142.48</td>
+<td>16.19</td>
+<td>9.94</td>
+<td>8.32</td>
+<td>4.26</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0065</td>
+<td>125654</td>
+<td>73.00</td>
+<td>159.25</td>
+<td>24.92</td>
+<td>8.20</td>
+<td>7.00</td>
+<td>3.89</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0066</td>
+<td>119533</td>
+<td>53.10</td>
+<td>168.69</td>
+<td>15.04</td>
+<td>8.66</td>
+<td>7.16</td>
+<td>4.25</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0067</td>
+<td>121760</td>
+<td>60.47</td>
+<td>97.52</td>
+<td>15.14</td>
+<td>8.68</td>
+<td>7.16</td>
+<td>4.46</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0068</td>
+<td>123664</td>
+<td>66.00</td>
+<td>77.58</td>
+<td>12.92</td>
+<td>7.80</td>
+<td>5.92</td>
+<td>4.65</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0069</td>
+<td>123890</td>
+<td>52.57</td>
+<td>163.58</td>
+<td>14.72</td>
+<td>8.46</td>
+<td>7.00</td>
+<td>4.26</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0070</td>
+<td>126241</td>
+<td>62.42</td>
+<td>137.08</td>
+<td>13.47</td>
+<td>7.70</td>
+<td>6.12</td>
+<td>4.31</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0071</td>
+<td>122834</td>
+<td>54.62</td>
+<td>102.01</td>
+<td>12.68</td>
+<td>8.17</td>
+<td>6.48</td>
+<td>4.45</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0072</td>
+<td>125630</td>
+<td>70.70</td>
+<td>86.95</td>
+<td>14.67</td>
+<td>7.98</td>
+<td>6.37</td>
+<td>4.20</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0073</td>
+<td>125053</td>
+<td>59.30</td>
+<td>162.56</td>
+<td>15.30</td>
+<td>8.57</td>
+<td>7.31</td>
+<td>4.00</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0074</td>
+<td>127668</td>
+<td>61.90</td>
+<td>155.82</td>
+<td>15.91</td>
+<td>8.43</td>
+<td>7.01</td>
+<td>4.14</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0075</td>
+<td>124426</td>
+<td>64.95</td>
+<td>148.38</td>
+<td>17.08</td>
+<td>7.73</td>
+<td>6.37</td>
+<td>4.16</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0076</td>
+<td>132248</td>
+<td>57.95</td>
+<td>97.41</td>
+<td>16.17</td>
+<td>8.18</td>
+<td>7.01</td>
+<td>3.86</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0077</td>
+<td>129486</td>
+<td>80.68</td>
+<td>72.33</td>
+<td>16.41</td>
+<td>8.28</td>
+<td>6.61</td>
+<td>4.30</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0078</td>
+<td>130453</td>
+<td>84.72</td>
+<td>70.64</td>
+<td>13.06</td>
+<td>8.38</td>
+<td>6.54</td>
+<td>4.14</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0079</td>
+<td>134488</td>
+<td>54.73</td>
+<td>107.58</td>
+<td>15.64</td>
+<td>7.24</td>
+<td>6.16</td>
+<td>3.92</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0080</td>
+<td>126663</td>
+<td>72.08</td>
+<td>79.78</td>
+<td>16.09</td>
+<td>8.84</td>
+<td>7.19</td>
+<td>4.15</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0081</td>
+<td>120700</td>
+<td>55.45</td>
+<td>174.62</td>
+<td>17.29</td>
+<td>8.24</td>
+<td>7.11</td>
+<td>3.76</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0082</td>
+<td>124856</td>
+<td>71.95</td>
+<td>78.08</td>
+<td>13.71</td>
+<td>8.54</td>
+<td>6.86</td>
+<td>4.12</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0083</td>
+<td>126594</td>
+<td>67.38</td>
+<td>90.35</td>
+<td>13.11</td>
+<td>8.39</td>
+<td>6.76</td>
+<td>4.19</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0084</td>
+<td>121831</td>
+<td>60.75</td>
+<td>120.33</td>
+<td>16.70</td>
+<td>6.66</td>
+<td>5.57</td>
+<td>3.87</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0085</td>
+<td>120698</td>
+<td>53.15</td>
+<td>138.99</td>
+<td>13.80</td>
+<td>7.06</td>
+<td>5.75</td>
+<td>4.24</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0086</td>
+<td>120737</td>
+<td>54.78</td>
+<td>160.47</td>
+<td>16.59</td>
+<td>6.47</td>
+<td>5.19</td>
+<td>4.33</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0087</td>
+<td>132398</td>
+<td>53.55</td>
+<td>99.58</td>
+<td>15.42</td>
+<td>6.99</td>
+<td>5.91</td>
+<td>3.97</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0088</td>
+<td>127970</td>
+<td>58.12</td>
+<td>140.04</td>
+<td>14.30</td>
+<td>7.38</td>
+<td>6.05</td>
+<td>4.45</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0089</td>
+<td>123522</td>
+<td>65.20</td>
+<td>136.20</td>
+<td>16.81</td>
+<td>7.08</td>
+<td>5.98</td>
+<td>3.90</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0090</td>
+<td>143307</td>
+<td>46.35</td>
+<td>150.70</td>
+<td>13.34</td>
+<td>6.26</td>
+<td>5.35</td>
+<td>3.78</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0091</td>
+<td>130597</td>
+<td>54.43</td>
+<td>113.05</td>
+<td>14.64</td>
+<td>7.42</td>
+<td>6.27</td>
+<td>3.87</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0092</td>
+<td>141366</td>
+<td>46.40</td>
+<td>161.10</td>
+<td>17.07</td>
+<td>6.36</td>
+<td>5.51</td>
+<td>3.69</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0093</td>
+<td>128028</td>
+<td>68.60</td>
+<td>104.26</td>
+<td>16.71</td>
+<td>6.68</td>
+<td>5.45</td>
+<td>3.89</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0094</td>
+<td>128450</td>
+<td>60.07</td>
+<td>127.42</td>
+<td>14.88</td>
+<td>6.89</td>
+<td>5.73</td>
+<td>3.81</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0095</td>
+<td>128962</td>
+<td>72.45</td>
+<td>106.14</td>
+<td>16.63</td>
+<td>6.91</td>
+<td>5.80</td>
+<td>3.43</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0096</td>
+<td>134736</td>
+<td>60.43</td>
+<td>111.87</td>
+<td>15.30</td>
+<td>6.79</td>
+<td>5.85</td>
+<td>3.58</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0097</td>
+<td>131109</td>
+<td>54.83</td>
+<td>174.01</td>
+<td>14.31</td>
+<td>6.62</td>
+<td>5.56</td>
+<td>3.81</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0098</td>
+<td>133717</td>
+<td>56.33</td>
+<td>120.46</td>
+<td>15.52</td>
+<td>6.86</td>
+<td>5.77</td>
+<td>3.70</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0099</td>
+<td>131704</td>
+<td>50.42</td>
+<td>135.45</td>
+<td>11.81</td>
+<td>6.40</td>
+<td>5.28</td>
+<td>4.12</td>
+</tr>
+<tr>
+<td>1</td>
+<td>0100</td>
+<td>136023</td>
+<td>49.25</td>
+<td>148.73</td>
+<td>14.92</td>
+<td>5.67</td>
+<td>4.80</td>
+<td>4.03</td>
+</tr>
+</table>
+<a name="Lane"><h2>Lane 2</h2></a><table border="1" cellpadding="5">
+<tr>
+<td colspan="1">Lane </td>
+<td colspan="1">Tile</td>
+<td colspan="1">Clusters (raw)</td>
+<td colspan="1">Av 1st Cycle Int (PF)</td>
+<td colspan="1">Av % intensity after 20 cycles (PF)</td>
+<td colspan="1">% PF Clusters </td>
+<td colspan="1">% Align (PF) </td>
+<td colspan="1">Av Alignment Score (PF)</td>
+<td colspan="1">% Error Rate (PF) </td>
+</tr>
+<tr>
+<td>2</td>
+<td>0001</td>
+<td>170860</td>
+<td>62.62</td>
+<td>102.59</td>
+<td>8.71</td>
+<td>11.68</td>
+<td>10.42</td>
+<td>5.37</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0002</td>
+<td>168047</td>
+<td>58.73</td>
+<td>101.28</td>
+<td>8.09</td>
+<td>12.01</td>
+<td>11.15</td>
+<td>4.68</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0003</td>
+<td>165611</td>
+<td>55.85</td>
+<td>114.91</td>
+<td>8.35</td>
+<td>12.79</td>
+<td>11.92</td>
+<td>4.72</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0004</td>
+<td>171147</td>
+<td>54.33</td>
+<td>89.00</td>
+<td>7.76</td>
+<td>12.51</td>
+<td>11.06</td>
+<td>5.30</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0005</td>
+<td>166702</td>
+<td>51.80</td>
+<td>98.41</td>
+<td>7.98</td>
+<td>12.93</td>
+<td>11.85</td>
+<td>4.94</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0006</td>
+<td>171530</td>
+<td>56.83</td>
+<td>93.14</td>
+<td>9.66</td>
+<td>13.46</td>
+<td>12.56</td>
+<td>4.80</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0007</td>
+<td>175956</td>
+<td>63.45</td>
+<td>86.25</td>
+<td>9.49</td>
+<td>13.15</td>
+<td>12.13</td>
+<td>4.82</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0008</td>
+<td>178220</td>
+<td>70.48</td>
+<td>77.55</td>
+<td>10.04</td>
+<td>12.91</td>
+<td>11.64</td>
+<td>5.18</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0009</td>
+<td>172420</td>
+<td>54.58</td>
+<td>107.88</td>
+<td>9.77</td>
+<td>12.79</td>
+<td>11.62</td>
+<td>5.02</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0010</td>
+<td>174119</td>
+<td>48.60</td>
+<td>103.65</td>
+<td>8.70</td>
+<td>12.46</td>
+<td>11.47</td>
+<td>4.93</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0011</td>
+<td>173224</td>
+<td>64.15</td>
+<td>83.09</td>
+<td>9.38</td>
+<td>12.71</td>
+<td>11.42</td>
+<td>5.11</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0012</td>
+<td>168945</td>
+<td>56.97</td>
+<td>76.92</td>
+<td>8.67</td>
+<td>13.60</td>
+<td>12.00</td>
+<td>5.37</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0013</td>
+<td>169698</td>
+<td>54.75</td>
+<td>110.09</td>
+<td>8.75</td>
+<td>12.89</td>
+<td>11.82</td>
+<td>4.99</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0014</td>
+<td>176430</td>
+<td>56.88</td>
+<td>76.44</td>
+<td>9.00</td>
+<td>12.51</td>
+<td>11.30</td>
+<td>5.14</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0015</td>
+<td>173392</td>
+<td>54.30</td>
+<td>109.39</td>
+<td>10.27</td>
+<td>13.47</td>
+<td>12.19</td>
+<td>5.16</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0016</td>
+<td>173752</td>
+<td>86.25</td>
+<td>51.80</td>
+<td>11.49</td>
+<td>13.88</td>
+<td>12.43</td>
+<td>5.24</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0017</td>
+<td>172890</td>
+<td>50.90</td>
+<td>95.19</td>
+<td>10.72</td>
+<td>12.90</td>
+<td>11.72</td>
+<td>5.07</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0018</td>
+<td>173359</td>
+<td>60.30</td>
+<td>87.69</td>
+<td>10.50</td>
+<td>13.45</td>
+<td>11.98</td>
+<td>5.21</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0019</td>
+<td>172871</td>
+<td>80.22</td>
+<td>49.80</td>
+<td>11.16</td>
+<td>12.86</td>
+<td>11.12</td>
+<td>5.26</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0020</td>
+<td>167500</td>
+<td>61.65</td>
+<td>109.57</td>
+<td>10.62</td>
+<td>13.95</td>
+<td>12.73</td>
+<td>4.99</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0021</td>
+<td>173061</td>
+<td>64.42</td>
+<td>86.03</td>
+<td>11.19</td>
+<td>13.38</td>
+<td>11.79</td>
+<td>5.27</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0022</td>
+<td>171387</td>
+<td>64.05</td>
+<td>96.57</td>
+<td>10.12</td>
+<td>13.48</td>
+<td>12.34</td>
+<td>5.01</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0023</td>
+<td>178113</td>
+<td>52.73</td>
+<td>81.41</td>
+<td>10.64</td>
+<td>12.86</td>
+<td>11.90</td>
+<td>4.82</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0024</td>
+<td>172379</td>
+<td>65.38</td>
+<td>74.99</td>
+<td>9.79</td>
+<td>13.20</td>
+<td>12.05</td>
+<td>5.08</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0025</td>
+<td>170661</td>
+<td>59.25</td>
+<td>90.97</td>
+<td>10.09</td>
+<td>13.21</td>
+<td>12.08</td>
+<td>5.06</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0026</td>
+<td>169053</td>
+<td>73.67</td>
+<td>66.98</td>
+<td>10.58</td>
+<td>13.00</td>
+<td>11.86</td>
+<td>4.82</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0027</td>
+<td>166006</td>
+<td>69.58</td>
+<td>99.82</td>
+<td>9.55</td>
+<td>13.00</td>
+<td>12.04</td>
+<td>4.98</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0028</td>
+<td>165399</td>
+<td>79.20</td>
+<td>83.78</td>
+<td>11.65</td>
+<td>13.70</td>
+<td>12.73</td>
+<td>4.79</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0029</td>
+<td>164659</td>
+<td>79.80</td>
+<td>59.65</td>
+<td>9.48</td>
+<td>14.10</td>
+<td>12.84</td>
+<td>5.11</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0030</td>
+<td>163419</td>
+<td>74.82</td>
+<td>84.20</td>
+<td>11.40</td>
+<td>13.31</td>
+<td>11.73</td>
+<td>5.30</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0031</td>
+<td>161881</td>
+<td>70.33</td>
+<td>74.65</td>
+<td>12.62</td>
+<td>14.05</td>
+<td>12.82</td>
+<td>5.10</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0032</td>
+<td>162247</td>
+<td>63.85</td>
+<td>78.19</td>
+<td>13.17</td>
+<td>14.19</td>
+<td>13.00</td>
+<td>5.15</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0033</td>
+<td>163268</td>
+<td>93.03</td>
+<td>93.15</td>
+<td>13.97</td>
+<td>14.16</td>
+<td>12.69</td>
+<td>5.25</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0034</td>
+<td>161905</td>
+<td>61.95</td>
+<td>74.01</td>
+<td>13.85</td>
+<td>14.15</td>
+<td>13.25</td>
+<td>4.92</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0035</td>
+<td>162020</td>
+<td>105.35</td>
+<td>45.16</td>
+<td>14.71</td>
+<td>14.62</td>
+<td>13.13</td>
+<td>5.07</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0036</td>
+<td>160276</td>
+<td>67.33</td>
+<td>125.58</td>
+<td>13.97</td>
+<td>13.30</td>
+<td>12.10</td>
+<td>5.19</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0037</td>
+<td>159586</td>
+<td>72.95</td>
+<td>65.08</td>
+<td>12.84</td>
+<td>13.92</td>
+<td>12.45</td>
+<td>5.41</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0038</td>
+<td>158070</td>
+<td>73.65</td>
+<td>138.42</td>
+<td>11.38</td>
+<td>14.15</td>
+<td>12.75</td>
+<td>5.15</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0039</td>
+<td>158382</td>
+<td>78.83</td>
+<td>110.37</td>
+<td>15.20</td>
+<td>14.59</td>
+<td>13.18</td>
+<td>5.21</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0040</td>
+<td>159069</td>
+<td>69.10</td>
+<td>130.35</td>
+<td>20.27</td>
+<td>14.25</td>
+<td>13.31</td>
+<td>4.96</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0041</td>
+<td>157785</td>
+<td>77.08</td>
+<td>88.58</td>
+<td>14.23</td>
+<td>13.85</td>
+<td>12.24</td>
+<td>5.43</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0042</td>
+<td>163264</td>
+<td>157.78</td>
+<td>47.50</td>
+<td>16.67</td>
+<td>13.62</td>
+<td>10.91</td>
+<td>5.45</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0043</td>
+<td>158246</td>
+<td>87.07</td>
+<td>78.95</td>
+<td>16.58</td>
+<td>13.63</td>
+<td>11.93</td>
+<td>5.45</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0044</td>
+<td>157316</td>
+<td>74.78</td>
+<td>113.57</td>
+<td>16.87</td>
+<td>14.24</td>
+<td>12.99</td>
+<td>5.16</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0045</td>
+<td>157310</td>
+<td>82.50</td>
+<td>76.33</td>
+<td>15.73</td>
+<td>14.08</td>
+<td>12.63</td>
+<td>5.31</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0046</td>
+<td>157533</td>
+<td>99.42</td>
+<td>74.53</td>
+<td>16.97</td>
+<td>13.97</td>
+<td>12.29</td>
+<td>5.51</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0047</td>
+<td>157552</td>
+<td>76.73</td>
+<td>102.38</td>
+<td>20.72</td>
+<td>14.55</td>
+<td>13.33</td>
+<td>5.06</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0048</td>
+<td>156112</td>
+<td>85.25</td>
+<td>90.56</td>
+<td>20.76</td>
+<td>14.36</td>
+<td>13.23</td>
+<td>4.99</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0049</td>
+<td>157664</td>
+<td>84.10</td>
+<td>88.70</td>
+<td>18.36</td>
+<td>14.14</td>
+<td>12.89</td>
+<td>5.18</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0050</td>
+<td>156643</td>
+<td>82.57</td>
+<td>85.35</td>
+<td>17.06</td>
+<td>14.13</td>
+<td>12.73</td>
+<td>5.23</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0051</td>
+<td>155494</td>
+<td>118.53</td>
+<td>66.78</td>
+<td>20.25</td>
+<td>14.35</td>
+<td>13.02</td>
+<td>5.06</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0052</td>
+<td>155567</td>
+<td>82.30</td>
+<td>103.52</td>
+<td>21.14</td>
+<td>13.60</td>
+<td>12.45</td>
+<td>5.08</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0053</td>
+<td>155000</td>
+<td>95.65</td>
+<td>82.02</td>
+<td>20.36</td>
+<td>14.14</td>
+<td>12.67</td>
+<td>5.20</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0054</td>
+<td>155088</td>
+<td>74.28</td>
+<td>120.13</td>
+<td>20.83</td>
+<td>13.90</td>
+<td>12.89</td>
+<td>4.98</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0055</td>
+<td>155123</td>
+<td>93.98</td>
+<td>85.98</td>
+<td>19.28</td>
+<td>14.74</td>
+<td>13.64</td>
+<td>4.80</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0056</td>
+<td>154192</td>
+<td>83.12</td>
+<td>93.77</td>
+<td>18.86</td>
+<td>14.62</td>
+<td>13.09</td>
+<td>5.31</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0057</td>
+<td>155718</td>
+<td>96.32</td>
+<td>62.70</td>
+<td>16.99</td>
+<td>14.48</td>
+<td>12.86</td>
+<td>5.32</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0058</td>
+<td>156380</td>
+<td>74.72</td>
+<td>129.91</td>
+<td>16.59</td>
+<td>15.16</td>
+<td>14.02</td>
+<td>5.07</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0059</td>
+<td>157556</td>
+<td>86.45</td>
+<td>104.86</td>
+<td>15.49</td>
+<td>13.58</td>
+<td>12.06</td>
+<td>5.39</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0060</td>
+<td>158323</td>
+<td>87.90</td>
+<td>76.79</td>
+<td>17.49</td>
+<td>14.02</td>
+<td>12.34</td>
+<td>5.52</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0061</td>
+<td>158102</td>
+<td>74.28</td>
+<td>110.06</td>
+<td>17.22</td>
+<td>14.35</td>
+<td>13.16</td>
+<td>5.21</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0062</td>
+<td>157689</td>
+<td>84.80</td>
+<td>94.58</td>
+<td>14.21</td>
+<td>15.18</td>
+<td>13.76</td>
+<td>5.28</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0063</td>
+<td>157540</td>
+<td>92.15</td>
+<td>101.44</td>
+<td>15.93</td>
+<td>15.29</td>
+<td>13.76</td>
+<td>5.24</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0064</td>
+<td>157219</td>
+<td>66.28</td>
+<td>76.80</td>
+<td>17.73</td>
+<td>15.35</td>
+<td>14.87</td>
+<td>4.50</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0065</td>
+<td>160003</td>
+<td>65.15</td>
+<td>82.08</td>
+<td>17.30</td>
+<td>15.00</td>
+<td>14.21</td>
+<td>4.71</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0066</td>
+<td>160320</td>
+<td>73.30</td>
+<td>93.93</td>
+<td>14.96</td>
+<td>14.98</td>
+<td>13.99</td>
+<td>4.88</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0067</td>
+<td>160868</td>
+<td>80.20</td>
+<td>115.27</td>
+<td>14.21</td>
+<td>14.93</td>
+<td>13.76</td>
+<td>5.02</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0068</td>
+<td>161772</td>
+<td>76.65</td>
+<td>66.89</td>
+<td>13.31</td>
+<td>13.73</td>
+<td>12.73</td>
+<td>4.89</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0069</td>
+<td>163754</td>
+<td>87.00</td>
+<td>77.39</td>
+<td>12.65</td>
+<td>13.72</td>
+<td>12.24</td>
+<td>5.22</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0070</td>
+<td>163426</td>
+<td>82.33</td>
+<td>99.85</td>
+<td>12.72</td>
+<td>13.60</td>
+<td>12.44</td>
+<td>4.93</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0071</td>
+<td>161780</td>
+<td>74.80</td>
+<td>90.91</td>
+<td>13.03</td>
+<td>14.13</td>
+<td>12.81</td>
+<td>5.38</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0072</td>
+<td>165226</td>
+<td>100.00</td>
+<td>96.10</td>
+<td>12.45</td>
+<td>14.19</td>
+<td>13.05</td>
+<td>5.12</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0073</td>
+<td>167863</td>
+<td>79.80</td>
+<td>83.36</td>
+<td>15.37</td>
+<td>14.03</td>
+<td>13.12</td>
+<td>4.85</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0074</td>
+<td>165913</td>
+<td>101.12</td>
+<td>54.68</td>
+<td>13.22</td>
+<td>14.19</td>
+<td>12.85</td>
+<td>5.15</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0075</td>
+<td>164802</td>
+<td>87.07</td>
+<td>93.11</td>
+<td>11.53</td>
+<td>13.77</td>
+<td>12.31</td>
+<td>5.10</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0076</td>
+<td>166823</td>
+<td>77.25</td>
+<td>66.38</td>
+<td>10.96</td>
+<td>13.49</td>
+<td>12.54</td>
+<td>4.87</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0077</td>
+<td>163962</td>
+<td>76.15</td>
+<td>69.63</td>
+<td>14.11</td>
+<td>14.17</td>
+<td>13.26</td>
+<td>4.85</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0078</td>
+<td>168087</td>
+<td>100.95</td>
+<td>81.40</td>
+<td>12.41</td>
+<td>13.36</td>
+<td>12.18</td>
+<td>4.90</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0079</td>
+<td>165569</td>
+<td>89.40</td>
+<td>58.89</td>
+<td>11.73</td>
+<td>13.73</td>
+<td>12.41</td>
+<td>5.03</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0080</td>
+<td>171955</td>
+<td>74.95</td>
+<td>102.03</td>
+<td>13.60</td>
+<td>12.95</td>
+<td>12.11</td>
+<td>4.78</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0081</td>
+<td>166480</td>
+<td>83.35</td>
+<td>96.94</td>
+<td>12.50</td>
+<td>12.76</td>
+<td>11.20</td>
+<td>5.42</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0082</td>
+<td>171089</td>
+<td>80.08</td>
+<td>64.10</td>
+<td>12.31</td>
+<td>12.98</td>
+<td>11.77</td>
+<td>5.16</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0083</td>
+<td>168658</td>
+<td>69.50</td>
+<td>122.05</td>
+<td>10.71</td>
+<td>13.22</td>
+<td>12.13</td>
+<td>4.99</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0084</td>
+<td>166958</td>
+<td>87.22</td>
+<td>68.13</td>
+<td>10.29</td>
+<td>12.87</td>
+<td>11.46</td>
+<td>5.13</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0085</td>
+<td>168593</td>
+<td>58.42</td>
+<td>82.97</td>
+<td>8.82</td>
+<td>12.17</td>
+<td>11.34</td>
+<td>4.81</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0086</td>
+<td>170048</td>
+<td>94.28</td>
+<td>60.33</td>
+<td>8.64</td>
+<td>12.48</td>
+<td>10.67</td>
+<td>5.22</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0087</td>
+<td>173518</td>
+<td>107.08</td>
+<td>51.27</td>
+<td>9.22</td>
+<td>12.73</td>
+<td>11.01</td>
+<td>5.26</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0088</td>
+<td>168833</td>
+<td>63.73</td>
+<td>131.42</td>
+<td>12.05</td>
+<td>12.97</td>
+<td>12.16</td>
+<td>4.62</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0089</td>
+<td>176200</td>
+<td>82.85</td>
+<td>63.79</td>
+<td>9.28</td>
+<td>12.75</td>
+<td>11.23</td>
+<td>5.20</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0090</td>
+<td>172411</td>
+<td>72.20</td>
+<td>121.95</td>
+<td>14.19</td>
+<td>13.10</td>
+<td>12.33</td>
+<td>4.75</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0091</td>
+<td>171660</td>
+<td>78.12</td>
+<td>82.91</td>
+<td>11.41</td>
+<td>13.20</td>
+<td>12.45</td>
+<td>4.73</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0092</td>
+<td>168257</td>
+<td>79.22</td>
+<td>66.93</td>
+<td>10.41</td>
+<td>13.04</td>
+<td>11.88</td>
+<td>4.96</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0093</td>
+<td>171960</td>
+<td>81.53</td>
+<td>75.80</td>
+<td>10.00</td>
+<td>12.92</td>
+<td>12.23</td>
+<td>4.62</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0094</td>
+<td>171243</td>
+<td>88.10</td>
+<td>68.13</td>
+<td>9.17</td>
+<td>12.34</td>
+<td>11.26</td>
+<td>5.08</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0095</td>
+<td>173740</td>
+<td>70.65</td>
+<td>90.59</td>
+<td>10.44</td>
+<td>12.90</td>
+<td>12.14</td>
+<td>4.76</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0096</td>
+<td>174659</td>
+<td>90.72</td>
+<td>90.88</td>
+<td>12.69</td>
+<td>12.94</td>
+<td>12.15</td>
+<td>4.56</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0097</td>
+<td>177182</td>
+<td>71.08</td>
+<td>114.70</td>
+<td>12.39</td>
+<td>12.77</td>
+<td>12.36</td>
+<td>4.43</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0098</td>
+<td>173849</td>
+<td>63.10</td>
+<td>131.34</td>
+<td>11.79</td>
+<td>12.33</td>
+<td>11.69</td>
+<td>4.50</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0099</td>
+<td>176016</td>
+<td>93.23</td>
+<td>92.01</td>
+<td>11.95</td>
+<td>12.38</td>
+<td>11.23</td>
+<td>4.99</td>
+</tr>
+<tr>
+<td>2</td>
+<td>0100</td>
+<td>174587</td>
+<td>87.28</td>
+<td>95.79</td>
+<td>10.07</td>
+<td>11.09</td>
+<td>10.10</td>
+<td>4.89</td>
+</tr>
+</table>
+<a name="Lane"><h2>Lane 3</h2></a><table border="1" cellpadding="5">
+<tr>
+<td colspan="1">Lane </td>
+<td colspan="1">Tile</td>
+<td colspan="1">Clusters (raw)</td>
+<td colspan="1">Av 1st Cycle Int (PF)</td>
+<td colspan="1">Av % intensity after 20 cycles (PF)</td>
+<td colspan="1">% PF Clusters </td>
+<td colspan="1">% Align (PF) </td>
+<td colspan="1">Av Alignment Score (PF)</td>
+<td colspan="1">% Error Rate (PF) </td>
+</tr>
+<tr>
+<td>3</td>
+<td>0001</td>
+<td>203758</td>
+<td>88.12</td>
+<td>74.13</td>
+<td>6.39</td>
+<td>1.00</td>
+<td>0.70</td>
+<td>6.55</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0002</td>
+<td>186222</td>
+<td>94.22</td>
+<td>65.83</td>
+<td>6.73</td>
+<td>1.07</td>
+<td>0.95</td>
+<td>4.76</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0003</td>
+<td>182117</td>
+<td>83.00</td>
+<td>100.81</td>
+<td>7.14</td>
+<td>1.32</td>
+<td>0.95</td>
+<td>6.20</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0004</td>
+<td>185998</td>
+<td>71.10</td>
+<td>107.91</td>
+<td>7.06</td>
+<td>1.19</td>
+<td>0.95</td>
+<td>5.98</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0005</td>
+<td>190532</td>
+<td>74.88</td>
+<td>104.04</td>
+<td>6.79</td>
+<td>1.00</td>
+<td>0.73</td>
+<td>6.18</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0006</td>
+<td>180425</td>
+<td>73.30</td>
+<td>104.33</td>
+<td>6.62</td>
+<td>0.90</td>
+<td>0.75</td>
+<td>5.91</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0007</td>
+<td>186680</td>
+<td>93.67</td>
+<td>81.10</td>
+<td>6.50</td>
+<td>1.04</td>
+<td>0.84</td>
+<td>5.98</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0008</td>
+<td>194001</td>
+<td>89.23</td>
+<td>61.39</td>
+<td>5.86</td>
+<td>1.04</td>
+<td>0.81</td>
+<td>6.44</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0009</td>
+<td>193360</td>
+<td>94.65</td>
+<td>85.16</td>
+<td>6.54</td>
+<td>0.99</td>
+<td>0.71</td>
+<td>6.31</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0010</td>
+<td>181288</td>
+<td>83.68</td>
+<td>78.67</td>
+<td>6.56</td>
+<td>1.07</td>
+<td>0.84</td>
+<td>6.66</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0011</td>
+<td>187873</td>
+<td>94.00</td>
+<td>82.45</td>
+<td>6.23</td>
+<td>1.01</td>
+<td>0.75</td>
+<td>5.91</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0012</td>
+<td>185405</td>
+<td>70.35</td>
+<td>80.56</td>
+<td>6.46</td>
+<td>1.09</td>
+<td>0.78</td>
+<td>6.35</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0013</td>
+<td>162638</td>
+<td>64.42</td>
+<td>80.87</td>
+<td>4.88</td>
+<td>1.06</td>
+<td>0.83</td>
+<td>6.76</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0014</td>
+<td>197166</td>
+<td>95.83</td>
+<td>60.68</td>
+<td>6.70</td>
+<td>1.05</td>
+<td>0.83</td>
+<td>6.14</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0015</td>
+<td>193488</td>
+<td>83.35</td>
+<td>69.86</td>
+<td>7.03</td>
+<td>1.07</td>
+<td>0.84</td>
+<td>6.26</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0016</td>
+<td>205537</td>
+<td>79.45</td>
+<td>116.65</td>
+<td>8.99</td>
+<td>1.08</td>
+<td>0.85</td>
+<td>6.17</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0017</td>
+<td>192552</td>
+<td>108.75</td>
+<td>55.95</td>
+<td>7.38</td>
+<td>0.89</td>
+<td>0.63</td>
+<td>7.00</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0018</td>
+<td>200383</td>
+<td>103.70</td>
+<td>59.23</td>
+<td>7.50</td>
+<td>1.00</td>
+<td>0.75</td>
+<td>5.93</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0019</td>
+<td>201926</td>
+<td>81.47</td>
+<td>66.40</td>
+<td>7.42</td>
+<td>0.85</td>
+<td>0.69</td>
+<td>5.70</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0020</td>
+<td>182571</td>
+<td>81.12</td>
+<td>70.39</td>
+<td>7.16</td>
+<td>1.13</td>
+<td>0.83</td>
+<td>6.48</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0021</td>
+<td>186698</td>
+<td>82.67</td>
+<td>80.04</td>
+<td>7.10</td>
+<td>1.00</td>
+<td>0.77</td>
+<td>5.77</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0022</td>
+<td>189532</td>
+<td>84.53</td>
+<td>80.95</td>
+<td>7.75</td>
+<td>1.00</td>
+<td>0.77</td>
+<td>6.23</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0023</td>
+<td>188323</td>
+<td>88.72</td>
+<td>73.12</td>
+<td>7.83</td>
+<td>1.15</td>
+<td>0.85</td>
+<td>6.81</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0024</td>
+<td>178383</td>
+<td>83.00</td>
+<td>119.94</td>
+<td>6.88</td>
+<td>0.97</td>
+<td>0.74</td>
+<td>5.47</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0025</td>
+<td>192049</td>
+<td>97.12</td>
+<td>73.72</td>
+<td>8.40</td>
+<td>0.97</td>
+<td>0.77</td>
+<td>5.42</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0026</td>
+<td>197028</td>
+<td>98.62</td>
+<td>92.19</td>
+<td>7.99</td>
+<td>0.85</td>
+<td>0.60</td>
+<td>6.68</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0027</td>
+<td>190899</td>
+<td>92.38</td>
+<td>69.01</td>
+<td>8.77</td>
+<td>1.18</td>
+<td>0.96</td>
+<td>5.56</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0028</td>
+<td>189399</td>
+<td>89.20</td>
+<td>96.30</td>
+<td>8.04</td>
+<td>1.05</td>
+<td>0.73</td>
+<td>6.62</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0029</td>
+<td>184662</td>
+<td>78.98</td>
+<td>108.67</td>
+<td>8.07</td>
+<td>1.08</td>
+<td>0.82</td>
+<td>5.86</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0030</td>
+<td>190688</td>
+<td>92.43</td>
+<td>66.08</td>
+<td>7.91</td>
+<td>0.95</td>
+<td>0.71</td>
+<td>6.76</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0031</td>
+<td>196290</td>
+<td>97.80</td>
+<td>110.61</td>
+<td>9.39</td>
+<td>1.04</td>
+<td>0.82</td>
+<td>5.42</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0032</td>
+<td>196748</td>
+<td>103.58</td>
+<td>108.28</td>
+<td>9.20</td>
+<td>0.99</td>
+<td>0.79</td>
+<td>5.86</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0033</td>
+<td>201464</td>
+<td>133.70</td>
+<td>54.15</td>
+<td>10.83</td>
+<td>1.09</td>
+<td>0.76</td>
+<td>7.01</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0034</td>
+<td>201778</td>
+<td>98.12</td>
+<td>115.41</td>
+<td>12.76</td>
+<td>1.09</td>
+<td>0.81</td>
+<td>6.42</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0035</td>
+<td>195338</td>
+<td>101.52</td>
+<td>65.77</td>
+<td>11.60</td>
+<td>1.16</td>
+<td>0.85</td>
+<td>6.61</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0036</td>
+<td>205839</td>
+<td>137.80</td>
+<td>74.55</td>
+<td>11.89</td>
+<td>1.14</td>
+<td>0.82</td>
+<td>6.16</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0037</td>
+<td>195386</td>
+<td>94.28</td>
+<td>112.54</td>
+<td>11.24</td>
+<td>1.03</td>
+<td>0.80</td>
+<td>6.15</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0038</td>
+<td>192726</td>
+<td>89.83</td>
+<td>67.16</td>
+<td>11.22</td>
+<td>1.02</td>
+<td>0.75</td>
+<td>6.73</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0039</td>
+<td>192642</td>
+<td>88.30</td>
+<td>126.64</td>
+<td>11.39</td>
+<td>1.13</td>
+<td>0.90</td>
+<td>6.10</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0040</td>
+<td>203095</td>
+<td>123.53</td>
+<td>85.10</td>
+<td>13.23</td>
+<td>1.00</td>
+<td>0.78</td>
+<td>6.57</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0041</td>
+<td>201324</td>
+<td>126.23</td>
+<td>77.20</td>
+<td>13.61</td>
+<td>1.23</td>
+<td>0.96</td>
+<td>6.23</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0042</td>
+<td>194426</td>
+<td>102.30</td>
+<td>75.07</td>
+<td>11.98</td>
+<td>0.91</td>
+<td>0.67</td>
+<td>6.30</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0043</td>
+<td>197512</td>
+<td>104.62</td>
+<td>72.88</td>
+<td>12.63</td>
+<td>1.15</td>
+<td>0.82</td>
+<td>6.74</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0044</td>
+<td>196006</td>
+<td>126.12</td>
+<td>61.63</td>
+<td>12.48</td>
+<td>1.15</td>
+<td>0.89</td>
+<td>6.33</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0045</td>
+<td>197592</td>
+<td>129.05</td>
+<td>71.48</td>
+<td>14.25</td>
+<td>1.30</td>
+<td>1.00</td>
+<td>6.77</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0046</td>
+<td>195797</td>
+<td>118.45</td>
+<td>88.64</td>
+<td>14.49</td>
+<td>1.14</td>
+<td>0.84</td>
+<td>6.48</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0047</td>
+<td>194741</td>
+<td>128.00</td>
+<td>78.38</td>
+<td>12.62</td>
+<td>1.19</td>
+<td>0.87</td>
+<td>6.64</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0048</td>
+<td>197436</td>
+<td>124.15</td>
+<td>79.68</td>
+<td>14.71</td>
+<td>1.20</td>
+<td>0.84</td>
+<td>6.80</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0049</td>
+<td>196566</td>
+<td>120.55</td>
+<td>84.55</td>
+<td>15.84</td>
+<td>1.24</td>
+<td>0.91</td>
+<td>6.51</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0050</td>
+<td>196255</td>
+<td>107.57</td>
+<td>89.36</td>
+<td>15.93</td>
+<td>1.15</td>
+<td>0.93</td>
+<td>5.66</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0051</td>
+<td>198773</td>
+<td>129.72</td>
+<td>95.86</td>
+<td>15.54</td>
+<td>1.19</td>
+<td>0.86</td>
+<td>6.44</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0052</td>
+<td>197968</td>
+<td>134.00</td>
+<td>79.46</td>
+<td>13.94</td>
+<td>1.07</td>
+<td>0.78</td>
+<td>6.47</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0053</td>
+<td>198600</td>
+<td>134.55</td>
+<td>57.32</td>
+<td>13.10</td>
+<td>1.16</td>
+<td>0.89</td>
+<td>6.35</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0054</td>
+<td>197173</td>
+<td>113.25</td>
+<td>83.20</td>
+<td>14.05</td>
+<td>1.14</td>
+<td>0.82</td>
+<td>6.53</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0055</td>
+<td>195506</td>
+<td>104.75</td>
+<td>79.33</td>
+<td>11.14</td>
+<td>1.06</td>
+<td>0.76</td>
+<td>6.73</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0056</td>
+<td>193200</td>
+<td>105.30</td>
+<td>88.82</td>
+<td>11.17</td>
+<td>1.08</td>
+<td>0.78</td>
+<td>6.80</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0057</td>
+<td>194216</td>
+<td>121.02</td>
+<td>73.06</td>
+<td>10.95</td>
+<td>1.09</td>
+<td>0.77</td>
+<td>6.45</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0058</td>
+<td>199530</td>
+<td>139.80</td>
+<td>67.01</td>
+<td>12.72</td>
+<td>1.19</td>
+<td>0.90</td>
+<td>6.09</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0059</td>
+<td>202163</td>
+<td>127.15</td>
+<td>68.93</td>
+<td>12.50</td>
+<td>1.08</td>
+<td>0.83</td>
+<td>5.87</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0060</td>
+<td>200721</td>
+<td>130.50</td>
+<td>53.98</td>
+<td>11.62</td>
+<td>1.11</td>
+<td>0.88</td>
+<td>6.27</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0061</td>
+<td>198041</td>
+<td>106.85</td>
+<td>65.79</td>
+<td>12.35</td>
+<td>1.07</td>
+<td>0.79</td>
+<td>6.75</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0062</td>
+<td>197475</td>
+<td>105.67</td>
+<td>74.92</td>
+<td>10.84</td>
+<td>1.13</td>
+<td>0.80</td>
+<td>7.21</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0063</td>
+<td>196647</td>
+<td>102.38</td>
+<td>88.62</td>
+<td>11.75</td>
+<td>1.03</td>
+<td>0.72</td>
+<td>6.89</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0064</td>
+<td>198472</td>
+<td>128.30</td>
+<td>83.94</td>
+<td>13.02</td>
+<td>1.13</td>
+<td>0.80</td>
+<td>7.11</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0065</td>
+<td>207784</td>
+<td>112.43</td>
+<td>91.97</td>
+<td>11.58</td>
+<td>0.96</td>
+<td>0.74</td>
+<td>6.31</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0066</td>
+<td>192575</td>
+<td>92.03</td>
+<td>105.92</td>
+<td>9.69</td>
+<td>1.13</td>
+<td>0.86</td>
+<td>6.44</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0067</td>
+<td>195245</td>
+<td>103.50</td>
+<td>91.38</td>
+<td>10.02</td>
+<td>1.08</td>
+<td>0.86</td>
+<td>6.17</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0068</td>
+<td>198723</td>
+<td>106.42</td>
+<td>81.54</td>
+<td>8.83</td>
+<td>1.25</td>
+<td>0.90</td>
+<td>7.09</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0069</td>
+<td>198214</td>
+<td>118.15</td>
+<td>58.99</td>
+<td>8.65</td>
+<td>0.97</td>
+<td>0.74</td>
+<td>5.99</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0070</td>
+<td>190770</td>
+<td>86.25</td>
+<td>73.45</td>
+<td>7.75</td>
+<td>0.91</td>
+<td>0.75</td>
+<td>5.41</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0071</td>
+<td>196479</td>
+<td>105.03</td>
+<td>52.37</td>
+<td>8.18</td>
+<td>1.13</td>
+<td>0.86</td>
+<td>5.94</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0072</td>
+<td>200551</td>
+<td>113.72</td>
+<td>54.80</td>
+<td>8.51</td>
+<td>1.01</td>
+<td>0.73</td>
+<td>6.82</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0073</td>
+<td>211144</td>
+<td>140.78</td>
+<td>46.47</td>
+<td>10.17</td>
+<td>0.98</td>
+<td>0.75</td>
+<td>6.30</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0074</td>
+<td>213445</td>
+<td>141.62</td>
+<td>47.27</td>
+<td>9.63</td>
+<td>1.10</td>
+<td>0.85</td>
+<td>6.29</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0075</td>
+<td>212336</td>
+<td>138.68</td>
+<td>43.63</td>
+<td>8.82</td>
+<td>0.98</td>
+<td>0.77</td>
+<td>6.20</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0076</td>
+<td>201544</td>
+<td>114.78</td>
+<td>55.11</td>
+<td>8.91</td>
+<td>0.92</td>
+<td>0.70</td>
+<td>5.75</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0077</td>
+<td>193099</td>
+<td>93.45</td>
+<td>130.82</td>
+<td>9.94</td>
+<td>1.03</td>
+<td>0.82</td>
+<td>5.86</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0078</td>
+<td>192730</td>
+<td>113.38</td>
+<td>57.53</td>
+<td>7.04</td>
+<td>0.83</td>
+<td>0.72</td>
+<td>5.07</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0079</td>
+<td>208296</td>
+<td>146.50</td>
+<td>83.21</td>
+<td>9.01</td>
+<td>1.06</td>
+<td>0.75</td>
+<td>7.18</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0080</td>
+<td>198441</td>
+<td>126.60</td>
+<td>46.54</td>
+<td>7.66</td>
+<td>1.07</td>
+<td>0.78</td>
+<td>6.69</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0081</td>
+<td>198612</td>
+<td>125.23</td>
+<td>101.20</td>
+<td>8.25</td>
+<td>1.07</td>
+<td>0.73</td>
+<td>6.21</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0082</td>
+<td>211383</td>
+<td>135.93</td>
+<td>69.38</td>
+<td>9.92</td>
+<td>1.09</td>
+<td>0.82</td>
+<td>5.97</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0083</td>
+<td>205392</td>
+<td>120.15</td>
+<td>49.52</td>
+<td>8.89</td>
+<td>1.06</td>
+<td>0.78</td>
+<td>7.11</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0084</td>
+<td>209166</td>
+<td>132.12</td>
+<td>48.00</td>
+<td>7.41</td>
+<td>1.02</td>
+<td>0.79</td>
+<td>5.75</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0085</td>
+<td>199610</td>
+<td>106.67</td>
+<td>107.97</td>
+<td>7.22</td>
+<td>1.03</td>
+<td>0.84</td>
+<td>5.81</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0086</td>
+<td>200884</td>
+<td>88.35</td>
+<td>63.95</td>
+<td>6.51</td>
+<td>1.22</td>
+<td>1.00</td>
+<td>5.74</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0087</td>
+<td>202820</td>
+<td>122.62</td>
+<td>88.34</td>
+<td>7.90</td>
+<td>1.07</td>
+<td>0.84</td>
+<td>6.27</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0088</td>
+<td>212070</td>
+<td>106.88</td>
+<td>72.70</td>
+<td>7.90</td>
+<td>1.09</td>
+<td>0.81</td>
+<td>5.97</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0089</td>
+<td>198992</td>
+<td>113.72</td>
+<td>78.19</td>
+<td>7.01</td>
+<td>1.03</td>
+<td>0.78</td>
+<td>5.93</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0090</td>
+<td>210462</td>
+<td>100.10</td>
+<td>123.98</td>
+<td>9.42</td>
+<td>1.05</td>
+<td>0.80</td>
+<td>5.99</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0091</td>
+<td>198131</td>
+<td>85.18</td>
+<td>78.02</td>
+<td>7.49</td>
+<td>1.02</td>
+<td>0.75</td>
+<td>6.21</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0092</td>
+<td>195888</td>
+<td>99.88</td>
+<td>76.57</td>
+<td>7.10</td>
+<td>1.08</td>
+<td>0.82</td>
+<td>6.54</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0093</td>
+<td>204882</td>
+<td>113.30</td>
+<td>58.27</td>
+<td>8.77</td>
+<td>1.13</td>
+<td>0.83</td>
+<td>6.31</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0094</td>
+<td>194269</td>
+<td>103.75</td>
+<td>66.31</td>
+<td>7.25</td>
+<td>1.00</td>
+<td>0.79</td>
+<td>6.23</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0095</td>
+<td>210191</td>
+<td>120.05</td>
+<td>55.10</td>
+<td>8.58</td>
+<td>1.10</td>
+<td>0.83</td>
+<td>6.06</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0096</td>
+<td>199862</td>
+<td>111.05</td>
+<td>59.70</td>
+<td>5.99</td>
+<td>1.08</td>
+<td>0.76</td>
+<td>6.31</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0097</td>
+<td>190392</td>
+<td>95.53</td>
+<td>69.33</td>
+<td>8.08</td>
+<td>1.37</td>
+<td>1.04</td>
+<td>6.40</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0098</td>
+<td>201697</td>
+<td>138.38</td>
+<td>74.92</td>
+<td>6.12</td>
+<td>1.02</td>
+<td>0.78</td>
+<td>6.28</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0099</td>
+<td>190314</td>
+<td>90.58</td>
+<td>92.58</td>
+<td>7.91</td>
+<td>1.21</td>
+<td>0.84</td>
+<td>6.13</td>
+</tr>
+<tr>
+<td>3</td>
+<td>0100</td>
+<td>211178</td>
+<td>123.43</td>
+<td>69.78</td>
+<td>8.24</td>
+<td>0.98</td>
+<td>0.78</td>
+<td>5.90</td>
+</tr>
+</table>
+<a name="Lane"><h2>Lane 4</h2></a><table border="1" cellpadding="5">
+<tr>
+<td colspan="1">Lane </td>
+<td colspan="1">Tile</td>
+<td colspan="1">Clusters (raw)</td>
+<td colspan="1">Av 1st Cycle Int (PF)</td>
+<td colspan="1">Av % intensity after 20 cycles (PF)</td>
+<td colspan="1">% PF Clusters </td>
+<td colspan="1">% Align (PF) </td>
+<td colspan="1">Av Alignment Score (PF)</td>
+<td colspan="1">% Error Rate (PF) </td>
+</tr>
+<tr>
+<td>4</td>
+<td>0001</td>
+<td>159972</td>
+<td>82.67</td>
+<td>89.45</td>
+<td>18.60</td>
+<td>4.24</td>
+<td>4.08</td>
+<td>3.55</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0002</td>
+<td>154345</td>
+<td>74.00</td>
+<td>102.60</td>
+<td>17.12</td>
+<td>4.43</td>
+<td>4.40</td>
+<td>3.29</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0003</td>
+<td>149038</td>
+<td>68.75</td>
+<td>107.49</td>
+<td>16.52</td>
+<td>4.70</td>
+<td>4.77</td>
+<td>3.29</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0004</td>
+<td>154208</td>
+<td>67.67</td>
+<td>96.27</td>
+<td>17.36</td>
+<td>4.16</td>
+<td>4.05</td>
+<td>3.58</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0005</td>
+<td>159793</td>
+<td>61.13</td>
+<td>119.88</td>
+<td>14.93</td>
+<td>4.15</td>
+<td>4.16</td>
+<td>3.49</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0006</td>
+<td>160185</td>
+<td>51.15</td>
+<td>122.09</td>
+<td>13.63</td>
+<td>4.29</td>
+<td>4.37</td>
+<td>3.45</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0007</td>
+<td>149174</td>
+<td>56.97</td>
+<td>168.06</td>
+<td>15.41</td>
+<td>4.60</td>
+<td>4.77</td>
+<td>3.43</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0008</td>
+<td>137721</td>
+<td>70.80</td>
+<td>142.13</td>
+<td>16.91</td>
+<td>5.38</td>
+<td>5.39</td>
+<td>3.64</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0009</td>
+<td>141360</td>
+<td>64.75</td>
+<td>144.09</td>
+<td>19.12</td>
+<td>4.63</td>
+<td>4.76</td>
+<td>3.44</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0010</td>
+<td>138912</td>
+<td>58.08</td>
+<td>103.87</td>
+<td>13.67</td>
+<td>5.66</td>
+<td>5.60</td>
+<td>3.77</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0011</td>
+<td>156192</td>
+<td>71.65</td>
+<td>94.73</td>
+<td>15.98</td>
+<td>4.57</td>
+<td>4.48</td>
+<td>3.66</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0012</td>
+<td>151485</td>
+<td>58.58</td>
+<td>98.89</td>
+<td>15.45</td>
+<td>5.08</td>
+<td>4.92</td>
+<td>3.94</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0013</td>
+<td>148542</td>
+<td>78.00</td>
+<td>73.14</td>
+<td>16.06</td>
+<td>4.65</td>
+<td>4.70</td>
+<td>3.34</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0014</td>
+<td>147284</td>
+<td>65.98</td>
+<td>109.06</td>
+<td>18.57</td>
+<td>4.86</td>
+<td>4.80</td>
+<td>3.68</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0015</td>
+<td>152295</td>
+<td>73.32</td>
+<td>88.71</td>
+<td>19.26</td>
+<td>4.26</td>
+<td>4.31</td>
+<td>3.42</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0016</td>
+<td>150073</td>
+<td>60.65</td>
+<td>137.26</td>
+<td>17.33</td>
+<td>5.14</td>
+<td>5.14</td>
+<td>3.69</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0017</td>
+<td>145657</td>
+<td>76.27</td>
+<td>134.74</td>
+<td>18.67</td>
+<td>4.72</td>
+<td>4.60</td>
+<td>3.82</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0018</td>
+<td>152274</td>
+<td>67.92</td>
+<td>146.23</td>
+<td>20.68</td>
+<td>4.64</td>
+<td>4.71</td>
+<td>3.51</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0019</td>
+<td>161998</td>
+<td>86.28</td>
+<td>61.95</td>
+<td>21.88</td>
+<td>4.27</td>
+<td>4.09</td>
+<td>3.95</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0020</td>
+<td>139178</td>
+<td>68.33</td>
+<td>94.80</td>
+<td>17.10</td>
+<td>5.07</td>
+<td>5.08</td>
+<td>3.43</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0021</td>
+<td>144251</td>
+<td>69.20</td>
+<td>119.80</td>
+<td>19.67</td>
+<td>5.20</td>
+<td>5.15</td>
+<td>3.57</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0022</td>
+<td>154202</td>
+<td>64.55</td>
+<td>150.50</td>
+<td>18.84</td>
+<td>4.82</td>
+<td>4.97</td>
+<td>3.12</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0023</td>
+<td>154802</td>
+<td>70.13</td>
+<td>120.71</td>
+<td>21.31</td>
+<td>4.39</td>
+<td>4.49</td>
+<td>3.33</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0024</td>
+<td>155617</td>
+<td>78.33</td>
+<td>108.97</td>
+<td>16.72</td>
+<td>4.88</td>
+<td>4.83</td>
+<td>3.42</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0025</td>
+<td>146497</td>
+<td>57.23</td>
+<td>141.68</td>
+<td>19.74</td>
+<td>4.81</td>
+<td>4.88</td>
+<td>3.47</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0026</td>
+<td>143544</td>
+<td>72.08</td>
+<td>87.51</td>
+<td>18.88</td>
+<td>5.03</td>
+<td>5.01</td>
+<td>3.55</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0027</td>
+<td>164614</td>
+<td>67.30</td>
+<td>85.10</td>
+<td>19.32</td>
+<td>4.44</td>
+<td>4.49</td>
+<td>3.56</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0028</td>
+<td>152714</td>
+<td>73.12</td>
+<td>81.91</td>
+<td>18.15</td>
+<td>4.84</td>
+<td>4.91</td>
+<td>3.49</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0029</td>
+<td>151961</td>
+<td>70.85</td>
+<td>83.42</td>
+<td>20.60</td>
+<td>4.43</td>
+<td>4.46</td>
+<td>3.48</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0030</td>
+<td>149751</td>
+<td>76.05</td>
+<td>93.29</td>
+<td>20.81</td>
+<td>5.11</td>
+<td>5.01</td>
+<td>3.74</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0031</td>
+<td>162295</td>
+<td>96.45</td>
+<td>96.73</td>
+<td>25.74</td>
+<td>4.26</td>
+<td>3.96</td>
+<td>4.04</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0032</td>
+<td>162442</td>
+<td>64.57</td>
+<td>171.35</td>
+<td>25.31</td>
+<td>4.25</td>
+<td>4.14</td>
+<td>3.93</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0033</td>
+<td>156596</td>
+<td>88.92</td>
+<td>72.25</td>
+<td>22.77</td>
+<td>4.43</td>
+<td>4.30</td>
+<td>3.92</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0034</td>
+<td>148376</td>
+<td>59.43</td>
+<td>174.63</td>
+<td>19.78</td>
+<td>5.25</td>
+<td>5.32</td>
+<td>3.63</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0035</td>
+<td>144206</td>
+<td>75.67</td>
+<td>79.35</td>
+<td>27.19</td>
+<td>4.80</td>
+<td>4.76</td>
+<td>3.76</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0036</td>
+<td>138987</td>
+<td>55.15</td>
+<td>153.94</td>
+<td>18.63</td>
+<td>4.79</td>
+<td>4.69</td>
+<td>3.85</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0037</td>
+<td>135083</td>
+<td>66.10</td>
+<td>85.29</td>
+<td>21.96</td>
+<td>4.85</td>
+<td>4.63</td>
+<td>4.11</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0038</td>
+<td>153934</td>
+<td>60.40</td>
+<td>106.91</td>
+<td>22.09</td>
+<td>4.95</td>
+<td>5.01</td>
+<td>3.59</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0039</td>
+<td>148137</td>
+<td>66.30</td>
+<td>138.76</td>
+<td>24.02</td>
+<td>4.56</td>
+<td>4.52</td>
+<td>3.76</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0040</td>
+<td>158443</td>
+<td>84.60</td>
+<td>141.02</td>
+<td>28.12</td>
+<td>4.55</td>
+<td>4.53</td>
+<td>3.62</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0041</td>
+<td>153719</td>
+<td>87.78</td>
+<td>70.38</td>
+<td>26.95</td>
+<td>4.69</td>
+<td>4.54</td>
+<td>3.91</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0042</td>
+<td>150869</td>
+<td>73.07</td>
+<td>87.65</td>
+<td>21.31</td>
+<td>4.70</td>
+<td>4.67</td>
+<td>3.77</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0043</td>
+<td>156425</td>
+<td>70.20</td>
+<td>107.34</td>
+<td>23.59</td>
+<td>4.24</td>
+<td>4.31</td>
+<td>3.60</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0044</td>
+<td>156878</td>
+<td>77.22</td>
+<td>99.48</td>
+<td>24.99</td>
+<td>4.66</td>
+<td>4.64</td>
+<td>3.74</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0045</td>
+<td>155133</td>
+<td>76.70</td>
+<td>106.91</td>
+<td>29.12</td>
+<td>4.56</td>
+<td>4.46</td>
+<td>3.80</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0046</td>
+<td>152250</td>
+<td>80.03</td>
+<td>101.72</td>
+<td>26.17</td>
+<td>4.61</td>
+<td>4.47</td>
+<td>3.94</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0047</td>
+<td>149874</td>
+<td>71.47</td>
+<td>99.97</td>
+<td>19.18</td>
+<td>4.98</td>
+<td>5.06</td>
+<td>3.68</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0048</td>
+<td>154015</td>
+<td>63.02</td>
+<td>121.98</td>
+<td>26.89</td>
+<td>4.36</td>
+<td>4.34</td>
+<td>3.75</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0049</td>
+<td>158335</td>
+<td>80.30</td>
+<td>119.77</td>
+<td>33.14</td>
+<td>4.10</td>
+<td>4.11</td>
+<td>3.47</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0050</td>
+<td>159798</td>
+<td>88.50</td>
+<td>116.13</td>
+<td>31.67</td>
+<td>4.27</td>
+<td>4.36</td>
+<td>3.16</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0051</td>
+<td>160079</td>
+<td>90.17</td>
+<td>119.46</td>
+<td>36.32</td>
+<td>4.20</td>
+<td>4.07</td>
+<td>3.25</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0052</td>
+<td>155290</td>
+<td>81.90</td>
+<td>131.84</td>
+<td>29.52</td>
+<td>4.51</td>
+<td>4.53</td>
+<td>3.55</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0053</td>
+<td>158980</td>
+<td>80.47</td>
+<td>101.27</td>
+<td>30.61</td>
+<td>4.14</td>
+<td>4.07</td>
+<td>3.67</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0054</td>
+<td>158421</td>
+<td>78.83</td>
+<td>121.47</td>
+<td>29.54</td>
+<td>4.57</td>
+<td>4.52</td>
+<td>3.52</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0055</td>
+<td>157750</td>
+<td>67.22</td>
+<td>112.01</td>
+<td>26.51</td>
+<td>4.15</td>
+<td>4.25</td>
+<td>3.66</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0056</td>
+<td>157016</td>
+<td>86.95</td>
+<td>96.12</td>
+<td>26.14</td>
+<td>4.38</td>
+<td>4.27</td>
+<td>3.80</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0057</td>
+<td>151799</td>
+<td>84.08</td>
+<td>119.98</td>
+<td>27.60</td>
+<td>4.26</td>
+<td>4.15</td>
+<td>3.83</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0058</td>
+<td>154316</td>
+<td>72.90</td>
+<td>153.43</td>
+<td>25.86</td>
+<td>4.28</td>
+<td>4.20</td>
+<td>3.87</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0059</td>
+<td>157082</td>
+<td>89.60</td>
+<td>73.19</td>
+<td>27.08</td>
+<td>4.40</td>
+<td>4.37</td>
+<td>3.60</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0060</td>
+<td>153022</td>
+<td>59.10</td>
+<td>137.94</td>
+<td>26.12</td>
+<td>4.53</td>
+<td>4.69</td>
+<td>3.38</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0061</td>
+<td>151043</td>
+<td>71.25</td>
+<td>90.21</td>
+<td>23.26</td>
+<td>4.39</td>
+<td>4.53</td>
+<td>3.42</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0062</td>
+<td>159487</td>
+<td>88.62</td>
+<td>113.68</td>
+<td>22.09</td>
+<td>4.56</td>
+<td>4.32</td>
+<td>4.05</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0063</td>
+<td>163188</td>
+<td>100.53</td>
+<td>115.72</td>
+<td>26.63</td>
+<td>4.32</td>
+<td>4.36</td>
+<td>3.45</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0064</td>
+<td>136701</td>
+<td>63.62</td>
+<td>120.43</td>
+<td>33.63</td>
+<td>3.67</td>
+<td>3.69</td>
+<td>3.82</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0065</td>
+<td>136062</td>
+<td>70.90</td>
+<td>79.58</td>
+<td>23.05</td>
+<td>4.78</td>
+<td>4.63</td>
+<td>3.88</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0066</td>
+<td>148835</td>
+<td>57.35</td>
+<td>146.38</td>
+<td>27.34</td>
+<td>4.19</td>
+<td>4.41</td>
+<td>3.17</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0067</td>
+<td>152325</td>
+<td>78.15</td>
+<td>131.80</td>
+<td>23.84</td>
+<td>4.34</td>
+<td>4.30</td>
+<td>3.67</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0068</td>
+<td>155420</td>
+<td>98.58</td>
+<td>58.69</td>
+<td>24.76</td>
+<td>4.44</td>
+<td>4.38</td>
+<td>3.54</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0069</td>
+<td>148803</td>
+<td>83.63</td>
+<td>102.96</td>
+<td>25.18</td>
+<td>4.71</td>
+<td>4.80</td>
+<td>3.46</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0070</td>
+<td>146666</td>
+<td>75.55</td>
+<td>138.45</td>
+<td>27.43</td>
+<td>4.72</td>
+<td>4.72</td>
+<td>3.76</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0071</td>
+<td>161358</td>
+<td>69.85</td>
+<td>118.58</td>
+<td>19.73</td>
+<td>4.10</td>
+<td>4.14</td>
+<td>3.60</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0072</td>
+<td>163756</td>
+<td>80.70</td>
+<td>93.80</td>
+<td>22.76</td>
+<td>4.30</td>
+<td>4.43</td>
+<td>3.27</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0073</td>
+<td>169752</td>
+<td>100.75</td>
+<td>97.79</td>
+<td>27.23</td>
+<td>4.44</td>
+<td>4.31</td>
+<td>3.45</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0074</td>
+<td>153606</td>
+<td>105.57</td>
+<td>90.48</td>
+<td>27.16</td>
+<td>4.76</td>
+<td>4.67</td>
+<td>3.36</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0075</td>
+<td>155840</td>
+<td>99.67</td>
+<td>65.34</td>
+<td>22.83</td>
+<td>4.75</td>
+<td>4.59</td>
+<td>3.70</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0076</td>
+<td>160580</td>
+<td>77.55</td>
+<td>129.98</td>
+<td>26.62</td>
+<td>4.53</td>
+<td>4.57</td>
+<td>3.35</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0077</td>
+<td>133543</td>
+<td>72.98</td>
+<td>149.54</td>
+<td>19.36</td>
+<td>5.16</td>
+<td>5.43</td>
+<td>2.96</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0078</td>
+<td>163879</td>
+<td>66.47</td>
+<td>87.06</td>
+<td>28.07</td>
+<td>4.03</td>
+<td>4.05</td>
+<td>3.61</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0079</td>
+<td>160311</td>
+<td>100.62</td>
+<td>76.52</td>
+<td>24.02</td>
+<td>4.63</td>
+<td>4.45</td>
+<td>3.70</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0080</td>
+<td>147552</td>
+<td>99.77</td>
+<td>122.15</td>
+<td>26.94</td>
+<td>4.32</td>
+<td>4.40</td>
+<td>3.18</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0081</td>
+<td>175728</td>
+<td>67.38</td>
+<td>147.01</td>
+<td>20.25</td>
+<td>3.89</td>
+<td>4.07</td>
+<td>3.24</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0082</td>
+<td>159621</td>
+<td>100.60</td>
+<td>73.86</td>
+<td>24.94</td>
+<td>4.54</td>
+<td>4.54</td>
+<td>3.50</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0083</td>
+<td>151723</td>
+<td>83.72</td>
+<td>97.13</td>
+<td>20.83</td>
+<td>4.60</td>
+<td>4.67</td>
+<td>3.38</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0084</td>
+<td>149291</td>
+<td>75.22</td>
+<td>114.46</td>
+<td>22.02</td>
+<td>4.83</td>
+<td>4.87</td>
+<td>3.48</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0085</td>
+<td>158640</td>
+<td>86.82</td>
+<td>86.55</td>
+<td>19.36</td>
+<td>4.26</td>
+<td>4.10</td>
+<td>3.60</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0086</td>
+<td>149953</td>
+<td>73.95</td>
+<td>111.43</td>
+<td>19.77</td>
+<td>5.06</td>
+<td>5.07</td>
+<td>3.66</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0087</td>
+<td>159982</td>
+<td>99.40</td>
+<td>103.14</td>
+<td>16.81</td>
+<td>4.60</td>
+<td>4.52</td>
+<td>3.48</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0088</td>
+<td>184055</td>
+<td>58.55</td>
+<td>114.90</td>
+<td>24.89</td>
+<td>3.46</td>
+<td>3.52</td>
+<td>3.55</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0089</td>
+<td>159972</td>
+<td>73.67</td>
+<td>104.24</td>
+<td>17.24</td>
+<td>4.25</td>
+<td>4.08</td>
+<td>3.85</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0090</td>
+<td>164917</td>
+<td>101.95</td>
+<td>109.44</td>
+<td>18.70</td>
+<td>4.84</td>
+<td>4.69</td>
+<td>3.52</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0091</td>
+<td>152453</td>
+<td>71.45</td>
+<td>112.07</td>
+<td>18.89</td>
+<td>4.41</td>
+<td>4.37</td>
+<td>3.50</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0092</td>
+<td>155219</td>
+<td>80.20</td>
+<td>93.52</td>
+<td>17.64</td>
+<td>4.49</td>
+<td>4.48</td>
+<td>3.60</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0093</td>
+<td>163900</td>
+<td>87.65</td>
+<td>70.68</td>
+<td>16.48</td>
+<td>4.30</td>
+<td>4.13</td>
+<td>3.60</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0094</td>
+<td>145355</td>
+<td>82.10</td>
+<td>82.86</td>
+<td>15.25</td>
+<td>4.45</td>
+<td>4.34</td>
+<td>3.59</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0095</td>
+<td>138112</td>
+<td>83.02</td>
+<td>123.49</td>
+<td>17.94</td>
+<td>4.70</td>
+<td>4.65</td>
+<td>3.55</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0096</td>
+<td>154654</td>
+<td>57.23</td>
+<td>169.77</td>
+<td>13.99</td>
+<td>4.48</td>
+<td>4.59</td>
+<td>3.47</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0097</td>
+<td>167709</td>
+<td>80.40</td>
+<td>87.13</td>
+<td>18.50</td>
+<td>4.09</td>
+<td>3.98</td>
+<td>3.77</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0098</td>
+<td>160175</td>
+<td>69.58</td>
+<td>131.30</td>
+<td>20.00</td>
+<td>3.95</td>
+<td>3.89</td>
+<td>3.57</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0099</td>
+<td>165398</td>
+<td>73.87</td>
+<td>121.93</td>
+<td>22.00</td>
+<td>3.91</td>
+<td>3.97</td>
+<td>3.17</td>
+</tr>
+<tr>
+<td>4</td>
+<td>0100</td>
+<td>156998</td>
+<td>78.80</td>
+<td>133.66</td>
+<td>23.09</td>
+<td>4.11</td>
+<td>4.14</td>
+<td>3.33</td>
+</tr>
+</table>
+<a name="Lane"><h2>Lane 5</h2></a><table border="1" cellpadding="5">
+<tr>
+<td colspan="1">Lane </td>
+<td colspan="1">Tile</td>
+<td colspan="1">Clusters (raw)</td>
+<td colspan="1">Av 1st Cycle Int (PF)</td>
+<td colspan="1">Av % intensity after 20 cycles (PF)</td>
+<td colspan="1">% PF Clusters </td>
+<td colspan="1">% Align (PF) </td>
+<td colspan="1">Av Alignment Score (PF)</td>
+<td colspan="1">% Error Rate (PF) </td>
+</tr>
+<tr>
+<td>5</td>
+<td>0001</td>
+<td>139658</td>
+<td>72.90</td>
+<td>128.74</td>
+<td>21.85</td>
+<td>3.75</td>
+<td>3.67</td>
+<td>3.28</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0002</td>
+<td>140964</td>
+<td>66.42</td>
+<td>101.92</td>
+<td>21.29</td>
+<td>3.73</td>
+<td>3.58</td>
+<td>3.77</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0003</td>
+<td>139085</td>
+<td>68.25</td>
+<td>102.42</td>
+<td>17.78</td>
+<td>4.29</td>
+<td>4.22</td>
+<td>3.33</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0004</td>
+<td>138352</td>
+<td>69.20</td>
+<td>110.30</td>
+<td>21.41</td>
+<td>3.83</td>
+<td>3.71</td>
+<td>3.75</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0005</td>
+<td>139444</td>
+<td>74.35</td>
+<td>106.86</td>
+<td>19.88</td>
+<td>4.11</td>
+<td>3.98</td>
+<td>3.56</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0006</td>
+<td>139621</td>
+<td>59.90</td>
+<td>104.13</td>
+<td>18.86</td>
+<td>3.67</td>
+<td>3.51</td>
+<td>3.93</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0007</td>
+<td>139048</td>
+<td>64.80</td>
+<td>103.74</td>
+<td>19.50</td>
+<td>3.72</td>
+<td>3.48</td>
+<td>3.69</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0008</td>
+<td>141160</td>
+<td>64.67</td>
+<td>98.76</td>
+<td>18.08</td>
+<td>3.59</td>
+<td>3.41</td>
+<td>3.78</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0009</td>
+<td>139245</td>
+<td>62.23</td>
+<td>94.09</td>
+<td>18.47</td>
+<td>3.89</td>
+<td>3.54</td>
+<td>3.97</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0010</td>
+<td>138211</td>
+<td>57.35</td>
+<td>94.42</td>
+<td>18.35</td>
+<td>3.83</td>
+<td>3.71</td>
+<td>3.57</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0011</td>
+<td>136975</td>
+<td>58.27</td>
+<td>93.22</td>
+<td>18.29</td>
+<td>4.04</td>
+<td>4.04</td>
+<td>3.34</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0012</td>
+<td>135992</td>
+<td>57.35</td>
+<td>86.09</td>
+<td>17.51</td>
+<td>4.11</td>
+<td>4.00</td>
+<td>3.85</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0013</td>
+<td>140930</td>
+<td>53.18</td>
+<td>140.48</td>
+<td>18.62</td>
+<td>3.97</td>
+<td>3.85</td>
+<td>3.73</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0014</td>
+<td>140313</td>
+<td>63.65</td>
+<td>93.05</td>
+<td>17.48</td>
+<td>3.75</td>
+<td>3.45</td>
+<td>3.85</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0015</td>
+<td>141815</td>
+<td>74.38</td>
+<td>81.45</td>
+<td>17.77</td>
+<td>3.92</td>
+<td>3.73</td>
+<td>3.70</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0016</td>
+<td>138206</td>
+<td>77.85</td>
+<td>109.34</td>
+<td>19.30</td>
+<td>3.82</td>
+<td>3.59</td>
+<td>3.65</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0017</td>
+<td>140661</td>
+<td>87.75</td>
+<td>105.41</td>
+<td>18.56</td>
+<td>4.57</td>
+<td>4.38</td>
+<td>3.42</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0018</td>
+<td>139133</td>
+<td>64.83</td>
+<td>77.82</td>
+<td>21.37</td>
+<td>4.05</td>
+<td>4.04</td>
+<td>3.54</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0019</td>
+<td>138919</td>
+<td>77.03</td>
+<td>122.36</td>
+<td>22.40</td>
+<td>4.04</td>
+<td>3.92</td>
+<td>3.62</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0020</td>
+<td>136707</td>
+<td>67.12</td>
+<td>98.32</td>
+<td>19.01</td>
+<td>4.60</td>
+<td>4.51</td>
+<td>3.41</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0021</td>
+<td>140284</td>
+<td>59.67</td>
+<td>110.98</td>
+<td>18.31</td>
+<td>4.13</td>
+<td>4.04</td>
+<td>3.82</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0022</td>
+<td>135887</td>
+<td>54.60</td>
+<td>97.71</td>
+<td>16.94</td>
+<td>3.72</td>
+<td>3.65</td>
+<td>3.73</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0023</td>
+<td>136065</td>
+<td>56.40</td>
+<td>125.98</td>
+<td>19.93</td>
+<td>4.12</td>
+<td>4.22</td>
+<td>3.49</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0024</td>
+<td>137793</td>
+<td>62.90</td>
+<td>131.28</td>
+<td>20.66</td>
+<td>4.12</td>
+<td>4.02</td>
+<td>3.70</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0025</td>
+<td>134349</td>
+<td>62.10</td>
+<td>115.34</td>
+<td>23.38</td>
+<td>4.07</td>
+<td>3.96</td>
+<td>3.62</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0026</td>
+<td>138802</td>
+<td>54.20</td>
+<td>131.55</td>
+<td>20.52</td>
+<td>4.05</td>
+<td>4.11</td>
+<td>3.44</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0027</td>
+<td>134191</td>
+<td>56.70</td>
+<td>167.20</td>
+<td>21.61</td>
+<td>4.64</td>
+<td>4.70</td>
+<td>3.55</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0028</td>
+<td>135596</td>
+<td>57.73</td>
+<td>151.93</td>
+<td>25.57</td>
+<td>4.54</td>
+<td>4.54</td>
+<td>3.61</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0029</td>
+<td>132493</td>
+<td>78.58</td>
+<td>126.44</td>
+<td>22.19</td>
+<td>5.03</td>
+<td>4.83</td>
+<td>3.77</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0030</td>
+<td>132570</td>
+<td>64.17</td>
+<td>157.65</td>
+<td>24.17</td>
+<td>4.67</td>
+<td>4.52</td>
+<td>4.17</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0031</td>
+<td>137864</td>
+<td>84.25</td>
+<td>111.90</td>
+<td>31.20</td>
+<td>4.31</td>
+<td>4.15</td>
+<td>3.87</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0032</td>
+<td>136471</td>
+<td>54.62</td>
+<td>150.30</td>
+<td>28.33</td>
+<td>4.67</td>
+<td>4.67</td>
+<td>3.68</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0033</td>
+<td>133065</td>
+<td>82.12</td>
+<td>107.40</td>
+<td>22.89</td>
+<td>4.72</td>
+<td>4.36</td>
+<td>4.29</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0034</td>
+<td>137713</td>
+<td>66.05</td>
+<td>126.57</td>
+<td>26.09</td>
+<td>4.33</td>
+<td>4.21</td>
+<td>3.91</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0035</td>
+<td>130155</td>
+<td>59.40</td>
+<td>83.16</td>
+<td>21.50</td>
+<td>4.69</td>
+<td>4.87</td>
+<td>3.50</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0036</td>
+<td>135783</td>
+<td>86.45</td>
+<td>90.66</td>
+<td>21.28</td>
+<td>4.70</td>
+<td>4.50</td>
+<td>3.77</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0037</td>
+<td>131641</td>
+<td>67.30</td>
+<td>80.79</td>
+<td>22.36</td>
+<td>4.29</td>
+<td>4.22</td>
+<td>3.68</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0038</td>
+<td>130728</td>
+<td>64.15</td>
+<td>98.99</td>
+<td>20.05</td>
+<td>5.02</td>
+<td>4.93</td>
+<td>3.91</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0039</td>
+<td>132090</td>
+<td>60.83</td>
+<td>99.75</td>
+<td>19.36</td>
+<td>4.95</td>
+<td>4.81</td>
+<td>4.01</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0040</td>
+<td>134211</td>
+<td>51.10</td>
+<td>136.94</td>
+<td>14.11</td>
+<td>4.84</td>
+<td>4.71</td>
+<td>4.03</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0041</td>
+<td>133102</td>
+<td>93.62</td>
+<td>66.57</td>
+<td>21.27</td>
+<td>4.60</td>
+<td>4.43</td>
+<td>3.70</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0042</td>
+<td>132925</td>
+<td>72.62</td>
+<td>104.23</td>
+<td>24.15</td>
+<td>4.12</td>
+<td>4.03</td>
+<td>3.89</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0043</td>
+<td>132602</td>
+<td>76.58</td>
+<td>129.02</td>
+<td>22.13</td>
+<td>4.71</td>
+<td>4.49</td>
+<td>3.87</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0044</td>
+<td>131413</td>
+<td>66.65</td>
+<td>108.93</td>
+<td>25.85</td>
+<td>4.47</td>
+<td>4.34</td>
+<td>3.85</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0045</td>
+<td>129925</td>
+<td>82.50</td>
+<td>115.24</td>
+<td>21.63</td>
+<td>4.65</td>
+<td>4.38</td>
+<td>4.19</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0046</td>
+<td>131687</td>
+<td>61.55</td>
+<td>108.57</td>
+<td>23.72</td>
+<td>4.24</td>
+<td>4.35</td>
+<td>3.43</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0047</td>
+<td>128928</td>
+<td>76.20</td>
+<td>134.02</td>
+<td>26.02</td>
+<td>4.51</td>
+<td>4.28</td>
+<td>3.99</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0048</td>
+<td>129338</td>
+<td>82.23</td>
+<td>98.24</td>
+<td>28.08</td>
+<td>4.53</td>
+<td>4.43</td>
+<td>3.53</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0049</td>
+<td>129280</td>
+<td>59.00</td>
+<td>149.62</td>
+<td>28.80</td>
+<td>4.32</td>
+<td>4.18</td>
+<td>3.96</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0050</td>
+<td>127491</td>
+<td>71.47</td>
+<td>121.55</td>
+<td>30.58</td>
+<td>4.26</td>
+<td>4.13</td>
+<td>3.81</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0051</td>
+<td>130493</td>
+<td>77.25</td>
+<td>106.02</td>
+<td>30.24</td>
+<td>4.35</td>
+<td>4.36</td>
+<td>3.34</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0052</td>
+<td>130852</td>
+<td>62.63</td>
+<td>157.52</td>
+<td>28.30</td>
+<td>4.45</td>
+<td>4.46</td>
+<td>3.44</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0053</td>
+<td>129993</td>
+<td>73.77</td>
+<td>114.50</td>
+<td>24.84</td>
+<td>4.84</td>
+<td>4.80</td>
+<td>3.68</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0054</td>
+<td>132071</td>
+<td>72.70</td>
+<td>113.55</td>
+<td>30.55</td>
+<td>4.38</td>
+<td>4.31</td>
+<td>3.65</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0055</td>
+<td>131652</td>
+<td>73.97</td>
+<td>109.70</td>
+<td>27.14</td>
+<td>4.72</td>
+<td>4.64</td>
+<td>3.69</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0056</td>
+<td>131590</td>
+<td>73.28</td>
+<td>113.99</td>
+<td>26.47</td>
+<td>4.24</td>
+<td>4.17</td>
+<td>3.62</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0057</td>
+<td>132278</td>
+<td>79.77</td>
+<td>91.10</td>
+<td>26.24</td>
+<td>4.49</td>
+<td>4.44</td>
+<td>3.56</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0058</td>
+<td>132006</td>
+<td>68.95</td>
+<td>114.68</td>
+<td>21.47</td>
+<td>4.62</td>
+<td>4.59</td>
+<td>3.93</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0059</td>
+<td>131096</td>
+<td>60.10</td>
+<td>146.55</td>
+<td>24.15</td>
+<td>4.32</td>
+<td>4.30</td>
+<td>3.87</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0060</td>
+<td>138359</td>
+<td>59.00</td>
+<td>169.92</td>
+<td>24.17</td>
+<td>4.29</td>
+<td>4.34</td>
+<td>3.45</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0061</td>
+<td>130472</td>
+<td>64.83</td>
+<td>124.84</td>
+<td>18.73</td>
+<td>4.32</td>
+<td>4.17</td>
+<td>3.86</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0062</td>
+<td>132110</td>
+<td>64.70</td>
+<td>105.41</td>
+<td>21.42</td>
+<td>4.45</td>
+<td>4.30</td>
+<td>4.10</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0063</td>
+<td>136926</td>
+<td>66.60</td>
+<td>115.24</td>
+<td>23.77</td>
+<td>4.11</td>
+<td>3.96</td>
+<td>3.84</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0064</td>
+<td>132948</td>
+<td>80.10</td>
+<td>102.68</td>
+<td>26.12</td>
+<td>3.64</td>
+<td>3.52</td>
+<td>3.83</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0065</td>
+<td>132630</td>
+<td>80.02</td>
+<td>90.82</td>
+<td>24.46</td>
+<td>4.50</td>
+<td>4.54</td>
+<td>3.53</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0066</td>
+<td>134827</td>
+<td>64.57</td>
+<td>103.60</td>
+<td>19.55</td>
+<td>5.25</td>
+<td>5.16</td>
+<td>3.94</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0067</td>
+<td>131930</td>
+<td>65.08</td>
+<td>105.92</td>
+<td>19.20</td>
+<td>4.87</td>
+<td>5.01</td>
+<td>3.46</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0068</td>
+<td>133975</td>
+<td>88.00</td>
+<td>88.32</td>
+<td>20.49</td>
+<td>4.40</td>
+<td>4.40</td>
+<td>3.39</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0069</td>
+<td>132322</td>
+<td>56.48</td>
+<td>119.65</td>
+<td>24.40</td>
+<td>4.37</td>
+<td>4.44</td>
+<td>3.43</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0070</td>
+<td>131826</td>
+<td>64.92</td>
+<td>142.36</td>
+<td>25.01</td>
+<td>4.18</td>
+<td>4.11</td>
+<td>3.85</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0071</td>
+<td>131859</td>
+<td>56.25</td>
+<td>163.07</td>
+<td>23.43</td>
+<td>4.71</td>
+<td>4.68</td>
+<td>3.57</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0072</td>
+<td>132713</td>
+<td>78.30</td>
+<td>124.27</td>
+<td>22.72</td>
+<td>5.16</td>
+<td>5.21</td>
+<td>3.22</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0073</td>
+<td>135125</td>
+<td>97.53</td>
+<td>98.77</td>
+<td>27.41</td>
+<td>4.58</td>
+<td>4.47</td>
+<td>3.51</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0074</td>
+<td>133319</td>
+<td>78.48</td>
+<td>169.32</td>
+<td>32.46</td>
+<td>4.22</td>
+<td>4.29</td>
+<td>3.25</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0075</td>
+<td>135778</td>
+<td>83.65</td>
+<td>98.83</td>
+<td>28.14</td>
+<td>4.50</td>
+<td>4.66</td>
+<td>2.93</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0076</td>
+<td>133109</td>
+<td>55.02</td>
+<td>125.85</td>
+<td>30.99</td>
+<td>3.68</td>
+<td>3.61</td>
+<td>3.76</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0077</td>
+<td>131627</td>
+<td>65.42</td>
+<td>140.73</td>
+<td>23.91</td>
+<td>4.56</td>
+<td>4.73</td>
+<td>2.95</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0078</td>
+<td>140665</td>
+<td>94.43</td>
+<td>81.73</td>
+<td>24.25</td>
+<td>3.91</td>
+<td>3.93</td>
+<td>3.36</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0079</td>
+<td>132710</td>
+<td>84.33</td>
+<td>83.90</td>
+<td>23.84</td>
+<td>4.03</td>
+<td>3.90</td>
+<td>3.52</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0080</td>
+<td>134728</td>
+<td>80.50</td>
+<td>145.93</td>
+<td>30.49</td>
+<td>4.08</td>
+<td>4.04</td>
+<td>3.45</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0081</td>
+<td>143692</td>
+<td>74.45</td>
+<td>113.36</td>
+<td>27.33</td>
+<td>4.02</td>
+<td>4.06</td>
+<td>3.57</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0082</td>
+<td>136092</td>
+<td>82.20</td>
+<td>149.82</td>
+<td>28.86</td>
+<td>4.35</td>
+<td>4.30</td>
+<td>3.50</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0083</td>
+<td>135430</td>
+<td>78.88</td>
+<td>141.58</td>
+<td>26.77</td>
+<td>4.83</td>
+<td>4.79</td>
+<td>3.37</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0084</td>
+<td>138000</td>
+<td>70.45</td>
+<td>145.56</td>
+<td>24.05</td>
+<td>4.52</td>
+<td>4.46</td>
+<td>3.36</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0085</td>
+<td>137474</td>
+<td>61.35</td>
+<td>168.99</td>
+<td>26.20</td>
+<td>3.80</td>
+<td>3.83</td>
+<td>3.54</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0086</td>
+<td>133607</td>
+<td>65.47</td>
+<td>99.58</td>
+<td>22.23</td>
+<td>3.73</td>
+<td>3.65</td>
+<td>3.71</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0087</td>
+<td>135370</td>
+<td>70.42</td>
+<td>147.64</td>
+<td>20.61</td>
+<td>4.04</td>
+<td>3.99</td>
+<td>3.39</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0088</td>
+<td>136082</td>
+<td>77.02</td>
+<td>91.07</td>
+<td>21.90</td>
+<td>3.62</td>
+<td>3.63</td>
+<td>3.55</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0089</td>
+<td>136187</td>
+<td>71.58</td>
+<td>110.20</td>
+<td>25.59</td>
+<td>4.01</td>
+<td>3.90</td>
+<td>3.72</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0090</td>
+<td>134994</td>
+<td>59.78</td>
+<td>195.98</td>
+<td>25.62</td>
+<td>4.13</td>
+<td>4.24</td>
+<td>3.15</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0091</td>
+<td>134835</td>
+<td>78.85</td>
+<td>101.33</td>
+<td>18.80</td>
+<td>4.17</td>
+<td>4.14</td>
+<td>3.48</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0092</td>
+<td>140599</td>
+<td>68.48</td>
+<td>98.80</td>
+<td>20.40</td>
+<td>3.53</td>
+<td>3.58</td>
+<td>3.39</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0093</td>
+<td>142219</td>
+<td>71.75</td>
+<td>133.38</td>
+<td>21.96</td>
+<td>4.09</td>
+<td>4.12</td>
+<td>3.30</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0094</td>
+<td>142205</td>
+<td>75.38</td>
+<td>135.06</td>
+<td>23.88</td>
+<td>3.61</td>
+<td>3.60</td>
+<td>3.47</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0095</td>
+<td>137110</td>
+<td>73.45</td>
+<td>102.11</td>
+<td>23.22</td>
+<td>4.14</td>
+<td>4.11</td>
+<td>3.57</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0096</td>
+<td>140431</td>
+<td>83.15</td>
+<td>105.95</td>
+<td>25.34</td>
+<td>4.28</td>
+<td>4.33</td>
+<td>3.12</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0097</td>
+<td>140497</td>
+<td>80.83</td>
+<td>123.35</td>
+<td>26.31</td>
+<td>3.75</td>
+<td>3.65</td>
+<td>3.45</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0098</td>
+<td>142486</td>
+<td>77.92</td>
+<td>96.82</td>
+<td>26.41</td>
+<td>3.70</td>
+<td>3.68</td>
+<td>3.50</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0099</td>
+<td>145817</td>
+<td>85.30</td>
+<td>110.93</td>
+<td>27.92</td>
+<td>3.85</td>
+<td>3.93</td>
+<td>3.16</td>
+</tr>
+<tr>
+<td>5</td>
+<td>0100</td>
+<td>139604</td>
+<td>74.98</td>
+<td>135.98</td>
+<td>24.92</td>
+<td>3.71</td>
+<td>3.72</td>
+<td>3.21</td>
+</tr>
+</table>
+<a name="Lane"><h2>Lane 6</h2></a><table border="1" cellpadding="5">
+<tr>
+<td colspan="1">Lane </td>
+<td colspan="1">Tile</td>
+<td colspan="1">Clusters (raw)</td>
+<td colspan="1">Av 1st Cycle Int (PF)</td>
+<td colspan="1">Av % intensity after 20 cycles (PF)</td>
+<td colspan="1">% PF Clusters </td>
+<td colspan="1">% Align (PF) </td>
+<td colspan="1">Av Alignment Score (PF)</td>
+<td colspan="1">% Error Rate (PF) </td>
+</tr>
+<tr>
+<td>6</td>
+<td>0001</td>
+<td>153653</td>
+<td>79.58</td>
+<td>136.00</td>
+<td>23.86</td>
+<td>3.57</td>
+<td>3.46</td>
+<td>3.57</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0002</td>
+<td>160739</td>
+<td>56.15</td>
+<td>162.78</td>
+<td>17.73</td>
+<td>3.21</td>
+<td>3.13</td>
+<td>3.67</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0003</td>
+<td>147132</td>
+<td>63.32</td>
+<td>127.79</td>
+<td>17.49</td>
+<td>3.75</td>
+<td>3.65</td>
+<td>3.56</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0004</td>
+<td>151602</td>
+<td>65.30</td>
+<td>109.95</td>
+<td>18.04</td>
+<td>3.53</td>
+<td>3.42</td>
+<td>3.54</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0005</td>
+<td>156959</td>
+<td>61.57</td>
+<td>109.87</td>
+<td>20.25</td>
+<td>3.16</td>
+<td>3.08</td>
+<td>3.64</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0006</td>
+<td>146995</td>
+<td>75.43</td>
+<td>108.25</td>
+<td>19.92</td>
+<td>3.68</td>
+<td>3.57</td>
+<td>3.55</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0007</td>
+<td>147619</td>
+<td>84.72</td>
+<td>103.16</td>
+<td>20.12</td>
+<td>3.92</td>
+<td>3.64</td>
+<td>3.62</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0008</td>
+<td>157868</td>
+<td>73.75</td>
+<td>139.22</td>
+<td>26.32</td>
+<td>3.44</td>
+<td>3.30</td>
+<td>3.66</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0009</td>
+<td>155003</td>
+<td>72.53</td>
+<td>126.27</td>
+<td>23.62</td>
+<td>3.37</td>
+<td>3.25</td>
+<td>3.32</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0010</td>
+<td>148015</td>
+<td>68.85</td>
+<td>141.54</td>
+<td>20.08</td>
+<td>3.68</td>
+<td>3.51</td>
+<td>3.62</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0011</td>
+<td>153943</td>
+<td>71.40</td>
+<td>106.06</td>
+<td>19.50</td>
+<td>3.47</td>
+<td>3.38</td>
+<td>3.37</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0012</td>
+<td>148858</td>
+<td>81.22</td>
+<td>86.43</td>
+<td>19.51</td>
+<td>3.36</td>
+<td>3.25</td>
+<td>3.65</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0013</td>
+<td>144234</td>
+<td>74.60</td>
+<td>90.42</td>
+<td>17.02</td>
+<td>3.59</td>
+<td>3.46</td>
+<td>3.55</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0014</td>
+<td>143006</td>
+<td>69.70</td>
+<td>91.32</td>
+<td>17.26</td>
+<td>3.78</td>
+<td>3.62</td>
+<td>3.63</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0015</td>
+<td>166330</td>
+<td>52.00</td>
+<td>154.47</td>
+<td>20.50</td>
+<td>2.77</td>
+<td>2.70</td>
+<td>3.69</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0016</td>
+<td>144632</td>
+<td>58.68</td>
+<td>215.25</td>
+<td>24.25</td>
+<td>3.59</td>
+<td>3.46</td>
+<td>4.03</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0017</td>
+<td>136245</td>
+<td>75.12</td>
+<td>98.80</td>
+<td>22.86</td>
+<td>4.31</td>
+<td>4.15</td>
+<td>3.81</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0018</td>
+<td>145428</td>
+<td>69.75</td>
+<td>143.51</td>
+<td>21.98</td>
+<td>3.68</td>
+<td>3.61</td>
+<td>3.65</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0019</td>
+<td>136914</td>
+<td>67.10</td>
+<td>160.80</td>
+<td>23.12</td>
+<td>4.20</td>
+<td>4.15</td>
+<td>3.46</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0020</td>
+<td>148171</td>
+<td>66.60</td>
+<td>162.05</td>
+<td>21.47</td>
+<td>3.73</td>
+<td>3.50</td>
+<td>4.06</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0021</td>
+<td>134914</td>
+<td>73.75</td>
+<td>128.85</td>
+<td>16.65</td>
+<td>3.90</td>
+<td>3.61</td>
+<td>4.09</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0022</td>
+<td>138651</td>
+<td>82.20</td>
+<td>76.09</td>
+<td>17.87</td>
+<td>4.14</td>
+<td>3.83</td>
+<td>3.75</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0023</td>
+<td>149423</td>
+<td>62.85</td>
+<td>105.49</td>
+<td>20.75</td>
+<td>3.75</td>
+<td>3.59</td>
+<td>3.61</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0024</td>
+<td>143451</td>
+<td>83.30</td>
+<td>104.17</td>
+<td>20.22</td>
+<td>4.24</td>
+<td>4.10</td>
+<td>3.46</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0025</td>
+<td>147540</td>
+<td>72.50</td>
+<td>91.83</td>
+<td>19.86</td>
+<td>3.74</td>
+<td>3.69</td>
+<td>3.28</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0026</td>
+<td>141154</td>
+<td>72.90</td>
+<td>100.79</td>
+<td>20.73</td>
+<td>4.06</td>
+<td>4.06</td>
+<td>3.25</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0027</td>
+<td>160782</td>
+<td>68.42</td>
+<td>127.91</td>
+<td>22.54</td>
+<td>3.52</td>
+<td>3.50</td>
+<td>3.22</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0028</td>
+<td>148134</td>
+<td>70.67</td>
+<td>121.01</td>
+<td>23.53</td>
+<td>3.48</td>
+<td>3.37</td>
+<td>3.61</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0029</td>
+<td>154465</td>
+<td>69.50</td>
+<td>109.93</td>
+<td>26.04</td>
+<td>3.61</td>
+<td>3.69</td>
+<td>3.26</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0030</td>
+<td>151767</td>
+<td>69.12</td>
+<td>187.70</td>
+<td>22.59</td>
+<td>3.96</td>
+<td>4.02</td>
+<td>3.28</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0031</td>
+<td>164147</td>
+<td>91.00</td>
+<td>96.92</td>
+<td>30.61</td>
+<td>3.14</td>
+<td>2.90</td>
+<td>3.97</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0032</td>
+<td>153275</td>
+<td>65.38</td>
+<td>174.23</td>
+<td>28.83</td>
+<td>3.63</td>
+<td>3.60</td>
+<td>3.56</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0033</td>
+<td>153722</td>
+<td>98.08</td>
+<td>89.12</td>
+<td>27.30</td>
+<td>3.92</td>
+<td>3.56</td>
+<td>3.76</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0034</td>
+<td>152193</td>
+<td>76.95</td>
+<td>102.40</td>
+<td>27.20</td>
+<td>3.90</td>
+<td>3.79</td>
+<td>3.54</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0035</td>
+<td>153463</td>
+<td>74.17</td>
+<td>107.99</td>
+<td>30.03</td>
+<td>3.52</td>
+<td>3.35</td>
+<td>3.75</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0036</td>
+<td>150895</td>
+<td>65.65</td>
+<td>166.83</td>
+<td>32.03</td>
+<td>3.85</td>
+<td>3.71</td>
+<td>3.55</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0037</td>
+<td>145745</td>
+<td>62.45</td>
+<td>116.49</td>
+<td>26.60</td>
+<td>3.77</td>
+<td>3.63</td>
+<td>3.81</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0038</td>
+<td>151284</td>
+<td>64.00</td>
+<td>181.25</td>
+<td>26.09</td>
+<td>3.81</td>
+<td>3.74</td>
+<td>3.52</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0039</td>
+<td>151424</td>
+<td>58.40</td>
+<td>199.40</td>
+<td>26.27</td>
+<td>3.49</td>
+<td>3.39</td>
+<td>3.83</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0040</td>
+<td>157649</td>
+<td>75.05</td>
+<td>119.05</td>
+<td>32.59</td>
+<td>3.05</td>
+<td>2.89</td>
+<td>3.62</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0041</td>
+<td>151062</td>
+<td>60.13</td>
+<td>208.94</td>
+<td>26.71</td>
+<td>3.52</td>
+<td>3.44</td>
+<td>3.68</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0042</td>
+<td>147213</td>
+<td>62.43</td>
+<td>124.99</td>
+<td>25.03</td>
+<td>3.79</td>
+<td>3.65</td>
+<td>3.87</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0043</td>
+<td>153123</td>
+<td>68.88</td>
+<td>123.41</td>
+<td>25.47</td>
+<td>3.55</td>
+<td>3.37</td>
+<td>3.77</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0044</td>
+<td>150099</td>
+<td>77.20</td>
+<td>115.03</td>
+<td>29.38</td>
+<td>3.73</td>
+<td>3.57</td>
+<td>3.46</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0045</td>
+<td>152952</td>
+<td>88.53</td>
+<td>108.39</td>
+<td>32.19</td>
+<td>3.43</td>
+<td>3.21</td>
+<td>3.53</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0046</td>
+<td>150981</td>
+<td>82.27</td>
+<td>104.98</td>
+<td>30.68</td>
+<td>3.82</td>
+<td>3.67</td>
+<td>3.54</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0047</td>
+<td>153505</td>
+<td>82.67</td>
+<td>139.40</td>
+<td>32.67</td>
+<td>3.89</td>
+<td>3.71</td>
+<td>3.62</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0048</td>
+<td>153834</td>
+<td>90.45</td>
+<td>119.02</td>
+<td>33.13</td>
+<td>3.87</td>
+<td>3.62</td>
+<td>3.64</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0049</td>
+<td>152222</td>
+<td>83.83</td>
+<td>121.38</td>
+<td>32.21</td>
+<td>4.08</td>
+<td>3.87</td>
+<td>3.47</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0050</td>
+<td>151798</td>
+<td>70.42</td>
+<td>130.88</td>
+<td>32.01</td>
+<td>3.89</td>
+<td>3.77</td>
+<td>3.43</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0051</td>
+<td>154430</td>
+<td>95.33</td>
+<td>91.40</td>
+<td>32.17</td>
+<td>3.61</td>
+<td>3.44</td>
+<td>3.52</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0052</td>
+<td>157873</td>
+<td>86.33</td>
+<td>102.00</td>
+<td>30.04</td>
+<td>3.52</td>
+<td>3.32</td>
+<td>3.76</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0053</td>
+<td>152489</td>
+<td>74.80</td>
+<td>127.61</td>
+<td>32.86</td>
+<td>3.80</td>
+<td>3.74</td>
+<td>3.43</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0054</td>
+<td>154402</td>
+<td>74.40</td>
+<td>132.02</td>
+<td>31.73</td>
+<td>3.96</td>
+<td>3.84</td>
+<td>3.72</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0055</td>
+<td>151912</td>
+<td>75.03</td>
+<td>96.43</td>
+<td>33.81</td>
+<td>3.97</td>
+<td>3.86</td>
+<td>3.42</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0056</td>
+<td>155460</td>
+<td>76.15</td>
+<td>111.46</td>
+<td>35.00</td>
+<td>3.87</td>
+<td>3.81</td>
+<td>3.45</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0057</td>
+<td>154114</td>
+<td>77.83</td>
+<td>138.19</td>
+<td>36.39</td>
+<td>3.70</td>
+<td>3.57</td>
+<td>3.66</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0058</td>
+<td>156369</td>
+<td>79.62</td>
+<td>125.87</td>
+<td>27.39</td>
+<td>4.04</td>
+<td>3.87</td>
+<td>3.57</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0059</td>
+<td>156151</td>
+<td>79.38</td>
+<td>106.24</td>
+<td>32.37</td>
+<td>3.29</td>
+<td>3.12</td>
+<td>3.89</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0060</td>
+<td>155109</td>
+<td>84.15</td>
+<td>108.53</td>
+<td>32.09</td>
+<td>3.38</td>
+<td>3.15</td>
+<td>4.09</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0061</td>
+<td>142351</td>
+<td>71.72</td>
+<td>140.54</td>
+<td>29.12</td>
+<td>3.92</td>
+<td>3.81</td>
+<td>3.84</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0062</td>
+<td>149140</td>
+<td>84.90</td>
+<td>95.47</td>
+<td>27.32</td>
+<td>3.50</td>
+<td>3.39</td>
+<td>3.86</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0063</td>
+<td>159848</td>
+<td>66.10</td>
+<td>158.89</td>
+<td>25.37</td>
+<td>3.46</td>
+<td>3.30</td>
+<td>4.01</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0064</td>
+<td>159352</td>
+<td>63.03</td>
+<td>119.28</td>
+<td>26.66</td>
+<td>4.04</td>
+<td>3.99</td>
+<td>3.69</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0065</td>
+<td>162441</td>
+<td>94.40</td>
+<td>128.87</td>
+<td>30.65</td>
+<td>3.66</td>
+<td>3.56</td>
+<td>3.35</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0066</td>
+<td>141798</td>
+<td>61.55</td>
+<td>109.63</td>
+<td>22.38</td>
+<td>3.94</td>
+<td>3.99</td>
+<td>3.42</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0067</td>
+<td>160686</td>
+<td>86.90</td>
+<td>121.63</td>
+<td>27.80</td>
+<td>3.62</td>
+<td>3.40</td>
+<td>3.98</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0068</td>
+<td>152539</td>
+<td>67.95</td>
+<td>125.46</td>
+<td>33.74</td>
+<td>3.60</td>
+<td>3.44</td>
+<td>3.78</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0069</td>
+<td>155253</td>
+<td>87.25</td>
+<td>78.65</td>
+<td>27.31</td>
+<td>3.28</td>
+<td>3.12</td>
+<td>3.79</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0070</td>
+<td>158082</td>
+<td>78.60</td>
+<td>92.14</td>
+<td>28.45</td>
+<td>3.35</td>
+<td>3.26</td>
+<td>3.74</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0071</td>
+<td>148641</td>
+<td>72.60</td>
+<td>160.74</td>
+<td>28.05</td>
+<td>3.78</td>
+<td>3.80</td>
+<td>3.25</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0072</td>
+<td>151821</td>
+<td>74.60</td>
+<td>146.15</td>
+<td>27.26</td>
+<td>4.19</td>
+<td>4.26</td>
+<td>3.19</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0073</td>
+<td>159347</td>
+<td>103.45</td>
+<td>121.82</td>
+<td>33.03</td>
+<td>3.82</td>
+<td>3.79</td>
+<td>3.19</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0074</td>
+<td>152598</td>
+<td>68.88</td>
+<td>190.09</td>
+<td>32.13</td>
+<td>4.18</td>
+<td>4.14</td>
+<td>3.25</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0075</td>
+<td>158017</td>
+<td>88.73</td>
+<td>137.81</td>
+<td>36.73</td>
+<td>3.73</td>
+<td>3.63</td>
+<td>3.36</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0076</td>
+<td>168580</td>
+<td>109.37</td>
+<td>119.43</td>
+<td>34.66</td>
+<td>3.25</td>
+<td>3.01</td>
+<td>3.49</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0077</td>
+<td>161724</td>
+<td>110.40</td>
+<td>78.60</td>
+<td>36.10</td>
+<td>3.53</td>
+<td>3.44</td>
+<td>3.10</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0078</td>
+<td>168315</td>
+<td>99.88</td>
+<td>82.25</td>
+<td>34.85</td>
+<td>3.29</td>
+<td>3.16</td>
+<td>3.28</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0079</td>
+<td>160481</td>
+<td>95.53</td>
+<td>124.55</td>
+<td>31.56</td>
+<td>3.73</td>
+<td>3.64</td>
+<td>3.13</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0080</td>
+<td>163992</td>
+<td>78.00</td>
+<td>120.32</td>
+<td>33.98</td>
+<td>3.24</td>
+<td>3.20</td>
+<td>3.01</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0081</td>
+<td>153658</td>
+<td>100.70</td>
+<td>101.49</td>
+<td>35.12</td>
+<td>3.70</td>
+<td>3.59</td>
+<td>3.04</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0082</td>
+<td>171593</td>
+<td>126.27</td>
+<td>113.32</td>
+<td>38.70</td>
+<td>3.15</td>
+<td>3.03</td>
+<td>3.29</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0083</td>
+<td>162775</td>
+<td>119.70</td>
+<td>65.29</td>
+<td>36.56</td>
+<td>3.44</td>
+<td>3.31</td>
+<td>3.08</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0084</td>
+<td>133248</td>
+<td>67.38</td>
+<td>133.40</td>
+<td>31.50</td>
+<td>3.89</td>
+<td>3.78</td>
+<td>3.26</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0085</td>
+<td>152741</td>
+<td>108.30</td>
+<td>119.39</td>
+<td>32.76</td>
+<td>3.35</td>
+<td>3.15</td>
+<td>3.27</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0086</td>
+<td>138270</td>
+<td>68.20</td>
+<td>127.97</td>
+<td>27.14</td>
+<td>3.83</td>
+<td>3.85</td>
+<td>3.13</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0087</td>
+<td>164771</td>
+<td>71.25</td>
+<td>147.44</td>
+<td>29.57</td>
+<td>3.16</td>
+<td>3.19</td>
+<td>2.98</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0088</td>
+<td>131121</td>
+<td>77.50</td>
+<td>134.52</td>
+<td>27.09</td>
+<td>3.87</td>
+<td>3.91</td>
+<td>3.14</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0089</td>
+<td>150558</td>
+<td>89.00</td>
+<td>81.35</td>
+<td>25.53</td>
+<td>3.63</td>
+<td>3.57</td>
+<td>3.25</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0090</td>
+<td>162180</td>
+<td>89.12</td>
+<td>102.92</td>
+<td>25.09</td>
+<td>4.06</td>
+<td>3.88</td>
+<td>3.31</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0091</td>
+<td>161673</td>
+<td>101.30</td>
+<td>108.96</td>
+<td>30.88</td>
+<td>3.20</td>
+<td>3.03</td>
+<td>3.33</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0092</td>
+<td>158822</td>
+<td>93.17</td>
+<td>117.12</td>
+<td>32.93</td>
+<td>3.69</td>
+<td>3.65</td>
+<td>2.97</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0093</td>
+<td>167698</td>
+<td>100.77</td>
+<td>111.51</td>
+<td>35.19</td>
+<td>3.48</td>
+<td>3.40</td>
+<td>2.87</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0094</td>
+<td>169111</td>
+<td>88.40</td>
+<td>117.02</td>
+<td>30.23</td>
+<td>3.32</td>
+<td>3.24</td>
+<td>3.01</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0095</td>
+<td>177848</td>
+<td>103.12</td>
+<td>117.89</td>
+<td>36.78</td>
+<td>3.33</td>
+<td>3.26</td>
+<td>3.07</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0096</td>
+<td>166133</td>
+<td>95.85</td>
+<td>110.67</td>
+<td>32.00</td>
+<td>3.53</td>
+<td>3.46</td>
+<td>3.08</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0097</td>
+<td>178023</td>
+<td>83.72</td>
+<td>145.63</td>
+<td>31.05</td>
+<td>3.19</td>
+<td>3.15</td>
+<td>2.88</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0098</td>
+<td>180358</td>
+<td>84.95</td>
+<td>151.68</td>
+<td>29.70</td>
+<td>3.08</td>
+<td>3.07</td>
+<td>2.89</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0099</td>
+<td>170568</td>
+<td>93.78</td>
+<td>102.45</td>
+<td>34.36</td>
+<td>3.46</td>
+<td>3.41</td>
+<td>2.97</td>
+</tr>
+<tr>
+<td>6</td>
+<td>0100</td>
+<td>159704</td>
+<td>94.20</td>
+<td>119.96</td>
+<td>29.87</td>
+<td>3.44</td>
+<td>3.36</td>
+<td>3.07</td>
+</tr>
+</table>
+<a name="Lane"><h2>Lane 7</h2></a><table border="1" cellpadding="5">
+<tr>
+<td colspan="1">Lane </td>
+<td colspan="1">Tile</td>
+<td colspan="1">Clusters (raw)</td>
+<td colspan="1">Av 1st Cycle Int (PF)</td>
+<td colspan="1">Av % intensity after 20 cycles (PF)</td>
+<td colspan="1">% PF Clusters </td>
+<td colspan="1">% Align (PF) </td>
+<td colspan="1">Av Alignment Score (PF)</td>
+<td colspan="1">% Error Rate (PF) </td>
+</tr>
+<tr>
+<td>7</td>
+<td>0001</td>
+<td>167397</td>
+<td>69.27</td>
+<td>175.60</td>
+<td>33.31</td>
+<td>0.90</td>
+<td>0.61</td>
+<td>2.48</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0002</td>
+<td>160455</td>
+<td>74.47</td>
+<td>139.78</td>
+<td>29.55</td>
+<td>0.88</td>
+<td>0.59</td>
+<td>2.91</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0003</td>
+<td>161412</td>
+<td>82.17</td>
+<td>112.08</td>
+<td>28.64</td>
+<td>0.85</td>
+<td>0.55</td>
+<td>3.00</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0004</td>
+<td>163624</td>
+<td>101.35</td>
+<td>84.07</td>
+<td>26.10</td>
+<td>0.87</td>
+<td>0.53</td>
+<td>3.76</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0005</td>
+<td>164021</td>
+<td>92.78</td>
+<td>113.18</td>
+<td>26.89</td>
+<td>0.96</td>
+<td>0.65</td>
+<td>3.07</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0006</td>
+<td>156806</td>
+<td>75.62</td>
+<td>117.16</td>
+<td>27.59</td>
+<td>0.86</td>
+<td>0.60</td>
+<td>2.80</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0007</td>
+<td>162038</td>
+<td>73.40</td>
+<td>114.44</td>
+<td>27.61</td>
+<td>0.86</td>
+<td>0.60</td>
+<td>2.45</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0008</td>
+<td>165511</td>
+<td>107.40</td>
+<td>105.26</td>
+<td>35.62</td>
+<td>0.86</td>
+<td>0.56</td>
+<td>2.85</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0009</td>
+<td>163282</td>
+<td>87.62</td>
+<td>129.24</td>
+<td>30.11</td>
+<td>0.81</td>
+<td>0.57</td>
+<td>3.20</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0010</td>
+<td>156992</td>
+<td>68.35</td>
+<td>119.09</td>
+<td>22.63</td>
+<td>0.81</td>
+<td>0.53</td>
+<td>2.96</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0011</td>
+<td>150552</td>
+<td>72.62</td>
+<td>118.73</td>
+<td>25.24</td>
+<td>0.87</td>
+<td>0.60</td>
+<td>2.60</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0012</td>
+<td>160140</td>
+<td>68.35</td>
+<td>105.56</td>
+<td>22.16</td>
+<td>0.72</td>
+<td>0.47</td>
+<td>3.23</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0013</td>
+<td>149302</td>
+<td>85.85</td>
+<td>100.93</td>
+<td>21.86</td>
+<td>0.79</td>
+<td>0.50</td>
+<td>3.30</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0014</td>
+<td>143740</td>
+<td>84.30</td>
+<td>87.37</td>
+<td>23.58</td>
+<td>0.82</td>
+<td>0.57</td>
+<td>2.92</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0015</td>
+<td>139427</td>
+<td>58.95</td>
+<td>157.80</td>
+<td>24.12</td>
+<td>0.92</td>
+<td>0.63</td>
+<td>3.22</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0016</td>
+<td>136177</td>
+<td>72.90</td>
+<td>146.60</td>
+<td>26.28</td>
+<td>0.93</td>
+<td>0.63</td>
+<td>2.91</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0017</td>
+<td>132237</td>
+<td>69.00</td>
+<td>151.45</td>
+<td>25.13</td>
+<td>1.10</td>
+<td>0.79</td>
+<td>2.68</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0018</td>
+<td>142869</td>
+<td>66.30</td>
+<td>171.00</td>
+<td>23.94</td>
+<td>0.99</td>
+<td>0.75</td>
+<td>2.93</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0019</td>
+<td>142456</td>
+<td>69.05</td>
+<td>165.86</td>
+<td>31.97</td>
+<td>0.97</td>
+<td>0.63</td>
+<td>2.91</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0020</td>
+<td>155029</td>
+<td>60.87</td>
+<td>100.82</td>
+<td>28.64</td>
+<td>0.75</td>
+<td>0.52</td>
+<td>3.65</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0021</td>
+<td>154783</td>
+<td>62.23</td>
+<td>154.28</td>
+<td>24.08</td>
+<td>0.73</td>
+<td>0.54</td>
+<td>3.34</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0022</td>
+<td>140060</td>
+<td>54.48</td>
+<td>172.10</td>
+<td>20.64</td>
+<td>1.05</td>
+<td>0.71</td>
+<td>2.94</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0023</td>
+<td>148347</td>
+<td>71.95</td>
+<td>95.55</td>
+<td>29.02</td>
+<td>0.83</td>
+<td>0.58</td>
+<td>2.65</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0024</td>
+<td>148929</td>
+<td>60.38</td>
+<td>114.87</td>
+<td>26.97</td>
+<td>0.80</td>
+<td>0.59</td>
+<td>2.99</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0025</td>
+<td>150950</td>
+<td>67.45</td>
+<td>106.30</td>
+<td>25.61</td>
+<td>0.90</td>
+<td>0.63</td>
+<td>3.29</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0026</td>
+<td>150448</td>
+<td>67.12</td>
+<td>151.32</td>
+<td>26.18</td>
+<td>0.83</td>
+<td>0.60</td>
+<td>3.44</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0027</td>
+<td>157343</td>
+<td>80.08</td>
+<td>155.89</td>
+<td>26.53</td>
+<td>0.82</td>
+<td>0.58</td>
+<td>3.90</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0028</td>
+<td>154919</td>
+<td>76.90</td>
+<td>169.70</td>
+<td>27.22</td>
+<td>0.90</td>
+<td>0.62</td>
+<td>3.35</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0029</td>
+<td>155916</td>
+<td>77.33</td>
+<td>153.28</td>
+<td>29.53</td>
+<td>0.86</td>
+<td>0.60</td>
+<td>3.52</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0030</td>
+<td>153327</td>
+<td>74.33</td>
+<td>98.79</td>
+<td>29.26</td>
+<td>0.83</td>
+<td>0.62</td>
+<td>3.44</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0031</td>
+<td>167377</td>
+<td>101.38</td>
+<td>100.96</td>
+<td>26.85</td>
+<td>0.89</td>
+<td>0.59</td>
+<td>3.32</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0032</td>
+<td>158600</td>
+<td>80.65</td>
+<td>86.73</td>
+<td>33.06</td>
+<td>0.93</td>
+<td>0.62</td>
+<td>3.56</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0033</td>
+<td>156070</td>
+<td>71.68</td>
+<td>169.06</td>
+<td>33.61</td>
+<td>0.94</td>
+<td>0.65</td>
+<td>2.99</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0034</td>
+<td>147991</td>
+<td>59.85</td>
+<td>160.90</td>
+<td>34.16</td>
+<td>1.11</td>
+<td>0.72</td>
+<td>3.15</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0035</td>
+<td>145454</td>
+<td>76.00</td>
+<td>130.00</td>
+<td>31.63</td>
+<td>1.02</td>
+<td>0.68</td>
+<td>2.89</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0036</td>
+<td>164600</td>
+<td>69.88</td>
+<td>89.91</td>
+<td>34.74</td>
+<td>0.70</td>
+<td>0.44</td>
+<td>3.41</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0037</td>
+<td>150892</td>
+<td>73.33</td>
+<td>136.99</td>
+<td>31.62</td>
+<td>0.79</td>
+<td>0.54</td>
+<td>3.98</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0038</td>
+<td>158740</td>
+<td>71.50</td>
+<td>126.50</td>
+<td>31.62</td>
+<td>0.74</td>
+<td>0.58</td>
+<td>3.22</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0039</td>
+<td>159579</td>
+<td>76.23</td>
+<td>117.74</td>
+<td>34.90</td>
+<td>0.82</td>
+<td>0.56</td>
+<td>3.78</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0040</td>
+<td>153009</td>
+<td>73.10</td>
+<td>124.42</td>
+<td>26.48</td>
+<td>1.01</td>
+<td>0.70</td>
+<td>3.34</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0041</td>
+<td>163129</td>
+<td>78.72</td>
+<td>69.58</td>
+<td>35.20</td>
+<td>0.66</td>
+<td>0.43</td>
+<td>3.48</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0042</td>
+<td>156652</td>
+<td>87.90</td>
+<td>121.42</td>
+<td>29.83</td>
+<td>0.79</td>
+<td>0.53</td>
+<td>3.56</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0043</td>
+<td>156757</td>
+<td>83.33</td>
+<td>126.79</td>
+<td>36.07</td>
+<td>0.94</td>
+<td>0.63</td>
+<td>3.16</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0044</td>
+<td>159822</td>
+<td>81.50</td>
+<td>114.72</td>
+<td>35.18</td>
+<td>0.88</td>
+<td>0.59</td>
+<td>2.99</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0045</td>
+<td>157761</td>
+<td>96.48</td>
+<td>106.92</td>
+<td>34.86</td>
+<td>0.98</td>
+<td>0.62</td>
+<td>3.11</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0046</td>
+<td>163550</td>
+<td>82.83</td>
+<td>155.06</td>
+<td>36.16</td>
+<td>1.03</td>
+<td>0.64</td>
+<td>2.59</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0047</td>
+<td>161738</td>
+<td>108.35</td>
+<td>83.71</td>
+<td>35.48</td>
+<td>0.97</td>
+<td>0.60</td>
+<td>3.02</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0048</td>
+<td>157517</td>
+<td>89.12</td>
+<td>125.83</td>
+<td>38.04</td>
+<td>0.97</td>
+<td>0.62</td>
+<td>3.51</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0049</td>
+<td>156268</td>
+<td>84.37</td>
+<td>120.39</td>
+<td>35.93</td>
+<td>0.93</td>
+<td>0.63</td>
+<td>3.00</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0050</td>
+<td>161985</td>
+<td>92.23</td>
+<td>115.94</td>
+<td>38.21</td>
+<td>0.94</td>
+<td>0.61</td>
+<td>3.05</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0051</td>
+<td>164236</td>
+<td>92.08</td>
+<td>118.27</td>
+<td>37.06</td>
+<td>0.96</td>
+<td>0.59</td>
+<td>2.93</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0052</td>
+<td>162032</td>
+<td>87.82</td>
+<td>125.70</td>
+<td>38.33</td>
+<td>0.92</td>
+<td>0.57</td>
+<td>3.20</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0053</td>
+<td>161985</td>
+<td>92.42</td>
+<td>106.98</td>
+<td>38.00</td>
+<td>0.91</td>
+<td>0.57</td>
+<td>3.14</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0054</td>
+<td>156507</td>
+<td>78.40</td>
+<td>109.38</td>
+<td>33.59</td>
+<td>0.91</td>
+<td>0.63</td>
+<td>2.56</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0055</td>
+<td>160991</td>
+<td>99.35</td>
+<td>90.09</td>
+<td>31.61</td>
+<td>0.75</td>
+<td>0.54</td>
+<td>3.26</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0056</td>
+<td>162825</td>
+<td>94.60</td>
+<td>105.84</td>
+<td>35.81</td>
+<td>0.91</td>
+<td>0.60</td>
+<td>3.07</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0057</td>
+<td>163135</td>
+<td>95.25</td>
+<td>103.15</td>
+<td>36.03</td>
+<td>0.88</td>
+<td>0.55</td>
+<td>2.93</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0058</td>
+<td>159693</td>
+<td>85.17</td>
+<td>115.12</td>
+<td>28.95</td>
+<td>0.79</td>
+<td>0.57</td>
+<td>3.45</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0059</td>
+<td>154052</td>
+<td>77.88</td>
+<td>137.17</td>
+<td>37.45</td>
+<td>0.88</td>
+<td>0.58</td>
+<td>3.13</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0060</td>
+<td>161198</td>
+<td>89.47</td>
+<td>108.30</td>
+<td>32.87</td>
+<td>0.79</td>
+<td>0.55</td>
+<td>3.51</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0061</td>
+<td>156782</td>
+<td>76.92</td>
+<td>114.40</td>
+<td>32.30</td>
+<td>0.81</td>
+<td>0.57</td>
+<td>3.58</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0062</td>
+<td>159970</td>
+<td>80.27</td>
+<td>126.28</td>
+<td>29.28</td>
+<td>0.87</td>
+<td>0.60</td>
+<td>3.59</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0063</td>
+<td>160696</td>
+<td>86.85</td>
+<td>105.01</td>
+<td>28.67</td>
+<td>0.74</td>
+<td>0.54</td>
+<td>3.37</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0064</td>
+<td>161082</td>
+<td>89.83</td>
+<td>128.19</td>
+<td>31.12</td>
+<td>0.78</td>
+<td>0.55</td>
+<td>2.98</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0065</td>
+<td>173381</td>
+<td>111.25</td>
+<td>122.36</td>
+<td>25.05</td>
+<td>0.84</td>
+<td>0.57</td>
+<td>3.98</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0066</td>
+<td>160085</td>
+<td>93.40</td>
+<td>86.24</td>
+<td>27.82</td>
+<td>0.82</td>
+<td>0.60</td>
+<td>2.96</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0067</td>
+<td>160301</td>
+<td>80.08</td>
+<td>98.63</td>
+<td>26.39</td>
+<td>0.83</td>
+<td>0.61</td>
+<td>3.07</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0068</td>
+<td>152691</td>
+<td>101.30</td>
+<td>91.19</td>
+<td>29.11</td>
+<td>0.88</td>
+<td>0.59</td>
+<td>3.66</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0069</td>
+<td>148325</td>
+<td>70.78</td>
+<td>150.83</td>
+<td>31.02</td>
+<td>0.88</td>
+<td>0.63</td>
+<td>3.20</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0070</td>
+<td>162125</td>
+<td>85.85</td>
+<td>92.05</td>
+<td>30.62</td>
+<td>0.68</td>
+<td>0.48</td>
+<td>3.06</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0071</td>
+<td>165962</td>
+<td>68.72</td>
+<td>103.56</td>
+<td>32.70</td>
+<td>0.66</td>
+<td>0.46</td>
+<td>3.51</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0072</td>
+<td>166535</td>
+<td>93.93</td>
+<td>124.57</td>
+<td>34.01</td>
+<td>0.80</td>
+<td>0.52</td>
+<td>3.13</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0073</td>
+<td>173264</td>
+<td>106.55</td>
+<td>126.56</td>
+<td>31.72</td>
+<td>0.79</td>
+<td>0.50</td>
+<td>3.45</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0074</td>
+<td>168644</td>
+<td>101.98</td>
+<td>125.55</td>
+<td>29.96</td>
+<td>0.76</td>
+<td>0.54</td>
+<td>3.06</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0075</td>
+<td>156199</td>
+<td>71.35</td>
+<td>151.23</td>
+<td>31.66</td>
+<td>0.89</td>
+<td>0.59</td>
+<td>3.52</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0076</td>
+<td>165594</td>
+<td>70.38</td>
+<td>157.19</td>
+<td>31.08</td>
+<td>0.93</td>
+<td>0.63</td>
+<td>3.48</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0077</td>
+<td>176072</td>
+<td>93.12</td>
+<td>73.80</td>
+<td>34.36</td>
+<td>0.80</td>
+<td>0.50</td>
+<td>3.20</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0078</td>
+<td>174206</td>
+<td>115.92</td>
+<td>96.74</td>
+<td>34.01</td>
+<td>0.80</td>
+<td>0.50</td>
+<td>3.53</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0079</td>
+<td>163065</td>
+<td>92.30</td>
+<td>111.24</td>
+<td>35.11</td>
+<td>0.86</td>
+<td>0.61</td>
+<td>2.78</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0080</td>
+<td>172367</td>
+<td>59.48</td>
+<td>116.86</td>
+<td>34.71</td>
+<td>1.00</td>
+<td>0.64</td>
+<td>3.07</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0081</td>
+<td>160853</td>
+<td>106.10</td>
+<td>116.71</td>
+<td>33.53</td>
+<td>0.93</td>
+<td>0.60</td>
+<td>2.77</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0082</td>
+<td>170809</td>
+<td>67.78</td>
+<td>153.19</td>
+<td>30.68</td>
+<td>0.80</td>
+<td>0.54</td>
+<td>2.83</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0083</td>
+<td>171250</td>
+<td>69.68</td>
+<td>153.50</td>
+<td>28.97</td>
+<td>0.82</td>
+<td>0.55</td>
+<td>2.95</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0084</td>
+<td>172523</td>
+<td>90.85</td>
+<td>116.62</td>
+<td>29.60</td>
+<td>0.89</td>
+<td>0.57</td>
+<td>2.94</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0085</td>
+<td>172083</td>
+<td>97.45</td>
+<td>107.52</td>
+<td>31.45</td>
+<td>0.80</td>
+<td>0.52</td>
+<td>2.91</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0086</td>
+<td>158879</td>
+<td>90.08</td>
+<td>108.60</td>
+<td>25.96</td>
+<td>0.95</td>
+<td>0.63</td>
+<td>3.05</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0087</td>
+<td>169708</td>
+<td>104.50</td>
+<td>114.45</td>
+<td>30.17</td>
+<td>0.77</td>
+<td>0.50</td>
+<td>3.53</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0088</td>
+<td>149451</td>
+<td>68.62</td>
+<td>147.83</td>
+<td>28.24</td>
+<td>0.78</td>
+<td>0.49</td>
+<td>3.16</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0089</td>
+<td>173370</td>
+<td>86.58</td>
+<td>119.55</td>
+<td>26.95</td>
+<td>0.91</td>
+<td>0.63</td>
+<td>2.82</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0090</td>
+<td>168059</td>
+<td>85.48</td>
+<td>160.16</td>
+<td>34.64</td>
+<td>0.90</td>
+<td>0.57</td>
+<td>3.04</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0091</td>
+<td>170018</td>
+<td>89.40</td>
+<td>120.47</td>
+<td>32.40</td>
+<td>0.81</td>
+<td>0.52</td>
+<td>2.71</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0092</td>
+<td>167933</td>
+<td>77.98</td>
+<td>104.17</td>
+<td>30.92</td>
+<td>0.87</td>
+<td>0.61</td>
+<td>2.71</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0093</td>
+<td>169305</td>
+<td>67.32</td>
+<td>132.27</td>
+<td>30.50</td>
+<td>0.90</td>
+<td>0.63</td>
+<td>2.64</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0094</td>
+<td>164209</td>
+<td>75.47</td>
+<td>143.19</td>
+<td>29.49</td>
+<td>0.89</td>
+<td>0.61</td>
+<td>2.61</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0095</td>
+<td>176239</td>
+<td>76.33</td>
+<td>160.83</td>
+<td>38.91</td>
+<td>0.83</td>
+<td>0.56</td>
+<td>2.70</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0096</td>
+<td>170534</td>
+<td>109.00</td>
+<td>82.61</td>
+<td>32.38</td>
+<td>1.00</td>
+<td>0.63</td>
+<td>2.80</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0097</td>
+<td>174053</td>
+<td>113.35</td>
+<td>90.05</td>
+<td>13.00</td>
+<td>0.49</td>
+<td>0.35</td>
+<td>2.45</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0098</td>
+<td>175159</td>
+<td>110.08</td>
+<td>98.09</td>
+<td>29.74</td>
+<td>0.98</td>
+<td>0.65</td>
+<td>2.79</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0099</td>
+<td>179218</td>
+<td>100.78</td>
+<td>130.69</td>
+<td>27.53</td>
+<td>0.94</td>
+<td>0.64</td>
+<td>2.59</td>
+</tr>
+<tr>
+<td>7</td>
+<td>0100</td>
+<td>155500</td>
+<td>88.42</td>
+<td>121.54</td>
+<td>31.60</td>
+<td>0.95</td>
+<td>0.63</td>
+<td>2.33</td>
+</tr>
+</table>
+<a name="Lane"><h2>Lane 8</h2></a><table border="1" cellpadding="5">
+<tr>
+<td colspan="1">Lane </td>
+<td colspan="1">Tile</td>
+<td colspan="1">Clusters (raw)</td>
+<td colspan="1">Av 1st Cycle Int (PF)</td>
+<td colspan="1">Av % intensity after 20 cycles (PF)</td>
+<td colspan="1">% PF Clusters </td>
+<td colspan="1">% Align (PF) </td>
+<td colspan="1">Av Alignment Score (PF)</td>
+<td colspan="1">% Error Rate (PF) </td>
+</tr>
+<tr>
+<td>8</td>
+<td>0001</td>
+<td>181113</td>
+<td>113.72</td>
+<td>71.64</td>
+<td>0.81</td>
+<td>42.07</td>
+<td>37.02</td>
+<td>2.90</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0002</td>
+<td>167526</td>
+<td>104.83</td>
+<td>59.46</td>
+<td>2.21</td>
+<td>36.25</td>
+<td>31.47</td>
+<td>3.80</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0003</td>
+<td>162131</td>
+<td>95.08</td>
+<td>71.29</td>
+<td>1.99</td>
+<td>38.35</td>
+<td>32.69</td>
+<td>3.67</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0004</td>
+<td>174931</td>
+<td>122.20</td>
+<td>56.30</td>
+<td>2.18</td>
+<td>39.64</td>
+<td>29.88</td>
+<td>3.55</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0005</td>
+<td>188914</td>
+<td>111.88</td>
+<td>73.43</td>
+<td>2.71</td>
+<td>40.55</td>
+<td>35.06</td>
+<td>3.68</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0006</td>
+<td>176574</td>
+<td>95.43</td>
+<td>75.37</td>
+<td>3.00</td>
+<td>43.17</td>
+<td>37.84</td>
+<td>3.46</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0007</td>
+<td>177213</td>
+<td>109.32</td>
+<td>59.39</td>
+<td>2.43</td>
+<td>38.56</td>
+<td>33.27</td>
+<td>3.59</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0008</td>
+<td>179148</td>
+<td>99.52</td>
+<td>93.77</td>
+<td>3.16</td>
+<td>48.02</td>
+<td>44.06</td>
+<td>2.89</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0009</td>
+<td>184260</td>
+<td>102.12</td>
+<td>94.66</td>
+<td>3.02</td>
+<td>46.61</td>
+<td>42.16</td>
+<td>3.10</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0010</td>
+<td>161721</td>
+<td>88.95</td>
+<td>65.91</td>
+<td>2.61</td>
+<td>42.64</td>
+<td>38.68</td>
+<td>3.11</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0011</td>
+<td>181269</td>
+<td>102.85</td>
+<td>58.07</td>
+<td>2.57</td>
+<td>35.97</td>
+<td>30.41</td>
+<td>4.29</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0012</td>
+<td>182179</td>
+<td>105.47</td>
+<td>64.92</td>
+<td>2.96</td>
+<td>44.43</td>
+<td>40.58</td>
+<td>3.19</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0013</td>
+<td>167058</td>
+<td>98.97</td>
+<td>72.42</td>
+<td>2.95</td>
+<td>43.82</td>
+<td>38.67</td>
+<td>3.18</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0014</td>
+<td>181070</td>
+<td>103.45</td>
+<td>69.43</td>
+<td>2.99</td>
+<td>44.72</td>
+<td>39.39</td>
+<td>3.40</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0015</td>
+<td>192314</td>
+<td>128.22</td>
+<td>52.41</td>
+<td>2.78</td>
+<td>40.50</td>
+<td>33.40</td>
+<td>3.57</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0016</td>
+<td>198144</td>
+<td>120.30</td>
+<td>56.90</td>
+<td>2.92</td>
+<td>44.39</td>
+<td>37.08</td>
+<td>3.62</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0017</td>
+<td>202972</td>
+<td>130.85</td>
+<td>59.51</td>
+<td>3.12</td>
+<td>47.29</td>
+<td>40.24</td>
+<td>3.47</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0018</td>
+<td>201819</td>
+<td>134.90</td>
+<td>52.71</td>
+<td>2.80</td>
+<td>43.91</td>
+<td>37.45</td>
+<td>3.57</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0019</td>
+<td>209515</td>
+<td>140.47</td>
+<td>47.41</td>
+<td>2.84</td>
+<td>46.16</td>
+<td>39.35</td>
+<td>3.52</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0020</td>
+<td>176152</td>
+<td>111.72</td>
+<td>60.06</td>
+<td>2.60</td>
+<td>40.87</td>
+<td>33.93</td>
+<td>3.67</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0021</td>
+<td>176568</td>
+<td>110.15</td>
+<td>68.32</td>
+<td>2.59</td>
+<td>44.62</td>
+<td>39.06</td>
+<td>3.50</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0022</td>
+<td>169602</td>
+<td>108.75</td>
+<td>75.79</td>
+<td>2.87</td>
+<td>48.08</td>
+<td>43.73</td>
+<td>2.98</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0023</td>
+<td>189324</td>
+<td>102.48</td>
+<td>83.46</td>
+<td>2.88</td>
+<td>47.06</td>
+<td>41.36</td>
+<td>3.52</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0024</td>
+<td>171625</td>
+<td>116.58</td>
+<td>60.63</td>
+<td>2.98</td>
+<td>47.61</td>
+<td>42.47</td>
+<td>3.45</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0025</td>
+<td>162092</td>
+<td>97.65</td>
+<td>114.41</td>
+<td>3.23</td>
+<td>48.32</td>
+<td>39.95</td>
+<td>3.10</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0026</td>
+<td>166734</td>
+<td>106.85</td>
+<td>113.29</td>
+<td>3.35</td>
+<td>50.81</td>
+<td>46.10</td>
+<td>3.18</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0027</td>
+<td>188771</td>
+<td>135.70</td>
+<td>89.90</td>
+<td>3.36</td>
+<td>51.36</td>
+<td>45.20</td>
+<td>3.37</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0028</td>
+<td>208995</td>
+<td>143.78</td>
+<td>74.37</td>
+<td>3.45</td>
+<td>50.66</td>
+<td>44.31</td>
+<td>3.37</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0029</td>
+<td>189249</td>
+<td>102.97</td>
+<td>101.46</td>
+<td>3.50</td>
+<td>50.70</td>
+<td>46.13</td>
+<td>3.16</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0030</td>
+<td>168319</td>
+<td>113.25</td>
+<td>101.50</td>
+<td>3.39</td>
+<td>52.52</td>
+<td>47.69</td>
+<td>3.17</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0031</td>
+<td>207260</td>
+<td>148.28</td>
+<td>65.94</td>
+<td>3.55</td>
+<td>53.16</td>
+<td>47.51</td>
+<td>3.22</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0032</td>
+<td>214286</td>
+<td>117.77</td>
+<td>99.77</td>
+<td>3.03</td>
+<td>50.93</td>
+<td>46.69</td>
+<td>3.12</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0033</td>
+<td>204280</td>
+<td>138.05</td>
+<td>64.51</td>
+<td>3.33</td>
+<td>51.13</td>
+<td>45.23</td>
+<td>3.30</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0034</td>
+<td>218862</td>
+<td>143.27</td>
+<td>49.69</td>
+<td>3.55</td>
+<td>51.46</td>
+<td>45.87</td>
+<td>3.09</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0035</td>
+<td>212945</td>
+<td>126.78</td>
+<td>69.43</td>
+<td>4.04</td>
+<td>53.35</td>
+<td>47.42</td>
+<td>3.14</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0036</td>
+<td>209646</td>
+<td>122.40</td>
+<td>79.74</td>
+<td>4.18</td>
+<td>55.94</td>
+<td>50.40</td>
+<td>2.95</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0037</td>
+<td>200910</td>
+<td>112.30</td>
+<td>88.69</td>
+<td>3.84</td>
+<td>54.07</td>
+<td>49.68</td>
+<td>2.95</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0038</td>
+<td>208228</td>
+<td>123.53</td>
+<td>81.38</td>
+<td>3.92</td>
+<td>55.25</td>
+<td>50.76</td>
+<td>2.99</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0039</td>
+<td>218373</td>
+<td>131.20</td>
+<td>81.63</td>
+<td>3.97</td>
+<td>56.80</td>
+<td>52.26</td>
+<td>2.90</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0040</td>
+<td>210877</td>
+<td>115.60</td>
+<td>85.62</td>
+<td>3.96</td>
+<td>54.54</td>
+<td>49.83</td>
+<td>2.95</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0041</td>
+<td>205484</td>
+<td>116.00</td>
+<td>87.26</td>
+<td>3.78</td>
+<td>52.58</td>
+<td>44.03</td>
+<td>4.66</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0042</td>
+<td>201755</td>
+<td>105.40</td>
+<td>94.12</td>
+<td>3.80</td>
+<td>55.66</td>
+<td>50.72</td>
+<td>3.11</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0043</td>
+<td>199044</td>
+<td>120.75</td>
+<td>80.35</td>
+<td>3.68</td>
+<td>52.99</td>
+<td>47.78</td>
+<td>3.14</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0044</td>
+<td>194716</td>
+<td>120.93</td>
+<td>74.05</td>
+<td>3.68</td>
+<td>53.35</td>
+<td>48.07</td>
+<td>3.23</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0045</td>
+<td>213702</td>
+<td>129.80</td>
+<td>66.41</td>
+<td>3.85</td>
+<td>53.60</td>
+<td>47.91</td>
+<td>3.10</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0046</td>
+<td>212117</td>
+<td>108.75</td>
+<td>94.67</td>
+<td>3.85</td>
+<td>58.02</td>
+<td>53.31</td>
+<td>3.05</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0047</td>
+<td>222320</td>
+<td>108.62</td>
+<td>96.18</td>
+<td>3.64</td>
+<td>58.31</td>
+<td>54.05</td>
+<td>2.86</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0048</td>
+<td>215015</td>
+<td>129.85</td>
+<td>70.45</td>
+<td>4.02</td>
+<td>55.05</td>
+<td>48.91</td>
+<td>3.28</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0049</td>
+<td>212941</td>
+<td>128.05</td>
+<td>67.36</td>
+<td>3.95</td>
+<td>53.58</td>
+<td>47.39</td>
+<td>3.30</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0050</td>
+<td>207426</td>
+<td>119.88</td>
+<td>70.70</td>
+<td>4.27</td>
+<td>54.22</td>
+<td>49.05</td>
+<td>3.14</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0051</td>
+<td>221088</td>
+<td>120.48</td>
+<td>80.27</td>
+<td>4.98</td>
+<td>57.34</td>
+<td>52.49</td>
+<td>3.09</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0052</td>
+<td>206850</td>
+<td>113.85</td>
+<td>77.05</td>
+<td>4.28</td>
+<td>54.50</td>
+<td>50.16</td>
+<td>3.13</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0053</td>
+<td>218985</td>
+<td>128.35</td>
+<td>71.39</td>
+<td>4.41</td>
+<td>55.46</td>
+<td>50.36</td>
+<td>3.15</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0054</td>
+<td>215826</td>
+<td>125.50</td>
+<td>80.32</td>
+<td>4.51</td>
+<td>55.57</td>
+<td>51.03</td>
+<td>3.05</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0055</td>
+<td>217025</td>
+<td>123.75</td>
+<td>81.94</td>
+<td>4.38</td>
+<td>55.06</td>
+<td>50.34</td>
+<td>3.06</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0056</td>
+<td>221504</td>
+<td>133.72</td>
+<td>65.40</td>
+<td>4.17</td>
+<td>53.49</td>
+<td>48.73</td>
+<td>3.11</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0057</td>
+<td>217253</td>
+<td>125.78</td>
+<td>59.09</td>
+<td>4.01</td>
+<td>53.60</td>
+<td>48.37</td>
+<td>3.17</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0058</td>
+<td>217739</td>
+<td>133.20</td>
+<td>59.31</td>
+<td>3.90</td>
+<td>53.23</td>
+<td>46.93</td>
+<td>3.35</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0059</td>
+<td>206901</td>
+<td>119.10</td>
+<td>103.57</td>
+<td>3.59</td>
+<td>51.59</td>
+<td>45.56</td>
+<td>3.32</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0060</td>
+<td>209276</td>
+<td>133.25</td>
+<td>75.55</td>
+<td>3.91</td>
+<td>53.23</td>
+<td>48.59</td>
+<td>3.14</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0061</td>
+<td>206912</td>
+<td>116.42</td>
+<td>80.82</td>
+<td>3.87</td>
+<td>53.37</td>
+<td>48.65</td>
+<td>3.08</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0062</td>
+<td>208293</td>
+<td>130.93</td>
+<td>76.97</td>
+<td>3.99</td>
+<td>53.83</td>
+<td>49.45</td>
+<td>3.02</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0063</td>
+<td>216117</td>
+<td>140.85</td>
+<td>68.02</td>
+<td>4.04</td>
+<td>53.96</td>
+<td>48.52</td>
+<td>3.11</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0064</td>
+<td>221626</td>
+<td>138.38</td>
+<td>68.51</td>
+<td>4.07</td>
+<td>53.99</td>
+<td>48.41</td>
+<td>3.20</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0065</td>
+<td>226495</td>
+<td>119.17</td>
+<td>90.85</td>
+<td>4.26</td>
+<td>56.13</td>
+<td>52.26</td>
+<td>2.98</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0066</td>
+<td>211217</td>
+<td>122.75</td>
+<td>77.13</td>
+<td>4.07</td>
+<td>53.13</td>
+<td>48.26</td>
+<td>3.22</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0067</td>
+<td>207572</td>
+<td>117.97</td>
+<td>85.40</td>
+<td>3.90</td>
+<td>53.48</td>
+<td>48.97</td>
+<td>3.05</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0068</td>
+<td>212274</td>
+<td>130.47</td>
+<td>77.97</td>
+<td>3.78</td>
+<td>50.92</td>
+<td>45.66</td>
+<td>3.15</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0069</td>
+<td>225642</td>
+<td>151.32</td>
+<td>67.64</td>
+<td>3.43</td>
+<td>47.99</td>
+<td>41.14</td>
+<td>3.46</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0070</td>
+<td>206267</td>
+<td>130.85</td>
+<td>44.73</td>
+<td>3.04</td>
+<td>45.20</td>
+<td>39.72</td>
+<td>3.49</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0071</td>
+<td>197697</td>
+<td>116.00</td>
+<td>55.88</td>
+<td>2.87</td>
+<td>44.86</td>
+<td>40.68</td>
+<td>3.36</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0072</td>
+<td>201670</td>
+<td>131.15</td>
+<td>54.33</td>
+<td>3.10</td>
+<td>49.63</td>
+<td>45.22</td>
+<td>3.23</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0073</td>
+<td>199697</td>
+<td>141.85</td>
+<td>52.34</td>
+<td>3.21</td>
+<td>50.81</td>
+<td>47.20</td>
+<td>2.95</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0074</td>
+<td>220224</td>
+<td>146.22</td>
+<td>66.88</td>
+<td>3.10</td>
+<td>50.24</td>
+<td>45.85</td>
+<td>3.01</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0075</td>
+<td>198282</td>
+<td>128.40</td>
+<td>72.59</td>
+<td>3.17</td>
+<td>50.41</td>
+<td>46.14</td>
+<td>3.12</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0076</td>
+<td>187773</td>
+<td>131.55</td>
+<td>66.25</td>
+<td>3.30</td>
+<td>50.96</td>
+<td>45.63</td>
+<td>3.20</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0077</td>
+<td>186366</td>
+<td>113.85</td>
+<td>100.55</td>
+<td>3.13</td>
+<td>50.32</td>
+<td>46.19</td>
+<td>3.15</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0078</td>
+<td>189058</td>
+<td>133.57</td>
+<td>91.37</td>
+<td>2.90</td>
+<td>47.01</td>
+<td>42.99</td>
+<td>3.19</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0079</td>
+<td>213947</td>
+<td>164.30</td>
+<td>57.35</td>
+<td>3.15</td>
+<td>48.49</td>
+<td>44.57</td>
+<td>3.09</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0080</td>
+<td>221739</td>
+<td>121.95</td>
+<td>74.93</td>
+<td>2.71</td>
+<td>44.15</td>
+<td>35.06</td>
+<td>3.23</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0081</td>
+<td>205044</td>
+<td>132.75</td>
+<td>49.51</td>
+<td>2.79</td>
+<td>42.39</td>
+<td>36.71</td>
+<td>3.48</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0082</td>
+<td>210695</td>
+<td>129.43</td>
+<td>80.63</td>
+<td>3.56</td>
+<td>49.50</td>
+<td>44.26</td>
+<td>3.29</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0083</td>
+<td>217007</td>
+<td>139.45</td>
+<td>72.43</td>
+<td>3.47</td>
+<td>48.63</td>
+<td>40.36</td>
+<td>3.69</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0084</td>
+<td>210797</td>
+<td>144.35</td>
+<td>70.66</td>
+<td>3.42</td>
+<td>48.64</td>
+<td>43.35</td>
+<td>3.29</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0085</td>
+<td>211477</td>
+<td>142.22</td>
+<td>73.72</td>
+<td>3.38</td>
+<td>47.64</td>
+<td>36.57</td>
+<td>3.26</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0086</td>
+<td>200320</td>
+<td>117.95</td>
+<td>63.59</td>
+<td>3.03</td>
+<td>45.15</td>
+<td>40.32</td>
+<td>3.25</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0087</td>
+<td>204295</td>
+<td>136.75</td>
+<td>57.18</td>
+<td>3.35</td>
+<td>48.24</td>
+<td>44.39</td>
+<td>3.04</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0088</td>
+<td>203778</td>
+<td>141.18</td>
+<td>65.79</td>
+<td>3.21</td>
+<td>46.79</td>
+<td>42.24</td>
+<td>3.13</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0089</td>
+<td>209681</td>
+<td>124.65</td>
+<td>82.43</td>
+<td>3.22</td>
+<td>47.89</td>
+<td>44.07</td>
+<td>2.95</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0090</td>
+<td>199690</td>
+<td>129.05</td>
+<td>69.82</td>
+<td>2.96</td>
+<td>46.20</td>
+<td>41.84</td>
+<td>3.11</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0091</td>
+<td>202793</td>
+<td>119.60</td>
+<td>79.18</td>
+<td>3.13</td>
+<td>47.41</td>
+<td>42.53</td>
+<td>3.12</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0092</td>
+<td>188850</td>
+<td>109.60</td>
+<td>74.64</td>
+<td>3.17</td>
+<td>44.81</td>
+<td>40.44</td>
+<td>3.26</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0093</td>
+<td>188951</td>
+<td>119.95</td>
+<td>77.43</td>
+<td>2.89</td>
+<td>44.14</td>
+<td>39.45</td>
+<td>3.29</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0094</td>
+<td>177924</td>
+<td>125.05</td>
+<td>71.75</td>
+<td>2.19</td>
+<td>37.05</td>
+<td>29.90</td>
+<td>3.82</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0095</td>
+<td>175164</td>
+<td>103.00</td>
+<td>88.76</td>
+<td>2.79</td>
+<td>40.67</td>
+<td>29.89</td>
+<td>3.75</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0096</td>
+<td>170691</td>
+<td>109.40</td>
+<td>74.77</td>
+<td>1.27</td>
+<td>37.54</td>
+<td>31.24</td>
+<td>4.00</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0097</td>
+<td>177715</td>
+<td>0.00</td>
+<td>N/A</td>
+<td>0.00</td>
+<td>0.00</td>
+<td>0.00</td>
+<td>0.00</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0098</td>
+<td>168260</td>
+<td>121.38</td>
+<td>64.30</td>
+<td>2.14</td>
+<td>36.20</td>
+<td>20.99</td>
+<td>3.23</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0099</td>
+<td>202629</td>
+<td>149.72</td>
+<td>72.23</td>
+<td>2.51</td>
+<td>41.13</td>
+<td>34.74</td>
+<td>3.56</td>
+</tr>
+<tr>
+<td>8</td>
+<td>0100</td>
+<td>162617</td>
+<td>125.72</td>
+<td>66.75</td>
+<td>2.53</td>
+<td>41.07</td>
+<td>31.79</td>
+<td>3.48</td>
+</tr>
+</table>
+<li><h3>IVC Plots</h3></li>
+<a href="../IVC.htm"> click here 
+   </a><h3><li>All Intensity Plots</li></h3>
+<a href="../All.htm"> click here
+   </a><h3><li>Error Graphs</li></h3>
+<a href="Error.htm"> click here
+   </a><h3><li>Error Curves</li></h3>
+<a href="Perfect.htm"> click here
+   </a>
+</body></html>
diff --git a/trunk/htsworkflow/pipelines/test/testdata/Summary-paired-pipeline110.htm b/trunk/htsworkflow/pipelines/test/testdata/Summary-paired-pipeline110.htm
new file mode 100644 (file)
index 0000000..5b41c1e
--- /dev/null
@@ -0,0 +1,662 @@
+<!--RUN_TIME Thu Nov 13 15:11:29 2008 -->
+<!--SOFTWARE_VERSION @(#) $Id: jerboa.pl,v 1.94 2007/12/04 09:59:07 rshaw Exp $-->
+<html>
+<body>
+
+<a name="Top"><h2><title>080920_HWI-EAS229_0057_30GBJAAXX Summary</title></h2></a>
+<h1>Summary Information For Experiment 080920_HWI-EAS229_0057_30GBJAAXX on Machine unknown</h1>
+<h2><br></br>Chip Summary<br></br></h2>
+<table border="1" cellpadding="5">
+<tr><td>Machine</td><td>UNKNOWN</td></tr>
+<tr><td>Run Folder</td><td>080920_HWI-EAS229_0057_30GBJAAXX</td></tr>
+<tr><td>Chip ID</td><td>unknown</td></tr>
+</table>
+<h2><br></br>Chip Results Summary<br></br></h2>
+<table border="1" cellpadding="5">
+<tr>
+<td>Clusters</td>
+<td>Clusters (PF)</td>
+<td>Yield (kbases)</td>
+</tr>
+<tr><td>126151880</td>
+<td>95923456</td>
+<td>3549167</td>
+</tr>
+</table>
+<h2><br></br>Lane Parameter Summary<br></br></h2>
+<table border="1" cellpadding="5">
+<tr>
+<td>Lane</td>
+<td>Sample ID</td>
+<td>Sample Target</td>
+<td>Sample Type</td>
+<td>Length</td>
+<td>Filter</td>
+<td>Num Tiles</td>
+<td>Tiles</td>
+</tr>
+<tr>
+<td>1</td>
+<td>unknown</td>
+<td>mm9</td>
+<td>ELAND_PAIR</td>
+<td>37, 37</td>
+<td>'((CHASTITY>=0.6))'</td>
+<td>100</td>
+<td><a href="#Lane1">Lane 1</a></td>
+</tr>
+<tr>
+<td>2</td>
+<td>unknown</td>
+<td>hg18</td>
+<td>ELAND_PAIR</td>
+<td>37, 37</td>
+<td>'((CHASTITY>=0.6))'</td>
+<td>100</td>
+<td><a href="#Lane2">Lane 2</a></td>
+</tr>
+<tr>
+<td>3</td>
+<td>unknown</td>
+<td>mm9</td>
+<td>ELAND_PAIR</td>
+<td>37, 37</td>
+<td>'((CHASTITY>=0.6))'</td>
+<td>100</td>
+<td><a href="#Lane3">Lane 3</a></td>
+</tr>
+<tr>
+<td>4</td>
+<td>unknown</td>
+<td>mm9</td>
+<td>ELAND_PAIR</td>
+<td>37, 37</td>
+<td>'((CHASTITY>=0.6))'</td>
+<td>100</td>
+<td><a href="#Lane4">Lane 4</a></td>
+</tr>
+<tr>
+<td>5</td>
+<td>unknown</td>
+<td>mm9</td>
+<td>ELAND_PAIR</td>
+<td>37, 37</td>
+<td>'((CHASTITY>=0.6))'</td>
+<td>100</td>
+<td><a href="#Lane5">Lane 5</a></td>
+</tr>
+<tr>
+<td>6</td>
+<td>unknown</td>
+<td>hg18</td>
+<td>ELAND_PAIR</td>
+<td>37, 37</td>
+<td>'((CHASTITY>=0.6))'</td>
+<td>100</td>
+<td><a href="#Lane6">Lane 6</a></td>
+</tr>
+<tr>
+<td>7</td>
+<td>unknown</td>
+<td>mm9</td>
+<td>ELAND_PAIR</td>
+<td>37, 37</td>
+<td>'((CHASTITY>=0.6))'</td>
+<td>100</td>
+<td><a href="#Lane7">Lane 7</a></td>
+</tr>
+<tr>
+<td>8</td>
+<td>unknown</td>
+<td>mm9</td>
+<td>ELAND_PAIR</td>
+<td>37, 37</td>
+<td>'((CHASTITY>=0.6))'</td>
+<td>100</td>
+<td><a href="#Lane8">Lane 8</a></td>
+</tr>
+</table>
+<h2><br></br>Lane Results Summary : Read 1<br></br></h2>
+<table border="1" cellpadding="5">
+<tr>
+<td colspan="2">Lane Info</td>
+<td colspan="8">Tile Mean +/- SD for Lane</td>
+</tr>
+<tr>
+<td>Lane </td>
+<td>Lane Yield (kbases) </td>
+<td>Clusters (raw)</td>
+<td>Clusters (PF) </td>
+<td>1st Cycle Int (PF) </td>
+<td>% intensity after 20 cycles (PF) </td>
+<td>% PF Clusters </td>
+<td>% Align (PF) </td>
+<td>Alignment Score (PF) </td>
+<td> % Error Rate (PF) </td>
+</tr>
+<tr>
+<td>1</td>
+<td>277083</td>
+<td>103646 +/- 4515</td>
+<td>74887 +/- 6080</td>
+<td>290 +/- 17</td>
+<td>99.34 +/- 3.52</td>
+<td>72.22 +/- 4.63</td>
+<td>89.19 +/- 0.59</td>
+<td>14.16 +/- 0.63</td>
+<td>0.94 +/- 0.17</td>
+</tr>
+<tr>
+<td>2</td>
+<td>289563</td>
+<td>106678 +/- 4652</td>
+<td>78260 +/- 2539</td>
+<td>294 +/- 16</td>
+<td>98.23 +/- 2.66</td>
+<td>73.43 +/- 2.52</td>
+<td>87.05 +/- 0.64</td>
+<td>16.81 +/- 0.55</td>
+<td>0.92 +/- 0.17</td>
+</tr>
+<tr>
+<td>3</td>
+<td>259242</td>
+<td>84583 +/- 5963</td>
+<td>70065 +/- 4194</td>
+<td>284 +/- 18</td>
+<td>99.82 +/- 3.05</td>
+<td>82.90 +/- 1.32</td>
+<td>89.49 +/- 0.20</td>
+<td>18.13 +/- 0.66</td>
+<td>0.81 +/- 0.13</td>
+</tr>
+<tr>
+<td>4</td>
+<td>210549</td>
+<td>68813 +/- 4782</td>
+<td>56905 +/- 4145</td>
+<td>300 +/- 29</td>
+<td>102.00 +/- 14.74</td>
+<td>82.91 +/- 5.89</td>
+<td>56.93 +/- 0.82</td>
+<td>25.85 +/- 2.30</td>
+<td>0.95 +/- 0.30</td>
+</tr>
+<tr>
+<td>5</td>
+<td>295555</td>
+<td>104854 +/- 4664</td>
+<td>79879 +/- 6270</td>
+<td>281 +/- 19</td>
+<td>98.26 +/- 5.85</td>
+<td>76.34 +/- 6.67</td>
+<td>57.71 +/- 0.30</td>
+<td>26.16 +/- 1.68</td>
+<td>0.97 +/- 0.19</td>
+</tr>
+<tr>
+<td>6</td>
+<td>140401</td>
+<td>43555 +/- 1632</td>
+<td>37946 +/- 2140</td>
+<td>233 +/- 16</td>
+<td>105.74 +/- 8.40</td>
+<td>87.14 +/- 3.87</td>
+<td>89.08 +/- 1.00</td>
+<td>33.53 +/- 2.18</td>
+<td>1.05 +/- 0.21</td>
+</tr>
+<tr>
+<td>7</td>
+<td>154217</td>
+<td>54265 +/- 1588</td>
+<td>41680 +/- 5319</td>
+<td>224 +/- 18</td>
+<td>111.33 +/- 8.90</td>
+<td>76.94 +/- 10.52</td>
+<td>84.50 +/- 1.41</td>
+<td>27.44 +/- 2.33</td>
+<td>1.32 +/- 0.25</td>
+</tr>
+<tr>
+<td>8</td>
+<td>147969</td>
+<td>64363 +/- 2697</td>
+<td>39991 +/- 6785</td>
+<td>248 +/- 43</td>
+<td>109.93 +/- 7.80</td>
+<td>62.45 +/- 12.05</td>
+<td>82.20 +/- 2.08</td>
+<td>24.63 +/- 2.53</td>
+<td>1.57 +/- 0.22</td>
+</tr>
+<tr><td colspan="13">Tile mean across chip</td></tr>
+<tr>
+<td>Av.</td>
+<td></td>
+<td>78844</td>
+<td>59952</td>
+<td>269</td>
+<td>103.08</td>
+<td>76.79</td>
+<td>79.52</td>
+<td>23.34</td>
+<td>1.06</td>
+</tr>
+</table>
+<h2><br></br>Lane Results Summary : Read 2<br></br></h2>
+<table border="1" cellpadding="5">
+<tr>
+<td colspan="2">Lane Info</td>
+<td colspan="8">Tile Mean +/- SD for Lane</td>
+</tr>
+<tr>
+<td>Lane </td>
+<td>Lane Yield (kbases) </td>
+<td>Clusters (raw)</td>
+<td>Clusters (PF) </td>
+<td>1st Cycle Int (PF) </td>
+<td>% intensity after 20 cycles (PF) </td>
+<td>% PF Clusters </td>
+<td>% Align (PF) </td>
+<td>Alignment Score (PF) </td>
+<td> % Error Rate (PF) </td>
+</tr>
+<tr>
+<td>1</td>
+<td>277083</td>
+<td>103647 +/- 4516</td>
+<td>74887 +/- 6080</td>
+<td>277 +/- 17</td>
+<td>94.42 +/- 5.68</td>
+<td>72.22 +/- 4.63</td>
+<td>81.54 +/- 2.13</td>
+<td>42.70 +/- 5.49</td>
+<td>0.89 +/- 0.27</td>
+</tr>
+<tr>
+<td>2</td>
+<td>289563</td>
+<td>106679 +/- 4653</td>
+<td>78260 +/- 2539</td>
+<td>259 +/- 13</td>
+<td>93.57 +/- 2.55</td>
+<td>73.43 +/- 2.52</td>
+<td>82.05 +/- 0.37</td>
+<td>43.98 +/- 3.02</td>
+<td>0.76 +/- 0.15</td>
+</tr>
+<tr>
+<td>3</td>
+<td>259242</td>
+<td>84584 +/- 5964</td>
+<td>70065 +/- 4194</td>
+<td>252 +/- 12</td>
+<td>94.23 +/- 2.19</td>
+<td>82.90 +/- 1.32</td>
+<td>84.94 +/- 0.28</td>
+<td>51.76 +/- 2.29</td>
+<td>0.59 +/- 0.07</td>
+</tr>
+<tr>
+<td>4</td>
+<td>210549</td>
+<td>68814 +/- 4783</td>
+<td>56905 +/- 4145</td>
+<td>226 +/- 16</td>
+<td>96.82 +/- 7.12</td>
+<td>82.91 +/- 5.89</td>
+<td>56.01 +/- 0.99</td>
+<td>27.86 +/- 3.48</td>
+<td>0.95 +/- 0.33</td>
+</tr>
+<tr>
+<td>5</td>
+<td>295555</td>
+<td>104855 +/- 4665</td>
+<td>79879 +/- 6270</td>
+<td>200 +/- 24</td>
+<td>103.56 +/- 15.45</td>
+<td>76.34 +/- 6.67</td>
+<td>56.76 +/- 0.41</td>
+<td>25.68 +/- 2.06</td>
+<td>0.98 +/- 0.17</td>
+</tr>
+<tr>
+<td>6</td>
+<td>140401</td>
+<td>43556 +/- 1633</td>
+<td>37946 +/- 2140</td>
+<td>179 +/- 10</td>
+<td>100.82 +/- 5.47</td>
+<td>87.14 +/- 3.87</td>
+<td>88.64 +/- 1.42</td>
+<td>34.05 +/- 2.60</td>
+<td>0.98 +/- 0.22</td>
+</tr>
+<tr>
+<td>7</td>
+<td>154217</td>
+<td>54266 +/- 1589</td>
+<td>41680 +/- 5319</td>
+<td>184 +/- 5</td>
+<td>103.42 +/- 3.47</td>
+<td>76.94 +/- 10.52</td>
+<td>83.90 +/- 1.32</td>
+<td>27.60 +/- 2.07</td>
+<td>1.26 +/- 0.16</td>
+</tr>
+<tr>
+<td>8</td>
+<td>147969</td>
+<td>64364 +/- 2698</td>
+<td>39991 +/- 6785</td>
+<td>206 +/- 31</td>
+<td>99.48 +/- 3.23</td>
+<td>62.45 +/- 12.05</td>
+<td>79.81 +/- 3.35</td>
+<td>23.06 +/- 2.50</td>
+<td>1.56 +/- 0.23</td>
+</tr>
+<tr><td colspan="13">Tile mean across chip</td></tr>
+<tr>
+<td>Av.</td>
+<td></td>
+<td>78844</td>
+<td>59952</td>
+<td>223</td>
+<td>98.29</td>
+<td>76.79</td>
+<td>76.70</td>
+<td>34.59</td>
+<td>1.00</td>
+</tr>
+</table>
+<h2><br></br>Expanded Lane Summary : Read 1<br></br></h2>
+<table border="1" cellpadding="5">
+<tr>
+
+<tr><td colspan="2">Lane Info</td>
+<td colspan="2">Phasing Info</td>
+<td colspan="2">Raw Data (tile mean)</td>
+<td colspan="7">Filtered Data (tile mean)</td></tr>
+<td>Lane </td>
+<td>Clusters (tile mean) (raw)</td>
+<td>% Phasing </td>
+<td>% Prephasing </td>
+<td>% Error Rate (raw) </td>
+<td> Equiv Perfect Clusters (raw) </td>
+<td>% retained </td>
+<td>Cycle 2-4 Av Int (PF) </td>
+<td>Cycle 2-10 Av % Loss (PF) </td>
+<td>Cycle 10-20 Av % Loss (PF) </td>
+<td>% Align (PF) </td>
+<td>% Error Rate (PF) </td>
+<td> Equiv Perfect Clusters (PF) </td>
+</tr>
+<tr>
+<td>1</td>
+<td>103646</td>
+<td>0.8600</td>
+<td>0.4900</td>
+<td>1.37</td>
+<td>74813</td>
+<td>72.22</td>
+<td>266 +/- 17</td>
+<td>-0.53 +/- 0.37</td>
+<td>-0.42 +/- 0.21</td>
+<td>89.19</td>
+<td>0.94</td>
+<td>64718</td>
+</tr>
+<tr>
+<td>2</td>
+<td>106678</td>
+<td>0.8600</td>
+<td>0.4900</td>
+<td>1.34</td>
+<td>74842</td>
+<td>73.43</td>
+<td>284 +/- 16</td>
+<td>0.08 +/- 0.43</td>
+<td>-0.17 +/- 0.34</td>
+<td>87.05</td>
+<td>0.92</td>
+<td>65850</td>
+</tr>
+<tr>
+<td>3</td>
+<td>84583</td>
+<td>0.8600</td>
+<td>0.4900</td>
+<td>1.09</td>
+<td>65493</td>
+<td>82.90</td>
+<td>286 +/- 14</td>
+<td>0.29 +/- 0.48</td>
+<td>-0.02 +/- 0.17</td>
+<td>89.49</td>
+<td>0.81</td>
+<td>60899</td>
+</tr>
+<tr>
+<td>4</td>
+<td>68813</td>
+<td>0.8600</td>
+<td>0.4900</td>
+<td>1.19</td>
+<td>33697</td>
+<td>82.91</td>
+<td>286 +/- 23</td>
+<td>-0.01 +/- 0.62</td>
+<td>-0.37 +/- 0.30</td>
+<td>56.93</td>
+<td>0.95</td>
+<td>31080</td>
+</tr>
+<tr>
+<td>5</td>
+<td>104854</td>
+<td>0.8600</td>
+<td>0.4900</td>
+<td>1.32</td>
+<td>50075</td>
+<td>76.34</td>
+<td>258 +/- 25</td>
+<td>-0.03 +/- 0.46</td>
+<td>-0.49 +/- 0.27</td>
+<td>57.71</td>
+<td>0.97</td>
+<td>44149</td>
+</tr>
+<tr>
+<td>6</td>
+<td>43555</td>
+<td>0.8600</td>
+<td>0.4900</td>
+<td>1.24</td>
+<td>34399</td>
+<td>87.14</td>
+<td>231 +/- 14</td>
+<td>-0.19 +/- 0.46</td>
+<td>-0.34 +/- 0.40</td>
+<td>89.08</td>
+<td>1.05</td>
+<td>32302</td>
+</tr>
+<tr>
+<td>7</td>
+<td>54265</td>
+<td>0.8600</td>
+<td>0.4900</td>
+<td>1.67</td>
+<td>38188</td>
+<td>76.94</td>
+<td>224 +/- 14</td>
+<td>-0.41 +/- 0.49</td>
+<td>-0.55 +/- 0.23</td>
+<td>84.50</td>
+<td>1.32</td>
+<td>33435</td>
+</tr>
+<tr>
+<td>8</td>
+<td>64363</td>
+<td>0.8600</td>
+<td>0.4900</td>
+<td>2.15</td>
+<td>38077</td>
+<td>62.45</td>
+<td>247 +/- 42</td>
+<td>-0.52 +/- 0.36</td>
+<td>-0.29 +/- 0.19</td>
+<td>82.20</td>
+<td>1.57</td>
+<td>31036</td>
+</tr>
+</table>
+<h2><br></br>Expanded Lane Summary : Read 2<br></br></h2>
+<table border="1" cellpadding="5">
+<tr>
+
+<tr><td colspan="2">Lane Info</td>
+<td colspan="2">Phasing Info</td>
+<td colspan="2">Raw Data (tile mean)</td>
+<td colspan="7">Filtered Data (tile mean)</td></tr>
+<td>Lane </td>
+<td>Clusters (tile mean) (raw)</td>
+<td>% Phasing </td>
+<td>% Prephasing </td>
+<td>% Error Rate (raw) </td>
+<td> Equiv Perfect Clusters (raw) </td>
+<td>% retained </td>
+<td>Cycle 2-4 Av Int (PF) </td>
+<td>Cycle 2-10 Av % Loss (PF) </td>
+<td>Cycle 10-20 Av % Loss (PF) </td>
+<td>% Align (PF) </td>
+<td>% Error Rate (PF) </td>
+<td> Equiv Perfect Clusters (PF) </td>
+</tr>
+<tr>
+<td>1</td>
+<td>103646</td>
+<td>0.7900</td>
+<td>0.4600</td>
+<td>1.24</td>
+<td>68870</td>
+<td>72.22</td>
+<td>254 +/- 15</td>
+<td>-0.53 +/- 0.37</td>
+<td>-0.42 +/- 0.21</td>
+<td>81.54</td>
+<td>0.89</td>
+<td>59272</td>
+</tr>
+<tr>
+<td>2</td>
+<td>106678</td>
+<td>0.7900</td>
+<td>0.4600</td>
+<td>1.11</td>
+<td>71980</td>
+<td>73.43</td>
+<td>247 +/- 12</td>
+<td>0.08 +/- 0.43</td>
+<td>-0.17 +/- 0.34</td>
+<td>82.05</td>
+<td>0.76</td>
+<td>62240</td>
+</tr>
+<tr>
+<td>3</td>
+<td>84583</td>
+<td>0.7900</td>
+<td>0.4600</td>
+<td>0.80</td>
+<td>63500</td>
+<td>82.90</td>
+<td>243 +/- 8</td>
+<td>0.29 +/- 0.48</td>
+<td>-0.02 +/- 0.17</td>
+<td>84.94</td>
+<td>0.59</td>
+<td>58029</td>
+</tr>
+<tr>
+<td>4</td>
+<td>68813</td>
+<td>0.7900</td>
+<td>0.4600</td>
+<td>1.12</td>
+<td>33534</td>
+<td>82.91</td>
+<td>210 +/- 19</td>
+<td>-0.01 +/- 0.62</td>
+<td>-0.37 +/- 0.30</td>
+<td>56.01</td>
+<td>0.95</td>
+<td>30548</td>
+</tr>
+<tr>
+<td>5</td>
+<td>104854</td>
+<td>0.7900</td>
+<td>0.4600</td>
+<td>1.24</td>
+<td>49951</td>
+<td>76.34</td>
+<td>193 +/- 12</td>
+<td>-0.03 +/- 0.46</td>
+<td>-0.49 +/- 0.27</td>
+<td>56.76</td>
+<td>0.98</td>
+<td>43366</td>
+</tr>
+<tr>
+<td>6</td>
+<td>43555</td>
+<td>0.7900</td>
+<td>0.4600</td>
+<td>1.12</td>
+<td>34751</td>
+<td>87.14</td>
+<td>174 +/- 7</td>
+<td>-0.19 +/- 0.46</td>
+<td>-0.34 +/- 0.40</td>
+<td>88.64</td>
+<td>0.98</td>
+<td>32208</td>
+</tr>
+<tr>
+<td>7</td>
+<td>54265</td>
+<td>0.7900</td>
+<td>0.4600</td>
+<td>1.55</td>
+<td>38418</td>
+<td>76.94</td>
+<td>178 +/- 4</td>
+<td>-0.41 +/- 0.49</td>
+<td>-0.55 +/- 0.23</td>
+<td>83.90</td>
+<td>1.26</td>
+<td>33240</td>
+</tr>
+<tr>
+<td>8</td>
+<td>64363</td>
+<td>0.7900</td>
+<td>0.4600</td>
+<td>2.07</td>
+<td>36968</td>
+<td>62.45</td>
+<td>198 +/- 32</td>
+<td>-0.52 +/- 0.36</td>
+<td>-0.29 +/- 0.19</td>
+<td>79.81</td>
+<td>1.56</td>
+<td>30181</td>
+</tr>
+</table>
+</body>
+</html>
\ No newline at end of file
diff --git a/trunk/htsworkflow/pipelines/test/testdata/Summary-pipeline100.htm b/trunk/htsworkflow/pipelines/test/testdata/Summary-pipeline100.htm
new file mode 100644 (file)
index 0000000..1b82467
--- /dev/null
@@ -0,0 +1,598 @@
+<!--RUN_TIME Wed Jul  2 06:47:44 2008 -->
+<!--SOFTWARE_VERSION @(#) $Id: jerboa.pl,v 1.94 2007/12/04 09:59:07 rshaw Exp $-->
+<html>
+<body>
+
+<a name="Top"><h2><title>080627_HWI-EAS229_0036_3055HAXX Summary</title></h2></a>
+<h1>Summary Information For Experiment 080627_HWI-EAS229_0036_3055HAXX on Machine HWI-EAS229</h1>
+<h2><br></br>Chip Summary<br></br></h2>
+<table border="1" cellpadding="5">
+<tr><td>Machine</td><td>HWI-EAS229</td></tr>
+<tr><td>Run Folder</td><td>080627_HWI-EAS229_0036_3055HAXX</td></tr>
+<tr><td>Chip ID</td><td>unknown</td></tr>
+</table>
+<h2><br></br>Chip Results Summary<br></br></h2>
+<table border="1" cellpadding="5">
+<tr>
+<td>Clusters</td>
+<td>Clusters (PF)</td>
+<td>Yield (kbases)</td>
+</tr>
+<tr><td>80933224</td>
+<td>43577803</td>
+<td>1133022</td>
+</tr>
+</table>
+<h2><br></br>Lane Parameter Summary<br></br></h2>
+<table border="1" cellpadding="5">
+<tr>
+<td>Lane</td>
+<td>Sample ID</td>
+<td>Sample Target</td>
+<td>Sample Type</td>
+<td>Length</td>
+<td>Filter</td>
+<td>Num Tiles</td>
+<td>Tiles</td>
+</tr>
+<tr>
+<td>1</td>
+<td>unknown</td>
+<td>mm9</td>
+<td>ELAND</td>
+<td>26</td>
+<td>'((CHASTITY>=0.6))'</td>
+<td>100</td>
+<td><a href="#Lane1">Lane 1</a></td>
+</tr>
+<tr>
+<td>2</td>
+<td>unknown</td>
+<td>mm9</td>
+<td>ELAND</td>
+<td>26</td>
+<td>'((CHASTITY>=0.6))'</td>
+<td>100</td>
+<td><a href="#Lane2">Lane 2</a></td>
+</tr>
+<tr>
+<td>3</td>
+<td>unknown</td>
+<td>mm9</td>
+<td>ELAND</td>
+<td>26</td>
+<td>'((CHASTITY>=0.6))'</td>
+<td>100</td>
+<td><a href="#Lane3">Lane 3</a></td>
+</tr>
+<tr>
+<td>4</td>
+<td>unknown</td>
+<td>elegans170</td>
+<td>ELAND</td>
+<td>26</td>
+<td>'((CHASTITY>=0.6))'</td>
+<td>100</td>
+<td><a href="#Lane4">Lane 4</a></td>
+</tr>
+<tr>
+<td>5</td>
+<td>unknown</td>
+<td>elegans170</td>
+<td>ELAND</td>
+<td>26</td>
+<td>'((CHASTITY>=0.6))'</td>
+<td>100</td>
+<td><a href="#Lane5">Lane 5</a></td>
+</tr>
+<tr>
+<td>6</td>
+<td>unknown</td>
+<td>elegans170</td>
+<td>ELAND</td>
+<td>26</td>
+<td>'((CHASTITY>=0.6))'</td>
+<td>100</td>
+<td><a href="#Lane6">Lane 6</a></td>
+</tr>
+<tr>
+<td>7</td>
+<td>unknown</td>
+<td>elegans170</td>
+<td>ELAND</td>
+<td>26</td>
+<td>'((CHASTITY>=0.6))'</td>
+<td>100</td>
+<td><a href="#Lane7">Lane 7</a></td>
+</tr>
+<tr>
+<td>8</td>
+<td>unknown</td>
+<td>elegans170</td>
+<td>ELAND</td>
+<td>26</td>
+<td>'((CHASTITY>=0.6))'</td>
+<td>100</td>
+<td><a href="#Lane8">Lane 8</a></td>
+</tr>
+</table>
+<h2><br></br>Lane Results Summary<br></br></h2>
+<table border="1" cellpadding="5">
+<tr>
+<td colspan="2">Lane Info</td>
+<td colspan="8">Tile Mean +/- SD for Lane</td>
+</tr>
+<tr>
+<td>Lane </td>
+<td>Lane Yield (kbases) </td>
+<td>Clusters (raw)</td>
+<td>Clusters (PF) </td>
+<td>1st Cycle Int (PF) </td>
+<td>% intensity after 20 cycles (PF) </td>
+<td>% PF Clusters </td>
+<td>% Align (PF) </td>
+<td>Alignment Score (PF) </td>
+<td> % Error Rate (PF) </td>
+</tr>
+<tr>
+<td>1</td>
+<td>158046</td>
+<td>96483 +/- 9074</td>
+<td>60787 +/- 4240</td>
+<td>329 +/- 35</td>
+<td>101.88 +/- 6.03</td>
+<td>63.21 +/- 3.29</td>
+<td>70.33 +/- 0.24</td>
+<td>9054.08 +/- 59.16</td>
+<td>0.46 +/- 0.18</td>
+</tr>
+<tr>
+<td>2</td>
+<td>156564</td>
+<td>133738 +/- 7938</td>
+<td>60217 +/- 1926</td>
+<td>444 +/- 39</td>
+<td>92.62 +/- 7.58</td>
+<td>45.20 +/- 3.31</td>
+<td>51.98 +/- 0.74</td>
+<td>6692.04 +/- 92.49</td>
+<td>0.46 +/- 0.09</td>
+</tr>
+<tr>
+<td>3</td>
+<td>185818</td>
+<td>152142 +/- 10002</td>
+<td>71468 +/- 2827</td>
+<td>366 +/- 36</td>
+<td>91.53 +/- 8.66</td>
+<td>47.19 +/- 3.80</td>
+<td>82.24 +/- 0.44</td>
+<td>10598.68 +/- 64.13</td>
+<td>0.41 +/- 0.04</td>
+</tr>
+<tr>
+<td>4</td>
+<td>34953</td>
+<td>15784 +/- 2162</td>
+<td>13443 +/- 1728</td>
+<td>328 +/- 40</td>
+<td>97.53 +/- 9.87</td>
+<td>85.29 +/- 1.91</td>
+<td>80.02 +/- 0.53</td>
+<td>10368.82 +/- 71.08</td>
+<td>0.15 +/- 0.05</td>
+</tr>
+<tr>
+<td>5</td>
+<td>167936</td>
+<td>119735 +/- 8465</td>
+<td>64590 +/- 2529</td>
+<td>417 +/- 37</td>
+<td>88.69 +/- 14.79</td>
+<td>54.10 +/- 2.59</td>
+<td>76.95 +/- 0.32</td>
+<td>9936.47 +/- 65.75</td>
+<td>0.28 +/- 0.02</td>
+</tr>
+<tr>
+<td>6</td>
+<td>173463</td>
+<td>152177 +/- 8146</td>
+<td>66716 +/- 2493</td>
+<td>372 +/- 39</td>
+<td>87.06 +/- 9.86</td>
+<td>43.98 +/- 3.12</td>
+<td>78.80 +/- 0.43</td>
+<td>10162.28 +/- 49.65</td>
+<td>0.38 +/- 0.03</td>
+</tr>
+<tr>
+<td>7</td>
+<td>149287</td>
+<td>84649 +/- 7325</td>
+<td>57418 +/- 3617</td>
+<td>295 +/- 28</td>
+<td>89.40 +/- 8.23</td>
+<td>67.97 +/- 1.82</td>
+<td>33.38 +/- 0.25</td>
+<td>4247.92 +/- 32.37</td>
+<td>1.00 +/- 0.03</td>
+</tr>
+<tr>
+<td>8</td>
+<td>106953</td>
+<td>54622 +/- 4812</td>
+<td>41136 +/- 3309</td>
+<td>284 +/- 37</td>
+<td>90.21 +/- 9.10</td>
+<td>75.39 +/- 2.27</td>
+<td>48.33 +/- 0.29</td>
+<td>6169.21 +/- 169.50</td>
+<td>0.86 +/- 1.22</td>
+</tr>
+<tr><td colspan="13">Tile mean across chip</td></tr>
+<tr>
+<td>Av.</td>
+<td></td>
+<td>101166</td>
+<td>54472</td>
+<td>354</td>
+<td>92.36</td>
+<td>60.29</td>
+<td>65.25</td>
+<td>8403.69</td>
+<td>0.50</td>
+</tr>
+</table>
+<h2><br></br>Expanded Lane Summary<br></br></h2>
+<table border="1" cellpadding="5">
+<tr>
+
+<tr><td colspan="2">Lane Info</td>
+<td colspan="2">Phasing Info</td>
+<td colspan="2">Raw Data (tile mean)</td>
+<td colspan="7">Filtered Data (tile mean)</td></tr>
+<td>Lane </td>
+<td>Clusters (tile mean) (raw)</td>
+<td>% Phasing </td>
+<td>% Prephasing </td>
+<td>% Error Rate (raw) </td>
+<td> Equiv Perfect Clusters (raw) </td>
+<td>% retained </td>
+<td>Cycle 2-4 Av Int (PF) </td>
+<td>Cycle 2-10 Av % Loss (PF) </td>
+<td>Cycle 10-20 Av % Loss (PF) </td>
+<td>% Align (PF) </td>
+<td>% Error Rate (PF) </td>
+<td> Equiv Perfect Clusters (PF) </td>
+</tr>
+<tr>
+<td>1</td>
+<td>96483</td>
+<td>0.7700</td>
+<td>0.3100</td>
+<td>1.00</td>
+<td>49676</td>
+<td>63.21</td>
+<td>317 +/- 32</td>
+<td>0.13 +/- 0.44</td>
+<td>-1.14 +/- 0.34</td>
+<td>70.33</td>
+<td>0.46</td>
+<td>41758</td>
+</tr>
+<tr>
+<td>2</td>
+<td>133738</td>
+<td>0.7700</td>
+<td>0.3100</td>
+<td>1.22</td>
+<td>40467</td>
+<td>45.20</td>
+<td>415 +/- 33</td>
+<td>0.29 +/- 0.40</td>
+<td>-0.79 +/- 0.35</td>
+<td>51.98</td>
+<td>0.46</td>
+<td>30615</td>
+</tr>
+<tr>
+<td>3</td>
+<td>152142</td>
+<td>0.7700</td>
+<td>0.3100</td>
+<td>1.30</td>
+<td>78588</td>
+<td>47.19</td>
+<td>344 +/- 26</td>
+<td>0.68 +/- 0.51</td>
+<td>-0.77 +/- 0.42</td>
+<td>82.24</td>
+<td>0.41</td>
+<td>57552</td>
+</tr>
+<tr>
+<td>4</td>
+<td>15784</td>
+<td>0.7700</td>
+<td>0.3100</td>
+<td>0.29</td>
+<td>11095</td>
+<td>85.29</td>
+<td>306 +/- 34</td>
+<td>0.20 +/- 0.69</td>
+<td>-1.28 +/- 0.66</td>
+<td>80.02</td>
+<td>0.15</td>
+<td>10671</td>
+</tr>
+<tr>
+<td>5</td>
+<td>119735</td>
+<td>0.7700</td>
+<td>0.3100</td>
+<td>0.85</td>
+<td>60335</td>
+<td>54.10</td>
+<td>380 +/- 32</td>
+<td>0.34 +/- 0.49</td>
+<td>-1.55 +/- 4.69</td>
+<td>76.95</td>
+<td>0.28</td>
+<td>49015</td>
+</tr>
+<tr>
+<td>6</td>
+<td>152177</td>
+<td>0.7700</td>
+<td>0.3100</td>
+<td>1.21</td>
+<td>70905</td>
+<td>43.98</td>
+<td>333 +/- 27</td>
+<td>0.57 +/- 0.50</td>
+<td>-0.91 +/- 0.39</td>
+<td>78.80</td>
+<td>0.38</td>
+<td>51663</td>
+</tr>
+<tr>
+<td>7</td>
+<td>84649</td>
+<td>0.7700</td>
+<td>0.3100</td>
+<td>1.38</td>
+<td>21069</td>
+<td>67.97</td>
+<td>272 +/- 20</td>
+<td>1.15 +/- 0.52</td>
+<td>-0.84 +/- 0.58</td>
+<td>33.38</td>
+<td>1.00</td>
+<td>18265</td>
+</tr>
+<tr>
+<td>8</td>
+<td>54622</td>
+<td>0.7700</td>
+<td>0.3100</td>
+<td>1.17</td>
+<td>21335</td>
+<td>75.39</td>
+<td>262 +/- 31</td>
+<td>1.10 +/- 0.59</td>
+<td>-1.01 +/- 0.47</td>
+<td>48.33</td>
+<td>0.86</td>
+<td>19104</td>
+</tr>
+</table>
+<b><br></br>IVC Plots</b>
+<p> <a href='IVC.htm' target="_blank"> IVC.htm
+ </a></p>
+<b><br></br>All Intensity Plots</b>
+<p> <a href='All.htm' target="_blank"> All.htm
+ </a></p>
+<b><br></br>Error graphs: </b>
+<p> <a href='Error.htm' target="_blank"> Error.htm
+ </a></p>
+<td><a href="#Top">Back to top</a></td>
+<a name="Lane1"><h2><br></br>Lane 1<br></br></h2></a>
+<table border="1" cellpadding="5">
+<tr>
+<td>Lane </td>
+<td>Tile </td>
+<td>Clusters (raw)</td>
+<td>Av 1st Cycle Int (PF) </td>
+<td>Av % intensity after 20 cycles (PF) </td>
+<td>% PF Clusters </td>
+<td>% Align (PF) </td>
+<td>Av Alignment Score (PF) </td>
+<td>% Error Rate (PF) </td>
+</tr>
+<tr>
+<td>1</td>
+<td>0001</td>
+<td>114972</td>
+<td>326.48</td>
+<td>94.39</td>
+<td>57.44</td>
+<td>70.2</td>
+<td>9038.6</td>
+<td>0.44</td>
+</tr>
+</table>
+<td><a href="#Top">Back to top</a></td>
+<a name="Lane2"><h2><br></br>Lane 2<br></br></h2></a>
+<table border="1" cellpadding="5">
+<tr>
+<td>Lane </td>
+<td>Tile </td>
+<td>Clusters (raw)</td>
+<td>Av 1st Cycle Int (PF) </td>
+<td>Av % intensity after 20 cycles (PF) </td>
+<td>% PF Clusters </td>
+<td>% Align (PF) </td>
+<td>Av Alignment Score (PF) </td>
+<td>% Error Rate (PF) </td>
+</tr>
+<tr>
+<td>2</td>
+<td>0001</td>
+<td>147793</td>
+<td>448.12</td>
+<td>83.68</td>
+<td>38.57</td>
+<td>53.7</td>
+<td>6905.4</td>
+<td>0.54</td>
+</tr>
+</table>
+<td><a href="#Top">Back to top</a></td>
+<a name="Lane3"><h2><br></br>Lane 3<br></br></h2></a>
+<table border="1" cellpadding="5">
+<tr>
+<td>Lane </td>
+<td>Tile </td>
+<td>Clusters (raw)</td>
+<td>Av 1st Cycle Int (PF) </td>
+<td>Av % intensity after 20 cycles (PF) </td>
+<td>% PF Clusters </td>
+<td>% Align (PF) </td>
+<td>Av Alignment Score (PF) </td>
+<td>% Error Rate (PF) </td>
+</tr>
+<tr>
+<td>3</td>
+<td>0001</td>
+<td>167904</td>
+<td>374.05</td>
+<td>86.91</td>
+<td>40.36</td>
+<td>81.3</td>
+<td>10465.0</td>
+<td>0.47</td>
+</tr>
+</table>
+<td><a href="#Top">Back to top</a></td>
+<a name="Lane4"><h2><br></br>Lane 4<br></br></h2></a>
+<table border="1" cellpadding="5">
+<tr>
+<td>Lane </td>
+<td>Tile </td>
+<td>Clusters (raw)</td>
+<td>Av 1st Cycle Int (PF) </td>
+<td>Av % intensity after 20 cycles (PF) </td>
+<td>% PF Clusters </td>
+<td>% Align (PF) </td>
+<td>Av Alignment Score (PF) </td>
+<td>% Error Rate (PF) </td>
+</tr>
+<tr>
+<td>4</td>
+<td>0001</td>
+<td>20308</td>
+<td>276.85</td>
+<td>92.87</td>
+<td>84.26</td>
+<td>80.4</td>
+<td>10413.8</td>
+<td>0.16</td>
+</tr>
+</table>
+<td><a href="#Top">Back to top</a></td>
+<a name="Lane5"><h2><br></br>Lane 5<br></br></h2></a>
+<table border="1" cellpadding="5">
+<tr>
+<td>Lane </td>
+<td>Tile </td>
+<td>Clusters (raw)</td>
+<td>Av 1st Cycle Int (PF) </td>
+<td>Av % intensity after 20 cycles (PF) </td>
+<td>% PF Clusters </td>
+<td>% Align (PF) </td>
+<td>Av Alignment Score (PF) </td>
+<td>% Error Rate (PF) </td>
+</tr>
+</table>
+<td><a href="#Top">Back to top</a></td>
+<a name="Lane6"><h2><br></br>Lane 6<br></br></h2></a>
+<table border="1" cellpadding="5">
+<tr>
+<td>Lane </td>
+<td>Tile </td>
+<td>Clusters (raw)</td>
+<td>Av 1st Cycle Int (PF) </td>
+<td>Av % intensity after 20 cycles (PF) </td>
+<td>% PF Clusters </td>
+<td>% Align (PF) </td>
+<td>Av Alignment Score (PF) </td>
+<td>% Error Rate (PF) </td>
+</tr>
+<tr>
+<td>6</td>
+<td>0001</td>
+<td>166844</td>
+<td>348.12</td>
+<td>77.59</td>
+<td>38.13</td>
+<td>79.7</td>
+<td>10264.4</td>
+<td>0.44</td>
+</tr>
+</table>
+<td><a href="#Top">Back to top</a></td>
+<a name="Lane7"><h2><br></br>Lane 7<br></br></h2></a>
+<table border="1" cellpadding="5">
+<tr>
+<td>Lane </td>
+<td>Tile </td>
+<td>Clusters (raw)</td>
+<td>Av 1st Cycle Int (PF) </td>
+<td>Av % intensity after 20 cycles (PF) </td>
+<td>% PF Clusters </td>
+<td>% Align (PF) </td>
+<td>Av Alignment Score (PF) </td>
+<td>% Error Rate (PF) </td>
+</tr>
+<tr>
+<td>7</td>
+<td>0001</td>
+<td>98913</td>
+<td>269.90</td>
+<td>86.66</td>
+<td>64.55</td>
+<td>33.2</td>
+<td>4217.5</td>
+<td>1.02</td>
+</tr>
+</table>
+<td><a href="#Top">Back to top</a></td>
+<a name="Lane8"><h2><br></br>Lane 8<br></br></h2></a>
+<table border="1" cellpadding="5">
+<tr>
+<td>Lane </td>
+<td>Tile </td>
+<td>Clusters (raw)</td>
+<td>Av 1st Cycle Int (PF) </td>
+<td>Av % intensity after 20 cycles (PF) </td>
+<td>% PF Clusters </td>
+<td>% Align (PF) </td>
+<td>Av Alignment Score (PF) </td>
+<td>% Error Rate (PF) </td>
+</tr>
+<tr>
+<td>8</td>
+<td>0001</td>
+<td>64972</td>
+<td>243.60</td>
+<td>89.40</td>
+<td>73.17</td>
+<td>48.3</td>
+<td>6182.8</td>
+<td>0.71</td>
+</tr>
+</table>
+<td><a href="#Top">Back to top</a></td>
+</body>
+</html>
diff --git a/trunk/htsworkflow/pipelines/test/testdata/Summary-pipeline110.htm b/trunk/htsworkflow/pipelines/test/testdata/Summary-pipeline110.htm
new file mode 100644 (file)
index 0000000..854bda6
--- /dev/null
@@ -0,0 +1,400 @@
+<!--RUN_TIME Tue Oct 28 09:45:50 2008 -->
+<!--SOFTWARE_VERSION @(#) $Id: jerboa.pl,v 1.10 2008/07/23 15:18:30 mzerara Exp $-->
+<html>
+<body>
+
+<a name="Top"><h2><title>081017_HWI-EAS229_0062_30J55AAXX Summary</title></h2></a>
+<h1>Summary Information For Experiment 081017_HWI-EAS229_0062_30J55AAXX on Machine HWI-EAS229</h1>
+<h2><br></br>Chip Summary<br></br></h2>
+<table border="1" cellpadding="5">
+<tr><td>Machine</td><td>HWI-EAS229</td></tr>
+<tr><td>Run Folder</td><td>081017_HWI-EAS229_0062_30J55AAXX</td></tr>
+<tr><td>Chip ID</td><td>unknown</td></tr>
+</table>
+<h2><br></br>Chip Results Summary<br></br></h2>
+<table border="1" cellpadding="5">
+<tr>
+<td>Clusters</td>
+<td>Clusters (PF)</td>
+<td>Yield (kbases)</td>
+</tr>
+<tr><td>162491175</td>
+<td>99622159</td>
+<td>3686019</td>
+</tr>
+</table>
+<h2><br></br>Lane Parameter Summary<br></br></h2>
+<table border="1" cellpadding="5">
+<tr>
+<td>Lane</td>
+<td>Sample ID</td>
+<td>Sample Target</td>
+<td>Sample Type</td>
+<td>Length</td>
+<td>Filter</td>
+<td>Chast. Thresh.</td>
+<td>Num Tiles</td>
+<td>Tiles</td>
+</tr>
+<tr>
+<td>1</td>
+<td>unknown</td>
+<td>mm9</td>
+<td>ELAND</td>
+<td>37</td>
+<td>'((FAILED_CHASTITY<=1))'</td>
+<td>0.6</td>
+<td>100</td>
+<td><a href="#Lane1">Lane 1</a></td>
+</tr>
+<tr>
+<td>2</td>
+<td>unknown</td>
+<td>mm9</td>
+<td>ELAND</td>
+<td>37</td>
+<td>'((FAILED_CHASTITY<=1))'</td>
+<td>0.6</td>
+<td>100</td>
+<td><a href="#Lane2">Lane 2</a></td>
+</tr>
+<tr>
+<td>3</td>
+<td>unknown</td>
+<td>mm9</td>
+<td>ELAND</td>
+<td>37</td>
+<td>'((FAILED_CHASTITY<=1))'</td>
+<td>0.6</td>
+<td>100</td>
+<td><a href="#Lane3">Lane 3</a></td>
+</tr>
+<tr>
+<td>4</td>
+<td>unknown</td>
+<td>hg18</td>
+<td>ELAND</td>
+<td>37</td>
+<td>'((FAILED_CHASTITY<=1))'</td>
+<td>0.6</td>
+<td>100</td>
+<td><a href="#Lane4">Lane 4</a></td>
+</tr>
+<tr>
+<td>5</td>
+<td>unknown</td>
+<td>hg18</td>
+<td>ELAND</td>
+<td>37</td>
+<td>'((FAILED_CHASTITY<=1))'</td>
+<td>0.6</td>
+<td>100</td>
+<td><a href="#Lane5">Lane 5</a></td>
+</tr>
+<tr>
+<td>6</td>
+<td>unknown</td>
+<td>mm9</td>
+<td>ELAND</td>
+<td>37</td>
+<td>'((FAILED_CHASTITY<=1))'</td>
+<td>0.6</td>
+<td>100</td>
+<td><a href="#Lane6">Lane 6</a></td>
+</tr>
+<tr>
+<td>7</td>
+<td>unknown</td>
+<td>mm9</td>
+<td>ELAND</td>
+<td>37</td>
+<td>'((FAILED_CHASTITY<=1))'</td>
+<td>0.6</td>
+<td>100</td>
+<td><a href="#Lane7">Lane 7</a></td>
+</tr>
+<tr>
+<td>8</td>
+<td>unknown</td>
+<td>mm9</td>
+<td>ELAND</td>
+<td>37</td>
+<td>'((FAILED_CHASTITY<=1))'</td>
+<td>0.6</td>
+<td>100</td>
+<td><a href="#Lane8">Lane 8</a></td>
+</tr>
+</table>
+<h2><br></br>Lane Results Summary<br></br></h2>
+<table border="1" cellpadding="5">
+<tr>
+<td colspan="2">Lane Info</td>
+<td colspan="8">Tile Mean +/- SD for Lane</td>
+</tr>
+<tr>
+<td>Lane </td>
+<td>Lane Yield (kbases) </td>
+<td>Clusters (raw)</td>
+<td>Clusters (PF) </td>
+<td>1st Cycle Int (PF) </td>
+<td>% intensity after 20 cycles (PF) </td>
+<td>% PF Clusters </td>
+<td>% Align (PF) </td>
+<td>Alignment Score (PF) </td>
+<td> % Error Rate (PF) </td>
+</tr>
+<tr>
+<td>1</td>
+<td>435340</td>
+<td>190220 +/- 15118</td>
+<td>117659 +/- 8144</td>
+<td>273 +/- 16</td>
+<td>80.02 +/- 2.52</td>
+<td>62.15 +/- 5.54</td>
+<td>77.18 +/- 0.22</td>
+<td>13447.28 +/- 43.35</td>
+<td>2.78 +/- 0.13</td>
+</tr>
+<tr>
+<td>2</td>
+<td>462364</td>
+<td>190560 +/- 14399</td>
+<td>124963 +/- 5687</td>
+<td>271 +/- 16</td>
+<td>75.73 +/- 2.46</td>
+<td>65.83 +/- 4.12</td>
+<td>70.06 +/- 0.39</td>
+<td>12082.95 +/- 64.81</td>
+<td>3.22 +/- 0.09</td>
+</tr>
+<tr>
+<td>3</td>
+<td>468929</td>
+<td>187597 +/- 12369</td>
+<td>126737 +/- 5549</td>
+<td>274 +/- 16</td>
+<td>72.61 +/- 2.67</td>
+<td>67.69 +/- 2.72</td>
+<td>74.03 +/- 0.22</td>
+<td>12470.18 +/- 50.02</td>
+<td>4.27 +/- 0.08</td>
+</tr>
+<tr>
+<td>4</td>
+<td>491642</td>
+<td>204142 +/- 16877</td>
+<td>132876 +/- 4023</td>
+<td>253 +/- 16</td>
+<td>80.43 +/- 3.10</td>
+<td>65.39 +/- 3.84</td>
+<td>72.95 +/- 0.15</td>
+<td>13273.80 +/- 39.75</td>
+<td>0.78 +/- 0.10</td>
+</tr>
+<tr>
+<td>5</td>
+<td>433033</td>
+<td>247308 +/- 11600</td>
+<td>117036 +/- 4489</td>
+<td>273 +/- 11</td>
+<td>68.60 +/- 2.40</td>
+<td>47.48 +/- 3.63</td>
+<td>66.91 +/- 0.54</td>
+<td>11700.08 +/- 66.33</td>
+<td>2.62 +/- 0.13</td>
+</tr>
+<tr>
+<td>6</td>
+<td>483012</td>
+<td>204298 +/- 15640</td>
+<td>130543 +/- 6972</td>
+<td>254 +/- 11</td>
+<td>81.35 +/- 1.96</td>
+<td>64.14 +/- 4.40</td>
+<td>77.28 +/- 0.11</td>
+<td>14084.01 +/- 23.09</td>
+<td>0.71 +/- 0.03</td>
+</tr>
+<tr>
+<td>7</td>
+<td>474325</td>
+<td>202707 +/- 15404</td>
+<td>128196 +/- 9745</td>
+<td>255 +/- 13</td>
+<td>79.95 +/- 2.08</td>
+<td>63.48 +/- 5.63</td>
+<td>75.78 +/- 0.18</td>
+<td>13758.74 +/- 60.86</td>
+<td>0.88 +/- 0.12</td>
+</tr>
+<tr>
+<td>8</td>
+<td>437372</td>
+<td>198075 +/- 14702</td>
+<td>118208 +/- 14798</td>
+<td>259 +/- 14</td>
+<td>81.80 +/- 2.53</td>
+<td>59.85 +/- 7.67</td>
+<td>74.55 +/- 0.36</td>
+<td>13586.07 +/- 103.97</td>
+<td>0.71 +/- 0.15</td>
+</tr>
+<tr><td colspan="13">Tile mean across chip</td></tr>
+<tr>
+<td>Av.</td>
+<td></td>
+<td>203113</td>
+<td>124527</td>
+<td>264</td>
+<td>77.56</td>
+<td>62.00</td>
+<td>73.59</td>
+<td>13050.39</td>
+<td>2.00</td>
+</tr>
+</table>
+<h2><br></br>Expanded Lane Summary<br></br></h2>
+<table border="1" cellpadding="5">
+<tr>
+
+<tr><td colspan="2">Lane Info</td>
+<td colspan="2">Phasing Info</td>
+<td colspan="2">Raw Data (tile mean)</td>
+<td colspan="7">Filtered Data (tile mean)</td></tr>
+<td>Lane </td>
+<td>Clusters (tile mean) (raw)</td>
+<td>% Phasing </td>
+<td>% Prephasing </td>
+<td>% Error Rate (raw) </td>
+<td> Equiv Perfect Clusters (raw) </td>
+<td>% retained </td>
+<td>Cycle 2-4 Av Int (PF) </td>
+<td>Cycle 2-10 Av % Loss (PF) </td>
+<td>Cycle 10-20 Av % Loss (PF) </td>
+<td>% Align (PF) </td>
+<td>% Error Rate (PF) </td>
+<td> Equiv Perfect Clusters (PF) </td>
+</tr>
+<tr>
+<td>1</td>
+<td>190220</td>
+<td>0.6800</td>
+<td>0.2800</td>
+<td>3.17</td>
+<td>107262</td>
+<td>62.15</td>
+<td>241 +/- 13</td>
+<td>0.56 +/- 0.22</td>
+<td>0.29 +/- 0.14</td>
+<td>77.18</td>
+<td>2.78</td>
+<td>86184</td>
+</tr>
+<tr>
+<td>2</td>
+<td>190560</td>
+<td>0.6800</td>
+<td>0.2800</td>
+<td>3.53</td>
+<td>98678</td>
+<td>65.83</td>
+<td>238 +/- 14</td>
+<td>0.78 +/- 0.15</td>
+<td>0.53 +/- 0.15</td>
+<td>70.06</td>
+<td>3.22</td>
+<td>83090</td>
+</tr>
+<tr>
+<td>3</td>
+<td>187597</td>
+<td>0.6800</td>
+<td>0.2800</td>
+<td>4.44</td>
+<td>104008</td>
+<td>67.69</td>
+<td>233 +/- 14</td>
+<td>0.56 +/- 0.17</td>
+<td>0.59 +/- 0.26</td>
+<td>74.03</td>
+<td>4.27</td>
+<td>89278</td>
+</tr>
+<tr>
+<td>4</td>
+<td>204142</td>
+<td>0.6800</td>
+<td>0.2800</td>
+<td>1.38</td>
+<td>115765</td>
+<td>65.39</td>
+<td>239 +/- 14</td>
+<td>1.28 +/- 0.21</td>
+<td>0.77 +/- 0.21</td>
+<td>72.95</td>
+<td>0.78</td>
+<td>93475</td>
+</tr>
+<tr>
+<td>5</td>
+<td>247308</td>
+<td>0.6800</td>
+<td>0.2800</td>
+<td>3.40</td>
+<td>103006</td>
+<td>47.48</td>
+<td>242 +/- 10</td>
+<td>1.61 +/- 0.39</td>
+<td>1.21 +/- 0.21</td>
+<td>66.91</td>
+<td>2.62</td>
+<td>73768</td>
+</tr>
+<tr>
+<td>6</td>
+<td>204298</td>
+<td>0.6800</td>
+<td>0.2800</td>
+<td>1.33</td>
+<td>122233</td>
+<td>64.14</td>
+<td>242 +/- 12</td>
+<td>1.30 +/- 0.11</td>
+<td>0.73 +/- 0.22</td>
+<td>77.28</td>
+<td>0.71</td>
+<td>97646</td>
+</tr>
+<tr>
+<td>7</td>
+<td>202707</td>
+<td>0.6800</td>
+<td>0.2800</td>
+<td>1.51</td>
+<td>117513</td>
+<td>63.48</td>
+<td>238 +/- 13</td>
+<td>1.27 +/- 0.38</td>
+<td>0.66 +/- 0.22</td>
+<td>75.78</td>
+<td>0.88</td>
+<td>93659</td>
+</tr>
+<tr>
+<td>8</td>
+<td>198075</td>
+<td>0.6800</td>
+<td>0.2800</td>
+<td>1.41</td>
+<td>111115</td>
+<td>59.85</td>
+<td>244 +/- 12</td>
+<td>1.19 +/- 0.16</td>
+<td>0.65 +/- 0.29</td>
+<td>74.55</td>
+<td>0.71</td>
+<td>85327</td>
+</tr>
+</table>
+</body>
+</html>
\ No newline at end of file
diff --git a/trunk/htsworkflow/pipelines/test/testdata/bustard-config132.xml b/trunk/htsworkflow/pipelines/test/testdata/bustard-config132.xml
new file mode 100644 (file)
index 0000000..2820451
--- /dev/null
@@ -0,0 +1,129 @@
+<?xml version="1.0"?>
+<BaseCallAnalysis>
+  <Run Name="Bustard1.3.2_25-03-2009_diane">
+    <BaseCallParameters>
+      <ChastityThreshold>0.600000</ChastityThreshold>
+      <Matrix Path="/home/diane/gec/090206_HWI-EAS229_0090_311AFAAXX/Data/C1-152_Firecrest1.9.5_14-02-2009_diane/Matrix/s_4_matrix.txt">
+        <AutoFlag>0</AutoFlag>
+        <AutoLane>0</AutoLane>
+        <Cycle>2</Cycle>
+        <CycleOffset>1</CycleOffset>
+        <FirstCycle>1</FirstCycle>
+        <LastCycle>37</LastCycle>
+        <Read>1</Read>
+      </Matrix>
+      <MatrixElements Path="/home/diane/gec/090206_HWI-EAS229_0090_311AFAAXX/Data/C1-152_Firecrest1.9.5_14-02-2009_diane/Matrix/s_4_matrix.txt">
+        <Base>A</Base>
+        <Base>C</Base>
+        <Base>G</Base>
+        <Base>T</Base>
+        <Element>1.270000</Element>
+        <Element>0.210000</Element>
+        <Element>-0.020000</Element>
+        <Element>-0.030000</Element>
+        <Element>0.570000</Element>
+        <Element>0.580000</Element>
+        <Element>-0.010000</Element>
+        <Element>-0.010000</Element>
+        <Element>-0.030000</Element>
+        <Element>-0.040000</Element>
+        <Element>1.510000</Element>
+        <Element>-0.020000</Element>
+        <Element>-0.020000</Element>
+        <Element>-0.020000</Element>
+        <Element>0.800000</Element>
+        <Element>1.070000</Element>
+      </MatrixElements>
+      <Phasing Path="">
+        <AutoFlag>1</AutoFlag>
+        <AutoLane>3</AutoLane>
+        <Cycle>1</Cycle>
+        <CycleOffset>0</CycleOffset>
+        <FirstCycle>1</FirstCycle>
+        <LastCycle>37</LastCycle>
+        <Read>1</Read>
+      </Phasing>
+      <PhasingRestarts />
+      <PrbCompression>none</PrbCompression>
+      <PureBases>25</PureBases>
+      <QhgCompression>none</QhgCompression>
+      <SeqCompression>none</SeqCompression>
+      <Sig2Compression>gzip</Sig2Compression>
+      <SmtFilter>failed-chastity</SmtFilter>
+      <SmtRelation>le</SmtRelation>
+      <SmtThreshold>1.000000</SmtThreshold>
+    </BaseCallParameters>
+    <Cycles First="1" Last="37" Number="37" />
+    <Input Path="C1-37_Firecrest1.3.2_25-03-2009_diane" />
+    <RunParameters>
+      <AutoCycleFlag>1</AutoCycleFlag>
+      <BasecallFlag>1</BasecallFlag>
+      <Compression>gzip</Compression>
+      <CompressionSuffix>.gz</CompressionSuffix>
+      <Deblocked>0</Deblocked>
+      <DebugFlag>0</DebugFlag>
+      <FirstRunOnlyFlag>0</FirstRunOnlyFlag>
+      <ImagingReads Index="1">
+        <FirstCycle>1</FirstCycle>
+        <LastCycle>37</LastCycle>
+        <RunFolder>/zfs1/wold-lab/diane/sequencer/090307_HWI-EAS229_0097_30U0BAAXX</RunFolder>
+      </ImagingReads>
+      <Instrument>HWI-EAS229</Instrument>
+      <IterativeMatrixFlag>0</IterativeMatrixFlag>
+      <MakeFlag>1</MakeFlag>
+      <MaxCycle>-1</MaxCycle>
+      <MinCycle>-1</MinCycle>
+      <Prb></Prb>
+      <Reads Index="1">
+        <FirstCycle>1</FirstCycle>
+        <LastCycle>37</LastCycle>
+        <RunFolder>/zfs1/wold-lab/diane/sequencer/090307_HWI-EAS229_0097_30U0BAAXX</RunFolder>
+      </Reads>
+      <RunFolder>/zfs1/wold-lab/diane/sequencer/090307_HWI-EAS229_0097_30U0BAAXX</RunFolder>
+      <RunFolderDate>090307</RunFolderDate>
+      <RunFolderId>97</RunFolderId>
+      <SelectedFolders />
+      <SelectedTiles />
+      <Sig2></Sig2>
+    </RunParameters>
+    <Software Name="Bustard" Version="1.3.2" />
+    <TileSelection>
+      <Lane Index="1">
+        <Sample>s</Sample>
+        <TileRange Max="100" Min="1" />
+      </Lane>
+      <Lane Index="2">
+        <Sample>s</Sample>
+        <TileRange Max="100" Min="1" />
+      </Lane>
+      <Lane Index="3">
+        <Sample>s</Sample>
+        <TileRange Max="100" Min="1" />
+      </Lane>
+      <Lane Index="4">
+        <Sample>s</Sample>
+        <TileRange Max="100" Min="1" />
+      </Lane>
+      <Lane Index="5">
+        <Sample>s</Sample>
+        <TileRange Max="100" Min="1" />
+      </Lane>
+      <Lane Index="6">
+        <Sample>s</Sample>
+        <TileRange Max="100" Min="1" />
+      </Lane>
+      <Lane Index="7">
+        <Sample>s</Sample>
+        <TileRange Max="100" Min="1" />
+      </Lane>
+      <Lane Index="8">
+        <Sample>s</Sample>
+        <TileRange Max="100" Min="1" />
+      </Lane>
+    </TileSelection>
+    <Time>
+      <Start>25-03-09 15:23:10 PDT</Start>
+    </Time>
+    <User Name="diane" />
+  </Run>
+</BaseCallAnalysis>
diff --git a/trunk/htsworkflow/pipelines/test/testdata/gerald_config_0.2.6.xml b/trunk/htsworkflow/pipelines/test/testdata/gerald_config_0.2.6.xml
new file mode 100644 (file)
index 0000000..c0753ad
--- /dev/null
@@ -0,0 +1,82 @@
+<RunParameters>
+<ChipWideRunParameters>
+  <ANALYSIS>default</ANALYSIS>
+  <BAD_LANES></BAD_LANES>
+  <BAD_TILES></BAD_TILES>
+  <CONTAM_DIR></CONTAM_DIR>
+  <CONTAM_FILE></CONTAM_FILE>
+  <ELAND_GENOME>Need_to_specify_ELAND_genome_directory</ELAND_GENOME>
+  <ELAND_MULTIPLE_INSTANCES>8</ELAND_MULTIPLE_INSTANCES>
+  <ELAND_REPEAT></ELAND_REPEAT>
+  <EMAIL_DOMAIN>domain.com</EMAIL_DOMAIN>
+  <EMAIL_LIST>diane</EMAIL_LIST>
+  <EMAIL_SERVER>localhost:25</EMAIL_SERVER>
+  <EXPT_DIR>/home/diane/gec/080416_HWI-EAS229_0024_207BTAAXX/Data/C1-33_Firecrest1.8.28_19-04-2008_diane/Bustard1.8.28_19-04-2008_diane</EXPT_DIR>
+  <EXPT_DIR_ROOT>/home/diane/gec</EXPT_DIR_ROOT>
+  <FORCE>1</FORCE>
+  <GENOME_DIR>/home/diane/proj/SolexaPipeline-0.2.2.6/Goat/../Gerald/../../Genomes</GENOME_DIR>
+  <GENOME_FILE>Need_to_specify_genome_file_name</GENOME_FILE>
+  <HAMSTER_FLAG>genome</HAMSTER_FLAG>
+  <OUT_DIR>/home/diane/gec/080416_HWI-EAS229_0024_207BTAAXX/Data/C1-33_Firecrest1.8.28_19-04-2008_diane/Bustard1.8.28_19-04-2008_diane/GERALD_19-04-2008_diane</OUT_DIR>
+  <POST_RUN_COMMAND></POST_RUN_COMMAND>
+  <PRB_FILE_SUFFIX>_prb.txt</PRB_FILE_SUFFIX>
+  <PURE_BASES>12</PURE_BASES>
+  <QF_PARAMS>'((CHASTITY&gt;=0.6))'</QF_PARAMS>
+  <QHG_FILE_SUFFIX>_qhg.txt</QHG_FILE_SUFFIX>
+  <QUALITY_FORMAT>--symbolic</QUALITY_FORMAT>
+  <READ_LENGTH>32</READ_LENGTH>
+  <SEQUENCE_FORMAT>--scarf</SEQUENCE_FORMAT>
+  <SEQ_FILE_SUFFIX>_seq.txt</SEQ_FILE_SUFFIX>
+  <SIG_FILE_SUFFIX_DEPHASED>_sig2.txt</SIG_FILE_SUFFIX_DEPHASED>
+  <SIG_FILE_SUFFIX_NOT_DEPHASED>_sig.txt</SIG_FILE_SUFFIX_NOT_DEPHASED>
+  <SOFTWARE_VERSION>@(#) Id: GERALD.pl,v 1.68.2.2 2007/06/13 11:08:49 km Exp</SOFTWARE_VERSION>
+  <TILE_REGEX>s_[1-8]_[0-9][0-9][0-9][0-9]</TILE_REGEX>
+  <TILE_ROOT>s</TILE_ROOT>
+  <TIME_STAMP>Sat Apr 19 19:08:30 2008</TIME_STAMP>
+  <TOOLS_DIR>/home/diane/proj/SolexaPipeline-0.2.2.6/Goat/../Gerald</TOOLS_DIR>
+  <USE_BASES>all</USE_BASES>
+  <WEB_DIR_ROOT>http://host.domain.com/yourshare/</WEB_DIR_ROOT>
+</ChipWideRunParameters>
+<LaneSpecificRunParameters>
+  <ANALYSIS>
+    <s_1>eland</s_1>
+    <s_2>eland</s_2>
+    <s_3>eland</s_3>
+    <s_4>eland</s_4>
+    <s_5>eland</s_5>
+    <s_6>eland</s_6>
+    <s_7>eland</s_7>
+    <s_8>eland</s_8>
+  </ANALYSIS>
+  <ELAND_GENOME>
+    <s_1>/g/dm3</s_1>
+    <s_2>/g/equcab1</s_2>
+    <s_3>/g/equcab1</s_3>
+    <s_4>/g/canfam2</s_4>
+    <s_5>/g/hg18</s_5>
+    <s_6>/g/hg18</s_6>
+    <s_7>/g/hg18</s_7>
+    <s_8>/g/hg18</s_8>
+  </ELAND_GENOME>
+  <READ_LENGTH>
+    <s_1>32</s_1>
+    <s_2>32</s_2>
+    <s_3>32</s_3>
+    <s_4>32</s_4>
+    <s_5>32</s_5>
+    <s_6>32</s_6>
+    <s_7>32</s_7>
+    <s_8>32</s_8>
+  </READ_LENGTH>
+  <USE_BASES>
+    <s_1>YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY</s_1>
+    <s_2>YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY</s_2>
+    <s_3>YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY</s_3>
+    <s_4>YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY</s_4>
+    <s_5>YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY</s_5>
+    <s_6>YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY</s_6>
+    <s_7>YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY</s_7>
+    <s_8>YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY</s_8>
+  </USE_BASES>
+</LaneSpecificRunParameters>
+</RunParameters>
diff --git a/trunk/htsworkflow/pipelines/test/testdata/gerald_config_1.0.xml b/trunk/htsworkflow/pipelines/test/testdata/gerald_config_1.0.xml
new file mode 100644 (file)
index 0000000..7ced7c6
--- /dev/null
@@ -0,0 +1,156 @@
+<RunParameters>
+<ChipWideRunParameters>
+  <ANALYSIS>none</ANALYSIS>
+  <BAD_LANES></BAD_LANES>
+  <BAD_LANES></BAD_LANES>
+  <BAD_LANES></BAD_LANES>
+  <BAD_TILES></BAD_TILES>
+  <BIN_DIR>/home/diane/proj/GAPipeline-1.0/Goat/../Gerald/../bin</BIN_DIR>
+  <CMDPREFIX></CMDPREFIX>
+  <CONTAM_DIR></CONTAM_DIR>
+  <CONTAM_FILE></CONTAM_FILE>
+  <ELAND_GENOME>Need_to_specify_ELAND_genome_directory</ELAND_GENOME>
+  <ELAND_MAX_MATCHES></ELAND_MAX_MATCHES>
+  <ELAND_MULTIPLE_INSTANCES>8</ELAND_MULTIPLE_INSTANCES>
+  <ELAND_REPEAT></ELAND_REPEAT>
+  <ELAND_SEED_LENGTH></ELAND_SEED_LENGTH>
+  <EMAIL_DOMAIN>domain.com</EMAIL_DOMAIN>
+  <EMAIL_LIST>diane</EMAIL_LIST>
+  <EMAIL_SERVER>localhost:25</EMAIL_SERVER>
+  <EXPT_DIR>/home/diane/gec/090220_HWI-EAS229_0093_30VR0AAXX/Data/C1-37_Firecrest1.9.5_22-02-2009_diane/Bustard1.9.5_22-02-2009_diane</EXPT_DIR>
+  <EXPT_DIR_ROOT>/home/diane/gec</EXPT_DIR_ROOT>
+  <FORCE>1</FORCE>
+  <GENOME_DIR>/home/diane/proj/GAPipeline-1.0/Goat/../Gerald/../../Genomes</GENOME_DIR>
+  <GENOME_FILE>Need_to_specify_genome_file_name</GENOME_FILE>
+  <GROUP_LANES></GROUP_LANES>
+  <HAMSTER_FLAG>genome</HAMSTER_FLAG>
+  <HAMSTER_FLAG>genome</HAMSTER_FLAG>
+  <HAMSTER_FLAG>genome</HAMSTER_FLAG>
+  <INDEX_BASES></INDEX_BASES>
+  <NUM_LEADING_DIRS_TO_STRIP>2</NUM_LEADING_DIRS_TO_STRIP>
+  <ORIG_READ_LENGTHS>37</ORIG_READ_LENGTHS>
+  <OUT_DIR>/home/diane/gec/090220_HWI-EAS229_0093_30VR0AAXX/Data/C1-37_Firecrest1.9.5_22-02-2009_diane/Bustard1.9.5_22-02-2009_diane/GERALD_22-02-2009_diane</OUT_DIR>
+  <PAIR_PARAMS></PAIR_PARAMS>
+  <POST_RUN_COMMAND></POST_RUN_COMMAND>
+  <PRB_FILE_SUFFIX>_prb.txt</PRB_FILE_SUFFIX>
+  <PURE_BASES>12</PURE_BASES>
+  <QCAL_SOURCE></QCAL_SOURCE>
+  <QCAL_SOURCE1></QCAL_SOURCE1>
+  <QCAL_SOURCE2></QCAL_SOURCE2>
+  <QF_PARAMS>'((CHASTITY&gt;=0.6))'</QF_PARAMS>
+  <QHG_FILE_SUFFIX>_qhg.txt</QHG_FILE_SUFFIX>
+  <QTABLE_PATH></QTABLE_PATH>
+  <QTABLE_PATH1></QTABLE_PATH1>
+  <QTABLE_PATH2></QTABLE_PATH2>
+  <QTABLE_REQUIRED>0</QTABLE_REQUIRED>
+  <QTABLE_REQUIRED1>0</QTABLE_REQUIRED1>
+  <QTABLE_REQUIRED2>0</QTABLE_REQUIRED2>
+  <QUALITY_FORMAT>--symbolic</QUALITY_FORMAT>
+  <READ_LENGTH>37</READ_LENGTH>
+  <READ_LENGTH1>37</READ_LENGTH1>
+  <READ_LENGTH2>0</READ_LENGTH2>
+  <SEQUENCE_FORMAT>--scarf</SEQUENCE_FORMAT>
+  <SEQ_FILE_SUFFIX>_seq.txt</SEQ_FILE_SUFFIX>
+  <SIG_FILE_SUFFIX_DEPHASED>_sig2.txt</SIG_FILE_SUFFIX_DEPHASED>
+  <SIG_FILE_SUFFIX_NOT_DEPHASED>_sig.txt</SIG_FILE_SUFFIX_NOT_DEPHASED>
+  <SMT_FILTER>chastity</SMT_FILTER>
+  <SMT_RELATION>ge</SMT_RELATION>
+  <SMT_THRESHOLD>0.6</SMT_THRESHOLD>
+  <SOFTWARE_VERSION>@(#) Id: GERALD.pl,v 1.171 2008/05/19 17:36:14 mzerara Exp</SOFTWARE_VERSION>
+  <SRF_2ND_CYCLE></SRF_2ND_CYCLE>
+  <SRF_ARCHIVE_REQUIRED></SRF_ARCHIVE_REQUIRED>
+  <SRF_CHASTITY>0.6</SRF_CHASTITY>
+  <SRF_FILE_SUFFIX>_traces.srf</SRF_FILE_SUFFIX>
+  <SRF_PROCESSED>-P</SRF_PROCESSED>
+  <SRF_QCAL></SRF_QCAL>
+  <SRF_RAW>-R</SRF_RAW>
+  <TILE_REGEX>s_[1-8]_[0-9][0-9][0-9][0-9]</TILE_REGEX>
+  <TILE_ROOT>s</TILE_ROOT>
+  <TIME_STAMP>Sun Feb 22 21:15:59 2009</TIME_STAMP>
+  <TOOLS_DIR>/home/diane/proj/GAPipeline-1.0/Goat/../Gerald</TOOLS_DIR>
+  <USE_BASES>YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY</USE_BASES>
+  <WEB_DIR_ROOT>http://host.domain.com/yourshare/</WEB_DIR_ROOT>
+</ChipWideRunParameters>
+<LaneSpecificRunParameters>
+  <ANALYSIS>
+    <s_1>eland_extended</s_1>
+    <s_2>eland_extended</s_2>
+    <s_3>eland_extended</s_3>
+    <s_4>eland_extended</s_4>
+    <s_5>eland_extended</s_5>
+    <s_6>eland_extended</s_6>
+    <s_7>eland_extended</s_7>
+    <s_8>eland_extended</s_8>
+  </ANALYSIS>
+  <ELAND_GENOME>
+    <s_1>/g/mm9</s_1>
+    <s_2>/g/mm9</s_2>
+    <s_3>/g/elegans190</s_3>
+    <s_4>/g/arabidopsis01222004</s_4>
+    <s_5>/g/mm9</s_5>
+    <s_6>/g/mm9</s_6>
+    <s_7>/g/mm9</s_7>
+    <s_8>/g/mm9</s_8>
+  </ELAND_GENOME>
+  <QTABLE_PATH>
+    <s_1>s_1$(QTABLE_SUFFIX)</s_1>
+    <s_2>s_2$(QTABLE_SUFFIX)</s_2>
+    <s_3>s_3$(QTABLE_SUFFIX)</s_3>
+    <s_4>s_4$(QTABLE_SUFFIX)</s_4>
+    <s_5>s_5$(QTABLE_SUFFIX)</s_5>
+    <s_6>s_6$(QTABLE_SUFFIX)</s_6>
+    <s_7>s_7$(QTABLE_SUFFIX)</s_7>
+    <s_8>s_8$(QTABLE_SUFFIX)</s_8>
+  </QTABLE_PATH>
+  <QTABLE_REQUIRED>
+    <s_1>1</s_1>
+    <s_2>1</s_2>
+    <s_3>1</s_3>
+    <s_4>1</s_4>
+    <s_5>1</s_5>
+    <s_6>1</s_6>
+    <s_7>1</s_7>
+    <s_8>1</s_8>
+  </QTABLE_REQUIRED>
+  <READ_LENGTH>
+    <s_1>37</s_1>
+    <s_2>37</s_2>
+    <s_3>37</s_3>
+    <s_4>37</s_4>
+    <s_5>37</s_5>
+    <s_6>37</s_6>
+    <s_7>37</s_7>
+    <s_8>37</s_8>
+  </READ_LENGTH>
+  <SRF_2ND_CYCLE>
+    <s_1></s_1>
+    <s_2></s_2>
+    <s_3></s_3>
+    <s_4></s_4>
+    <s_5></s_5>
+    <s_6></s_6>
+    <s_7></s_7>
+    <s_8></s_8>
+  </SRF_2ND_CYCLE>
+  <SRF_ARCHIVE_REQUIRED>
+    <s_1></s_1>
+    <s_2></s_2>
+    <s_3></s_3>
+    <s_4></s_4>
+    <s_5></s_5>
+    <s_6></s_6>
+    <s_7></s_7>
+    <s_8></s_8>
+  </SRF_ARCHIVE_REQUIRED>
+  <USE_BASES>
+    <s_1>YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY</s_1>
+    <s_2>YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY</s_2>
+    <s_3>YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY</s_3>
+    <s_4>YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY</s_4>
+    <s_5>YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY</s_5>
+    <s_6>YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY</s_6>
+    <s_7>YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY</s_7>
+    <s_8>YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY</s_8>
+  </USE_BASES>
+</LaneSpecificRunParameters>
+</RunParameters>
diff --git a/trunk/htsworkflow/util/__init__.py b/trunk/htsworkflow/util/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/trunk/htsworkflow/util/alphanum.py b/trunk/htsworkflow/util/alphanum.py
new file mode 100644 (file)
index 0000000..5e25606
--- /dev/null
@@ -0,0 +1,69 @@
+#
+# The Alphanum Algorithm is an improved sorting algorithm for strings
+# containing numbers.  Instead of sorting numbers in ASCII order like
+# a standard sort, this algorithm sorts numbers in numeric order.
+#
+# The Alphanum Algorithm is discussed at http://www.DaveKoelle.com
+#
+#* Python implementation provided by Chris Hulan (chris.hulan@gmail.com)
+#* Distributed under same license as original
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+#
+
+import re
+import types
+
+#
+# TODO: Make decimal points be considered in the same class as digits
+#
+
+def chunkify(str):
+    """
+    return a list of numbers and non-numeric substrings of +str+
+    the numeric substrings are converted to integer, non-numeric are left as is
+    """
+    if type(str) in types.StringTypes: 
+        chunks = re.findall("(\d+|\D+)",str)
+        #convert numeric strings to numbers
+        chunks = [re.match('\d',x) and int(x) or x for x in chunks] 
+        return chunks
+    elif type(str) in [types.IntType, types.LongType, types.FloatType]:
+        return [str]
+    else:
+        raise ValueError("Unsupported type %s for input %s" % (type(str), str))
+
+def alphanum(a,b):
+    """
+    breaks +a+ and +b+ into pieces and returns left-to-right comparison of the pieces
+
+    +a+ and +b+ are expected to be strings (for example file names) with numbers and non-numeric characters
+    Split the values into list of numbers and non numeric sub-strings and so comparison of numbers gives
+    Numeric sorting, comparison of non-numeric gives Lexicographic order
+    """
+    # split strings into chunks
+    aChunks = chunkify(a)
+    bChunks = chunkify(b)
+
+    return cmp(aChunks,bChunks) #built in comparison works once data is prepared
+
+
+
+if __name__ == "__main__":
+       unsorted = ["1000X Radonius Maximus","10X Radonius","200X Radonius","20X Radonius","20X Radonius Prime","30X Radonius","40X Radonius","Allegia 50 Clasteron","Allegia 500 Clasteron","Allegia 51 Clasteron","Allegia 51B Clasteron","Allegia 52 Clasteron","Allegia 60 Clasteron","Alpha 100","Alpha 2","Alpha 200","Alpha 2A","Alpha 2A-8000","Alpha 2A-900","Callisto Morphamax","Callisto Morphamax 500","Callisto Morphamax 5000","Callisto Morphamax 600","Callisto Morphamax 700","Callisto Morphamax 7000","Callisto Morphamax 7000 SE","Callisto Morphamax 7000 SE2","QRS-60 Intrinsia Machine","QRS-60F Intrinsia Machine","QRS-62 Intrinsia Machine","QRS-62F Intrinsia Machine","Xiph Xlater 10000","Xiph Xlater 2000","Xiph Xlater 300","Xiph Xlater 40","Xiph Xlater 5","Xiph Xlater 50","Xiph Xlater 500","Xiph Xlater 5000","Xiph Xlater 58"]
+       sorted = unsorted[:]
+       sorted.sort(alphanum)
+       print '+++++Sorted...++++'
+       print '\n'.join(sorted)
diff --git a/trunk/htsworkflow/util/ethelp.py b/trunk/htsworkflow/util/ethelp.py
new file mode 100644 (file)
index 0000000..19f6c9f
--- /dev/null
@@ -0,0 +1,32 @@
+"""
+ElementTree helper functions
+"""
+def indent(elem, level=0):
+    """
+    reformat an element tree to be 'pretty' (indented)
+    """
+    i = "\n" + level*"  "
+    if len(elem):
+        if not elem.text or not elem.text.strip():
+            elem.text = i + "  "
+        for child in elem:
+            indent(child, level+1)
+        # we don't want the closing tag indented too far
+        child.tail = i
+        if not elem.tail or not elem.tail.strip():
+            elem.tail = i
+    else:
+        if level and (not elem.tail or not elem.tail.strip()):
+            elem.tail = i
+
+def flatten(elem, include_tail=0):
+    """
+    Extract the text from an element tree 
+    (AKA extract the text that not part of XML tags)
+    """
+    text = elem.text or ""
+    for e in elem:
+        text += flatten(e, 1)
+    if include_tail and elem.tail: text += elem.tail
+    return text
+
diff --git a/trunk/htsworkflow/util/fctracker.py b/trunk/htsworkflow/util/fctracker.py
new file mode 100644 (file)
index 0000000..5ba3389
--- /dev/null
@@ -0,0 +1,201 @@
+"""
+Provide some quick and dirty access and reporting for the fctracker database.
+
+The advantage to this code is that it doesn't depend on django being
+installed, so it can run on machines other than the webserver.
+"""
+import datetime
+import os
+import re
+import sys
+import time
+
+if sys.version_info[0] + sys.version_info[1] * 0.1 >= 2.5:
+  # we're python 2.5
+  import sqlite3
+else:
+  import pysqlite2.dbapi2 as sqlite3
+
+
+class fctracker:
+    """
+    provide a simple way to interact with the flowcell data in fctracker.db
+    """
+    def __init__(self, database):
+        # default to the current directory
+        if database is None: 
+            self.database = self._guess_fctracker_path()
+        else:
+            self.database = database
+        self.conn = sqlite3.connect(self.database)
+        self._get_library()
+        self._get_species()
+
+    def _guess_fctracker_path(self):
+        """
+        Guess a few obvious places for the database
+        """
+        fctracker = 'fctracker.db'
+        name = fctracker
+        # is it in the current dir?
+        if os.path.exists(name): 
+            return name
+        name = os.path.expanduser(os.path.join('~', fctracker))
+        if os.path.exists(name):
+            return name
+        raise RuntimeError("Can't find fctracker")
+
+    def _make_dict_from_table(self, table_name, pkey_name):
+        """
+        Convert a django table into a dictionary indexed by the primary key.
+        Yes, it really does just load everything into memory, hopefully
+        we stay under a few tens of thousands of runs for a while.
+        """
+        table = {}
+        c = self.conn.cursor()
+        c.execute('select * from %s;' % (table_name))
+        # extract just the field name
+        description = [ f[0] for f in c.description]
+        for row in c:
+            row_dict = dict(zip(description, row))
+            table[row_dict[pkey_name]] = row_dict
+        c.close()
+        return table
+
+    def _add_lanes_to_libraries(self):
+        """
+        add flowcell/lane ids to new attribute 'lanes' in the library dictionary
+        """
+        library_id_re = re.compile('lane_\d_library_id')
+
+        for fc_id, fc in self.flowcells.items():
+            lane_library = [ (x[0][5], x[1]) for x in fc.items() 
+                                             if library_id_re.match(x[0]) ]
+            for lane, library_id in lane_library:
+                if not self.library[library_id].has_key('lanes'):
+                    self.library[library_id]['lanes'] = []
+                self.library[library_id]['lanes'].append((fc_id, lane))
+
+    def _get_library(self):
+        """
+        attach the library dictionary to the instance
+        """
+        self.library = self._make_dict_from_table(
+                         'samples_library', 
+                         'id')
+                                                  
+        
+    def _get_species(self):
+        """
+        attach the species dictionary to the instance
+        """
+        self.species = self._make_dict_from_table(
+                         'samples_species',
+                         'id'
+                       )
+        
+    def _get_flowcells(self, where=None):
+        """
+        attach the flowcell dictionary to the instance
+
+        where is a sql where clause. (eg "where run_date > '2008-1-1'")
+        that can be used to limit what flowcells we select
+        FIXME: please add sanitization code
+        """
+        if where is None:
+            where = ""
+        self.flowcells = {}
+        c = self.conn.cursor()
+        c.execute('select * from experiments_flowcell %s;' % (where))
+        # extract just the field name
+        description = [ f[0] for f in c.description ]
+        for row in c:
+            row_dict = dict(zip(description, row))
+            fcid, status = self._parse_flowcell_id(row_dict)
+            row_dict['flowcell_id'] = fcid
+            row_dict['flowcell_status'] = status
+
+            for lane in [ 'lane_%d_library' % (i) for i in range(1,9) ]:
+                lane_library = self.library[row_dict[lane+"_id"]]
+                species_id = lane_library['library_species_id']
+                lane_library['library_species'] = self.species[species_id]
+                row_dict[lane] = lane_library
+            # some useful parsing
+            run_date = time.strptime(row_dict['run_date'],  '%Y-%m-%d %H:%M:%S')
+            run_date = datetime.datetime(*run_date[:6])
+            row_dict['run_date'] = run_date
+            self.flowcells[row_dict['flowcell_id']] = row_dict
+
+        self._add_lanes_to_libraries()
+        return self.flowcells
+
+    def _parse_flowcell_id(self, flowcell_row):
+      """
+      Return flowcell id and status
+      
+      We stored the status information in the flowcell id name.
+      this was dumb, but database schemas are hard to update.
+      """
+      fields = flowcell_row['flowcell_id'].split()
+      fcid = None
+      status = None
+      if len(fields) > 0:
+        fcid = fields[0]
+      if len(fields) > 1:
+        status = fields[1]
+      return fcid, status
+      
+
+def flowcell_gone(cell):
+    """
+    Use a variety of heuristics to determine if the flowcell drive
+    has been deleted.
+    """
+    status = cell['flowcell_status']
+    if status is None:
+        return False
+    failures = ['failed', 'deleted', 'not run']
+    for f in failures:
+      if re.search(f, status):
+        return True
+    else:
+      return False
+
+def recoverable_drive_report(flowcells):
+    """
+    Attempt to report what flowcells are still on a hard drive
+    """
+    def format_status(status):
+      if status is None:
+        return ""
+      else:
+        return status+" "
+
+    # sort flowcells by run date
+    flowcell_list = []
+    for key, cell in flowcells.items():
+        flowcell_list.append( (cell['run_date'], key) )
+    flowcell_list.sort()
+
+    report = []
+    line = "%(date)s %(id)s %(status)s%(lane)s %(library_name)s (%(library_id)s) "
+    line += "%(species)s"
+    for run_date, flowcell_id in flowcell_list:
+        cell = flowcells[flowcell_id]
+        if flowcell_gone(cell):
+            continue
+        for l in range(1,9):
+            lane = 'lane_%d' % (l)
+            cell_library = cell['%s_library'%(lane)]
+            fields = {
+              'date': cell['run_date'].strftime('%y-%b-%d'),
+              'id': cell['flowcell_id'],
+              'lane': l,
+              'library_name': cell_library['library_name'],
+              'library_id': cell['%s_library_id'%(lane)],
+              'species': cell_library['library_species']['scientific_name'],
+              'status': format_status(cell['flowcell_status']),
+            }
+            report.append(line % (fields))
+    return os.linesep.join(report)
+
diff --git a/trunk/htsworkflow/util/hdquery.py b/trunk/htsworkflow/util/hdquery.py
new file mode 100644 (file)
index 0000000..ff16387
--- /dev/null
@@ -0,0 +1,25 @@
+import os
+
+#try:
+import py_sg
+#except:
+#    print 'ERROR: Please install py_sg (easy_install py_sg)'
+    
+    
+def get_hd_serial_num(device):
+    """
+    device = '/dev/sdX'
+    
+    returns hard drive serial number for a device; requires read permissions.
+    """
+    fd = os.open(device, os.O_RDONLY)
+    
+    # fd: device object
+    # \x12: INQUIRY CMD; \x01: EVPD bit set to 1; \x80: Unit Serial Number page
+    #  See http://en.wikipedia.org/wiki/SCSI_Inquiry_Command for helpful chart
+    # ##: # byte buffer for returned data
+    data = py_sg.read(fd, "\x12\x01\x80", 32)
+    
+    # Remove extra \x00's, and split remaining data into two chunks,
+    #  the 2nd of which is the serial number
+    return data.strip('\x00').split()[1]
diff --git a/trunk/htsworkflow/util/makebed.py b/trunk/htsworkflow/util/makebed.py
new file mode 100755 (executable)
index 0000000..e82968a
--- /dev/null
@@ -0,0 +1,170 @@
+"""
+Utility functions to make bedfiles.
+"""
+import os
+import re
+
+__docformat__ = "restructredtext en"
+
+# map eland_result.txt sense 
+sense_map = { 'F': '+', 'R': '-'}
+sense_color = { 'F': '0,0,255', 'R': '255,255,0' }
+
+def create_bed_header(name, description):
+  """
+  Produce the headerline for a bedfile
+  """
+  # provide default track names
+  if name is None: name = "track"
+  if description is None: description = "eland result file"
+  bed_header = 'track name="%s" description="%s" visibility=4 itemRgb="ON"' % (name, description)
+  bed_header += os.linesep
+  return bed_header
+
+def make_bed_from_eland_stream(instream, outstream, name, description, chromosome_prefix='chr'):
+  """
+  read an eland result file from instream and write a bedfile to outstream
+
+  :Parameters:
+    - `instream`: stream containing the output from eland 
+    - `outstream`: stream to write the bed file too
+    - `name`: name of bed-file (must be unique)
+    - `description`: longer description of the bed file
+    - `chromosome_prefix`: restrict output lines to fasta records that start with this pattern
+  """
+  for line in make_bed_from_eland_generator(instream, name, description, chromosome_prefix):
+      outstream.write(line)
+
+def make_bed_from_eland_generator(instream, name, description, chromosome_prefix='chr'):
+  """
+  read an eland result file from instream and write a bedfile to outstream
+
+  :Parameters:
+    - `instream`: stream containing the output from eland 
+    - `name`: name of bed-file (must be unique)
+    - `description`: longer description of the bed file
+    - `chromosome_prefix`: restrict output lines to fasta records that start with this pattern
+
+  :Return: generator which yields lines of bedfile
+  """
+  # indexes into fields in eland_result.txt file
+  SEQ = 1
+  CHR = 6
+  START = 7
+  SENSE = 8
+
+  yield create_bed_header(name, description)
+  prefix_len = len(chromosome_prefix)
+
+  for line in instream:
+    fields = line.split()
+    # we need more than the CHR field, and it needs to match a chromosome
+    if len(fields) <= CHR or fields[CHR][:prefix_len] != chromosome_prefix:
+      continue
+    start = fields[START]
+    stop = int(start) + len(fields[SEQ])
+    # strip off filename extension
+    chromosome = fields[CHR].split('.')[0]
+
+    yield '%s %s %d read 0 %s - - %s%s' % (
+      chromosome,
+      start,
+      stop,
+      sense_map[fields[SENSE]], 
+      sense_color[fields[SENSE]],
+      os.linesep  
+    )
+
+def make_bed_from_multi_eland_stream(
+  instream, 
+  outstream, 
+  name, 
+  description, 
+  chr_prefix='chr', 
+  max_reads=255
+  ):
+  """
+  read a multi eland result file from instream and write the bedfile to outstream
+
+  :Parameters:
+    - `instream`: stream containing the output from eland 
+    - `outstream`: stream to write the bed file too
+    - `name`: name of bed-file (must be unique)
+    - `description`: longer description of the bed file
+    - `chromosome_prefix`: restrict output lines to fasta records that start with this pattern
+    - `max_reads`: maximum number of reads to write to bed stream
+  """
+  for lane in make_bed_from_multi_eland_generator(instream, name, description, chr_prefix, max_reads):
+      outstream.write(lane)
+
+def make_bed_from_multi_eland_generator(instream, name, description, chr_prefix, max_reads=255):
+  loc_pattern = '(?P<fullloc>(?P<start>[0-9]+)(?P<dir>[FR])(?P<count>[0-9]+))'
+  other_pattern = '(?P<chr>[^:,]+)'
+  split_re = re.compile('(%s|%s)' % (loc_pattern, other_pattern))
+
+  yield create_bed_header(name, description)
+  for line in instream:
+    rec = line.split()
+    if len(rec) > 3:
+      # colony_id = rec[0]
+      seq = rec[1]
+      # number of matches for 0, 1, and 2 mismatches
+      # m0, m1, m2 = [int(x) for x in rec[2].split(':')]
+      compressed_reads = rec[3]
+      cur_chr = ""
+      reads = {0: [], 1: [], 2:[]}
+
+      for token in split_re.finditer(compressed_reads):
+        if token.group('chr') is not None:
+          cur_chr =  token.group('chr')
+         # strip off extension if present
+         cur_chr = os.path.splitext(cur_chr)[0] 
+        elif token.group('fullloc') is not None:
+          matches = int(token.group('count'))
+          # only emit a bed line if 
+          #  our current chromosome starts with chromosome pattern
+          if chr_prefix is None or cur_chr.startswith(chr_prefix):
+            start = int(token.group('start'))
+            stop = start + len(seq)
+            orientation = token.group('dir')
+            strand = sense_map[orientation]
+            color = sense_color[orientation]
+            # build up list of reads for this record
+            reads[matches].append((cur_chr, start, stop, strand, color))
+
+      # report up to our max_read threshold reporting the fewer-mismatch
+      # matches first
+      reported_reads = 0
+      keys = [0,1,2]
+      for mismatch, read_list in ((k, reads[k]) for k in keys): 
+        reported_reads += len(read_list)
+        if reported_reads <= max_reads:
+          for cur_chr, start, stop, strand, color in read_list:
+            reported_reads += 1
+            yield '%s %d %d read 0 %s - - %s%s' % (
+                cur_chr,
+                start,
+                stop,
+                sense_map[orientation],
+                sense_color[orientation],
+                os.linesep
+            )
+
+def make_description(flowcell_id, lane):
+    """
+    compute a bedfile name and description from the django database
+    """
+    from htsworkflow.frontend.experiments import models as experiments
+
+    lane = int(lane)
+    if lane < 1 or lane > 8:
+      raise RuntimeError("flowcells only have lanes 1-8")
+
+    cell = experiments.FlowCell.objects.get(flowcell_id=flowcell_id)
+
+    name = "%s-%s" % (flowcell_id, lane)
+
+    cell_library = getattr(cell, 'lane_%d_library' %(lane,))
+    cell_library_id = cell_library.library_id
+    description = "%s-%s" % (cell_library.library_name, cell_library_id)
+    return name, description
diff --git a/trunk/htsworkflow/util/mount.py b/trunk/htsworkflow/util/mount.py
new file mode 100644 (file)
index 0000000..bc0c26f
--- /dev/null
@@ -0,0 +1,64 @@
+"""
+Utilities for working with unix-style mounts.
+"""
+import os
+import subprocess
+
+def list_mount_points():
+    """
+    Return list of current mount points
+
+    Note: unix-like OS specific
+    """
+    mount_points = []
+    likely_locations = ['/sbin/mount', '/bin/mount']
+    for mount in likely_locations:
+        if os.path.exists(mount):
+            p = subprocess.Popen(mount, stdout=subprocess.PIPE)
+            p.wait()
+            for l in p.stdout.readlines():
+                rec = l.split()
+                device = rec[0]            
+                mount_point = rec[2]
+                assert rec[1] == 'on'
+                # looking at the output of mount on linux, osx, and 
+                # sunos, the first 3 elements are always the same
+                # devicename on path
+                # everything after that displays the attributes
+                # of the mount points in wildly differing formats
+                mount_points.append(mount_point)
+            return mount_points
+    else:
+        raise RuntimeError("Couldn't find a mount executable")
+
+def is_mounted(point_to_check):
+    """
+    Return true if argument exactly matches a current mount point.
+    """
+    for mount_point in list_mount_points():
+        if point_to_check == mount_point:
+            return True
+    else:
+        return False
+
+def find_mount_point_for(pathname):
+    """
+    Find the deepest mount point pathname is located on
+    """
+    realpath = os.path.realpath(pathname)
+    mount_points = list_mount_points()
+
+    prefixes = set()
+    for current_mount in mount_points:
+        cp = os.path.commonprefix([current_mount, realpath])
+        prefixes.add((len(cp), cp))
+
+    prefixes = list(prefixes)
+    prefixes.sort()
+    if len(prefixes) == 0:
+        return None
+    else:
+        # return longest common prefix
+        return prefixes[-1][1]
+
+
diff --git a/trunk/htsworkflow/util/opener.py b/trunk/htsworkflow/util/opener.py
new file mode 100644 (file)
index 0000000..035bb24
--- /dev/null
@@ -0,0 +1,57 @@
+"""
+Helpful utilities for turning random names/objects into streams.
+"""
+import os
+import gzip
+import bz2
+import types
+import urllib2
+
+def isfilelike(file_ref, mode):
+    """Does file_ref have the core file operations?
+    """
+    # if mode is w/a check to make sure we writeable ops
+    # but always check to see if we can read
+    read_operations = ['read', 'readline', 'readlines']
+    write_operations = [ 'write', 'writelines' ]
+    #random_operations = [ 'seek', 'tell' ]
+    if mode[0] in ('w', 'a'):
+        for o in write_operations:
+            if not hasattr(file_ref, o):
+                return False
+    for o in read_operations:
+        if not hasattr(file_ref, o):
+            return False
+          
+    return True
+
+def isurllike(file_ref, mode):
+    """
+    does file_ref look like a url?
+    (AKA does it start with protocol:// ?)
+    """
+    #what if mode is 'w'?
+    parsed = urllib2.urlparse.urlparse(file_ref)
+    schema, netloc, path, params, query, fragment = parsed
+    
+    return len(schema) > 0
+
+def autoopen(file_ref, mode='r'):
+    """
+    Attempt to intelligently turn file_ref into a readable stream
+    """
+    # catch being passed a file
+    if type(file_ref) is types.FileType:
+        return file_ref
+    # does it look like a file?
+    elif isfilelike(file_ref, mode):
+        return file_ref
+    elif isurllike(file_ref, mode):
+        return urllib2.urlopen(file_ref)
+    elif os.path.splitext(file_ref)[1] == ".gz":
+        return gzip.open(file_ref, mode)
+    elif os.path.splitext(file_ref)[1] == '.bz2':
+        return bz2.BZ2File(file_ref, mode)
+    else:
+        return open(file_ref,mode)
+
diff --git a/trunk/htsworkflow/util/queuecommands.py b/trunk/htsworkflow/util/queuecommands.py
new file mode 100644 (file)
index 0000000..23fff16
--- /dev/null
@@ -0,0 +1,99 @@
+"""
+Run up to N simultanous jobs from provided of commands 
+"""
+
+import logging
+import os
+from subprocess import PIPE
+import subprocess
+import select
+import sys
+import time
+
+class QueueCommands(object):
+    """
+    Queue up N commands from cmd_list, launching more jobs as the first
+    finish.
+    """
+
+    def __init__(self, cmd_list, N=0, cwd=None, env=None):
+        """
+        cmd_list is a list of elements suitable for subprocess
+        N is the  number of simultanious processes to run. 
+        0 is all of them.
+        
+        WARNING: this will not work on windows
+        (It depends on being able to pass local file descriptors to the 
+        select call with isn't supported by the Win32 API)
+        """
+        self.to_run = cmd_list[:]
+        self.running = {}
+        self.N = N
+        self.cwd = cwd
+        self.env = env
+
+    def under_process_limit(self):
+        """
+        are we still under the total number of allowable jobs?
+        """
+        if self.N == 0:
+            return True
+
+        if len(self.running) < self.N:
+            return True
+
+        return False
+
+    def start_jobs(self):
+        """
+        Launch jobs until we have the maximum allowable running
+        (or have run out of jobs)
+        """
+        queue_log = logging.getLogger('queue')
+
+        while (len(self.to_run) > 0) and self.under_process_limit():
+            queue_log.info('%d left to run', len(self.to_run))
+            cmd = self.to_run.pop(0)
+            p = subprocess.Popen(cmd, 
+                                 stdout=PIPE, 
+                                 shell=True, 
+                                 cwd=self.cwd, 
+                                 env=self.env)
+            self.running[p.stdout] = p
+            queue_log.info("Created process %d from %s" % (p.pid, str(cmd)))
+
+    def run(self):
+        """
+        run up to N jobs until we run out of jobs
+        """
+        queue_log = logging.getLogger('queue')
+        queue_log.debug('using %s as cwd' % (self.cwd,))
+
+        # to_run slowly gets consumed by start_jobs
+        while len(self.to_run) > 0 or len(self.running) > 0:
+            # fill any empty spots in our job queue
+            self.start_jobs()
+
+            # build a list of file descriptors
+            # fds=file desciptors
+            fds = [ x.stdout for x in self.running.values()]
+
+            # wait for something to finish
+            # wl= write list, xl=exception list (not used so get bad names)
+            read_list, wl, xl = select.select(fds, [], fds, 1 )
+
+            # for everything that might have finished...
+            for pending_fd in read_list:
+                pending = self.running[pending_fd]
+                # if it really did finish, remove it from running jobs
+                if pending.poll() is not None:
+                    queue_log.info("Process %d finished [%d]",
+                                   pending.pid, pending.returncode)
+                    del self.running[pending_fd]
+                else:
+                    # It's still running, but there's some output
+                    buffer = pending_fd.readline()
+                    buffer = buffer.strip()
+                    msg = "%d:(%d) %s" %(pending.pid, len(buffer), buffer)
+                    logging.debug(msg)
+            time.sleep(1)
diff --git a/trunk/htsworkflow/util/test/test_alphanum.py b/trunk/htsworkflow/util/test/test_alphanum.py
new file mode 100644 (file)
index 0000000..bfb2eda
--- /dev/null
@@ -0,0 +1,39 @@
+import copy
+import os
+import unittest
+
+from htsworkflow.util.alphanum import alphanum
+
+class testAlphanum(unittest.TestCase):
+    def test_string(self):
+      unsorted = ['z5', 'b3', 'b10', 'a001', 'a2']
+      sorted = [ 'a001', 'a2', 'b3', 'b10', 'z5']
+      scratch = copy.copy(unsorted)
+      scratch.sort(alphanum)
+
+      for i in xrange(len(scratch)):
+        self.failIfEqual(scratch[i], unsorted[i])
+      for i in xrange(len(scratch)):
+        self.failUnlessEqual(scratch[i], sorted[i])
+
+    def test_numbers(self):
+      unsorted = [5,7,10,18,-1,3]
+      sorted = [-1,3,5,7,10,18]
+      scratch = copy.copy(unsorted)
+      scratch.sort(alphanum)
+
+      for i in xrange(len(scratch)):
+        self.failIfEqual(scratch[i], unsorted[i])
+      for i in xrange(len(scratch)):
+        self.failUnlessEqual(scratch[i], sorted[i])
+
+
+def suite():
+    return unittest.makeSuite(testAlphanum, 'test')
+
+if __name__ == "__main__":
+    unittest.main(defaultTest='suite')
+
+
+
+
diff --git a/trunk/htsworkflow/util/test/test_ethelp.py b/trunk/htsworkflow/util/test/test_ethelp.py
new file mode 100644 (file)
index 0000000..63f0ac3
--- /dev/null
@@ -0,0 +1,35 @@
+import os
+import unittest
+
+try:
+  from xml.etree import ElementTree
+except ImportError, e:
+  from elementtree import ElementTree
+
+from htsworkflow.util.ethelp import indent, flatten
+
+class testETHelper(unittest.TestCase):
+    def setUp(self):
+        self.foo = '<foo><bar>asdf</bar><br/></foo>'
+        self.foo_tree = ElementTree.fromstring(self.foo)
+
+    def test_indent(self):
+        flat_foo = ElementTree.tostring(self.foo_tree)
+        self.failUnlessEqual(len(flat_foo.split('\n')), 1)
+
+        indent(self.foo_tree)
+        pretty_foo = ElementTree.tostring(self.foo_tree)
+        self.failUnlessEqual(len(pretty_foo.split('\n')), 5)
+
+    def test_flatten(self):
+        self.failUnless(flatten(self.foo_tree), 'asdf')
+
+def suite():
+    return unittest.makeSuite(testETHelper, 'test')
+
+if __name__ == "__main__":
+    unittest.main(defaultTest='suite')
+
+
+
+
diff --git a/trunk/htsworkflow/util/test/test_makebed.py b/trunk/htsworkflow/util/test/test_makebed.py
new file mode 100644 (file)
index 0000000..b5d3026
--- /dev/null
@@ -0,0 +1,55 @@
+import os
+from StringIO import StringIO
+import unittest
+
+from htsworkflow.util import makebed
+
+class testMakeBed(unittest.TestCase):
+    def test_multi_1_0_0_limit_1(self):
+      instream = StringIO('>HWI-EAS229_26_209LVAAXX:7:3:112:383    TCAAATCTTATGCTANGAATCNCAAATTTTCT 1:0:0   mm9_chr13_random.fa:1240R0')
+      out = StringIO()
+
+      out = list(makebed.make_bed_from_multi_eland_generator(instream, 'name', 'description', 'mm9_chr', 1))
+      self.failUnlessEqual(out[1], 'mm9_chr13_random 1240 1272 read 0 - - - 255,255,0\n')
+
+    def test_multi_1_0_0_limit_255(self):
+      instream = StringIO('>HWI-EAS229_26_209LVAAXX:7:3:112:383    TCAAATCTTATGCTANGAATCNCAAATTTTCT 1:0:0   mm9_chr13_random.fa:1240R0')
+      out = StringIO()
+
+      out = list(makebed.make_bed_from_multi_eland_generator(instream, 'name', 'desc', 'mm9_chr', 255))
+      self.failUnlessEqual(out[1], 'mm9_chr13_random 1240 1272 read 0 - - - 255,255,0\n')
+
+
+    def test_multi_2_0_0_limit_1(self):
+      instream = StringIO('>HWI-EAS229_26_209LVAAXX:7:3:104:586    GTTCTCGCATAAACTNACTCTNAATAGATTCA 2:0:0   mm9_chr4.fa:42995432F0,mm9_chrX.fa:101541458F0')
+      out = StringIO()
+
+      out = list(makebed.make_bed_from_multi_eland_generator(instream, 'name', 'desc', 'mm9_chr', 1))
+      self.failUnlessEqual(len(out), 1)
+
+    def test_multi_2_0_0_limit_255(self):
+      instream = StringIO('>HWI-EAS229_26_209LVAAXX:7:3:104:586    GTTCTCGCATAAACTNACTCTNAATAGATTCA 2:0:0   mm9_chr4.fa:42995432F0,mm9_chrX.fa:101541458F0')
+      out = StringIO()
+
+      out = list(makebed.make_bed_from_multi_eland_generator(instream, 'name', 'desc', 'mm9_chr', 255))
+      self.failUnlessEqual(len(out), 3)
+      self.failUnlessEqual(out[1], 
+        'mm9_chr4 42995432 42995464 read 0 + - - 0,0,255\n')
+      self.failUnlessEqual(out[2], 
+        'mm9_chrX 101541458 101541490 read 0 + - - 0,0,255\n')
+
+    def test_multi_0_2_0_limit_1(self):
+      instream = StringIO('>HWI-EAS229_26_209LVAAXX:7:3:115:495    TCTCCCTGAAAAATANAAGTGNTGTTGGTGAG        0:2:1   mm9_chr14.fa:104434729F2,mm9_chr16.fa:63263818R1,mm9_chr2.fa:52265438R1')
+      out = StringIO()
+
+      out = list(makebed.make_bed_from_multi_eland_generator(instream, 'name', 'desc', 'mm9_chr', 1))
+      print out
+      self.failUnlessEqual(len(out), 1)
+
+def suite():
+    return unittest.makeSuite(testMakeBed, 'test')
+
+if __name__ == "__main__":
+    unittest.main(defaultTest='suite')
+
+
diff --git a/trunk/htsworkflow/util/test/test_queuecommands.py b/trunk/htsworkflow/util/test/test_queuecommands.py
new file mode 100644 (file)
index 0000000..f4807d7
--- /dev/null
@@ -0,0 +1,58 @@
+import os
+import logging
+import time
+import unittest
+
+
+from htsworkflow.util.queuecommands import QueueCommands
+
+class testQueueCommands(unittest.TestCase):
+    def setUp(self):
+        logging.basicConfig(level=logging.DEBUG,
+                            format='%(asctime)s %(name)-8s %(message)s')
+
+       
+
+    def test_unlimited_run_slow(self):
+        """
+        Run everything at once
+        """
+        cmds = ['/bin/sleep 0',
+                '/bin/sleep 1',
+                '/bin/sleep 2',]
+
+        q = QueueCommands(cmds)
+        start = time.time()
+        q.run()
+        end = time.time()-start
+        # we should only take the length of the longest sleep
+        # pity I had to add a 1 second sleep
+        self.failUnless( end > 2.9 and end < 3.1,
+                         "took %s seconds, exected ~3" % (end,))
+
+    def test_limited_run_slow(self):
+        """
+        Run a limited number of jobs
+        """
+        cmds = ['/bin/sleep 1',
+                '/bin/sleep 2',
+                '/bin/sleep 3',]
+
+        q = QueueCommands(cmds, 2)
+
+        start = time.time()
+        q.run()
+        end = time.time()-start
+        # pity I had to add a 1 second sleep
+        self.failUnless( end > 5.9 and end < 6.1,
+                         "took %s seconds, expected ~6" % (end,)) 
+
+def suite():
+    return unittest.makeSuite(testQueueCommands, 'test')
+
+if __name__ == "__main__":
+    unittest.main(defaultTest='suite')
+
+
+
+
diff --git a/trunk/scripts/configure_pipeline b/trunk/scripts/configure_pipeline
new file mode 100644 (file)
index 0000000..0251337
--- /dev/null
@@ -0,0 +1,88 @@
+#!/usr/bin/env python
+import os
+import sys
+import re
+from htsworkflow.pipelines.configure_run import *
+from htsworkflow.pipelines import retrieve_config as _rc
+from htsworkflow.pipelines.run_status import startCmdLineStatusMonitor
+
+s_fc = re.compile('FC[0-9]+')
+
+#Turn on built-in command-line parsing.
+_rc.DISABLE_CMDLINE = False
+
+GENOME_DIR = '/data-store01/compbio/genomes/'
+
+
+
+def main(args=None):
+  ci = ConfigInfo()
+  ci.analysis_dir = os.getcwd()
+  ci.base_analysis_dir, junk = os.path.split(ci.analysis_dir)
+
+  #FIXME: make a better command line tool
+  skip_retrieve_config = False
+  if len(args) == 1:
+    arg = args[0]
+
+    #If FC##### found
+    if s_fc.search(arg):
+      cfg_filepath = os.path.abspath('config32auto.txt')
+      flowcell = arg
+    #else, config file provide
+    else:
+      cfg_filepath = os.path.abspath(args[0])
+      skip_retrieve_config = True
+  else:
+    print "usage:\n" \
+          "       configure_pipeline FC#####\n" \
+          " or:\n" \
+          "       configure_pipeline <conf_filepath>\n"
+    return 3
+
+  genome_dir = GENOME_DIR
+
+  if not skip_retrieve_config:
+    status_retrieve_cfg = retrieve_config(ci, flowcell, cfg_filepath, genome_dir)
+    if status_retrieve_cfg:
+      print "Retrieve config file successful"
+    else:
+      print "Failed to retrieve config file"
+  else:
+    print "Config file %s provided from command-line" % (cfg_filepath)
+    ci.config_filepath = cfg_filepath
+    status_retrieve_cfg = True
+  
+  if status_retrieve_cfg:
+    status = configure(ci)
+    if status:
+      print "Configure success"
+    else:
+      print "Configure failed"
+    
+    print 'Run Dir:', ci.run_path
+    print 'Bustard Dir:', ci.bustard_path
+    
+    if status:
+      # Setup status cmdline status monitor
+      startCmdLineStatusMonitor(ci)
+      
+      print 'Running pipeline now!'
+      run_status = run_pipeline(ci)
+      if run_status is True:
+        print 'Pipeline ran successfully.'
+        return 0
+      else:
+        print 'Pipeline run failed.'
+        return 1
+
+    return 2
+
+if __name__ == "__main__":
+  logging.basicConfig(level=logging.DEBUG,
+                    format='%(asctime)s %(levelname)-8s %(message)s',
+                    datefmt='%a, %d %b %Y %H:%M:%S',
+                    #filename='pipeline_main.log',
+                    filemode='w')
+
+  sys.exit(main(sys.argv[1:]))
diff --git a/trunk/scripts/copier b/trunk/scripts/copier
new file mode 100644 (file)
index 0000000..9338b07
--- /dev/null
@@ -0,0 +1,6 @@
+#!/usr/bin/env python
+import sys
+from htsworkflow.automation.copier import main
+
+if __name__ == "__main__":
+  sys.exit(main(sys.argv[1:]))
diff --git a/trunk/scripts/elandseq b/trunk/scripts/elandseq
new file mode 100755 (executable)
index 0000000..6a5178c
--- /dev/null
@@ -0,0 +1,51 @@
+#!/usr/bin/env python
+import optparse
+import os
+import sys
+
+from htsworkflow.pipelines.eland import extract_eland_sequence
+
+def make_parser():
+  usage = "usage: %prog [options] infile [outfile]"
+
+  parser = optparse.OptionParser(usage)
+  parser.add_option("-e", "--extract", dest="slice",
+    default=":",
+    help="provide a python slice operator to select a portion of an eland file")
+  return parser
+
+def main(argv):
+  parser = make_parser()
+
+  (opt, args) = parser.parse_args(argv)
+
+  if len(args) not in (0, 1, 2):
+    parser.error('incorrect number of arguments')
+
+  # get our slice coordinates
+  start, end = opt.slice.split(':')
+  if len(start) > 0:
+    start = int(start)
+  else:
+    start = None
+  if len(end) > 0:
+    end = int(end)
+  else:
+    end = None
+
+  # open infile
+  if len(args) > 0:
+    instream = open(args[0],'r')
+  else:
+    instream = sys.stdin
+
+  if len(args) > 1:
+    outstream = open(args[1],'w')
+  else:
+    outstream = sys.stdout
+
+  extract_eland_sequence(instream, outstream, start, end)
+
+if __name__ == "__main__":
+    sys.exit(main(sys.argv[1:]))
+
diff --git a/trunk/scripts/gerald2bed.py b/trunk/scripts/gerald2bed.py
new file mode 100644 (file)
index 0000000..7a726e7
--- /dev/null
@@ -0,0 +1,96 @@
+#!/usr/bin/python
+"""
+Convert a group of eland_result files from a sequencer run to bed files.
+"""
+from glob import glob
+import logging
+import optparse
+import sys
+import os
+
+from htsworkflow.util.makebed import make_bed_from_eland_stream, make_description
+
+def make_bed_for_gerald(eland_dir, output_dir, prefix, database, flowcell):
+    """
+    convert s_[1-8]_eland_result.txt to corresponding bed files
+    """
+    eland_files = glob(os.path.join(eland_dir, 's_[1-8]_eland_result.txt'))
+    out_files = glob(os.path.join(eland_dir, 's_[1-8]_eland_result.bed'))
+    if len(out_files) > 0:
+        raise RuntimeError("please move old bedfiles")
+
+    logging.info('Processing %s using flowcell id %s' % (eland_dir, flowcell))
+    for pathname in eland_files:
+        path, name = os.path.split(pathname)
+        lane = int(name[2])
+        outname = 's_%d_eland_result.bed' %(lane,)
+        logging.info('Converting lane %d to %s' % (lane, outname))
+
+        outpathname = os.path.join(eland_dir, outname)
+        # look up descriptions
+        bed_name, description = make_description(database, flowcell, lane)
+
+        # open files
+        instream = open(pathname,'r')
+        outstream = open(outpathname,'w')
+
+        make_bed_from_eland_stream(
+          instream, outstream, name, description, prefix
+        )
+
+def make_parser():
+  usage = """%prog: --flowcell <flowcell id> directory_name
+
+directory should contain a set of 8 eland result files named like
+s_[12345678]_eland_result.txt"""
+
+
+  parser = optparse.OptionParser(usage)
+
+  parser.add_option('-o', '--output', dest='output',
+                    help="destination directory for our bed files" \
+                         "defaults to eland directory",
+                    default=None)
+  parser.add_option('--chromosome', dest='prefix',
+                    help='Set the chromosome prefix name. defaults to "chr"',
+                    default='chr')
+  parser.add_option("--database", dest='database',
+                    help="specify location of fctracker database",
+                    default=None)
+  parser.add_option("--flowcell", dest='flowcell',
+                    help="specify the flowcell id for this run",
+                    default=None)
+  parser.add_option('-v', '--verbose', dest='verbose', action='store_true',
+                    help='increase verbosity',
+                    default=False)
+  return parser
+
+def main(command_line=None):
+    logging.basicConfig(level=logging.WARNING)
+    if command_line is None:
+        command_line = sys.argv[1:]
+
+    parser = make_parser()
+    (opts, args) = parser.parse_args(command_line)
+
+    if len(args) != 1:
+        parser.error('Directory name required')
+
+    eland_dir = args[0]
+    if not os.path.isdir(eland_dir):
+        parser.error('%s must be a directory' % (eland_dir,))
+
+    if opts.flowcell is None:
+        parser.error('Flowcell ID required')
+
+    if opts.verbose:
+        logger = logging.getLogger()
+        logger.setLevel(logging.INFO)
+
+    make_bed_for_gerald(eland_dir, opts.output, opts.prefix, opts.database, opts.flowcell)
+
+    return 0
+
+if __name__ == "__main__":
+    sys.exit(main(sys.argv[1:]))
+
diff --git a/trunk/scripts/library.py b/trunk/scripts/library.py
new file mode 100644 (file)
index 0000000..35532f4
--- /dev/null
@@ -0,0 +1,39 @@
+"""
+Provide some quick and dirty access and reporting for the fctracker database.
+
+The advantage to this code is that it doesn't depend on django being
+installed, so it can run on machines other than the webserver.
+"""
+from optparse import OptionParser
+import sys
+
+from htsworkflow.util import fctracker
+
+def make_parser():
+    """
+    Make parser
+    """
+    parser = OptionParser()
+    parser.add_option("-d", "--database", dest="database",
+                      help="path to the fctracker.db",
+                      default=None)
+    parser.add_option("-w", "--where", dest="where",
+                      help="add a where clause",
+                      default=None)
+    return parser
+
+def main(argv=None):
+    if argv is None:
+        argv = []
+    parser = make_parser()
+
+    opt, args = parser.parse_args(argv)
+    
+    fc = fctracker.fctracker(opt.database)
+    cells = fc._get_flowcells(opt.where)
+
+    print fctracker.recoverable_drive_report(cells)
+    return 0
+
+if __name__ == "__main__":
+    sys.exit(main(sys.argv[1:]))
diff --git a/trunk/scripts/make-library-tree b/trunk/scripts/make-library-tree
new file mode 100644 (file)
index 0000000..50ce7a9
--- /dev/null
@@ -0,0 +1,241 @@
+#!/usr/bin/python
+"""
+Make a tree of symlinks organized by library id.
+"""
+from ConfigParser import SafeConfigParser
+from glob import glob
+import logging
+from optparse import OptionParser
+import logging
+import os
+import stat
+import sys
+
+from htsworkflow.util import fctracker
+
+def find_lanes(flowcell_dir, flowcell_id, lane):
+    lane_name = "s_%s_eland_*" %(lane)
+    pattern = os.path.join(flowcell_dir, flowcell_id, "*", lane_name)
+    lanes = glob(pattern)
+    return lanes
+
+def make_long_lane_name(flowcell_dir, lane_pathname):
+    """
+    make a name from the eland result file name
+    """
+    if flowcell_dir == lane_pathname[0:len(flowcell_dir)]:
+        subpath = lane_pathname[len(flowcell_dir):]
+        long_name = subpath.replace(os.path.sep, "_")
+        return long_name
+    else:
+        return None
+    
+def parse_srf_directory(srf_dir):
+    """
+    search srf_dir for *.srf files
+
+    builds a dictionary indexed by flowcell name.
+    """
+    flowcells = {}
+    srfs = glob(os.path.join(srf_dir,'*.srf'))
+    for pathname in srfs:
+        path, filename = os.path.split(pathname)
+        basename, ext = os.path.splitext(filename)
+        record = basename.split('_')
+        if len(record) != 6:
+            logging.error("Unrecognized srf file: %s expected 6 fields got %d" % (pathname,len(record)))
+            continue
+
+        site = record[0]
+        date = record[1]
+        machine = record[2]
+        runid = record[3]
+        flowcellid = record[4]
+        laneid = record[5]
+
+        desc = "_".join([site,date,machine,runid,flowcellid])
+        flowcells[flowcellid] = desc
+    return flowcells
+
+
+def carefully_make_hardlink(source, destination, dry_run=False):
+    """
+    Make a hard link, failing if a different link already exists
+
+    Checking to see if the link already exists and is
+    the same as the link we want to make.
+    If the link already exists and is different, throw an error.
+    """
+    logging.debug("CHECKING: %s -> %s", source, destination)
+
+    if not os.path.exists(source):
+        logging.warning("%s doesn't exist", source)
+        return
+
+    if os.path.exists(destination):
+        if os.path.samefile(source, destination):
+            logging.debug('SAME: %s -> %s' % (source, destination))
+            return
+        else:
+            raise IOError('%s and %s are different files' % \
+                           (source, destination))
+    logging.info('Linking: %s -> %s' % (source, destination))
+
+    if dry_run: return 
+
+    os.link(source, destination)
+    os.chmod(destination,
+             stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH )
+
+def link_all_eland_lanes(library_path, flowcell_dir, flowcell_id, lane, dry_run):
+    """
+    find eland files at different alignment lengths
+    and put each of those in the file 
+    """
+    lanes = find_lanes(flowcell_dir, flowcell_id, lane)
+    for lane_pathname in lanes:
+        long_name = make_long_lane_name(flowcell_dir, 
+                                        lane_pathname)
+        long_pathname = os.path.join(library_path, long_name)
+        carefully_make_hardlink(lane_pathname,
+                                long_pathname,
+                                dry_run)
+
+def link_srf_lanes(srf_names, library_path, srf_dir, flowcell_id, lane, dry_run):
+    """
+    Link srf files into our library directories.
+
+    the srf files must be named:
+    <site>_<date>_<machine>_<run>_<flowcellid>_<lane>.srf
+    """
+    srf_basename = srf_names.get(flowcell_id, None)
+    if srf_basename is None:
+        logging.info("srf file for %s was not found", flowcell_id)
+    else:
+        srf_filename = "%s_%s.srf" % (srf_basename, lane)
+        source = os.path.join(srf_dir, srf_filename)
+        destination = os.path.join(library_path, srf_filename)
+        carefully_make_hardlink(source, destination, dry_run)
+    
+
+def make_library_tree(fcdb, library_dir, flowcell_dir, srfs_dir,
+                      dry_run=False):
+    """
+    Iterate over the library 
+    """
+    library_dir = os.path.normpath(library_dir) + os.path.sep
+    flowcell_dir = os.path.normpath(flowcell_dir) + os.path.sep
+    srfs_dir = os.path.normpath(srfs_dir) + os.path.sep
+
+    srf_names = parse_srf_directory(srfs_dir)
+
+    for lib_id, lib in fcdb.library.items():
+        library_path = os.path.join(library_dir, str(lib_id))
+        if not os.path.exists(library_path):
+            os.mkdir(library_path)
+
+        for flowcell_id, lane in lib.get('lanes', []):
+            link_all_eland_lanes(library_path, 
+                                 flowcell_dir, 
+                                 flowcell_id, 
+                                 lane, 
+                                 dry_run)
+
+            link_srf_lanes(srf_names, 
+                           library_path, 
+                           srfs_dir,
+                           flowcell_id,
+                           lane,
+                           dry_run)
+
+def make_parser():
+    """
+    Make parser
+    """
+    parser = OptionParser()
+    parser.add_option('-c', '--config', default=None,
+                      help='path to a configuration file containing a '
+                           'sequence archive section')
+                      
+    parser.add_option("--database", dest="database",
+                      help="path to the fctracker.db",
+                      default=None)
+    parser.add_option('-a', '--sequence-archive', default=None,
+                      help='path to where the sequence archive lives')
+    parser.add_option("-w", "--where", dest="where",
+                      help="add a where clause",
+                      default=None)
+
+    parser.add_option('-v', '--verbose', action='store_true', default=False,
+                      help='be more verbose')
+    parser.add_option('-d', '--debug', action='store_true', default=False,
+                      help='report everything')
+             
+    parser.add_option("--dry-run", dest="dry_run", action="store_true",
+                      default=False,
+                      help="Don't modify the filesystem")
+    return parser
+
+def main(argv=None):
+    FRONTEND_NAME = 'frontend'
+    SECTION_NAME = 'sequence_archive'
+    DATABASE_OPT = 'database_name'
+    ARCHIVE_OPT = 'archive_path'
+
+    if argv is None:
+        argv = []
+    parser = make_parser()
+
+    # parse command line arguments
+    opt, args = parser.parse_args(argv)
+
+    # setup logging
+    level = logging.WARN
+    if opt.verbose:
+        level = logging.INFO
+    if opt.debug:
+        level = logging.DEBUG
+    logging.basicConfig(level=level)
+
+    # figure out what config file to read
+    config_path = [os.path.expanduser('~/.htsworkflow.ini'),
+                   '/etc/htsworkflow.ini']
+    if opt.config is not None:
+        config_path = [opt.config]
+    
+    # parse options from config file
+    config_file = SafeConfigParser()
+    config_file.read(config_path)
+
+    # load defaults from config file if not overriden by the command line
+    print opt.database
+    if opt.database is None and \
+       config_file.has_option(FRONTEND_NAME, DATABASE_OPT):
+        opt.database = config_file.get(FRONTEND_NAME, DATABASE_OPT)
+
+    if opt.sequence_archive is None and \
+       config_file.has_option(SECTION_NAME, ARCHIVE_OPT):
+        opt.sequence_archive = config_file.get(SECTION_NAME, ARCHIVE_OPT)
+  
+    # complain if critical things are missing
+    if opt.database is None:
+       parser.error('Need location of htsworkflow frontend database')
+
+    if opt.sequence_archive is None:
+       parser.error('Need the root path for the sequence archive')
+
+    fcdb = fctracker.fctracker(opt.database)
+    cells = fcdb._get_flowcells(opt.where)
+
+    library_dir = os.path.join(opt.sequence_archive, 'libraries')
+    flowcell_dir = os.path.join(opt.sequence_archive, 'flowcells')
+    srfs_dir = os.path.join(opt.sequence_archive, 'srfs')
+    make_library_tree(fcdb, 
+                      library_dir, flowcell_dir, srfs_dir, 
+                      opt.dry_run)
+
+    return 0
+
+if __name__ == "__main__":
+    rv = main(sys.argv[1:])
+    # sys.exit(rv)
diff --git a/trunk/scripts/makebed b/trunk/scripts/makebed
new file mode 100755 (executable)
index 0000000..577b868
--- /dev/null
@@ -0,0 +1,113 @@
+#!/usr/bin/python
+import optparse
+import sys
+import os
+
+from htsworkflow.util.opener import autoopen
+from htsworkflow.util.makebed import make_bed_from_eland_stream, make_bed_from_multi_eland_stream, make_description
+
+def make_parser():
+  parser = optparse.OptionParser()
+  parser.add_option('-e', '--eland', dest='inname',
+                    help='specify input eland filename')
+  parser.add_option('-b', '--bed', dest='outname',
+                    help='specify output befilename')
+  parser.add_option('-n', '--name', dest='name',
+                    help='specify the track (short) name.',
+                    default=None)
+  parser.add_option('-d', '--description', dest='description',
+                    help='specify the track description',
+                    default=None)
+  parser.add_option('--chromosome', dest='prefix',
+                    help='Set the chromosome prefix name. defaults to "chr"',
+                    default='chr')
+  parser.add_option("--database", dest='database',
+                    help="specify location of fctracker database",
+                    default=None)
+  parser.add_option("--flowcell", dest='flowcell',
+                    help="compute name and description from database using flowcell id",
+                    default=None)
+  parser.add_option("--lane", dest='lane',
+                    help='specify which lane to use when retrieving description from database',
+                    default=None)
+
+  multi = optparse.OptionGroup(parser, 'Multi-read ELAND support')
+
+  multi.add_option('-m', '--multi', action='store_true',
+                    help='Enable parsing multi-read eland files',
+                    default=False)
+  multi.add_option('--reads', type='int',
+                   help='limit reporting multi reads to this many reads'
+                        '(most usefully --reads=1 will turn a multi-read '
+                        'file into a single read file)',
+                   default=255)
+  parser.add_option_group(multi)
+
+  return parser
+
+def main(command_line=None):
+  instream = None
+  outstream = None
+
+  if command_line is None:
+    command_line = sys.argv[1:]
+
+  parser = make_parser()
+  (options, args) = parser.parse_args(command_line)
+
+  if options.inname is None:
+    parser.error("Need eland input file name")
+    return 1
+
+  if options.inname == '-':
+    instream = sys.stdin
+  elif os.path.exists(options.inname):
+    instream = autoopen(options.inname, 'r')
+  else:
+    parser.error('%s was not found' % (options.inname))
+    return 1
+
+  # figure out name for output file
+  if options.outname is None:
+      # if outname wasn't defined, and we're reading from stdout
+      if instream is sys.stdin:
+          # write to stdout
+          outstream = sys.stdout
+      else:
+          # if there's a name write to name.bed
+          options.outname = os.path.splitext(options.inname)[0]+'.bed'
+          print >>sys.stderr, "defaulting to outputname", options.outname
+  elif options.outname == '-':
+      outstream = sys.stdout
+
+  if outstream is None:
+      if os.path.exists(options.outname):
+          parser.error("not overwriting %s" % (options.outname))
+          return 1
+      else:
+          outstream = open(options.outname, 'w')
+
+  if options.flowcell is not None and options.lane is not None:
+    # get our name/description out of the database
+    name, description = make_description(
+                           options.database, options.flowcell, options.lane
+                        )
+  else:
+    name = options.name
+    description = options.description
+
+  if options.multi:
+    make_bed_from_multi_eland_stream(instream, outstream, 
+                                     name, description, 
+                                     options.prefix,
+                                     options.reads)
+
+  else:
+    make_bed_from_eland_stream(instream, outstream, 
+                               name, description, 
+                               options.prefix)
+  return 0
+
+if __name__ == "__main__":
+  sys.exit(main(sys.argv[1:]))
+
diff --git a/trunk/scripts/mark_archived_data b/trunk/scripts/mark_archived_data
new file mode 100755 (executable)
index 0000000..eadafb6
--- /dev/null
@@ -0,0 +1,102 @@
+#!/usr/bin/env python
+
+#import os
+#os.environ['DJANGO_SETTINGS_MODULE'] = 'htsworkflow.frontend.settings'
+
+from htsworkflow.util.hdquery import get_hd_serial_num
+from htsworkflow.frontend import settings
+
+#from django.conf import settings
+from optparse import OptionParser
+
+import sys
+import urllib2
+
+
+
+def construct_parser():
+    """
+    """
+    parser = OptionParser("usage: %prog -f <flowcell> -d </dev/sdX> OR\n\t %prog -f <flowcell> -s <dev_serial_num>")
+    parser.add_option("-f", "--flowcell", action="store", type="string", dest="flowcell",
+                      help="flowcell being archived")
+    parser.add_option("-d", "--device", action="store", type="string", dest="device",
+                      help="device flowcell is being archived to")
+    parser.add_option("-s", "--serial", action="store", type="string", dest="serial",
+                      help="serial num. of archive device")
+    parser.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False)
+    
+    return parser
+
+
+def update_db(flowcell, serial, debug=False):
+    """
+    Creates link between flowcell and storage device over http
+    """
+    url = settings.LINK_FLOWCELL_STORAGE_DEVICE_URL+'%s/%s/' % (flowcell, serial)
+    
+    req = urllib2.Request(url)
+    try:
+        response = urllib2.urlopen(req)
+    except urllib2.URLError, e:
+        print 'ERROR - HTTP OUTPUT (Return Code: %s); use -v/--verbose for more details.' % (e.code)
+        if debug:
+            print e.read()
+        sys.exit(e.code)
+    
+    print "DB Update of %s & %s succeeded" % (flowcell, serial)
+    print response.read()
+
+
+def process_args(parser):
+    """
+    returns flowcell and serial#
+    """
+    options, args = parser.parse_args()
+    
+    msg = []
+    
+    # Only provide device or serial
+    if options.device is not None and options.serial is not None:
+        print "ERROR: Please only provide --device or --serial.\n" \
+              "  The serial number is extracted automatically if the device is provided."
+        sys.exit(2)
+    
+    print 'Flowcell:', options.flowcell
+    print '  Device:', options.device
+    print '  Serial:', options.serial
+    
+    if options.flowcell is None:
+        msg.append("  --flowcell required")
+    
+    # if device and serial missing:
+    if options.device is None and options.serial is None:
+        msg.append("  --device OR --serial required")
+    
+    if len(msg) > 0:
+        print '\n'.join(msg)
+        sys.exit(3)
+    
+    # Update db records
+    if options.device is not None:
+        serial = get_hd_serial_num(options.device)
+        update_db(flowcell=options.flowcell, serial=serial, debug=options.verbose)
+    elif options.serial is not None:
+        update_db(flowcell=options.flowcell, serial=options.serial, debug=options.verbose)
+    else:
+        msg ="FATAL should not happen error occured; i.e. the best kind!"
+        raise ValueError, msg
+    
+    
+
+def main():
+    """
+    """
+    parser = construct_parser()
+    process_args(parser)
+    
+    #print "Database Updated."
+    sys.exit(0)
+
+if __name__ == '__main__':
+    main()
\ No newline at end of file
diff --git a/trunk/scripts/rerun_eland.py b/trunk/scripts/rerun_eland.py
new file mode 100644 (file)
index 0000000..af06cdd
--- /dev/null
@@ -0,0 +1,158 @@
+#!/usr/bin/env python
+
+import logging
+from optparse import OptionParser
+import os
+import subprocess
+import sys
+
+from htsworkflow.pipelines import gerald
+from htsworkflow.pipelines.eland import extract_eland_sequence
+from htsworkflow.pipelines import runfolder
+
+def make_query_filename(eland_obj, output_dir):
+    query_name = '%s_%s_eland_query.txt' 
+    query_name %= (eland_obj.sample_name, eland_obj.lane_id)
+
+    query_pathname = os.path.join(output_dir, query_name)
+    
+    if os.path.exists(query_pathname):
+        logging.warn("overwriting %s" % (query_pathname,))
+
+    return query_pathname
+
+def make_result_filename(eland_obj, output_dir):
+    result_name = '%s_%s_eland_result.txt' 
+    result_name %= (eland_obj.sample_name, eland_obj.lane_id)
+
+    result_pathname = os.path.join(output_dir, result_name)
+    
+    if os.path.exists(result_pathname):
+        logging.warn("overwriting %s" % (result_pathname,))
+
+    return result_pathname
+
+def extract_sequence(inpathname, query_pathname, length, dry_run=False):
+    logging.info('extracting %d bases' %(length,))
+    logging.info('extracting from %s' %(inpathname,))
+    logging.info('extracting to %s' %(query_pathname,))
+    
+    if not dry_run: 
+        try:
+            instream = open(inpathname, 'r')
+            outstream = open(query_pathname, 'w')
+            extract_eland_sequence(instream, outstream, 0, length)
+        finally:
+            outstream.close()
+            instream.close()
+    
+def run_eland(length, query_name, genome, result_name, multi=False, dry_run=False):
+    cmdline = ['eland_%d' % (length,), query_name, genome, result_name]
+    if multi:
+        cmdline += ['--multi']
+
+    logging.info('running eland: ' + " ".join(cmdline))
+    if not dry_run:
+        return subprocess.Popen(cmdline)
+    else:
+        return None
+
+
+def rerun(gerald_dir, output_dir, length=25, dry_run=False):
+    """
+    look for eland files in gerald_dir and write a subset to output_dir
+    """
+    logging.info("Extracting %d bp from files in %s" % (length, gerald_dir))
+    g = gerald.gerald(gerald_dir)
+
+    # this will only work if we're only missing the last dir in output_dir
+    if not os.path.exists(output_dir):
+        logging.info("Making %s" %(output_dir,))
+        if not dry_run: os.mkdir(output_dir)
+
+    processes = []
+    for lane_id, lane_param in g.lanes.items():
+        eland = g.eland_results[lane_id]
+
+        inpathname = eland.pathname
+        query_pathname = make_query_filename(eland, output_dir)
+        result_pathname = make_result_filename(eland, output_dir)
+
+        extract_sequence(inpathname, query_pathname, length, dry_run=dry_run)
+
+        p = run_eland(length, 
+                      query_pathname, 
+                      lane_param.eland_genome, 
+                      result_pathname, 
+                      dry_run=dry_run)
+        if p is not None:
+            processes.append(p)
+
+    for p in processes:
+        p.wait()
+        
+def make_parser():
+    usage = '%prog: [options] runfolder'
+
+    parser = OptionParser(usage)
+    
+    parser.add_option('--gerald', 
+                      help='specify location of GERALD directory',
+                      default=None)
+    parser.add_option('-o', '--output',
+                      help='specify output location of files',
+                      default=None)
+    parser.add_option('-l', '--read-length', type='int',
+                      help='specify new eland length',
+                      dest='length',
+                      default=25)
+    parser.add_option('--dry-run', action='store_true',
+                      help='only pretend to run',
+                      default=False)
+    parser.add_option('-v', '--verbose', action='store_true',
+                      help='increase verbosity',
+                      default=False)
+
+    return parser
+
+
+def main(cmdline=None):
+    logging.basicConfig(level=logging.WARNING)
+
+    parser = make_parser()
+    opts, args = parser.parse_args(cmdline)
+
+    if opts.length < 16 or opts.length > 32:
+        parser.error("eland can only process reads in the range 16-32")
+
+    if len(args) > 1:
+        parser.error("Can only process one runfolder directory")
+    elif len(args) == 1:
+        runs = runfolder.get_runs(args[0])
+        if len(runs) != 1:
+            parser.error("Not a runfolder")
+        opts.gerald = runs[0].gerald.pathname
+        if opts.output is None:
+            opts.output = os.path.join(
+                runs[0].pathname, 
+                'Data', 
+                # pythons 0..n ==> elands 1..n+1
+                'C1-%d' % (opts.length+1,) 
+            )
+
+    elif opts.gerald is None:
+        parser.error("need gerald directory")
+    
+    if opts.output is None:
+        parser.error("specify location for the new eland files")
+
+    if opts.verbose:
+        root_logger = logging.getLogger()
+        root_logger.setLevel(logging.INFO)
+
+    rerun(opts.gerald, opts.output, opts.length, dry_run=opts.dry_run)
+
+    return 0
+
+if __name__ == "__main__":
+    sys.exit(main(sys.argv[1:]))
diff --git a/trunk/scripts/retrieve_config b/trunk/scripts/retrieve_config
new file mode 100644 (file)
index 0000000..3fab6ad
--- /dev/null
@@ -0,0 +1,52 @@
+#!/usr/bin/env python
+import sys
+from htsworkflow.pipelines.retrieve_config import *
+from htsworkflow.pipelines import retrieve_config
+from htsworkflow.pipelines.genome_mapper import getAvailableGenomes
+from htsworkflow.pipelines.genome_mapper import constructMapperDict
+
+#Turn on built-in command-line parsing.
+retrieve_config.DISABLE_CMDLINE = False
+
+def main(args=None):
+  #Display help if no args are presented
+  if len(sys.argv) == 1:
+    sys.argv.append('-h')
+    
+  options = getCombinedOptions()
+  msg_list = ['ERROR MESSAGES:']
+  if options.output_filepath is None:
+    msg_list.append("  Output filepath argument required. -o <filepath> or --output=<filepath>")
+    
+  if options.flowcell is None:
+    msg_list.append("  Flow cell argument required. -f <flowcell> or --flowcell=<flowcell>")
+    
+  if options.url is None:
+    msg_list.append("  URL argument required (-u <url> or --url=<url>), or entry\n" \
+                    "    in /etc/ga_frontend/ga_frontend.conf or ~/.ga_frontend.conf")
+  if options.genome_dir is None:
+    msg_list.append("  genome_dir argument required (-g <genome_dir> or \n" \
+                    "    --genome_dir=<genome_dir>, or entry in \n" \
+                    "    /etc/ga_frontend/ga_frontend.conf or ~/.ga_frontend.conf")
+    
+  if len(msg_list) > 1:
+    print '\n'.join(msg_list)
+    return 1
+  
+  saveConfigFile(options.flowcell, options.url, options.output_filepath)
+
+  f = open(options.output_filepath, 'r')
+  data = f.read()
+  f.close()
+
+  genome_dict = getAvailableGenomes(options.genome_dir)
+  mapper_dict = constructMapperDict(genome_dict)
+
+  f = open(options.output_filepath, 'w')
+  f.write(data % (mapper_dict))
+  f.close()
+  
+  return 0
+  
+if __name__ == "__main__":
+  sys.exit(main(sys.argv[1:]))
diff --git a/trunk/scripts/runfolder b/trunk/scripts/runfolder
new file mode 100644 (file)
index 0000000..1380fbf
--- /dev/null
@@ -0,0 +1,138 @@
+#!/usr/bin/env python
+"""
+Runfolder.py can generate a xml file capturing all the 'interesting' parameters from a finished pipeline run. (using the -a option). The information currently being captured includes:
+
+  * Flowcell ID
+  * run dates
+  * start/stop cycle numbers
+  * Firecrest, bustard, gerald version numbers
+  * Eland analysis types, and everything in the eland configuration file.
+  * cluster numbers and other values from the Summary.htm 
+    LaneSpecificParameters table. 
+  * How many reads mapped to a genome from an eland file
+
+The ELAND "mapped reads" counter will also check for eland squashed file
+that were symlinked from another directory. This is so I can track how 
+many reads landed on the genome of interest and on the spike ins. 
+
+Basically my subdirectories something like:
+
+genomes/hg18
+genomes/hg18/chr*.2bpb <- files for hg18 genome
+genomes/hg18/chr*.vld  
+genomes/hg18/VATG.fa.2bp <- symlink to genomes/spikeins
+genomes/spikein 
+
+runfolder.py can also spit out a simple summary report (-s option) 
+that contains the per lane post filter cluster numbers and the mapped 
+read counts. (The report isn't currently very pretty)
+"""
+from glob import glob
+import logging
+import optparse
+import os
+import sys
+
+from htsworkflow.pipelines import runfolder
+from htsworkflow.pipelines.runfolder import ElementTree
+        
+def make_parser():
+    usage = 'usage: %prog [options] runfolder_root_dir'
+    parser = optparse.OptionParser(usage)
+
+    parser.add_option('-v', '--verbose', dest='verbose', action='store_true',
+                      default=False,
+                      help='turn on verbose mode')
+    parser.add_option('--dry-run', action='store_true', default=False,
+                      help="Don't delete anything (in clean mode)")
+
+    commands = optparse.OptionGroup(parser, 'Commands')
+
+    commands.add_option('-s', '--summary', dest='summary', action='store_true',
+                        default=False,
+                        help='produce summary report')
+    commands.add_option('-a', '--archive', dest='archive', action='store_true',
+                        default=False,
+                        help='generate run configuration archive')
+    commands.add_option('--extract-results', action='store_true',
+           default=False,
+           help='create run-xml summary, compress the eland result files, and '
+                'copy them and the Summary.htm file into archival directory.')
+    commands.add_option('-c', '--clean', action='store_true', default=False,
+                        help='Clean runfolder, preparing it for long-term storage')
+    parser.add_option_group(commands)
+
+    parser.add_option('-o', '--output-dir', default=None,
+           help="specify the default output directory for extract results")
+
+    parser.add_option('-u', '--use-run', dest='use_run', default=None,
+                      help='Specify which run to use instead of autoscanning '
+                           'the runfolder. You do this by providing the final '
+                           ' GERALD directory, and it assumes the parent '
+                           'directories are the bustard and image processing '
+                           'directories.')
+                    
+    parser.add_option('--run-xml', dest='run_xml',
+           default=None,
+           help='specify a run_<FlowCell>.xml file for summary reports')
+    
+
+    return parser
+
+def main(cmdlist=None):
+    parser = make_parser()
+    opt, args = parser.parse_args(cmdlist)
+
+    logging.basicConfig()
+    if opt.verbose:
+        root_log = logging.getLogger()
+        root_log.setLevel(logging.INFO)
+
+    logging.info('Starting htsworkflow illumina runfolder processing tool.')
+    runs = []
+    if opt.run_xml:
+        # handle ~ shortcut
+        opt.run_xml = os.path.expanduser(opt.run_xml)
+        tree = ElementTree.parse(opt.run_xml).getroot()
+        runs.append(runfolder.PipelineRun(xml=tree))
+
+    # look for manually specified run
+    if opt.use_run is not None:
+        specific_run = runfolder.get_specific_run(opt.use_run)
+        if specific_run is not None:
+            runs.append(specific_run)
+        else:
+            logging.warn("Couldn't find a run in %s" % (opt.use_run,))
+
+    # scan runfolders for runs
+    for run_pattern in args:
+        # expand args on our own if needed
+        for run_dir in glob(run_pattern):
+            runs.extend(runfolder.get_runs(run_dir))
+
+    if len(runs) > 0:
+        command_run = False
+        if opt.summary:
+            print runfolder.summary_report(runs)
+            command_run = True
+        if opt.archive:
+            runfolder.extract_run_parameters(runs)
+            command_run = True
+        if opt.extract_results:
+            runfolder.extract_results(runs, opt.output_dir)
+            command_run = True
+        if opt.clean:
+            runfolder.clean_runs(runs, opt.dry_run)
+            command_run = True
+
+        if command_run == False:
+            print "You need to specify a command."+os.linesep
+            parser.print_help()
+    else:
+        print "You need to specify some run folders to process..."+os.linesep
+        parser.print_help()
+
+    return 0
+
+if __name__ == "__main__":
+  sys.exit(main(sys.argv[1:]))
diff --git a/trunk/scripts/runner b/trunk/scripts/runner
new file mode 100644 (file)
index 0000000..560299f
--- /dev/null
@@ -0,0 +1,6 @@
+#!/usr/bin/env python
+import sys
+from htsworkflow.automation.runner import main
+
+if __name__ == "__main__":
+  sys.exit(main(sys.argv[1:]))
diff --git a/trunk/scripts/spoolwatcher b/trunk/scripts/spoolwatcher
new file mode 100644 (file)
index 0000000..b2f833e
--- /dev/null
@@ -0,0 +1,6 @@
+#!/usr/bin/env python
+import sys
+from htsworkflow.automation.spoolwatcher import main
+
+if __name__ == "__main__":
+    sys.exit(main(sys.argv[1:]))
diff --git a/trunk/scripts/srf b/trunk/scripts/srf
new file mode 100644 (file)
index 0000000..e7478a9
--- /dev/null
@@ -0,0 +1,183 @@
+#!/usr/bin/python
+
+from glob import glob
+import logging
+import optparse
+import os
+import subprocess
+import sys
+
+from htsworkflow.util import queuecommands
+from htsworkflow.pipelines import runfolder
+
+SOLEXA2SRF = 0
+ILLUMINA2SRF10 = 1
+ILLUMINA2SRF11 = 2
+
+def make_commands(run_name, lanes, site_name, destdir, cmdlevel=ILLUMINA2SRF11):
+  """
+  make a subprocess-friendly list of command line arguments to run solexa2srf
+  generates files like: 
+  woldlab:080514_HWI-EAS229_0029_20768AAXX:8.srf
+   site        run name                    lane
+             
+  run_name - most of the file name (run folder name is a good choice)
+  lanes - list of integers corresponding to which lanes to process
+  site_name - name of your "sequencing site" or "Individual"
+  destdir - where to write all the srf files
+  """
+  cmd_list = []
+  for lane in lanes:
+    name_prefix = '%s_%%l_%%t_' % (run_name,)
+    destname = '%s_%s_%d.srf' % (site_name, run_name, lane)
+    destdir = os.path.normpath(destdir)
+    dest_path = os.path.join(destdir, destname)
+    seq_pattern = 's_%d_*_seq.txt' % (lane,)
+
+    if cmdlevel == SOLEXA2SRF:
+        cmd = ['solexa2srf', 
+               '-N', name_prefix,
+               '-n', '%3x:%3y', 
+               '-o', dest_path, 
+               seq_pattern]
+    elif cmdlevel == ILLUMINA2SRF10:
+        cmd = ['illumina2srf', 
+               '-v1.0',
+               '-o', dest_path,
+               seq_pattern]
+    elif cmdlevel == ILLUMINA2SRF11:
+        seq_pattern = 's_%d_*_qseq.txt' % (lane,)
+        cmd = ['illumina2srf', 
+               '-o', dest_path,
+               seq_pattern]
+    else:
+        raise ValueError("Unrecognized run level %d" % (cmdlevel,))
+
+    cmd_list.append(" ".join(cmd))
+  return cmd_list
+
+def pathname_to_run_name(base):
+  """
+  Convert a pathname to a base runfolder name
+  handle the case with a trailing /
+  """
+  name = ""
+  while len(name) == 0:
+    base, name = os.path.split(base)
+    if len(base) == 0:
+      return None
+  return name
+
+def make_parser():
+  usage = '%prog: [options] runfolder -l 1,2,3 [runfolder -l 5,6 ...]'
+
+  parser = optparse.OptionParser(usage)
+  parser.add_option('--dry-run', action='store_true',
+                    help='print what would be done',
+                    default=False)
+
+  parser.add_option('-d', '--dest-dir', dest='dest_dir',
+                    help='location to write srf files to',
+                    default='.')
+  parser.add_option('-s', '--site',
+                    help='site name',
+                    default='Individual')
+  parser.add_option('-l', '--lanes', dest='lanes', action="append",
+         default=[],
+         help='comma seperated list of lanes to add to srf'
+  )
+  parser.add_option('-j', '--jobs', default=1, type='int',
+                    help='how many jobs to run simultaneously')
+  parser.add_option('-r', '--runfolder-version', default=ILLUMINA2SRF11, type='int',
+                    help='Which class of srf file should we attempt to create\n'
+                         '0 = Solexa pipeline 0.2.6 - 0.3\n'
+                         '1 = illumina pipeline 1.0\n'
+                         '2 = illumina pipeline 1.1rc1 and later \n')
+                     
+  parser.add_option('-v', '--verbose', dest='verbose',
+                    default=False, action='store_true',
+                    help='report more about internals (INFO)')
+  parser.add_option('--debug', dest='debug',
+                    default=False, action='store_true',
+                    help='report even more about internals (DEBUG)')
+  return parser
+
+def parse_lane_arg(lane_arg):
+    """
+    Convert comma sperated list of lane ids to a list of integers
+    """
+    lanes = []
+    for lane in lane_arg.split(','):
+        try:
+            lane = int(lane)
+            if lane < 1 or lane > 8:
+                parser.error('Lanes must be in range [1..8]')
+            lanes.append(lane)
+        except ValueError:
+            parser.error('Lane selections must be integers')
+    return lanes
+
+def main(cmdline=None):
+    parser = make_parser()
+    opts, args = parser.parse_args(cmdline)
+   
+    if opts.debug: 
+        logging.basicConfig(level=logging.DEBUG)
+    elif opts.verbose:
+        logging.basicConfig(level=logging.INFO)
+    else:
+        logging.basicConfig(level=logging.WARNING)
+
+    if len(args) == 0:
+        parser.error('need runfolder arguments')
+
+    # parse lane arguemnts
+    lanes_list = []
+    if len(opts.lanes) == 0:
+        lanes_list = [[1,2,3,4,5,6,7,8]] * len(args)
+    elif len(opts.lanes) == len(args):
+        for lane_arg in opts.lanes:
+            lanes_list.append(parse_lane_arg(lane_arg))
+    else:
+        parser.error(
+          "Number of lane arguments must match number of runfolders"
+        )
+    
+    # build list of commands
+    cmds = {}
+    for runfolder_path, lanes in zip(args, lanes_list):
+        # normalize paths, either relative to home dirs or current dir
+        runfolder_path = os.path.abspath(runfolder_path)
+        # the last part of the path should be a runfolder name
+        name = pathname_to_run_name(runfolder_path)
+        # so any bustard directories?
+        runs = runfolder.get_runs(runfolder_path)
+        # give up if there are anything other than 1 run
+        if len(runs) > 1:
+          print 'ERROR: Too many run directories in %s' %(runfolder_path,)
+          return 1
+        elif len(runs) == 1:
+          bustard_dir = runs[0].bustard.pathname
+          cmds[bustard_dir] = make_commands(name, lanes, opts.site, opts.dest_dir, opts.runfolder_version)
+        else:
+          print "ERROR: Couldn't find a bustard directory in", runfolder_path
+          return 1
+
+    if not opts.dry_run:
+      for cwd, cmd_list in cmds.items():
+        curdir = os.getcwd()
+        os.chdir(cwd)
+        q = queuecommands.QueueCommands(cmd_list, opts.jobs)
+        q.run()
+        os.chdir(curdir)
+    else:
+      for cwd, cmd_list in cmds.items():
+        print cwd
+        print cmd_list
+        print 'jobs: ', opts.jobs
+
+    return 0
+
+if __name__ == "__main__":
+    sys.exit(main(sys.argv[1:]))
diff --git a/trunk/setup.py b/trunk/setup.py
new file mode 100644 (file)
index 0000000..6d3c606
--- /dev/null
@@ -0,0 +1,34 @@
+from setuptools import setup
+
+setup(
+  name="htsworkflow",
+  description="some bots and other utilities to help deal with data from an illumina sequencer",
+  author="Diane Trout & Brandon King",
+  author_email="diane@caltech.edu",
+  packages=["htsworkflow", 
+            "htsworkflow.pipelines",
+            "htsworkflow.frontend",
+            "htsworkflow.frontend.analysis",
+            "htsworkflow.frontend.eland_config",
+            "htsworkflow.frontend.experiments",
+            "htsworkflow.frontend.inventory",
+            "htsworkflow.frontend.reports",
+            "htsworkflow.frontend.samples",
+            "htsworkflow.automation",
+            "htsworkflow.util"
+             ],
+  scripts=[
+        'scripts/configure_pipeline',
+        'scripts/copier',
+        'scripts/gerald2bed.py',
+        'scripts/library.py',
+        'scripts/makebed',
+        'scripts/rerun_eland.py',
+        'scripts/retrieve_config',
+        'scripts/runfolder',
+        'scripts/runner',
+        'scripts/spoolwatcher', 
+        'scripts/srf',
+        'scripts/mark_archived_data'
+        ],
+)
diff --git a/trunk/templates/config_form.html b/trunk/templates/config_form.html
new file mode 100644 (file)
index 0000000..3fbbbee
--- /dev/null
@@ -0,0 +1,17 @@
+<html>
+  <head>
+    <title>Test Config Form</title>
+    <!--
+    <link rel="stylesheet" type="text/css" href="ext-all.css"/>
+    <script type="text/javascript" src="ext.js"></script>
+    <script type="text/javascript" src="elandifier.js"></script>
+    -->
+  </head>
+  <body>
+    <form method="post" action="">
+     <table>{{ form.as_custom }}</table>
+     <input type="submit" />
+   </form>    
+  </body>
+</html>
+  
\ No newline at end of file
diff --git a/trunk/test/test_copier.py b/trunk/test/test_copier.py
new file mode 100644 (file)
index 0000000..f34be14
--- /dev/null
@@ -0,0 +1,69 @@
+import unittest
+
+from StringIO import StringIO
+from htsworkflow.automation import copier
+
+class testCopier(unittest.TestCase):
+    def test_runfolder_validate(self):
+        self.failUnlessEqual(copier.runfolder_validate(""), False)
+        self.failUnlessEqual(copier.runfolder_validate("1345_23"), False)
+        self.failUnlessEqual(copier.runfolder_validate("123456_asdf-$23'"), False)
+        self.failUnlessEqual(copier.runfolder_validate("123456_USI-EAS44"), True)
+        self.failUnlessEqual(copier.runfolder_validate("123456_USI-EAS44 "), False)
+        
+    def test_empty_config(self):
+        cfg = StringIO("""[fake]
+something: unrelated
+""")
+        bot = copier.CopierBot('fake', configfile=cfg)
+        self.failUnlessRaises(KeyError, bot.read_config)
+        
+    def test_full_config(self):
+        cfg = StringIO("""[copier]        
+jid: copier@example.fake
+password: badpassword
+authorized_users: user1@example.fake user2@example.fake
+rsync_password_file: ~/.sequencer
+rsync_sources: /tmp/sequencer_source
+rsync_destination: /tmp/sequencer_destination
+notify_users: user3@example.fake
+# who to run to
+#runner:
+""")
+        c = copier.CopierBot("copier", configfile=cfg)
+        c.read_config()
+        c._init_rsync()
+        self.failUnlessEqual(c.jid, 'copier@example.fake')
+        self.failUnlessEqual(c.cfg['password'], 'badpassword')
+        self.failUnlessEqual(len(c.authorized_users), 2)
+        self.failUnlessEqual(c.authorized_users[0], 'user1@example.fake')
+        self.failUnlessEqual(c.authorized_users[1], 'user2@example.fake')
+        self.failUnlessEqual(c.rsync.source_base_list[0], 
+                             '/tmp/sequencer_source/')
+        self.failUnlessEqual(c.rsync.dest_base, '/tmp/sequencer_destination')
+        self.failUnlessEqual(len(c.notify_users), 1)
+        self.failUnlessEqual(c.notify_users[0], 'user3@example.fake')
+
+    def test_dirlist_filter(self):
+       """
+       test our dir listing parser
+       """
+       # everyone should have a root dir, and since we're not
+       # currently writing files... it should all be good
+       r = copier.rsync('/', '/', '/')
+
+       listing = [
+         'drwxrwxr-x           0 2007/12/29 12:34:56 071229_USI-EAS229_001_FC1234\n',
+         '-rwxrw-r--      123268 2007/12/29 17:39:31 2038EAAXX.rtf\n',
+         '-rwxrw-r--           6 2007/12/29 15:10:29 New Text Document.txt\n',
+       ]
+
+       result = r.list_filter(listing)
+       self.failUnlessEqual(len(result), 1)
+       self.failUnlessEqual(result[0][-1], '4')
+
+def suite():
+    return unittest.makeSuite(testCopier,'test')
+
+if __name__ == "__main__":
+    unittest.main(defaultTest="suite")
diff --git a/trunk/test/tree.py b/trunk/test/tree.py
new file mode 100644 (file)
index 0000000..4f666cc
--- /dev/null
@@ -0,0 +1,81 @@
+#!/usr/bin/env python
+
+"""
+Build a fake directory tree for testing rsync management code.
+"""
+
+import os
+import random
+
+def make_random_string(length=8):
+  """Make a random string, length characters long
+  """
+  symbols = "abcdefhijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
+  name = []
+  for i in xrange(length):
+    name.append(random.choice(symbols))
+  return "".join(name)
+
+def make_file(pathname):
+  """Make a file with some random stuff in it
+  """
+  stream = open(pathname,'w')
+  stream.write(make_random_string(16))
+  stream.close()
+
+def make_tree(root, depth=3, directories=5, files=10):
+  """
+  Make a tree of random directories and files
+
+  depth is how many levels of subdirectories
+  directories is how many directories each subdirectory should have
+  files is how many files to create in each directory
+  """
+  if not os.path.exists(root):
+    os.mkdir(root)
+
+  paths = []
+  # make files
+  for i in range(files):
+    name = make_random_string()
+    paths.append(name)
+    pathname = os.path.join(root, name)
+    make_file(pathname)
+
+  # make subdirectories if we still have some depth to go
+  if depth > 0:
+    for i in range(directories):
+      name = make_random_string()
+      # paths.append(name)
+      pathname = os.path.join(root, name)
+      subpaths = make_tree(pathname, depth-1, directories, files)
+      paths.extend([ os.path.join(name, x) for x in subpaths ])
+
+  return paths
+
+def generate_paths(root):
+  """Make a list of relative paths like generated by make_tree
+  """
+  paths = []
+  for curdir, subdirs, files in os.walk(root):
+    paths.extend([ os.path.join(curdir, f) for f in files ])
+
+  # an inefficient way of getting the correct common prefix
+  # (e.g. root might not have a trailing /)
+  common_root = os.path.commonprefix(paths)
+  common_len = len(common_root)
+  return [ p[common_len:] for p in paths ]
+    
+def compare_tree(root, paths, verbose=False):
+  """Make sure the tree matches our relative list of paths
+  """
+  # what we find when we look
+  experimental_set = set(generate_paths(root))
+  # what we expect
+  theoretical_set = set(paths)
+  # true if the difference of the two sets is the empty set
+  difference = experimental_set - theoretical_set
+  issame = (len(difference) == 0)
+  if verbose and not issame:
+    print difference
+  return issame