From 33769ab1eb4c3fff7b935685c50a95bdfb7cf49a Mon Sep 17 00:00:00 2001 From: David Emms <david_emms@hotmail.com> Date: Tue, 6 Sep 2016 11:51:00 +0100 Subject: [PATCH] Organise files as package --- Tests/test_orthofinder.py | 199 +++--- .../Mycoplasma_agalactiae_5632_FP671138.faa | 0 ...coplasma_gallisepticum_uid409_AE015450.faa | 0 .../Mycoplasma_genitalium_uid97_L43967.faa | 0 .../Mycoplasma_hyopneumoniae_AE017243.faa | 0 License.md => orthofinder/License.md | 0 README.md => orthofinder/README.md | 13 +- orthofinder/Workflow.png | Bin 0 -> 115526 bytes orthofinder/__init__.py | 1 + orthofinder.py => orthofinder/orthofinder.py | 672 ++++-------------- orthofinder/scripts/__init__.py | 1 + orthofinder/scripts/blast_file_processor.py | 75 ++ .../scripts/get_orthologues.py | 117 +-- orthofinder/scripts/matrices.py | 63 ++ orthofinder/scripts/mcl.py | 105 +++ newick.py => orthofinder/scripts/newick.py | 0 .../scripts/orthologues_from_recon_trees.py | 48 +- .../scripts/root_from_duplications.py | 44 +- tree.py => orthofinder/scripts/tree.py | 15 - orthofinder/scripts/util.py | 388 ++++++++++ .../trees_from_MSA.py | 163 +---- 21 files changed, 1049 insertions(+), 855 deletions(-) rename {ExampleDataset => orthofinder/ExampleDataset}/Mycoplasma_agalactiae_5632_FP671138.faa (100%) rename {ExampleDataset => orthofinder/ExampleDataset}/Mycoplasma_gallisepticum_uid409_AE015450.faa (100%) rename {ExampleDataset => orthofinder/ExampleDataset}/Mycoplasma_genitalium_uid97_L43967.faa (100%) rename {ExampleDataset => orthofinder/ExampleDataset}/Mycoplasma_hyopneumoniae_AE017243.faa (100%) rename License.md => orthofinder/License.md (100%) rename README.md => orthofinder/README.md (95%) create mode 100755 orthofinder/Workflow.png create mode 100644 orthofinder/__init__.py rename orthofinder.py => orthofinder/orthofinder.py (71%) create mode 100644 orthofinder/scripts/__init__.py create mode 100644 orthofinder/scripts/blast_file_processor.py rename get_orthologues.py => orthofinder/scripts/get_orthologues.py (87%) create mode 100644 orthofinder/scripts/matrices.py create mode 100644 orthofinder/scripts/mcl.py rename newick.py => orthofinder/scripts/newick.py (100%) rename process_trees.py => orthofinder/scripts/orthologues_from_recon_trees.py (83%) rename root_from_duplications.py => orthofinder/scripts/root_from_duplications.py (93%) rename tree.py => orthofinder/scripts/tree.py (99%) create mode 100644 orthofinder/scripts/util.py rename trees_for_orthogroups.py => orthofinder/trees_from_MSA.py (62%) diff --git a/Tests/test_orthofinder.py b/Tests/test_orthofinder.py index bdc96f7..a2b9b27 100755 --- a/Tests/test_orthofinder.py +++ b/Tests/test_orthofinder.py @@ -9,6 +9,7 @@ Created on Wed Dec 16 11:25:10 2015 import os import sys import time +import types import datetime import unittest import subprocess @@ -22,17 +23,17 @@ __skipLongTests__ = False baseDir = os.path.dirname(os.path.realpath(__file__)) + os.sep qBinary = False -orthofinder = baseDir + "../orthofinder.py" -orthofinder_bin = baseDir + "../bin/orthofinder" -trees_for_orthogroups = baseDir + "../trees_for_orthogroups.py" -trees_for_orthogroups_bin = baseDir + "../bin/trees_for_orthogroups" +orthofinder = baseDir + "../orthofinder/orthofinder.py" +orthofinder_bin = baseDir + "../orthofinder/bin/orthofinder" +trees_for_orthogroups = baseDir + "../orthofinder/trees_from_MSA.py" +trees_for_orthogroups_bin = baseDir + "../orthofinder/bin/trees_from_MSA" exampleFastaDir = baseDir + "Input/SmallExampleDataset/" exampleBlastDir = baseDir + "Input/SmallExampleDataset_ExampleBlastDir/" goldResultsDir_smallExample = baseDir + "ExpectedOutput/SmallExampleDataset/" goldPrepareBlastDir = baseDir + "ExpectedOutput/SmallExampleDataset_PreparedForBlast/" -version = "1.0.0" +version = "1.0.1" requiredBlastVersion = "2.2.28+" citation = """When publishing work that uses OrthoFinder please cite: @@ -92,6 +93,9 @@ Arguments limiting factor above about 5-10 threads and additional threads may have little effect other than increase RAM requirements. [Default is 1] +-g, --groups + Only infer orthogroups, do not infer gene trees of orthologues. + -I inflation_parameter, --inflation inflation_parameter Specify a non-default inflation parameter for MCL. [Default is 1.5] @@ -126,6 +130,7 @@ class CleanUp(object): self.newFiles = newFiles self.modifiedFiles = modifiedFiles self.copies = [] + assert(types.ListType == type(newDirs)) # if it were a string the code could attempt to delete every file on computer self.newDirs = newDirs self.qSaveFiles = qSaveFiles def __enter__(self): @@ -176,27 +181,30 @@ class TestCommandLine(unittest.TestCase): currentResultsDir = exampleFastaDir + "Results_%s/" % datetime.date.today().strftime("%b%d") expectedCSVFile = currentResultsDir + "OrthologousGroups.csv" with CleanUp([], [], [currentResultsDir, ]): - stdout, stderr = self.RunOrthoFinder("-f %s" % exampleFastaDir) - self.CheckStandardRun(stdout, stderr, goldResultsDir_smallExample, expectedCSVFile) + self.stdout, self.stderr = self.RunOrthoFinder("-f %s -g" % exampleFastaDir) + self.CheckStandardRun(self.stdout, self.stderr, goldResultsDir_smallExample, expectedCSVFile) + self.test_passed = True def test_fromfasta_threads(self): currentResultsDir = exampleFastaDir + "Results_%s/" % datetime.date.today().strftime("%b%d") expectedCSVFile = currentResultsDir + "OrthologousGroups.csv" with CleanUp([], [], [currentResultsDir, ]): - stdout, stderr = self.RunOrthoFinder("-f %s -t 4 -a 3" % exampleFastaDir) - self.CheckStandardRun(stdout, stderr, goldResultsDir_smallExample, expectedCSVFile) + self.stdout, self.stderr = self.RunOrthoFinder("-f %s -t 4 -a 3 -g" % exampleFastaDir) + self.CheckStandardRun(self.stdout, self.stderr, goldResultsDir_smallExample, expectedCSVFile) + self.test_passed = True @unittest.skipIf(__skipLongTests__, "Only performing quick tests") def test_fromfasta_full(self): currentResultsDir = exampleFastaDir + "Results_%s/" % datetime.date.today().strftime("%b%d") expectedCSVFile = currentResultsDir + "OrthologousGroups.csv" with CleanUp([], [], [currentResultsDir, ]): - stdout, stderr = self.RunOrthoFinder("--fasta %s" % exampleFastaDir) - self.CheckStandardRun(stdout, stderr, goldResultsDir_smallExample, expectedCSVFile) + self.stdout, self.stderr = self.RunOrthoFinder("--fasta %s -g" % exampleFastaDir) + self.CheckStandardRun(self.stdout, self.stderr, goldResultsDir_smallExample, expectedCSVFile) + self.test_passed = True def test_justPrepare(self): self.currentResultsDir = exampleFastaDir + "Results_%s/" % datetime.date.today().strftime("%b%d") - stdout, stderr = self.RunOrthoFinder("-f %s -p" % exampleFastaDir) + self.stdout, self.stderr = self.RunOrthoFinder("-f %s -p" % exampleFastaDir) expectedFiles = ['BlastDBSpecies0.phr', 'BlastDBSpecies0.pin', 'BlastDBSpecies0.psq', 'BlastDBSpecies1.phr', 'BlastDBSpecies1.pin', 'BlastDBSpecies1.psq', 'BlastDBSpecies2.phr', 'BlastDBSpecies2.pin', 'BlastDBSpecies2.psq', @@ -213,10 +221,11 @@ class TestCommandLine(unittest.TestCase): # no other files in directory or intermediate directory self.assertTrue(len(list(glob.glob(self.currentResultsDir + "WorkingDirectory/*"))), len(expectedFiles)) self.assertTrue(len(list(glob.glob(self.currentResultsDir + "*"))), 1) # Only 'WorkingDirectory/" + self.test_passed = True def test_justPrepareFull(self): self.currentResultsDir = exampleFastaDir + "Results_%s/" % datetime.date.today().strftime("%b%d") - stdout, stderr = self.RunOrthoFinder("-f %s --prepare" % exampleFastaDir) + self.stdout, self.stderr = self.RunOrthoFinder("-f %s --prepare" % exampleFastaDir) expectedFiles = ['BlastDBSpecies0.phr', 'BlastDBSpecies0.pin', 'BlastDBSpecies0.psq', 'BlastDBSpecies1.phr', 'BlastDBSpecies1.pin', 'BlastDBSpecies1.psq', 'BlastDBSpecies2.phr', 'BlastDBSpecies2.pin', 'BlastDBSpecies2.psq', @@ -232,48 +241,56 @@ class TestCommandLine(unittest.TestCase): # no other files in directory or intermediate directory self.assertTrue(len(list(glob.glob(self.currentResultsDir + "WorkingDirectory/*"))), len(expectedFiles)) - self.assertTrue(len(list(glob.glob(self.currentResultsDir + "*"))), 1) # Only 'WorkingDirectory/" + self.assertTrue(len(list(glob.glob(self.currentResultsDir + "*"))), 1) # Only 'WorkingDirectory/" + self.test_passed = True def test_fromblast(self): expectedCSVFile = exampleBlastDir + "OrthologousGroups.csv" newFiles = ("OrthologousGroups.csv OrthologousGroups_UnassignedGenes.csv OrthologousGroups.txt clusters_OrthoFinder_v%s_I1.5.txt_id_pairs.txt clusters_OrthoFinder_v%s_I1.5.txt OrthoFinder_v%s_graph.txt Statistics_PerSpecies.csv Statistics_Overall.csv OrthologousGroups_SpeciesOverlaps.csv" % (version, version, version)).split() newFiles = [exampleBlastDir + fn for fn in newFiles] with CleanUp(newFiles, []): - stdout, stderr = self.RunOrthoFinder("-b %s" % exampleBlastDir) - self.CheckStandardRun(stdout, stderr, goldResultsDir_smallExample, expectedCSVFile) + self.stdout, self.stderr = self.RunOrthoFinder("-b %s -g" % exampleBlastDir) + self.CheckStandardRun(self.stdout, self.stderr, goldResultsDir_smallExample, expectedCSVFile) + self.test_passed = True def test_fromblast_full(self): expectedCSVFile = exampleBlastDir + "OrthologousGroups.csv" newFiles = ("OrthologousGroups.csv OrthologousGroups_UnassignedGenes.csv OrthologousGroups.txt clusters_OrthoFinder_v%s_I1.5.txt_id_pairs.txt clusters_OrthoFinder_v%s_I1.5.txt OrthoFinder_v%s_graph.txt Statistics_PerSpecies.csv Statistics_Overall.csv OrthologousGroups_SpeciesOverlaps.csv" % (version, version, version)).split() newFiles = [exampleBlastDir + fn for fn in newFiles] with CleanUp(newFiles, []): - stdout, stderr = self.RunOrthoFinder("--blast %s" % exampleBlastDir) - self.CheckStandardRun(stdout, stderr, goldResultsDir_smallExample, expectedCSVFile) + self.stdout, self.stderr = self.RunOrthoFinder("--blast %s -g" % exampleBlastDir) + self.CheckStandardRun(self.stdout, self.stderr, goldResultsDir_smallExample, expectedCSVFile) + self.test_passed = True def test_fromblast_algthreads(self): expectedCSVFile = exampleBlastDir + "OrthologousGroups.csv" newFiles = ("Statistics_PerSpecies.csv Statistics_Overall.csv OrthologousGroups_SpeciesOverlaps.csv OrthologousGroups.csv OrthologousGroups_UnassignedGenes.csv OrthologousGroups.txt clusters_OrthoFinder_v%s_I1.5.txt_id_pairs.txt clusters_OrthoFinder_v%s_I1.5.txt OrthoFinder_v%s_graph.txt" % (version, version, version)).split() newFiles = [exampleBlastDir + fn for fn in newFiles] with CleanUp(newFiles, []): - stdout, stderr = self.RunOrthoFinder("-b %s -a 3" % exampleBlastDir) - self.CheckStandardRun(stdout, stderr, goldResultsDir_smallExample, expectedCSVFile) + self.stdout, self.stderr = self.RunOrthoFinder("-b %s -a 3 -g" % exampleBlastDir) + self.CheckStandardRun(self.stdout, self.stderr, goldResultsDir_smallExample, expectedCSVFile) + self.test_passed = True def test_blast_results_error(self): - with CleanUp([], []): - stdout, stderr = self.RunOrthoFinder("-a 2 -b " + baseDir + "Input/SmallExampleDataset_ExampleBadBlast/") - self.assertTrue("Traceback" not in stderr) - self.assertTrue("Offending line was:" in stderr) - self.assertTrue("0_0 0_0 100.00 466" in stderr) - self.assertTrue("Connected putatitive homologs" not in stdout) - self.assertTrue("ERROR: An error occurred, please review previous error messages for more information." in stdout) + d = baseDir + "Input/SmallExampleDataset_ExampleBadBlast/" + newFiles = [d + "%s%d_%d.pic" % (s, i,j) for i in xrange(1, 3) for j in xrange(3) for s in ["B", "BH"]] + with CleanUp(newFiles, []): + self.stdout, self.stderr = self.RunOrthoFinder("-a 2 -g -b " + d) + self.assertTrue("Traceback" not in self.stderr) + self.assertTrue("Offending line was:" in self.stderr) + self.assertTrue("0_0 0_0 100.00 466" in self.stderr) + self.assertTrue("Connected putatitive homologs" not in self.stdout) + self.assertTrue("ERROR: An error occurred, please review previous error messages for more information." in self.stdout) + self.test_passed = True def test_inflation(self): expectedCSVFile = exampleBlastDir + "OrthologousGroups.csv" newFiles = ("Statistics_PerSpecies.csv Statistics_Overall.csv OrthologousGroups_SpeciesOverlaps.csv OrthologousGroups.csv OrthologousGroups_UnassignedGenes.csv OrthologousGroups.txt clusters_OrthoFinder_v%s_I1.8.txt_id_pairs.txt clusters_OrthoFinder_v%s_I1.8.txt OrthoFinder_v%s_graph.txt" % (version, version, version)).split() newFiles = [exampleBlastDir + fn for fn in newFiles] with CleanUp(newFiles, []): - stdout, stderr = self.RunOrthoFinder("-I 1.8 -b %s" % exampleBlastDir) - self.CheckStandardRun(stdout, stderr, baseDir + "ExpectedOutput/SmallExampleDataset_I1.8/", expectedCSVFile) + self.stdout, self.stderr = self.RunOrthoFinder("-I 1.8 -b %s -g" % exampleBlastDir) + self.CheckStandardRun(self.stdout, self.stderr, baseDir + "ExpectedOutput/SmallExampleDataset_I1.8/", expectedCSVFile) + self.test_passed = True # @unittest.skipIf(__skipLongTests__, "Only performing quick tests") # def test_fromblastOrthobench(self): @@ -282,21 +299,23 @@ class TestCommandLine(unittest.TestCase): # self.currentResultsDir = None # expectedNewFiles = [baseDir + "Input/Orthobench_blast/" + x for x in "OrthoFinder_v0.4.0_graph.txt clusters_OrthoFinder_v0.4.0_I1.5.txt clusters_OrthoFinder_v0.4.0_I1.5.txt_id_pairs.txt".split()] # with CleanUp(expectedNewFiles, []): -# stdout, stderr = self.RunOrthoFinder("-b %sInput/Orthobench_blast" % baseDir) -# self.CheckStandardRun(stdout, stderr, goldResultsDir_orthobench, expectedCSVFileLocation, qDelete = True) +# self.stdout, self.stderr = self.RunOrthoFinder("-b %sInput/Orthobench_blast" % baseDir) +# self.CheckStandardRun(stdout, stderr, goldResultsDir_orthobench, expectedCSVFileLocation, qDelete = True) +# self.test_passed = True def test_help(self): - stdout, stderr = self.RunOrthoFinder("") - self.assertTrue(expectedHelp in stdout) - self.assertEqual(len(stderr), 0) - - stdout, stderr = self.RunOrthoFinder("-h") - self.assertTrue(expectedHelp in stdout) - self.assertEqual(len(stderr), 0) + self.stdout, self.stderr = self.RunOrthoFinder("") + self.assertTrue(expectedHelp in self.stdout) + self.assertEqual(len(self.stderr), 0) + + self.stdout, self.stderr = self.RunOrthoFinder("-h") + self.assertTrue(expectedHelp in self.stdout) + self.assertEqual(len(self.stderr), 0) - stdout, stderr = self.RunOrthoFinder("--help") - self.assertTrue(expectedHelp in stdout) - self.assertEqual(len(stderr), 0) + self.stdout, self.stderr = self.RunOrthoFinder("--help") + self.assertTrue(expectedHelp in self.stdout) + self.assertEqual(len(self.stderr), 0) + self.test_passed = True # def test_numberOfThreads(self): # pass @@ -316,8 +335,9 @@ class TestCommandLine(unittest.TestCase): expectedChangedFiles = [exampleBlastDir + fn for fn in "SpeciesIDs.txt SequenceIDs.txt".split()] # cleanup afterwards including failed test goldDir = baseDir + "ExpectedOutput/AddOneSpecies/" - with CleanUp(expectedExtraFiles, expectedChangedFiles): - stdout, stderr = self.RunOrthoFinder("-b %s -f %s" % (exampleBlastDir, baseDir + "Input/ExtraFasta")) + expectedExtraDir = exampleBlastDir + "Orthologues_%s/" % datetime.date.today().strftime("%b%d") + with CleanUp(expectedExtraFiles, expectedChangedFiles, [expectedExtraDir]): + self.stdout, self.stderr = self.RunOrthoFinder("-b %s -f %s" % (exampleBlastDir, baseDir + "Input/ExtraFasta")) # check extra blast files # check extra fasta file: simple couple of checks to ensure all ok for fn in expectedExtraFiles: @@ -325,7 +345,8 @@ class TestCommandLine(unittest.TestCase): self.assertTrue(os.path.exists(fn), msg=fn) # mcl output files contain a variable header, these files are an implementation detail that I don't want to test (I want the final orthogroups to be correct) if "clusters" in os.path.split(fn)[1]: continue - self.CompareFile(goldDir + os.path.split(fn)[1], fn) + self.CompareFile(goldDir + os.path.split(fn)[1], fn) + self.test_passed = True def test_addTwoSpecies(self): expectedExtraFiles = [exampleBlastDir + fn for fn in ("Blast0_3.txt Blast3_0.txt Blast1_3.txt Blast3_1.txt Blast2_3.txt Blast3_2.txt Blast3_3.txt Species3.fa \ @@ -336,12 +357,13 @@ class TestCommandLine(unittest.TestCase): expectedChangedFiles = [exampleBlastDir + fn for fn in "SpeciesIDs.txt SequenceIDs.txt".split()] goldDir = baseDir + "ExpectedOutput/AddTwoSpecies/" with CleanUp(expectedExtraFiles, expectedChangedFiles): - stdout, stderr = self.RunOrthoFinder("-b %s -f %s" % (exampleBlastDir, baseDir + "Input/ExtraFasta2")) + self.stdout, self.stderr = self.RunOrthoFinder("-b %s -g -f %s" % (exampleBlastDir, baseDir + "Input/ExtraFasta2")) for fn in expectedExtraFiles: os.path.split(fn)[1] self.assertTrue(os.path.exists(fn), msg=fn) if "clusters" in os.path.split(fn)[1]: continue self.CompareFile(goldDir + os.path.split(fn)[1], fn) + self.test_passed = True def test_addTwoSpecies_blastsRequired(self): expectedExtraFiles = [exampleBlastDir + fn for fn in "Species3.fa Species4.fa".split()] @@ -349,18 +371,19 @@ class TestCommandLine(unittest.TestCase): expectedChangedFiles = [exampleBlastDir + fn for fn in "SpeciesIDs.txt SequenceIDs.txt".split()] # cleanup afterwards including failed test with CleanUp(expectedExtraFiles, expectedChangedFiles): - stdout, stderr = self.RunOrthoFinder("-b %s -f %s -p" % (exampleBlastDir, baseDir + "Input/ExtraFasta2")) + self.stdout, self.stderr = self.RunOrthoFinder("-b %s -f %s -p" % (exampleBlastDir, baseDir + "Input/ExtraFasta2")) original = [0, 1, 2] new = [3, 4] for i in new: for j in original: - assert("Blast%d_%d.txt" % (i,j) in stdout) - assert("Blast%d_%d.txt" % (j,i) in stdout) + assert("Blast%d_%d.txt" % (i,j) in self.stdout) + assert("Blast%d_%d.txt" % (j,i) in self.stdout) for j in new: - assert("Blast%d_%d.txt" % (i,j) in stdout) - assert("Blast%d_%d.txt" % (j,i) in stdout) + assert("Blast%d_%d.txt" % (i,j) in self.stdout) + assert("Blast%d_%d.txt" % (j,i) in self.stdout) - assert(stdout.count("blastp") == 2*len(new)*len(original) + len(new)**2) + assert(self.stdout.count("blastp") == 2*len(new)*len(original) + len(new)**2) + self.test_passed = True def test_removeFirstSpecies(self): self.RemoveSpeciesTest(baseDir + "Input/ExampleDataset_removeFirst/", @@ -379,10 +402,11 @@ class TestCommandLine(unittest.TestCase): requiredResults = [inputDir + fn for fn in "OrthologousGroups.csv OrthologousGroups_UnassignedGenes.csv OrthologousGroups.txt".split()] expectedExtraFiles = [inputDir + fn for fn in ("Statistics_PerSpecies.csv Statistics_Overall.csv OrthologousGroups_SpeciesOverlaps.csv clusters_OrthoFinder_v%s_I1.5.txt clusters_OrthoFinder_v%s_I1.5.txt_id_pairs.txt OrthoFinder_v%s_graph.txt" % (version, version, version)).split()] with CleanUp(expectedExtraFiles + requiredResults, []): - stdout, stderr = self.RunOrthoFinder("-b %s" % inputDir) + self.stdout, self.stderr = self.RunOrthoFinder("-b %s" % inputDir) for fn in requiredResults: self.assertTrue(os.path.exists(fn), msg=fn) - self.CompareFile(goldDir + os.path.split(fn)[1], fn) + self.CompareFile(goldDir + os.path.split(fn)[1], fn) + self.test_passed = True # def test_removeMultipleSpecies(self): # pass @@ -396,7 +420,7 @@ class TestCommandLine(unittest.TestCase): expectedChangedFiles = [inputDir + fn for fn in "SpeciesIDs.txt SequenceIDs.txt".split()] goldDir = baseDir + "ExpectedOutput/AddOneRemoveOne/" with CleanUp(expectedExtraFiles, expectedChangedFiles): - stdout, stderr = self.RunOrthoFinder("-b %s -f %s" % (inputDir, baseDir + "Input/ExampleDataset_addOneRemoveOne/ExtraFasta/")) + self.stdout, self.stderr = self.RunOrthoFinder("-b %s -f %s" % (inputDir, baseDir + "Input/ExampleDataset_addOneRemoveOne/ExtraFasta/")) # print(stdout) # print(stderr) for fn in expectedExtraFiles: @@ -405,7 +429,8 @@ class TestCommandLine(unittest.TestCase): if "OrthologousGroups" in os.path.split(fn)[1]: self.CompareFile(goldDir + os.path.split(fn)[1], fn) self.CompareFile(goldDir + "SpeciesIDs.txt", inputDir + "SpeciesIDs.txt") - self.CompareFile(goldDir + "SequenceIDs.txt", inputDir + "SequenceIDs.txt") + self.CompareFile(goldDir + "SequenceIDs.txt", inputDir + "SequenceIDs.txt") + self.test_passed = True # def test_addMultipleSpecies_prepare(selg): # pass @@ -434,7 +459,7 @@ class TestCommandLine(unittest.TestCase): goldDirs = [baseDir + "ExpectedOutput/SmallExampleDataset_trees/" + d + "/" for d in expectedDirs] nTrees = 427 with CleanUp([], [], newDirs): - stdout, stderr = self.RunTrees(baseDir + "Input/SmallExampleDataset_forTrees/Results_Jan28/") + self.stdout, self.stderr = self.RunTrees(baseDir + "Input/SmallExampleDataset_forTrees/Results_Jan28/") for i in xrange(nTrees): self.assertTrue(os.path.exists(newDirs[0] + "/OG%07d.fa" % i), msg=str(i)) self.assertTrue(os.path.exists(newDirs[1] + "/OG%07d_tree.txt" % i)) @@ -443,38 +468,42 @@ class TestCommandLine(unittest.TestCase): for goldFN in glob.glob(goldD + "*"): newFN = newD + os.path.split(goldFN)[1] self.assertTrue(os.path.exists(newFN), msg=goldFN) - self.CompareFile(goldFN, newFN) + self.CompareFile(goldFN, newFN) + self.test_passed = True def test_treesAfterOption_b(self): expectedDirs = "Alignments Trees Sequences".split() newDirs = [baseDir + "Input/SmallExampleDataset_forTreesFromBlast/" + d +"/" for d in expectedDirs] goldDirs = [baseDir + "ExpectedOutput/SmallExampleDataset_trees/" + d +"/" for d in expectedDirs] with CleanUp([], [], newDirs): - stdout, stderr = self.RunTrees(baseDir + "Input/SmallExampleDataset_forTreesFromBlast/") + self.stdout, self.stderr = self.RunTrees(baseDir + "Input/SmallExampleDataset_forTreesFromBlast/") for goldD, newD in zip(goldDirs, newDirs): for goldFN in glob.glob(goldD + "*"): newFN = newD + os.path.split(goldFN)[1] self.assertTrue(os.path.exists(newFN), msg=goldFN) if newD[:-1].rsplit("/", 1)[-1] == "Sequences": - self.CompareFile(goldFN, newFN) + self.CompareFile(goldFN, newFN) + self.test_passed = True def test_treesMultipleResults(self): expectedDirs = "Alignments Trees Sequences".split() newDirs = [baseDir + "Input/MultipleResults/Results_Jan28/WorkingDirectory/" + d +"/" for d in expectedDirs] goldDirs = [baseDir + "ExpectedOutput/MultipleResultsInDirectory/" + d +"/" for d in expectedDirs] with CleanUp([], [], newDirs): - stdout, stderr = self.RunTrees(baseDir + "Input/MultipleResults/Results_Jan28/WorkingDirectory/clusters_OrthoFinder_v0.4.0_I1.5.txt_id_pairs.txt") + self.stdout, self.stderr = self.RunTrees(baseDir + "Input/MultipleResults/Results_Jan28/WorkingDirectory/clusters_OrthoFinder_v0.4.0_I1.5.txt_id_pairs.txt") for goldD, newD in zip(goldDirs, newDirs): for goldFN in glob.glob(goldD + "*"): newFN = newD + os.path.split(goldFN)[1] self.assertTrue(os.path.exists(newFN), msg=goldFN) if newD[:-1].rsplit("/", 1)[-1] == "Sequences": - self.CompareFile(goldFN, newFN) + self.CompareFile(goldFN, newFN) + self.test_passed = True def test_treesSubset_unspecified(self): - stdout, stderr = self.RunTrees(baseDir + "/Input/Trees_OneSpeciesRemoved") - self.assertTrue("ERROR: Results from multiple OrthoFinder runs found" in stdout) - self.assertTrue("Please run with only one set of results in directories or specifiy the specific clusters_OrthoFinder_*.txt_id_pairs.txt file on the command line" in stdout) + self.stdout, self.stderr = self.RunTrees(baseDir + "/Input/Trees_OneSpeciesRemoved") + self.assertTrue("ERROR: Results from multiple OrthoFinder runs found" in self.stdout) + self.assertTrue("Please run with only one set of results in directories or specifiy the specific clusters_OrthoFinder_*.txt_id_pairs.txt file on the command line" in self.stdout) + self.test_passed = True def test_treesResultsChoice_subset(self): expectedDirs = "Alignments Trees Sequences".split() @@ -482,7 +511,7 @@ class TestCommandLine(unittest.TestCase): goldDirs = [baseDir + "ExpectedOutput/SmallExampleDataset_trees/" + d + "/" for d in expectedDirs] nTrees = 427 with CleanUp([], [], newDirs): - stdout, stderr = self.RunTrees(baseDir + "/Input/Trees_OneSpeciesRemoved/clusters_OrthoFinder_v0.6.1_I1.5_1.txt_id_pairs.txt") + self.stdout, self.stderr = self.RunTrees(baseDir + "/Input/Trees_OneSpeciesRemoved/clusters_OrthoFinder_v0.6.1_I1.5_1.txt_id_pairs.txt") for i in xrange(nTrees): self.assertTrue(os.path.exists(newDirs[0] + "/OG%07d.fa" % i), msg=str(i)) self.assertTrue(os.path.exists(newDirs[1] + "/OG%07d_tree.txt" % i)) @@ -490,7 +519,8 @@ class TestCommandLine(unittest.TestCase): newD = newDirs[2] for goldFN in glob.glob(goldD + "*"): newFN = newD + os.path.split(goldFN)[1] - self.assertTrue(os.path.exists(newFN), msg=goldFN) + self.assertTrue(os.path.exists(newFN), msg=goldFN) + self.test_passed = True # def test_treesResultsChoice_full(self): dirs = ["Trees/", "Alignments/", "Sequences/"] @@ -499,22 +529,21 @@ class TestCommandLine(unittest.TestCase): nExpected = [536, 536, 1330] fnPattern = [baseDir + "/Input/Trees_OneSpeciesRemoved/" + p for p in ["Trees/OG%07d_tree.txt", "Alignments/OG%07d.fa", "Sequences/OG%07d.fa"]] with CleanUp([], [], expectedDirs): - stdout, stderr = self.RunTrees(baseDir + "/Input/Trees_OneSpeciesRemoved/clusters_OrthoFinder_v0.6.1_I1.5.txt_id_pairs.txt") + self.stdout, self.stderr = self.RunTrees(baseDir + "/Input/Trees_OneSpeciesRemoved/clusters_OrthoFinder_v0.6.1_I1.5.txt_id_pairs.txt") for goldD, expD, n, fnPat in zip(goldDirs, expectedDirs, nExpected, fnPattern): for i in xrange(n): self.assertTrue(os.path.exists(fnPat % i)) # Only test the contents of a limited number for goldFN in glob.glob(goldD + "*"): newFN = expD + os.path.split(goldFN)[1] - self.CompareFile(goldFN, newFN) + self.CompareFile(goldFN, newFN) + self.test_passed = True def test_ProblemCharacters_issue28(self): - """Deal with problematic characters in accession names - """ # Can't have ( ) : , inputDir = baseDir + "Input/DisallowedCharacters/" with CleanUp([], [], [inputDir + x for x in "Trees/ Sequences/ Alignments/".split()]): - stdout, stderr = self.RunTrees(inputDir) + self.stdout, self.stderr = self.RunTrees(inputDir) fn = inputDir + "Trees/OG0000000_tree.txt" self.assertTrue(os.path.exists(fn)) with open(fn, 'rb') as infile: @@ -523,7 +552,8 @@ class TestCommandLine(unittest.TestCase): self.assertTrue("(" in l) self.assertTrue(")" in l) self.assertTrue("," in l) - self.assertTrue(";" in l) + self.assertTrue(";" in l) + self.test_passed = True # def test_treesExtraSpecies(self): @@ -531,9 +561,15 @@ class TestCommandLine(unittest.TestCase): def setUp(self): self.currentResultsDir = None + self.test_passed = False + self.stdout = None + self.stderr = None def tearDown(self): self.CleanCurrentResultsDir() + if not self.test_passed: + print(self.stdout) + print(self.stderr) def RunOrthoFinder(self, commands): if qBinary: @@ -634,16 +670,15 @@ if __name__ == "__main__": if len(sys.argv) > 2: test = sys.argv[2] else: - test = sys.argv[1] - - if test == None: - suite = unittest.TestLoader().loadTestsFromTestCase(TestCommandLine) - unittest.TextTestRunner(verbosity=2).run(suite) - else: - suite = unittest.TestSuite() - suite.addTest(TestCommandLine(test)) - runner = unittest.TextTestRunner() - runner.run(suite) + test = sys.argv[1] + if test == None: + suite = unittest.TestLoader().loadTestsFromTestCase(TestCommandLine) + unittest.TextTestRunner(verbosity=2).run(suite) + else: + suite = unittest.TestSuite() + suite.addTest(TestCommandLine(test)) + runner = unittest.TextTestRunner() + runner.run(suite) diff --git a/ExampleDataset/Mycoplasma_agalactiae_5632_FP671138.faa b/orthofinder/ExampleDataset/Mycoplasma_agalactiae_5632_FP671138.faa similarity index 100% rename from ExampleDataset/Mycoplasma_agalactiae_5632_FP671138.faa rename to orthofinder/ExampleDataset/Mycoplasma_agalactiae_5632_FP671138.faa diff --git a/ExampleDataset/Mycoplasma_gallisepticum_uid409_AE015450.faa b/orthofinder/ExampleDataset/Mycoplasma_gallisepticum_uid409_AE015450.faa similarity index 100% rename from ExampleDataset/Mycoplasma_gallisepticum_uid409_AE015450.faa rename to orthofinder/ExampleDataset/Mycoplasma_gallisepticum_uid409_AE015450.faa diff --git a/ExampleDataset/Mycoplasma_genitalium_uid97_L43967.faa b/orthofinder/ExampleDataset/Mycoplasma_genitalium_uid97_L43967.faa similarity index 100% rename from ExampleDataset/Mycoplasma_genitalium_uid97_L43967.faa rename to orthofinder/ExampleDataset/Mycoplasma_genitalium_uid97_L43967.faa diff --git a/ExampleDataset/Mycoplasma_hyopneumoniae_AE017243.faa b/orthofinder/ExampleDataset/Mycoplasma_hyopneumoniae_AE017243.faa similarity index 100% rename from ExampleDataset/Mycoplasma_hyopneumoniae_AE017243.faa rename to orthofinder/ExampleDataset/Mycoplasma_hyopneumoniae_AE017243.faa diff --git a/License.md b/orthofinder/License.md similarity index 100% rename from License.md rename to orthofinder/License.md diff --git a/README.md b/orthofinder/README.md similarity index 95% rename from README.md rename to orthofinder/README.md index 4bdfca7..7479499 100644 --- a/README.md +++ b/orthofinder/README.md @@ -1,4 +1,7 @@ # OrthoFinder — Accurate inference of orthogroups, orthologues, gene trees and rooted species tree made easy! + + + What does OrthoFinder do? ========== OrthoFinder is a fast, accurate and comprehensive analysis tool for comparative genomics. It finds orthologs, orthogroups, infers gene trees for all orthogroups and infers a species tree for the species being analysed. OrthoFinder also identifies the root of the species tree and provides lots of useful statistics for comparative genomic analyses. OrthoFinder is very simple to use and all you need to run it is a set of protein sequence files (one per species) in FASTA format . @@ -25,7 +28,7 @@ What's New **Jan. 2016**: Added the ability to **add and remove species**. -**Sept. 2015**: Added the **trees_for_orthogroups.py** utility to automatically calculate multiple sequence alignments and gene trees for the orthogroups calcualted using OrthoFinder. +**Sept. 2015**: Added the **trees_from_MSA.py** utility to automatically calculate multiple sequence alignments and gene trees for the orthogroups calcualted using OrthoFinder. Usage ===== @@ -75,7 +78,7 @@ Most of the terms in the files **Statistics_Overall.csv** and **Statistics_PerSp - Species-specific orthogroup: An orthogroups that consist entirely of genes from one species. - G50: The number of genes in the orthogroup such that 50% of genes are in orthogroups of that size or larger. - O50: The smallest number of orthogroups such that 50% of genes are in orthogroups of that size or larger. -- Single-copy orthogroup: An orthogroup with exactly one gene (and no more) from each species. These orthogroups are ideal for inferring a species tree. Note that trees for all orthogroups can be generated using the trees_for_orthogroups.py script. +- Single-copy orthogroup: An orthogroup with exactly one gene (and no more) from each species. These orthogroups are ideal for inferring a species tree. Note that trees for all orthogroups can be generated using the trees_from_MSA.py script. - Unassigned gene: A gene that has not been put into an orthogroup with any other genes. ###Orthologues, Gene Trees & Rooted Species Tree @@ -97,7 +100,7 @@ After the orthogroups have been calculated OrthoFinder will infer gene trees, th 2. DLCpar-search -To use the trees_for_orthogroups.py utility, which infers trees using multiple sequence alignments, there are two additional dependencies which should be installed and in the system path: +To use the trees_from_MSA.py utility, which infers trees using multiple sequence alignments, there are two additional dependencies which should be installed and in the system path: 1. MAFFT @@ -296,9 +299,9 @@ Information on the orthoxml format can be found here: http://orthoxml.org/0.3/or Trees for Orthogroups ===================== -The 'trees_for_orthogroups.py' utility will automatically generate multiple sequence alignments and gene trees for each orthologous group generated by OrthoFinder. For example, once OrthoFinder has been run on the example dataset, trees_for_orthogroups can be run using: +The 'trees_from_MSA.py' utility will automatically generate multiple sequence alignments and gene trees for each orthologous group generated by OrthoFinder. For example, once OrthoFinder has been run on the example dataset, trees_from_MSA can be run using: -**python trees_for_orthogroups.py ExampleDataset/Results_\<date\> -t 16** +**python trees_from_MSA.py ExampleDataset/Results_\<date\> -t 16** where "ExampleDataset/Results_\<date\>" is the results directory from running OrthoFinder. diff --git a/orthofinder/Workflow.png b/orthofinder/Workflow.png new file mode 100755 index 0000000000000000000000000000000000000000..59fd36fe4d04ac499def48406b8eb8e8ee9bad30 GIT binary patch literal 115526 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⁣moBE65*N3yeeWo;PD zUzx2xXuZ*;YU9T<&z{edFuYT<?AM9?^Xk9sp8VV5M#GUMKen&0?Kae6IQ+0fmVv=x z#gub9zpZ=!KTb-zf7yaLx;cmBazE6cu@cxHtTBm|>0IsZ?ut{KoLnyN7!EzSQ+|9; z>=esPtKIXhcRF3DOo_2jC}E#8ao6IDH;vuD*K9d@d+B8_@#2Mo7xWEVLQZsiR99*^ z`1^%@eSZFOjwvyby9$0yy}qcn;zlszLn$N0(&G`n&$BXao}0Md{z=-mcjoIuO%1K9 zm)ln-v}j)eRVEhSZ~a^os4jfc+gMz0_9i{^RJM<^EqqIo?AEO}U3qb4!~8R9kEQ?D zoT|-@l-CivtZp6mFyXydUts<3SKF(40vRQEj=yDKV0bZo+OnA9=l;{y?U46<SiSAT z?&}-frQM8+o_<c<bYo-k)RQMyCK;;py%P2hUg)7V&*<7TR*wG5_unkGiC39akhoQ$ zs8t~)W`}|Z*t@4M>X+AjxHkX(-CuK#CN0`?Xiep55lyeYEo{G3)lPVbxc)IzIk{m| z(1v~1wLeZQJa2oxzd!s;^cJbNkDF{o1zssm_EB+v)wglh>{l;tU75Jve$C%+-0Msi z7*~JSD&AaP*6kSo`KHJ9&Vq}(+uGbteHP_%?Ra<bxYg!$Ct{~5-7i%uoMo|GVegUX zKj*SGN93k`QGd|??VH%Cq%-@f!}HSKcX)oxpRkuX!9WJI4|Ks~51q(8Id7f&|7zCX z%Gq+PV}rXilk!B5;Gj#9GVaB$Je|uu=N!=H6Og-leDAtf5g}3MCc3>izr&!eRe?ip zFUZT>_xjeJ&#n5j%|0h)cl!6)SC{iu@4hwVP~>LuB5ft-<o!#sF6Px2=AYfDw&%WE zE9cT@YRebii4Exa^^X7kU-74|Q&-n3IM4mJaKk+DmXZ@CweP%^yp!-=oVT<v`?vkg ztShf~Tx&aW;_b}bt7mF*Y)z}*h&}EIPfokI`^b?1*7q9MXNq#Uu)b7vsedZBv-rIP z_hs*zBkwod(%JDkAa+@Kv-`e(A8PIz@@;$a)66#6I)cFgv@yOxE;I9#s`<LVKbKv9 zrWez9?Zf?6v%mRqhs{eB1fDRhd3>xlTw{`|&@`DY<#`+*OHQaCJ?L7>sI<bl^yKYT zeM%EkqFx5QGhyB7B(SMGMnR-w^Q2FiH$(DX@ho}QwB%~v9+lw0lZRf1|7mFMZLZVY zl$PT1uUcW}N})Xh2P?k!KG(edVOD&Yp<T7st>ePqLJn+D44zmdr#m}HEhYKHk(60} zqP@|ZBYc+kzk3uVed>r||60wITZ=c9#hGls))(=vFJ-$#sd~B4^?r{(ev2=*{FM9Z ze0`>>a{1|HYqU$+ULD!@+s&x%x#YRSS(^p2ewXb(cQD<zWJ9^`%$9KZhM5Hh{0s~Y za{Tr`)7uXxmM>6s>s>SH?`iAwV_pVEjSbJ{Y>IevJa7AKAGgI99scy~TCOnBV`-4& zN9ormnOs^fF1Y=3+4WG_^!Uv&QrhJOFO}~ly!-ohb?E*b#ctPbSnRRAk$WR>T1S5S z>0Mjq+UbdQs-${f^VQWVwy?0e@33FQQL=N9N~F(n#YvnhYAyd4T~C+KS-E=cCmZ|X zLrb#7)^D}mAl@wXEu>+0SuMBj?Tb=^Zi!p2UF*K5C)BO7`B~ijx$#pB3s*Hi{}W$) zd1l+K$?V_Wf7y6&vio<DX*z=5Ph~P4W?DS^e^>C~#aU~YFW&B+`*x4V9EG~?-%143 z-F8o$H_P@()8?9qAv30Ec6f^&S=uql=y=DHM;m_%R>oOc-c-D`>37iPotl^3&)nv9 zj@fBaoak8P>3e&ln(teO`1Y=Hfhb$6+^Y}ym!IF&Xzf4qMS`I$TY`~~6e9zJ!l!fF zU)KM9pI=`p{w>4#!HK7lv79!WZ@T<Z^E3X*=&cg8F`{XYtV4<<@7>#R|9ZV#Yjt$1 zFKwAwocG{?VrKKs>3=eg`Rz5h@!>$TW1i#65VkVsU40!E{nwT}p31|OYN$26Niy7b z@1maLZ!37Be=l9i%Jt0t#LdW0v8J;;?*3k*vooaQVo`&hNl5+2pM2*(A5}WJY3hCF z-#<1lfAsQwZ0E9sj+u|wO*)m%wz<_MvqAfqRK~BqM#lT02?n+P<>BIA+wWfG))v=^ z^q#ZfHv4{-iH3!%iVjWDes=G}ziIYn=JOZtS$H&QmHpqRTY~&toaZc@V4dc}^Vs6- z)Qy{?d#3GUD!q0tt?GT#jZHhZ=G~a7==<2z%&cPG%7B)F9UfuJPX|2_Uo!nj7ZXpX zka<b}{fE_&5A-Zc=h%nOHcS2{TD<Q3iCJDg)t%?Q-3Yh#m%aZ!H(mDOlZpO2g!gdQ zvvV^nZcPT=PQ!3OYaNSR;jOH#Gv@k;b<gBfw0X3#(<NzRgwvmR4UvaOnK~?*9lt+I zTffqJqKDD@U!@tU99!pj3Px+UtTS!+5+)PL>E-^L<2Z-E+->hapmxGjQ*-S}#um2& zR(1+KYESjg%1jhtd4KAYkdnln=ozyc;*=8J@Bhnx>ht>bdu%mJZEmHzL?#F3PIMGn z($!*->hf;ZI)Tn>Em{F*M5V?5|9*b&XUTIhqv&*#7yUA~l!d&*)>LY&FPOI7)o;nB zAEw{`KfAAQv+SGE%ACAa51l^vsrCK*`S$W*Nv@62JhJouPyM!JZhg6}P4Ds<_0w}c z#YCC?J8OR2_WYT1=lJekKG|`ePp!!ON&gD{y6H<#tPc)f=*{@Q({++*x&6F}rdJ)s zMBWA)?O#+8cl7%FT<aH0H)Qnt-M$txd)mbFzrJyT0+JD24l*d^CspZ)6*LA2aJe4w zkC{{aeB#xtjaHE-OyYigll_wV=2u+{$8AP~uE6!x#h=&L*3_2$yyY%>Qj*W_+KfX| zVlfF@XRbBl)}3t>QF7#(+g3Hf9j^m^Zf&?b^Of?E^NYHwKc(wEcsAvs_zWYLj*t`a zQ42aezUA#QGE)|Ge!=jz=JnEVWnJ%<rhMCK?0x!enU0Zl6RYuM`IveAWeiPS%a*29 z)~-IQ<`sNlNzc|5NwxQ{o5{M!@637n_nb&a@)M>PpVvo!y*Iml*&dJUNB?b6HLSc- z@tY@Uh1fakr`;)<-gT?){XO21^8U`n8M_`^YTq-hnm2RuWZ_-M<7e%haF2J{G0k9Q zkx6WAWrBsuN35o=ugs|V7IJD`*}c;2ThX)5&9!x{Jly?!RfPPWZVz^D2F3&Xv=|r| zIt-F-efa<GfT9V9rP1GGkH7i{=WdH@x|vgE@g{8b(Y$hj{muKi?do|xR-cjB7opSF z*(u2J;TT)OigVwdtzDn<Y2&u!r?1+Nr#`c`sPu?Z>-$+Bakz>3MO6B=t)?>ZoHN{N zZ~oV=-}QNZ?A+X?soRSRPAMFoqF8k5lhC)T<&tY3u2Zb_4m6UoH@GkJ=gWnIw)37J zKdvpN6W9Dr@}jBnW%)HOulP<l_RTuB_T^eDmiIT*)4f)DsBDbznI~S_lQu)Vcgdp} z>dGQqiB8}8?p?Y2?d@$-sjga)qeqYa&~ARA+Wj?jpH=apC3_A<+6kRodR^zc*q+zd zR$VyA#NeDzF3P~b;Bu+u%Z>OucFgS;6#lR<rEB@=$nA@glsuvAAH6m#pZm(EpEU~u zUX)3dS}-i~EPg!m|GuA1>#y9MC7=3ys)dZq+R}DY9g9VC_ubOnvoADTa1ZwkrPMor zHg#S4Y2KAu^!$+(-@S|a<yWrC<nF8J`}E6w-#>%6iSx{xwM*X{q$V(}oUnhL$=9dM zxexbTUdr0FBx29I#(=GHe;({#9~4(|dRP0>jUO|V?ys9S^PlaKcL@#mRxK{fj!JfS zt1*k3S{roPNY6C5|IQZK=|Yo?Z&=z09Jkq7_p9YZtCP~~y^GCD-EZvqnR@DT__~-* zd&7=L?+V{k$WKr6)%sFf{Pc)s(N3r3i*Gb9e;u@W$K4LTvSb(Q2nGq!H=u(n8#)du znj|O~O>m$0t>-Bt19wc$nw#I&FFzY{m4icR;*&N}exEb?4-2<myL<lqf$f<)4<#)8 ztg&g{wY7!5aSzwrX$op@PM1INd)k@xJBsR#hR@Hl-chgq)R(XJW8SkT+DC#cm+=Ug z85<>~r=ILLX?@vWTeK=Ab0O1Nxf%a+{;t01V|Zm}T4HM2w~#f4uWw&9z9hfmjI(tE z_g&Yfs~JIIUH(2F#r7m?uZ;MxxpX3btBY2(d-bcl-{0O=e+!thN>^Y1{7J_s@kP;2 zO^a4{|JuDi=;ev{-#z{wi+JwMJnL4nf#HC_jZDy)Cleigq|MeHS2ST@;8=a@m2&?6 zZ)>)DKP@`St1TboHz(_P)>`W|i3c_7+@1y{Pt~%|dVI0kN6O>#y69x1FLNxmUH`mD zZtk})6YqxWMYL)%w_MhBJvn{LGw&$x74J1X#WpA2i!iB3u)F#3*U!)Cw&(r5gY{x| zi7m9g|LnZS{{2iVttHG(eAL!@{(i#A7DeI3wY!SDSAY09^YZaqx1+tD$LuaS`Zr@{ z;*z9;!r3#0xHyZy_32L%Dp&V8wWM(N@qYRG4+q;d^qqEECp2z+Y_ym4{hx=rH_u#q zAiLNqg5iVkw`kDOGAsg@+tdp=co`TDoi2Z+XBry0@s~TNrsSO*nKg+A9hK#z&q1rR zCso~`#cGq6T{}K?tJ;)&&d!}rw}*wxZV3>5nX|(B+`9*NTTGHp8OR2+rPW4o{N$gm zIsg83p-XRM3-zU+^{Un_KFD1B=SAJA&$n+!*Zuu5i^1&2q4)0!{-?(sT(hZ*J@RJB z=kz7{4zKuZdLmRTOv55)#7&Y~{w4iHio?W&w0);+@9w`_!T;3tx%9rz>D+gt);B*q z@`Ptv>%65&2Oslrx3=h3i+6!@x_r$C#ojihX(2H&dlC=V{@`xSSbpZjy1(nXJ}E8U zn0{pTY+trhpC3P-9AWV=DUO|+;opIE+6)W~9NT?loR}FJHdya?|JUE{&a1bVJa=Bs z-}g72uf2J8_j=ExhW<*W#|`(LzPnSY{E2<1@Y0#woyC1|cV6qROWzu~#%yzdg4^v& z-b$1E=TEp@@NLR-SJ_vm)0+K`T)LWOn40j;-K}GD`IB$PLarHE9Gib$zWmPl72k>< zD*~T)-Qkrs`q_7XWmQy+Ib+*|qerWwIXCXzdvw8dL1&};(E(fI7uncbrpmll>u)-q zH~ZLulgcyqzW(zk|Jk)&6YjFkUAeoh?bzGeK6(3hZ>{wAeo^{o)5tn0Xz#R5d3&!| zJUY93_X_(CS0PEO2!@1H7A&AaX@-X_&KV300(I=)etF#$ovs&Cc>KJ@ZHu6d3@feq zK859~nubMA3yz$%^TO0!#fs-NXNP#kc*V4T+7^*(=5pzn@0YTP+ZMM@^_L80TUKB2 zCH3(#?U(-_obV9&dwvtA$6d!CXPsa1?Ww&|6`3C9*3(_2)wytDdP`r={hty>FV|{R z9d}7SUg;YwdvE{c@45Z;e;HG2lbUQ4XQoa*e*C(TxrkHR`;^J+xy~NT^uBBR_lvQi z;h{Zy_Vh}d|MIw7`{(1kM~{;J<T7#l%(c6H+x2Vd@}6IL^4m=%OR_?{r}LSy%IYu} zu#_c$()o{+Wk>BcmPzp~WMpu#s4f5X;rPAa^6Pq$!A9#39lUyMVi&7_0x!2;)_08< z^ED~m=0`+NJ<<xb)xDZ~ye)M9<yluW?mueYxBT~k=kF))?FEe<-FD8*JoWzXmzxRO zGL61&SijI9wWF_}|9RGim%m@_E(^`>3A5I{9TT-6B5H-hZKoe+XU{8pzbR%5b3lDf zb<2|!B}>DUQ$bJIZBi~JZ;f>Ov{C5M^y=~_PsCHx!~^28OiJpDKP2bRmYpAZVA;vX z{`s@rc&<wPvk?9KUae00oZOPa*$X2QuU!ug(Abt+y?M``If1;VXE8lLa$~B*sYRE$ zm*3d&<If+-ty{0uzUj-=zf*PIEq}Xh-2>-*@uj_|-PpMqcp7UXK+EFPj%<0j@irHv z6nN>Be0KK#Z?m#@rv6o)ynKq4jLg})iM-r;#p^57g-@pU##^j9BU7of(=#~wZrPcw znjw2WKU-V>_x?#?5%+($ZvR~so^JHYFqI)q>$LBTg*|`IpX>|Pb=>Q%zT7KM)5~$8 zbw|fekGqZj?+#1bE0@o6Rtxn%J@MJzb&A3t{o|}=O)GpBySwK7yzhHscfFnOzkcWQ zk3F5i?Q`=@O7{Ofm0xPb`*f{~lIOcEt0RpoYf^j@x3oCDID4<vvUGdV(^KN{e`Y%! zT$8UHk^8Lnd6~|s=%Y>MpP#LdzCL}K>d~ZQf?<}Gm-n*1fA!jL@89#jtKTg<ac}PL zcP|#|+ke=0mUD7skdV<Xs|bb#Euhg_1`hV;id(j}8W|pwV_*=lTea=kb+i8mZ!gup zZf{hXr5WyA{&%JIo9mw^yIuZybNT!qm1kE}xINjG$tYuA=6+|wkHVwx1NI%2b}gE< zz1vUjrt#*ImLRdaQCqint4y-+kyO|;DJj|PjQ9F*Nq+OMGMQ4P>ISir(bL4|&(?DF zc5*p0cUsx`O(!$hrM`YEE6wEpS?s%crKOdci0!#4djb?r`Ocg;-N<%BmjBAaQ>nq% zc=={-xT2$c^w`n!I<r2%^?YT%;bv0R*QNKToZ0v7)J|{Fm6As+Wmi3$B^)pJVJFwy zDVfD8MwZ;m<{WwQQGGS{ardiF0uBG?Mf<n!+^YA(w^g?^JTC6vkH`InYcI&I^sDvC zex>khV}p7>k7G~N`BgshS2M0nU$*Ez_wt`l&1G-=4-6MC_1U@a-Ie%%-xt0NKeNB; zW9}2n>F4HK7QVPV>vH?v+<=)AoB6~TW-wQ8VgL<>cVr&+&p!;VV6~n7zpvK+cP+^* zFs|xx7w=?luSyxeyNSHrffut(s{e4`bLH;78#HU@glUV_*8RA9a@zWjVGF0kN9_4- z{EA6Sy|3n-b;a_)*er|cQ@59Po_rH|+ngtuZJ~x*=*$(@XYyDH-<$U3l<!m_`|TpC z;jx#(V~ys7O4hEox3&y5QdTZ&GZNeCrqHx**0i$kydHNi`rI#G_UcHMnXU5bvoRCm zgbJSY6`csVTgH<wnl`KSYf%0{>q{FC&lk3@%kx#4G$m&9B-zQw<?luvcpA`gH;LoC ztmZF+&FX8yt+S^J-VNWv{=O{Kx;fr`R*AWYhJgU@=AGB?Y{-5teNXT0yK6OnkNQ12 zetvDNVL7;X=Brr*nlj8#EM;e45d1h_xBlI$W&gC^ev#aqJ|!;u`3J7e{Ykvs=c3J0 zW%o*}^s6pT)byPbq9NveCdBeVxA?iaHm_E{xGTPUd&O=I%hWkDMB_iIFX!oBr^)ek zilU)v-oa1WS$$^LWU>$4Hd2|d@AU7Dj^#(^Iq$A~FOyg^eT&%QRD(uyOJ5~nt_@3c z3>bsTA1vSGIrX6XyEfjLMvpdM*l=cw>B<A{7k6LO($bxN?9|(DFXUFcmTDZiRXK6m z+Z$C@hdwuK|DO0DXftSW%j{N*#0@VN?Rp^_`L55VcTxF6^ZkF8<@3GI&Clno3qI8| zr*o$&A3LaG;*w!vXJBX$P+G>@-||mDih<z(r~|NO+qP4;mo8t$H-FWx+z{!W$?EI= zA1v0%kX#w@fg`A#A<=y6gRaTyd_i$rihrlA^qChO@T&4+ef;mkmhYdwGg8~2^0Gsd zSzGn!F#-DsXEUX`Z@16dv~OOu{_w*M*2VpN)<5~#e|EdKIh@ex_*~6#?sKL4{yQIk z9>1+&{^M}`9=E#KgZIA67g-11btzu9^xjU9mDV4)_paQ1Ay@dtrktm3%Z^3mcPswV zufDY9(x&K0gUXtU&@F9FIkvK8-4mzzer?mPaKE*$_V>rf_j7J<Tf1Y&3{KsbTIKFt zuNIZ3?6el@y61Xi*6#XWmx`tLuJwJWKG7p6Y$GG6`r?uYSJa9|cBbFdEn3(Z7!sGi ze|c~7@7yiT>MJ8Y2rqfZpp;nk(MEpyzQ4CNTW_<wekbIy)$`T<+yDNZDgVRcuHdq% zas8Ui3<ok|Lp!d>GE|64i`Tz?YcJHPdN4=DGWAaHcH47JCm$ca7}K?KySwl8WjbrO zCf0r6<?Zbj*SD<uV<8-MDfC^Ri`d@oW7}J*cUoU^H<glW3D2r}c53T={vze>{9W-A z*SN5AGxRip_e-|8xUG6K(Udg^+M4?C^Y_8&8VU00Yj<C5y|JEMKrlG{%uIuW;pZNQ zTzz=`-@n`TIWfQ6=dZ7QC7#Xy=KjsQZ&%CD4zkcTv}6uuV`vDR*?dKop(n-3@8MkO zI<bOpewKFWZ@<q=I{4&bWS6FCmwNxRYgW6=T&s`nJ}zIsX8ZN3Cnp~I&Nh4cBqcb` zeeuOLk(=9o$lgqo347NUa<@!J>b}ssr|&%6XO+zT6<1RAaGsR^{Jr1Sxih~`c2)8` zx|Tov>C^?ytPIN_=WV@sZqu?&s{H~3gMv|?{Ox`>2{$dVKOFa$v39IDdOCl3cCgaK zly{4)4N@5xB&ObEUAbL<W=k1EgP`vT&58%dXV=tcH|?4~v;Og^>N?rC{_#p;e+ohw z4MVzLr6(?4|7fnRiT7DCadG>9KaxvJOJ|$qF52Vd?ELsxue9>*9?%l#MH^p$C)2v_ zxw`!Oa+Ckhi|?74i&sZYH?&Ltx_5uqw=1&NphkTQIQTF0%rN`&BMcn)CoIYy1W(y5 z&9>5-LG8q>kbtsWRo&UU>VC3iu8|35V_^6XDr6Q<nq>CxUVHuinyY{7+?<#B&8~g! zTt6dtpVh7TJ6v>r96YsBhokkv>}kgBMO$wkm#=^GxW7C&ZcF9o)79VJbcRR=P1~*P z8hg?Dd{McI$lmS*`QI0k>vz@Ny!E5vXh-<8)1usBYvU@<uNPhU_~Jn(2IIu<prbJ_ zbj~od*^{LOZj>#VvwE$Q@($(e8xGXjGN|oX_o&P_{r7fte%8$sv)4t>of`kBz~%Rp zg$*y}ewfD4@L@*igiUpZLFEhvS>b22^tJYWb9y{;bGqM_Z6?>Q&n?;?0$z$PSomnw zr>jS9oO#&sigDIt7nL9Xbc1ggRxT-A85wt?>)y^O)2HX(+S0jcA83f6(_8sc8))xa z%EjF}u5}@MB==0+ty~+jI5K`#*E>C>bx{G9>5fm!m-lapSz&)ap!nk<%kSRmzVqfS zJ2T6o^4Si~V3~{e4{uu8%*x<zp~1(%z_3Me882uBJUI0<ox3u}L3xM8^$iEMXRiG7 z{&M{ioB#6qdvDw*;CsuwaPz0W`5mhD_C*>84vBVay_;#Y`OLxYN6CCMj2IkdoYnG( zY`+q_AT8#j*wdfSU;TH#U(EKFS!Uu)TT|t1t-?cj<!bi7{$HQIW%GC5Q*Q$2?|41i zri+DtZGxx9k@<6E;#GQeclb@2ax?hz%?{1I_XODASJalikE^RJc6NTe;q&(V|L42) z_euOS`w|GrCQ(i0_fFlN*k|%GPa{C(sFIPOVP)&)B5p6)l>N89+gHcmfAuOM^1@N2 zQuYH?KSV%h!&Pa3n~_SsS8wH*&Az~=vL=!7fRFjQs)U$lOTOR#d3;6Q)Jd!Ml-<0W zZ2WoguNBP8co+oIma=MQuRrf-&7z}N$oKT8p&@663`3Z&oNE(%_>;|PpKjb<{H;(^ za%Z=;#`Equ=fjN_o+x>^<lBj8yGK=G&x1CLF08xp{qFXIn_tXzTYPh0?e9}lwGZEE zvpaQn;-aN5=Dsf~Uvk)Y*V0z5FU$>mVhl`ekbL4WQ|Y%IB=OD7cX-%aC$#aw@9heu z><0plZa)2)+xz>wy3a2bo_F6<<h82kmT36-J+F@KyQLK<v-|KAMutN<*Fu)OV_^6* z0~E~n=B!V-_~fEHtM$z%s~>f1u@;_mj!JO4c!SfmZ8Q5W|94Az?nhedw>?aZFORjn znzY&GyirBsgWY#zHMP3#{nRUWzZHHzblP+U(N7np8LT20IC#KoZWBLUIREceRNH}r z(lPxFtV=h|ZNHE=vkBaBD^x8O7qtI7e_P)E%3AM`FaIZBuRrvzAfV3VbH%!D8Rq{_ zzsB2pCGc`HJez#<*!_aj%--R5>I?4wR%`xu`*D0(IlrY^h(T>xlTz-ho}ZkWl6zjn zAL0pSV_3$&=vjD-<^Cp_4?L2xHy3aHtT}g9(d|V!dp0pJDErCn->x)YV%5f%o`KJv zXegIEuQ!<f!*Gu6h0-UwyN|x86a47E`=~?M3GTU%9woIpExgFX-z95bXLE-4#*&p^ zW8d|qY<Ll3tGlw$>)1ZwKbqz422pQ+zuFb>h3~>aCWiyy<v<M!1TMF=hdlr_0vLil zT$mm$mRxDwkYSj9u!z0C{OqLLHSA4>sSFGs;$L2wVtO4^jv0J<^09ID=NsZ&8+l{1 z!fed^4gcrKPqZ%Ibh21gxT`Ab+ly?w;(ky(bRBQovn#r+Y+L$=bqg0N+Pq!%Ds$tF zvr)<Jlb2mPKYLEj�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 literal 0 HcmV?d00001 diff --git a/orthofinder/__init__.py b/orthofinder/__init__.py new file mode 100644 index 0000000..120282a --- /dev/null +++ b/orthofinder/__init__.py @@ -0,0 +1 @@ +__all__ = ["orthofinder", "scripts"] diff --git a/orthofinder.py b/orthofinder/orthofinder.py similarity index 71% rename from orthofinder.py rename to orthofinder/orthofinder.py index b4e72a9..1aab96e 100755 --- a/orthofinder.py +++ b/orthofinder/orthofinder.py @@ -25,9 +25,6 @@ # For any enquiries send an email to David Emms # david_emms@hotmail.com -nThreadsDefault = 16 -nAlgDefault = 1 - import sys # Y import subprocess # Y import os # Y @@ -42,30 +39,23 @@ import csv # Y import scipy.sparse as sparse # install import os.path # Y import numpy.core.numeric as numeric # install -import cPickle as pic # Y -from collections import defaultdict, namedtuple # Y +from collections import defaultdict # Y import xml.etree.ElementTree as ET # Y from xml.etree.ElementTree import SubElement # Y from xml.dom import minidom # Y import Queue # Y import warnings # Y -import time # Y +import scripts.mcl as MCLread +import scripts.blast_file_processor as BlastFileProcessor +from scripts import util, matrices, get_orthologues -version = "1.0.0" fastaExtensions = {"fa", "faa", "fasta", "fas"} -picProtocol = 1 if sys.platform.startswith("linux"): with open(os.devnull, "w") as f: subprocess.call("taskset -p 0xffffffffffff %d" % os.getpid(), shell=True, stdout=f) # get round problem with python multiprocessing library that can set all cpu affinities to a single cpu - -""" -Utilities -------------------------------------------------------------------------------- -""" -def RunCommand(command): - subprocess.call(command) - + + def RunBlastDBCommand(command): capture = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout = [x for x in capture.stdout] @@ -75,162 +65,7 @@ def RunBlastDBCommand(command): print("\nWarning:") print("".join(stdout[2:])) if len(stderr) > 0: print(stderr) - -def Worker_RunCommand(cmd_queue, nTotal): - while True: - try: - iCommand, command = cmd_queue.get(True, 1) - util.PrintTime("Running Blast %d of %d" % (iCommand, nTotal)) - subprocess.call(command) - util.PrintTime("Finished Blast %d of %d" % (iCommand, nTotal)) - except Queue.Empty: - return - -class util: - @staticmethod - def GetDirectoryName(baseDirName, dateString, i): - if i == 0: - return baseDirName + dateString + os.sep - else: - return baseDirName + dateString + ("_%d" % i) + os.sep - - """Call GetNameForNewWorkingDirectory before a call to CreateNewWorkingDirectory to find out what directory will be created""" - @staticmethod - def CreateNewWorkingDirectory(baseDirectoryName): - dateStr = datetime.date.today().strftime("%b%d") - iAppend = 0 - newDirectoryName = util.GetDirectoryName(baseDirectoryName, dateStr, iAppend) - while os.path.exists(newDirectoryName): - iAppend += 1 - newDirectoryName = util.GetDirectoryName(baseDirectoryName, dateStr, iAppend) - os.mkdir(newDirectoryName) - return newDirectoryName - - @staticmethod - def GetUnusedFilename(baseFilename, ext): - iAppend = 0 - newFilename = baseFilename + ext - while os.path.exists(newFilename): - iAppend += 1 - newFilename = baseFilename + ("_%d" % iAppend) + ext - return newFilename, iAppend - - @staticmethod - def PrintTime(message): - print(str(datetime.datetime.now()).rsplit(".", 1)[0] + " : " + message) - - @staticmethod - def SortArrayPairByFirst(useForSortAr, keepAlignedAr, qLargestFirst=False): - sortedTuples = sorted(zip(useForSortAr, keepAlignedAr), reverse=qLargestFirst) - useForSortAr = [i for i, j in sortedTuples] - keepAlignedAr = [j for i, j in sortedTuples] - return useForSortAr, keepAlignedAr - - @staticmethod - def PrintNoNewLine(text): - sys.stdout.write(text) - - @staticmethod - def SortFastaFilenames(fastaFilenames): - speciesIndices = [] - for f in fastaFilenames: - start = f.rfind("Species") - speciesIndices.append(int(f[start+7:-3])) - indices, sortedFasta = util.SortArrayPairByFirst(speciesIndices, fastaFilenames) - return sortedFasta - -def Fail(): - print("ERROR: An error occurred, please review previous error messages for more information.") - sys.exit() - -def ManageQueue(runningProcesses, cmd_queue): - """Manage a set of runningProcesses working through cmd_queue. - If there is an error the exit all processes as quickly as possible and - exit via Fail() methods. Otherwise return when all work is complete - """ - # set all completed processes to None - qError = False -# dones = [False for _ in runningProcesses] - nProcesses = len(runningProcesses) - while True: - if runningProcesses.count(None) == len(runningProcesses): break - time.sleep(2) -# for proc in runningProcesses: - for i in xrange(nProcesses): - proc = runningProcesses[i] - if proc == None: continue - if not proc.is_alive(): - if proc.exitcode != 0: - qError = True - while True: - try: - cmd_queue.get(True, 1) - except Queue.Empty: - break - runningProcesses[i] = None - if qError: - DeleteMatrices(fileInfo) - Fail() - -""" -IDExtractor -------------------------------------------------------------------------------- -""" -class IDExtractor(object): - """IDExtractor deals with the fact that for different datasets a user will - want to extract a unique sequence ID from the fasta file accessions uin different - ways.""" - def GetIDToNameDict(self): - raise NotImplementedError("Should not be implemented") - def GetNameToIDDict(self): - raise NotImplementedError("Should not be implemented") - -class FullAccession(IDExtractor): - def __init__(self, idsFilename): - # only want the first part and nothing else (easy!) - self.idToNameDict = dict() - self.nameToIDDict = dict() - with open(idsFilename, 'rb') as idsFile: - for line in idsFile: - if line.startswith("#"): continue - id, accession = line.rstrip().split(": ", 1) - # Replace problematic characters - accession = accession.replace(":", "_").replace(",", "_").replace("(", "_").replace(")", "_") - if id in self.idToNameDict: - raise RuntimeError("ERROR: A duplicate id was found in the fasta files: % s" % id) - self.idToNameDict[id] = accession - self.nameToIDDict[accession] = id - - def GetIDToNameDict(self): - return self.idToNameDict - - def GetNameToIDDict(self): - return self.nameToIDDict - -class FirstWordExtractor(IDExtractor): - def __init__(self, idsFilename): - # only want the first part and nothing else (easy!) - self.idToNameDict = dict() - self.nameToIDDict = dict() - with open(idsFilename, 'rb') as idsFile: - for line in idsFile: - id, rest = line.split(": ", 1) - accession = rest.split(None, 1)[0] - # Replace problematic characters - accession = accession.replace(":", "_").replace(",", "_").replace("(", "_").replace(")", "_") - if accession in self.nameToIDDict: - raise RuntimeError("A duplicate accession was found using just first part: % s" % accession) - if id in self.idToNameDict: - raise RuntimeError("ERROR: A duplicate id was found in the fasta files: % s" % id) - self.idToNameDict[id] = accession - self.nameToIDDict[accession] = id - - def GetIDToNameDict(self): - return self.idToNameDict - - def GetNameToIDDict(self): - return self.nameToIDDict - + def SpeciesNameDict(speciesIDsFN): speciesNamesDict = dict() with open(speciesIDsFN, 'rb') as speciesNamesFile: @@ -243,92 +78,9 @@ def SpeciesNameDict(speciesIDsFN): """ MCL ------------------------------------------------------------------------------- -""" - -class MCL: - @staticmethod - def GetPredictedOGs(clustersFilename): - predictedOGs = [] - nOGsString = "" - qContainsProfiles = False - with open(clustersFilename, 'rb') as clusterFile: - header = True - og = set() - for line in clusterFile: - if header: - if line.count("begin"): - header = False - else: - if line.find(")") != -1: - break - if line[-2] == "$": - line = line[:-3] - if line[0] == " ": - # continuation of group - x = line.split() - y = [x_ for x_ in x if not x_.startswith('Prof')] - og = og.union(y) - else: - # new OG - if len(og) != 0: - predictedOGs.append(og) - nOGsString, line = line.split(" ", 1) - x = line.split() - y = [x_ for x_ in x if not x_.startswith('Prof')] - if len(x) != len(y): - qContainsProfiles = True - og = set(y) - if len(og) > 0: - predictedOGs.append(og) - if not qContainsProfiles: - assert(len(predictedOGs) == int(nOGsString) + 1) - return predictedOGs - - @staticmethod - def GetSingleID(speciesStartingIndices, seq, speciesToUse): - iSpecies, iSeq = map(int, seq.split("_")) - offset = speciesStartingIndices[speciesToUse.index(iSpecies)] - return iSeq + offset - - @staticmethod - def GetIDPair(speciesStartingIndices, singleID, speciesToUse): - for i, startingIndex in enumerate(speciesStartingIndices): - if startingIndex > singleID: - return "%d_%d" % (speciesToUse[i-1], singleID - speciesStartingIndices[i-1]) - return "%d_%d" % (speciesToUse[-1], singleID - speciesStartingIndices[len(speciesStartingIndices)-1]) +""" - @staticmethod - def ConvertSingleIDsToIDPair(seqsInfo, clustersFilename, newFilename): - with open(clustersFilename, 'rb') as clusterFile, open(newFilename, "wb") as output: - header = True - for line in clusterFile: - appendDollar = False - initialText = "" - idsString = "" - ids = [] - if header: - output.write(line) - if line.count("begin"): - header = False - else: - if line.find(")") != -1: - output.write(line) - break - if line[-2] == "$": - line = line[:-3] - appendDollar = True - if line[0] != " ": - initialText, line = line.split(None, 1) - # continuation of group - ids = line.split() - for id in ids: - idsString += MCL.GetIDPair(seqsInfo.seqStartingIndices, int(id), seqsInfo.speciesToUse) + " " - output.write(initialText + " " + idsString) - if appendDollar: - output.write("$\n") - else: - output.write("\n") - +class MCL: @staticmethod def CreateOGs(predictedOGs, outputFilename, idDict): with open(outputFilename, 'wb') as outputFile: @@ -338,30 +90,28 @@ class MCL: outputFile.write(" ".join(accessions)) outputFile.write("\n") - @staticmethod + @staticmethod def prettify(elem): """Return a pretty-printed XML string for the Element. """ rough_string = ET.tostring(elem, 'utf-8') reparsed = minidom.parseString(rough_string) return reparsed.toprettyxml(indent=" ") - - @staticmethod + + @staticmethod def WriteOrthoXML(speciesInfo, predictedOGs, numbersOfSequences, idDict, orthoxmlFilename, speciesToUse): """ speciesInfo: ordered array for which each element has fastaFilename, speciesName, NCBITaxID, sourceDatabaseName, databaseVersionFastaFile """ - - # Write OrthoXML file root = ET.Element("orthoXML") root.set('xsi:schemaLocation', "http://orthoXML.org/2011/ http://www.orthoxml.org/0.3/orthoxml.xsd") - root.set('originVersion', version) + root.set('originVersion', util.version) root.set('origin', 'OrthoFinder') root.set('version', "0.3") root.set('xmlns:xsi', "http://www.w3.org/2001/XMLSchema-instance") #notes = SubElement(root, 'notes') - + # Species: details of source of genomes and sequences they contain speciesStartingIndices = [] iGene_all = 0 @@ -372,80 +122,80 @@ class MCL: speciesDatabaseNode = SubElement(speciesNode, "database") speciesDatabaseNode.set('name', thisSpeciesInfo[3]) # required speciesDatabaseNode.set('version', thisSpeciesInfo[4]) # required -# speciesDatabaseNode.set('geneLink', "") # skip -# speciesDatabaseNode.set('protLink', "") # skip -# speciesDatabaseNode.set('transcriptLink', "") # skip + # speciesDatabaseNode.set('geneLink', "") # skip + # speciesDatabaseNode.set('protLink', "") # skip + # speciesDatabaseNode.set('transcriptLink', "") # skip allGenesNode = SubElement(speciesDatabaseNode, "genes") speciesStartingIndices.append(iGene_all) for iGene_species in xrange(nSeqs): geneNode = SubElement(allGenesNode, 'gene') geneNode.set("geneId", idDict["%d_%d" % (iSpecies , iGene_species)]) geneNode.set('id', str(iGene_all)) # required -# geneNode.set("protID", "") # skip + # geneNode.set("protID", "") # skip iGene_all += 1 # Scores tag - unused -# scoresNode = SubElement(root, 'scores') # skip - + # scoresNode = SubElement(root, 'scores') # skip + # Orthogroups allGroupsNode = SubElement(root, 'groups') for iOg, og in enumerate(predictedOGs): groupNode = SubElement(allGroupsNode, 'orthologGroup') groupNode.set('id', str(iOg)) -# groupScoreNode = SubElement(groupNode, 'score') # skip -# groupScoreNode.set('id', "") # skip -# groupScoreNode.set('value', "") # skip -# SubElement(groupNode, 'property') # skip + # groupScoreNode = SubElement(groupNode, 'score') # skip + # groupScoreNode.set('id', "") # skip + # groupScoreNode.set('value', "") # skip + # SubElement(groupNode, 'property') # skip for seq in og: geneNode = SubElement(groupNode, 'geneRef') - geneNode.set('id', str(MCL.GetSingleID(speciesStartingIndices, seq, speciesToUse))) -# SubElement(geneNode, 'score') # skip + geneNode.set('id', str(MCLread.GetSingleID(speciesStartingIndices, seq, speciesToUse))) + # SubElement(geneNode, 'score') # skip with open(orthoxmlFilename, 'wb') as orthoxmlFile: -# ET.ElementTree(root).write(orthoxmlFile) + # ET.ElementTree(root).write(orthoxmlFile) orthoxmlFile.write(MCL.prettify(root)) print("Orthologous groups have been written to orthoxml file:\n %s" % orthoxmlFilename) - - @staticmethod + + @staticmethod def RunMCL(graphFilename, clustersFilename, nProcesses, inflation): nProcesses = 4 if nProcesses > 4 else nProcesses # MCL appears to take *longer* as more than 4 processes are used command = ["mcl", graphFilename, "-I", str(inflation), "-o", clustersFilename, "-te", str(nProcesses), "-V", "all"] - RunCommand(command) + util.RunCommand(command) util.PrintTime("Ran MCL") - - @staticmethod + + @staticmethod def WriteOrthogroupFiles(ogs, idsFilenames, resultsBaseFilename, clustersFilename_pairs): outputFN = resultsBaseFilename + ".txt" try: fullDict = dict() for idsFilename in idsFilenames: - idExtract = FirstWordExtractor(idsFilename) + idExtract = util.FirstWordExtractor(idsFilename) idDict = idExtract.GetIDToNameDict() fullDict.update(idDict) MCL.CreateOGs(ogs, outputFN, fullDict) except KeyError as e: sys.stderr.write("ERROR: Sequence ID not found in %s\n" % idsFilename) sys.stderr.write(str(e) + "\n") - Fail() + util.Fail() except RuntimeError as error: print(error.message) if error.message.startswith("ERROR"): print("ERROR: %s contains a duplicate ID. The IDs for the orthologous groups in %s will not be replaced with the sequence accessions. If %s was prepared manually then please check the IDs are correct. " % (idsFilename, clustersFilename_pairs, idsFilename)) - Fail() + util.Fail() else: print("Tried to use only the first part of the accession in order to list the sequences in each orthologous group\nmore concisely but these were not unique. The full accession line will be used instead.\n") try: fullDict = dict() for idsFilename in idsFilenames: - idExtract = FullAccession(idsFilename) + idExtract = util.tFullAccession(idsFilename) idDict = idExtract.GetIDToNameDict() fullDict.update(idDict) MCL.CreateOGs(ogs, outputFN, fullDict) except: print("ERROR: %s contains a duplicate ID. The IDs for the orthologous groups in %s will not be replaced with the sequence accessions. If %s was prepared manually then please check the IDs are correct. " % (idsFilename, clustersFilename_pairs, idsFilename)) - Fail() + util.Fail() return fullDict - - @staticmethod + + @staticmethod def CreateOrthogroupTable(ogs, idToNameDict, speciesNamesDict, @@ -549,19 +299,10 @@ RunInfo ------------------------------------------------------------------------------- """ -SequencesInfo = namedtuple("SequencesInfo", "nSeqs nSpecies speciesToUse seqStartingIndices") -FileInfo = namedtuple("FileInfo", "inputDir outputDir graphFilename") - -def GetIDPairFromString(line): - return map(int, line.split("_")) - -def NumberOfSequences(seqsInfo, iSpecies): - return (seqsInfo.seqStartingIndices[iSpecies+1] if iSpecies != seqsInfo.nSpecies-1 else seqsInfo.nSeqs) - seqsInfo.seqStartingIndices[iSpecies] - def GetSequenceLengths(seqsInfo, fileInfo): sequenceLengths = [] for iSpecies, iFasta in enumerate(seqsInfo.speciesToUse): - sequenceLengths.append(np.zeros(NumberOfSequences(seqsInfo, iSpecies))) + sequenceLengths.append(np.zeros(BlastFileProcessor.NumberOfSequences(seqsInfo, iSpecies))) fastaFilename = fileInfo.inputDir + "Species%d.fa" % iFasta currentSequenceLength = 0 iCurrentSequence = -1 @@ -574,7 +315,7 @@ def GetSequenceLengths(seqsInfo, fileInfo): else: sequenceLengths[iSpecies][iCurrentSequence] = currentSequenceLength currentSequenceLength = 0 - _, iCurrentSequence = GetIDPairFromString(row[1:]) + _, iCurrentSequence = util.GetIDPairFromString(row[1:]) else: currentSequenceLength += len(row.rstrip()) sequenceLengths[iSpecies][iCurrentSequence] = currentSequenceLength @@ -588,30 +329,6 @@ def GetNumberOfSequencesInFile(filename): if line.startswith(">"): count+=1 return count -# Get Info from seqs IDs file? -def GetSeqsInfo(inputDirectory, speciesToUse): - seqStartingIndices = [0] - nSeqs = 0 - for i, iFasta in enumerate(speciesToUse): - fastaFilename = inputDirectory + "Species%d.fa" % iFasta - with open(fastaFilename) as infile: - for line in infile: - if len(line) > 1 and line[0] == ">": - nSeqs+=1 - seqStartingIndices.append(nSeqs) - seqStartingIndices = seqStartingIndices[:-1] - nSpecies = len(speciesToUse) - return SequencesInfo(nSeqs=nSeqs, nSpecies=nSpecies, speciesToUse=speciesToUse, seqStartingIndices=seqStartingIndices) - -def GetSpeciesToUse(speciesIDsFN): - speciesToUse = [] - with open(speciesIDsFN, 'rb') as speciesF: - for line in speciesF: - if len(line) == 0 or line[0] == "#": continue - speciesToUse.append(int(line.split(":")[0])) - return speciesToUse - - """ Question: Do I want to do all BLASTs or just the required ones? It's got to be all BLASTs I think. They could potentially be run after the clustering has finished.""" def GetOrderedBlastCommands(seqsInfo, previousFastaFiles, newFastaFiles, workingDir): @@ -629,138 +346,55 @@ def GetOrderedBlastCommands(seqsInfo, previousFastaFiles, newFastaFiles, working taskSizes, speciesPairs = util.SortArrayPairByFirst(taskSizes, speciesPairs, True) commands = [["blastp", "-outfmt", "6", "-evalue", "0.001", "-query", workingDir + "Species%d.fa" % iFasta, "-db", workingDir + "BlastDBSpecies%d" % iDB, "-out", "%sBlast%d_%d.txt" % (workingDir, iFasta, iDB)] for iFasta, iDB in speciesPairs] - return commands - -""" -BlastFileProcessor -------------------------------------------------------------------------------- -""" -class BlastFileProcessor(object): - @staticmethod - def GetBH_s(pairwiseScoresMatrices, seqsInfo, iSpecies, tol=1e-3): - nSeqs_i = NumberOfSequences(seqsInfo, iSpecies) - bestHitForSequence = -1*np.ones(nSeqs_i) - H = [None for i_ in xrange(seqsInfo.nSpecies)] # create array of Nones to be replace by matrices - for j in xrange(seqsInfo.nSpecies): - if iSpecies == j: - # identify orthologs then come back to paralogs - continue - W = pairwiseScoresMatrices[j] - I = [] - J = [] - for kRow in xrange(nSeqs_i): - values=W.getrowview(kRow) - if values.nnz == 0: - continue - m = max(values.data[0]) - bestHitForSequence[kRow] = m if m > bestHitForSequence[kRow] else bestHitForSequence[kRow] - # get all above this value with tolerance - temp = [index for index, value in zip(values.rows[0], values.data[0]) if value > m - tol] - J.extend(temp) - I.extend(kRow * np.ones(len(temp), dtype=np.dtype(int))) - H[j] = sparse.csr_matrix((np.ones(len(I)), (I, J)), shape=W.get_shape()) - # now look for paralogs - I = [] - J = [] - W = pairwiseScoresMatrices[iSpecies] - for kRow in xrange(nSeqs_i): - values=W.getrowview(kRow) - if values.nnz == 0: - continue - temp = [index for index, value in zip(values.rows[0], values.data[0]) if value > bestHitForSequence[kRow] - tol] - J.extend(temp) - I.extend(kRow * np.ones(len(temp), dtype=np.dtype(int))) - H[iSpecies] = sparse.csr_matrix((np.ones(len(I)), (I, J)), shape=W.get_shape()) - return H - - @staticmethod - def GetBLAST6Scores(seqsInfo, fileInfo, iSpecies, jSpecies, qExcludeSelfHits = True, sep = "_"): - nSeqs_i = NumberOfSequences(seqsInfo, iSpecies) - nSeqs_j = NumberOfSequences(seqsInfo, jSpecies) - B = sparse.lil_matrix((nSeqs_i, nSeqs_j)) - row = "" - try: - with open(fileInfo.inputDir + "Blast%d_%d.txt" % (seqsInfo.speciesToUse[iSpecies], seqsInfo.speciesToUse[jSpecies]), 'rb') as blastfile: - blastreader = csv.reader(blastfile, delimiter='\t') - for row in blastreader: - # Get hit and query IDs - try: - species1ID, sequence1ID = map(int, row[0].split(sep, 1)) - species2ID, sequence2ID = map(int, row[1].split(sep, 1)) - except (IndexError, ValueError): - sys.stderr.write("\nERROR: Query or hit sequence ID in BLAST results file was missing or incorrectly formatted.\n") - raise - # Get bit score for pair - try: - score = float(row[11]) - except (IndexError, ValueError): - sys.stderr.write("\nERROR: 12th field in BLAST results file line should be the bit-score for the hit\n") - raise - if (qExcludeSelfHits and species1ID == species2ID and sequence1ID == sequence2ID): - continue - # store bit score - try: - if score > B[sequence1ID, sequence2ID]: - B[sequence1ID, sequence2ID] = score - except IndexError: - def ord(n): - return str(n)+("th" if 4<=n%100<=20 else {1:"st",2:"nd",3:"rd"}.get(n%10, "th")) -# sys.stderr.write("\nError in input files, expected only %d sequences in species %d and %d sequences in species %d but found a hit in the Blast%d_%d.txt between sequence %d_%d (i.e. %s sequence in species) and sequence %d_%d (i.e. %s sequence in species)\n" % (nSeqs_i, iSpecies, nSeqs_j, jSpecies, iSpecies, jSpecies, iSpecies, sequence1ID, ord(sequence1ID+1), jSpecies, sequence2ID, ord(sequence2ID+1))) - sys.stderr.write("\nERROR: Inconsistent input files.\n") - kSpecies, nSeqs_k, sequencekID = (iSpecies, nSeqs_i, sequence1ID) if sequence1ID >= nSeqs_i else (jSpecies, nSeqs_j, sequence2ID) - sys.stderr.write("Species%d.fa contains only %d sequences " % (kSpecies, nSeqs_k)) - sys.stderr.write("but found a query/hit in the Blast%d_%d.txt for sequence %d_%d (i.e. %s sequence in species %d).\n" % (iSpecies, jSpecies, kSpecies, sequencekID, ord(sequencekID+1), kSpecies)) - sys.exit() - except Exception: - sys.stderr.write("Malformatted line in %sBlast%d_%d.txt\nOffending line was:\n" % (fileInfo.inputDir, seqsInfo.speciesToUse[iSpecies], seqsInfo.speciesToUse[jSpecies])) - sys.stderr.write("\t".join(row) + "\n") - sys.exit() - return B + return commands """ Matrices ------------------------------------------------------------------------------- """ -def DumpMatrix(name, m, fileInfo, iSpecies, jSpecies): - with open(fileInfo.outputDir + "%s%d_%d.pic" % (name, iSpecies, jSpecies), 'wb') as picFile: - pic.dump(m, picFile, protocol=picProtocol) - -def DumpMatrixArray(name, matrixArray, fileInfo, iSpecies): - for jSpecies, m in enumerate(matrixArray): - DumpMatrix(name, m, fileInfo, iSpecies, jSpecies) - def DeleteMatrices(fileInfo): for f in glob.glob(fileInfo.outputDir + "B*_*.pic"): if os.path.exists(f): os.remove(f) for f in glob.glob(fileInfo.outputDir + "connect*_*.pic"): if os.path.exists(f): os.remove(f) - -def LoadMatrix(name, fileInfo, iSpecies, jSpecies): - with open(fileInfo.outputDir + "%s%d_%d.pic" % (name, iSpecies, jSpecies), 'rb') as picFile: - M = pic.load(picFile) - return M - -def LoadMatrixArray(name, fileInfo, seqsInfo, iSpecies, row=True): - matrixArray = [] - for jSpecies in xrange(seqsInfo.nSpecies): - if row == True: - matrixArray.append(LoadMatrix(name, fileInfo, iSpecies, jSpecies)) - else: - matrixArray.append(LoadMatrix(name, fileInfo, jSpecies, iSpecies)) - return matrixArray - -def MatricesAnd_s(Xarr, Yarr): - Zarr = [] - for x, y in zip(Xarr, Yarr): - Zarr.append(x.multiply(y)) - return Zarr - -def MatricesAndTr_s(Xarr, Yarr): - Zarr = [] - for x, y in zip(Xarr, Yarr): - Zarr.append(x.multiply(y.transpose())) - return Zarr + +def GetBH_s(pairwiseScoresMatrices, seqsInfo, iSpecies, tol=1e-3): + nSeqs_i = BlastFileProcessor.NumberOfSequences(seqsInfo, iSpecies) + bestHitForSequence = -1*np.ones(nSeqs_i) + H = [None for i_ in xrange(seqsInfo.nSpecies)] # create array of Nones to be replace by matrices + for j in xrange(seqsInfo.nSpecies): + if iSpecies == j: + # identify orthologs then come back to paralogs + continue + W = pairwiseScoresMatrices[j] + I = [] + J = [] + for kRow in xrange(nSeqs_i): + values=W.getrowview(kRow) + if values.nnz == 0: + continue + m = max(values.data[0]) + bestHitForSequence[kRow] = m if m > bestHitForSequence[kRow] else bestHitForSequence[kRow] + # get all above this value with tolerance + temp = [index for index, value in zip(values.rows[0], values.data[0]) if value > m - tol] + J.extend(temp) + I.extend(kRow * np.ones(len(temp), dtype=np.dtype(int))) + H[j] = sparse.csr_matrix((np.ones(len(I)), (I, J)), shape=W.get_shape()) + # now look for paralogs + I = [] + J = [] + W = pairwiseScoresMatrices[iSpecies] + for kRow in xrange(nSeqs_i): + values=W.getrowview(kRow) + if values.nnz == 0: + continue + temp = [index for index, value in zip(values.rows[0], values.data[0]) if value > bestHitForSequence[kRow] - tol] + J.extend(temp) + I.extend(kRow * np.ones(len(temp), dtype=np.dtype(int))) + H[iSpecies] = sparse.csr_matrix((np.ones(len(I)), (I, J)), shape=W.get_shape()) + return H + """ WaterfallMethod @@ -773,14 +407,14 @@ def WriteGraph_perSpecies(args): with open(fileInfo.graphFilename + "_%d" % iSpec, 'wb') as graphFile: connect2 = [] for jSpec in xrange(seqsInfo.nSpecies): - m1 = LoadMatrix("connect", fileInfo, iSpec, jSpec) - m2tr = numeric.transpose(LoadMatrix("connect", fileInfo, jSpec, iSpec)) + m1 = matrices.LoadMatrix("connect", fileInfo, iSpec, jSpec) + m2tr = numeric.transpose(matrices.LoadMatrix("connect", fileInfo, jSpec, iSpec)) connect2.append(m1 + m2tr) - B = LoadMatrixArray("B", fileInfo, seqsInfo, iSpec) - B_connect = MatricesAnd_s(connect2, B) + B = matrices.LoadMatrixArray("B", fileInfo, seqsInfo, iSpec) + B_connect = matrices.MatricesAnd_s(connect2, B) W = [b.sorted_indices().tolil() for b in B_connect] - for query in xrange(NumberOfSequences(seqsInfo, iSpec)): + for query in xrange(BlastFileProcessor.NumberOfSequences(seqsInfo, iSpec)): offset = seqsInfo.seqStartingIndices[iSpec] graphFile.write("%d " % (offset + query)) for jSpec in xrange(seqsInfo.nSpecies): @@ -817,9 +451,9 @@ class WaterfallMethod: Bij = BlastFileProcessor.GetBLAST6Scores(seqsInfo, fileInfo, iSpecies, jSpecies) Bij = WaterfallMethod.NormaliseScores(Bij, Lengths, iSpecies, jSpecies) Bi.append(Bij) - DumpMatrixArray("B", Bi, fileInfo, iSpecies) - BH = BlastFileProcessor.GetBH_s(Bi, seqsInfo, iSpecies) - DumpMatrixArray("BH", BH, fileInfo, iSpecies) + matrices.DumpMatrixArray("B", Bi, fileInfo, iSpecies) + BH = GetBH_s(Bi, seqsInfo, iSpecies) + matrices.DumpMatrixArray("BH", BH, fileInfo, iSpecies) util.PrintTime("Initial processing of species %d complete" % iSpecies) @staticmethod @@ -834,12 +468,12 @@ class WaterfallMethod: @staticmethod def ConnectCognates(seqsInfo, fileInfo, iSpecies): # calculate RBH for species i - BHix = LoadMatrixArray("BH", fileInfo, seqsInfo, iSpecies) - BHxi = LoadMatrixArray("BH", fileInfo, seqsInfo, iSpecies, row=False) - RBHi = MatricesAndTr_s(BHix, BHxi) # twice as much work as before (only did upper triangular before) - B = LoadMatrixArray("B", fileInfo, seqsInfo, iSpecies) + BHix = matrices.LoadMatrixArray("BH", fileInfo, seqsInfo, iSpecies) + BHxi = matrices.LoadMatrixArray("BH", fileInfo, seqsInfo, iSpecies, row=False) + RBHi = matrices.MatricesAndTr_s(BHix, BHxi) # twice as much work as before (only did upper triangular before) + B = matrices.LoadMatrixArray("B", fileInfo, seqsInfo, iSpecies) connect = WaterfallMethod.ConnectAllBetterThanAnOrtholog_s(RBHi, B, seqsInfo, iSpecies) - DumpMatrixArray("connect", connect, fileInfo, iSpecies) + matrices.DumpMatrixArray("connect", connect, fileInfo, iSpecies) @staticmethod def Worker_ConnectCognates(cmd_queue): @@ -865,7 +499,7 @@ class WaterfallMethod: @staticmethod def GetMostDistant_s(RBH, B, seqsInfo, iSpec): - mostDistant = numeric.transpose(np.ones(NumberOfSequences(seqsInfo, iSpec))*1e9) + mostDistant = numeric.transpose(np.ones(BlastFileProcessor.NumberOfSequences(seqsInfo, iSpec))*1e9) for kSpec in xrange(seqsInfo.nSpecies): B[kSpec] = B[kSpec].tocsr() if iSpec == kSpec: @@ -878,7 +512,7 @@ class WaterfallMethod: @staticmethod def ConnectAllBetterThanCutoff_s(B, mostDistant, seqsInfo, iSpec): connect = [] - nSeqs_i = NumberOfSequences(seqsInfo, iSpec) + nSeqs_i = BlastFileProcessor.NumberOfSequences(seqsInfo, iSpec) for jSpec in xrange(seqsInfo.nSpecies): M=B[jSpec].tolil() if iSpec != jSpec: @@ -888,7 +522,7 @@ class WaterfallMethod: II = [i for (i, j) in IIJJ] JJ = [j for (i, j) in IIJJ] onesArray = np.ones(len(IIJJ)) - mat = sparse.csr_matrix( (onesArray, (II, JJ)), shape=(nSeqs_i, NumberOfSequences(seqsInfo, jSpec))) + mat = sparse.csr_matrix( (onesArray, (II, JJ)), shape=(nSeqs_i, BlastFileProcessor.NumberOfSequences(seqsInfo, jSpec))) connect.append(mat) return connect @@ -1078,20 +712,8 @@ OrthoFinder """ mclInflation = 1.5 -def CanRunCommand(command, qAllowStderr = False): - util.PrintNoNewLine("Test can run \"%s\"" % command) # print without newline - capture = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - stdout = [x for x in capture.stdout] - stderr = [x for x in capture.stderr] - if len(stdout) > 0 and (qAllowStderr or len(stderr) == 0): - print(" - ok") - return True - else: - print(" - failed") - return False - def CanRunBLAST(): - if CanRunCommand("makeblastdb -help") and CanRunCommand("blastp -help"): + if util.CanRunCommand("makeblastdb -help") and util.CanRunCommand("blastp -help"): return True else: print("ERROR: Cannot run BLAST+") @@ -1100,17 +722,12 @@ def CanRunBLAST(): def CanRunMCL(): command = "mcl -h" - if CanRunCommand(command): + if util.CanRunCommand(command): return True else: print("ERROR: Cannot run MCL with the command \"%s\"" % command) print("Please check MCL is installed and in the system path\n") return False - -def PrintCitation(): - print("""\nWhen publishing work that uses OrthoFinder please cite: - D.M. Emms & S. Kelly (2015), OrthoFinder: solving fundamental biases in whole genome comparisons - dramatically improves orthogroup inference accuracy, Genome Biology 16:157.\n""") def PrintHelp(): print("Simple Usage") @@ -1149,7 +766,7 @@ def PrintHelp(): print("""-t number_of_blast_threads, --threads number_of_blast_threads The number of BLAST processes to be run simultaneously. This should be increased by the user to at least the number of cores on the computer so as to minimise the time taken to perform the BLAST all-versus-all - queries. [Default is %d]\n""" % nThreadsDefault) + queries. [Default is %d]\n""" % util.nThreadsDefault) print("""-a number_of_orthofinder_threads, --algthreads number_of_orthofinder_threads The number of threads to use for the OrthoFinder algorithm and MCL after BLAST searches have been completed. @@ -1157,7 +774,10 @@ def PrintHelp(): requirements proportionally so be aware of the amount of RAM you have available (and see README file). Additionally, as the algorithm implementation is very fast, file reading is likely to be the limiting factor above about 5-10 threads and additional threads may have little effect other than - increase RAM requirements. [Default is %d]\n""" % nAlgDefault) + increase RAM requirements. [Default is %d]\n""" % util.nAlgDefault) + + print("""-g, --groups + Only infer orthogroups, do not infer gene trees of orthologues.\n""") print("""-I inflation_parameter, --inflation inflation_parameter Specify a non-default inflation parameter for MCL. [Default is %0.1f]\n""" % mclInflation) @@ -1171,7 +791,7 @@ def PrintHelp(): print("""-h, --help Print this help text""") - PrintCitation() + util.PrintCitation() """ Main @@ -1181,7 +801,7 @@ Main def GetDirectoryArgument(arg, args): if len(args) == 0: print("Missing option for command line argument %s" % arg) - Fail() + util.Fail() directory = os.path.abspath(args.pop(0)) if directory[-1] != os.sep: directory += os.sep @@ -1228,10 +848,8 @@ def AssignIDsToSequences(fastaDirectory, outputDirectory): if len(originalFastaFilenames) > 0: outputFasta.close() return returnFilenames, originalFastaFilenames, idsFilename, speciesFilename, newSpeciesIDs, previousSpeciesIDs -if __name__ == "__main__": - import get_orthologues - - print("\nOrthoFinder version %s Copyright (C) 2014 David Emms\n" % version) +if __name__ == "__main__": + print("\nOrthoFinder version %s Copyright (C) 2014 David Emms\n" % util.version) print(""" This program comes with ABSOLUTELY NO WARRANTY. This is free software, and you are welcome to redistribute it under certain conditions. For details please see the License.md that came with this software.\n""") @@ -1240,8 +858,8 @@ if __name__ == "__main__": sys.exit() # Control - nBlast = nThreadsDefault - nProcessAlg = nAlgDefault + nBlast = util.nThreadsDefault + nProcessAlg = util.nAlgDefault qUsePrecalculatedBlast = False # remove, just store BLAST to do qUseFastaFiles = False # local to argument checking qXML = False @@ -1264,70 +882,72 @@ if __name__ == "__main__": if arg == "-f" or arg == "--fasta": if qUseFastaFiles: print("Repeated argument: -f/--fasta") - Fail() + util.Fail() qUseFastaFiles = True fastaDir = GetDirectoryArgument(arg, args) elif arg == "-b" or arg == "--blast": if qUsePrecalculatedBlast: print("Repeated argument: -b/--blast") - Fail() + util.Fail() qUsePrecalculatedBlast = True workingDir_previous = GetDirectoryArgument(arg, args) elif arg == "-t" or arg == "--threads": if len(args) == 0: print("Missing option for command line argument -t") - Fail() + util.Fail() arg = args.pop(0) try: nBlast = int(arg) except: print("Incorrect argument for number of BLAST threads: %s" % arg) - Fail() + util.Fail() elif arg == "-a" or arg == "--algthreads": if len(args) == 0: print("Missing option for command line argument -a") - Fail() + util.Fail() arg = args.pop(0) try: nProcessAlg = int(arg) except: print("Incorrect argument for number of BLAST threads: %s" % arg) - Fail() + util.Fail() elif arg == "-I" or arg == "--inflation": if len(args) == 0: print("Missing option for command line argument -I") - Fail() + util.Fail() arg = args.pop(0) try: mclInflation = float(arg) except: print("Incorrect argument for MCL inflation parameter: %s" % arg) - Fail() + util.Fail() elif arg == "-x" or arg == "--orthoxml": if qXML: print("Repeated argument: -x/--orthoxml") - Fail() + util.Fail() qXML = True if len(args) == 0: print("Missing option for command line argument %s" % arg) - Fail() + util.Fail() speciesInfoFilename = args.pop(0) # elif arg == "-s" or arg == "--subset": # if qUseSubset: # print("Repeated argument: -s/--subset") -# Fail() +# util.Fail() # qUseSubset = True # qUsePrecalculatedBlast = True # workingDir_previous = GetDirectoryArgument(arg, args) elif arg == "-p" or arg == "--prepare": qOnlyPrepare = True qOrthologues = False + elif arg == "-g" or arg == "--groups": + qOrthologues = False elif arg == "-h" or arg == "--help": PrintHelp() sys.exit() else: print("Unrecognised argument: %s\n" % arg) - Fail() + util.Fail() # check argument combinations if qUseFastaFiles and qUsePrecalculatedBlast: @@ -1338,8 +958,8 @@ if __name__ == "__main__": speciesIdsFilename = workingDir_previous + "SpeciesIDs.txt" if not os.path.exists(speciesIdsFilename): print("%s file must be provided if using previously calculated BLAST results" % speciesIdsFilename) - Fail() - speciesToUse = GetSpeciesToUse(speciesIdsFilename) + util.Fail() + speciesToUse = util.GetSpeciesToUse(speciesIdsFilename) workingDir = os.path.abspath(workingDir) + os.sep if resultsDir == None: workingDir = workingDir_previous @@ -1350,13 +970,13 @@ if __name__ == "__main__": # check BLAST results directory exists if not os.path.exists(workingDir_previous): print("Previous/Pre-calculated BLAST results directory does not exist: %s\n" % workingDir_previous) - Fail() + util.Fail() # check fasta files are present previousFastaFiles = util.SortFastaFilenames(glob.glob(workingDir_previous + "Species*.fa")) if len(previousFastaFiles) == 0: print("No processed fasta files in the supplied previous working directory: %s\n" % workingDir_previous) - Fail() + util.Fail() tokens = previousFastaFiles[-1][:-3].split("Species") lastFastaNumberString = tokens[-1] iLastFasta = 0 @@ -1365,10 +985,10 @@ if __name__ == "__main__": iLastFasta = int(lastFastaNumberString) except: print("Filenames for processed fasta files are incorrect: %s\n" % previousFastaFiles[-1]) - Fail() + util.Fail() if nFasta != iLastFasta + 1: print("Not all expected fasta files are present. Index of last fasta file is %s but found %d fasta files.\n" % (lastFastaNumberString, len(previousFastaFiles))) - Fail() + util.Fail() # check BLAST files qHaveBlast = True @@ -1378,13 +998,13 @@ if __name__ == "__main__": if not os.path.exists(filename): print("BLAST results file is missing: %s" % filename) qHaveBlast = False - if not qHaveBlast: Fail() + if not qHaveBlast: util.Fail() # check SequenceIDs.txt and SpeciesIDs.txt files are present idsFilename = workingDir_previous + "SequenceIDs.txt" if not os.path.exists(idsFilename): print("%s file must be provided if using previous calculated BLAST results" % idsFilename) - Fail() + util.Fail() print("Using previously calculated BLAST results in %s" % workingDir_previous) else: # - create working directory @@ -1400,9 +1020,9 @@ if __name__ == "__main__": print("\n1. Checking required programs are installed") print("-------------------------------------------") if (not qUsePrecalculatedBlast) and (not CanRunBLAST()): - Fail() + util.Fail() if not CanRunMCL(): - Fail() + util.Fail() # - rename sequences with unique, simple identifiers print("\n2. Temporarily renaming sequences with unique, simple identifiers") @@ -1413,7 +1033,7 @@ if __name__ == "__main__": newFastaFiles, userFastaFilenames, idsFilename, speciesIdsFilename, newSpeciesIDs, previousSpeciesIDs = AssignIDsToSequences(fastaDir, workingDir) speciesToUse = speciesToUse + newSpeciesIDs print("Done!") - seqsInfo = GetSeqsInfo(workingDir_previous if qUsePrecalculatedBlast else workingDir, speciesToUse) + seqsInfo = util.GetSeqsInfo(workingDir_previous if qUsePrecalculatedBlast else workingDir, speciesToUse) if qXML: print("\n2b. Reading species information file") @@ -1437,7 +1057,7 @@ if __name__ == "__main__": print("Each line should contain 5 tab-delimited fields:") print(" fastaFilename, speciesName, NCBITaxID, sourceDatabaseName, databaseFastaFilename") print("See README file for more information.") - Fail() + util.Fail() fastaFilename, speciesName, NCBITaxID, sourceDatabaseName, databaseVersionFastaFile = line try: iSpecies = fastaFileIndices[fastaFilename] @@ -1448,7 +1068,7 @@ if __name__ == "__main__": for filename in userFastaFilenames_justNames: print(filename) print("Please provide information for each of these species in the species information file") - Fail() + util.Fail() speciesInfo[iSpecies] = line # check information has been provided for all species speciesMissing = False @@ -1461,7 +1081,7 @@ if __name__ == "__main__": speciesMissing = True print(fastaFilename) if speciesMissing: - Fail() + util.Fail() print("\n3. Dividing up work for BLAST for parallel processing") print( "-----------------------------------------------------") @@ -1496,9 +1116,9 @@ if __name__ == "__main__": cmd_queue = mp.Queue() for iCmd, cmd in enumerate(commands): cmd_queue.put((iCmd+1, cmd)) - runningProcesses = [mp.Process(target=Worker_RunCommand, args=(cmd_queue, len(commands))) for i_ in xrange(nBlast)] + runningProcesses = [mp.Process(target=util.Worker_RunCommand, args=(cmd_queue, nBlast, len(commands))) for i_ in xrange(nBlast)] for proc in runningProcesses: - proc.start() + proc.start()# for proc in runningProcesses: while proc.is_alive(): proc.join() @@ -1511,13 +1131,13 @@ if __name__ == "__main__": # Run Algorithm, cluster and output cluster files with original accessions print("\n5. Running OrthoFinder algorithm") print( "--------------------------------") - fileIdentifierString = "OrthoFinder_v%s" % version + fileIdentifierString = "OrthoFinder_v%s" % util.version graphFilename = workingDir + "%s_graph.txt" % fileIdentifierString # it's important to free up the memory from python used for processing the genomes # before launching MCL becuase both use sizeable ammounts of memory. The only # way I can find to do this is to launch the memory intensive python code # as separate process that exitsbefore MCL is launched. - fileInfo = FileInfo(inputDir=workingDir_previous if qUsePrecalculatedBlast else workingDir, outputDir = workingDir, graphFilename=graphFilename) + fileInfo = util.FileInfo(inputDir=workingDir_previous if qUsePrecalculatedBlast else workingDir, outputDir = workingDir, graphFilename=graphFilename) if not os.path.exists(fileInfo.outputDir): os.mkdir(fileInfo.outputDir) Lengths = GetSequenceLengths(seqsInfo, fileInfo) @@ -1530,7 +1150,7 @@ if __name__ == "__main__": runningProcesses = [mp.Process(target=WaterfallMethod.Worker_ProcessBlastHits, args=(cmd_queue, )) for i_ in xrange(nProcessAlg)] for proc in runningProcesses: proc.start() - ManageQueue(runningProcesses, cmd_queue) + util.ManageQueue(runningProcesses, cmd_queue) cmd_queue = mp.Queue() for iSpecies in xrange(seqsInfo.nSpecies): @@ -1538,7 +1158,7 @@ if __name__ == "__main__": runningProcesses = [mp.Process(target=WaterfallMethod.Worker_ConnectCognates, args=(cmd_queue, )) for i_ in xrange(nProcessAlg)] for proc in runningProcesses: proc.start() - ManageQueue(runningProcesses, cmd_queue) + util.ManageQueue(runningProcesses, cmd_queue) util.PrintTime("Connected putatitive homologs") WaterfallMethod.WriteGraphParallel(seqsInfo, fileInfo) @@ -1547,12 +1167,12 @@ if __name__ == "__main__": clustersFilename, iResultsVersion = util.GetUnusedFilename(workingDir + "clusters_%s_I%0.1f" % (fileIdentifierString, mclInflation), ".txt") MCL.RunMCL(graphFilename, clustersFilename, nProcessAlg, mclInflation) clustersFilename_pairs = clustersFilename + "_id_pairs.txt" - MCL.ConvertSingleIDsToIDPair(seqsInfo, clustersFilename, clustersFilename_pairs) + MCLread.ConvertSingleIDsToIDPair(seqsInfo, clustersFilename, clustersFilename_pairs) print("\n6. Creating files for Orthologous Groups") print( "----------------------------------------") - if not qOrthologues: PrintCitation() - ogs = MCL.GetPredictedOGs(clustersFilename_pairs) + if not qOrthologues: util.PrintCitation() + ogs = MCLread.GetPredictedOGs(clustersFilename_pairs) resultsBaseFilename = util.GetUnusedFilename(resultsDir + "OrthologousGroups", ".csv")[:-4] # remove .csv from base filename resultsBaseFilename = resultsDir + "OrthologousGroups" + ("" if iResultsVersion == 0 else "_%d" % iResultsVersion) idsDict = MCL.WriteOrthogroupFiles(ogs, [idsFilename], resultsBaseFilename, clustersFilename_pairs) @@ -1575,4 +1195,4 @@ if __name__ == "__main__": print(statsFile) print("") print(summaryText) - PrintCitation() + util.PrintCitation() diff --git a/orthofinder/scripts/__init__.py b/orthofinder/scripts/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/orthofinder/scripts/__init__.py @@ -0,0 +1 @@ + diff --git a/orthofinder/scripts/blast_file_processor.py b/orthofinder/scripts/blast_file_processor.py new file mode 100644 index 0000000..ad556f4 --- /dev/null +++ b/orthofinder/scripts/blast_file_processor.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2014 David Emms +# +# This program (OrthoFinder) is distributed under the terms of the GNU General Public License v3 +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# When publishing work that uses OrthoFinder please cite: +# Emms, D.M. and Kelly, S. (2015) OrthoFinder: solving fundamental biases in whole genome comparisons dramatically +# improves orthogroup inference accuracy, Genome Biology 16:157 +# +# For any enquiries send an email to David Emms +# david_emms@hotmail.com + +import sys +import csv +from scipy import sparse + +def NumberOfSequences(seqsInfo, iSpecies): + return (seqsInfo.seqStartingIndices[iSpecies+1] if iSpecies != seqsInfo.nSpecies-1 else seqsInfo.nSeqs) - seqsInfo.seqStartingIndices[iSpecies] + +def GetBLAST6Scores(seqsInfo, fileInfo, iSpecies, jSpecies, qExcludeSelfHits = True, sep = "_"): + nSeqs_i = NumberOfSequences(seqsInfo, iSpecies) + nSeqs_j = NumberOfSequences(seqsInfo, jSpecies) + B = sparse.lil_matrix((nSeqs_i, nSeqs_j)) + row = "" + try: + with open(fileInfo.inputDir + "Blast%d_%d.txt" % (seqsInfo.speciesToUse[iSpecies], seqsInfo.speciesToUse[jSpecies]), 'rb') as blastfile: + blastreader = csv.reader(blastfile, delimiter='\t') + for row in blastreader: + # Get hit and query IDs + try: + species1ID, sequence1ID = map(int, row[0].split(sep, 1)) + species2ID, sequence2ID = map(int, row[1].split(sep, 1)) + except (IndexError, ValueError): + sys.stderr.write("\nERROR: Query or hit sequence ID in BLAST results file was missing or incorrectly formatted.\n") + raise + # Get bit score for pair + try: + score = float(row[11]) + except (IndexError, ValueError): + sys.stderr.write("\nERROR: 12th field in BLAST results file line should be the bit-score for the hit\n") + raise + if (qExcludeSelfHits and species1ID == species2ID and sequence1ID == sequence2ID): + continue + # store bit score + try: + if score > B[sequence1ID, sequence2ID]: + B[sequence1ID, sequence2ID] = score + except IndexError: + def ord(n): + return str(n)+("th" if 4<=n%100<=20 else {1:"st",2:"nd",3:"rd"}.get(n%10, "th")) +# sys.stderr.write("\nError in input files, expected only %d sequences in species %d and %d sequences in species %d but found a hit in the Blast%d_%d.txt between sequence %d_%d (i.e. %s sequence in species) and sequence %d_%d (i.e. %s sequence in species)\n" % (nSeqs_i, iSpecies, nSeqs_j, jSpecies, iSpecies, jSpecies, iSpecies, sequence1ID, ord(sequence1ID+1), jSpecies, sequence2ID, ord(sequence2ID+1))) + sys.stderr.write("\nERROR: Inconsistent input files.\n") + kSpecies, nSeqs_k, sequencekID = (iSpecies, nSeqs_i, sequence1ID) if sequence1ID >= nSeqs_i else (jSpecies, nSeqs_j, sequence2ID) + sys.stderr.write("Species%d.fa contains only %d sequences " % (kSpecies, nSeqs_k)) + sys.stderr.write("but found a query/hit in the Blast%d_%d.txt for sequence %d_%d (i.e. %s sequence in species %d).\n" % (iSpecies, jSpecies, kSpecies, sequencekID, ord(sequencekID+1), kSpecies)) + sys.exit() + except Exception: + sys.stderr.write("Malformatted line in %sBlast%d_%d.txt\nOffending line was:\n" % (fileInfo.inputDir, seqsInfo.speciesToUse[iSpecies], seqsInfo.speciesToUse[jSpecies])) + sys.stderr.write("\t".join(row) + "\n") + sys.exit() + return B \ No newline at end of file diff --git a/get_orthologues.py b/orthofinder/scripts/get_orthologues.py similarity index 87% rename from get_orthologues.py rename to orthofinder/scripts/get_orthologues.py index ac7a1e7..82e32d7 100755 --- a/get_orthologues.py +++ b/orthofinder/scripts/get_orthologues.py @@ -1,13 +1,33 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -""" -Created on Thu Jun 9 16:00:33 2016 - -@author: david -""" +# +# Copyright 2014 David Emms +# +# This program (OrthoFinder) is distributed under the terms of the GNU General Public License v3 +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# When publishing work that uses OrthoFinder please cite: +# Emms, D.M. and Kelly, S. (2015) OrthoFinder: solving fundamental biases in whole genome comparisons dramatically +# improves orthogroup inference accuracy, Genome Biology 16:157 +# +# For any enquiries send an email to David Emms +# david_emms@hotmail.comhor: david -import sys import os +import sys +import glob import subprocess import numpy as np from collections import Counter, defaultdict @@ -16,15 +36,15 @@ import itertools import multiprocessing as mp import Queue +import util import tree -import trees_for_orthogroups as tfo -import orthofinder +import matrices +import mcl as MCL import root_from_duplications as rfd -import process_trees as pt - -#print(orthofinder.__file__) +import orthologues_from_recon_trees as pt +import blast_file_processor as BlastFileProcessor -nThreads = orthofinder.nThreadsDefault +nThreads = util.nThreadsDefault class Seq(object): def __init__(self, seqInput): @@ -61,21 +81,19 @@ class Seq(object): # ============================================================================================================================== class OrthoGroupsSet(object): - def __init__(self, orthofinderWorkingDir, clustersFilename_pairs, idExtractor = orthofinder.FirstWordExtractor): + def __init__(self, orthofinderWorkingDir, clustersFilename_pairs, idExtractor = util.FirstWordExtractor): self.workingDirOF = orthofinderWorkingDir self.seqIDsFN = orthofinderWorkingDir + "SequenceIDs.txt" self.speciesIDsFN = orthofinderWorkingDir + "SpeciesIDs.txt" - self.speciesIDsEx = orthofinder.FullAccession(self.speciesIDsFN) + self.speciesIDsEx = util.FullAccession(self.speciesIDsFN) self._Spec_SeqIDs = None self._extractor = idExtractor self.clustersFN = clustersFilename_pairs self.seqIDsEx = None self.ogs = self.OGs() - self.speciesToUse = orthofinder.GetSpeciesToUse(self.speciesIDsFN) - self.seqsInfo = orthofinder.GetSeqsInfo(orthofinderWorkingDir, self.speciesToUse) - self.fileInfo = orthofinder.FileInfo(inputDir=orthofinderWorkingDir, outputDir = orthofinderWorkingDir, graphFilename="") -# nSeqs, nSpecies, speciesStartingIndices = orthofinder.BlastFileProcessor.GetNumberOfSequencesInFileFromDir(workingDir, self.speciesToUse) -# self.bfp = orthofinder.BlastFileProcessor(workingDir, self.speciesToUse, nSeqs, nSpecies, speciesStartingIndices) + self.speciesToUse = util.GetSpeciesToUse(self.speciesIDsFN) + self.seqsInfo = util.GetSeqsInfo(orthofinderWorkingDir, self.speciesToUse) + self.fileInfo = util.FileInfo(inputDir=orthofinderWorkingDir, outputDir = orthofinderWorkingDir, graphFilename="") def SequenceDict(self): if self.seqIDsEx == None: @@ -103,15 +121,9 @@ class OrthoGroupsSet(object): return self._Spec_SeqIDs def OGs(self): - ogs = orthofinder.MCL.GetPredictedOGs(self.clustersFN) + ogs = MCL.GetPredictedOGs(self.clustersFN) ogs = [[Seq(g) for g in og] for og in ogs if len(og) >= 4] return ogs - -# def BitScores(self, iSp, jSp): -## return self.bfp.GetBLAST6Scores(self.speciesToUse.index(iSp), self.speciesToUse.index(jSp), False) -# return orthofinder.BlastFileProcessor.GetBLAST6Scores(self.seqsInfo, self.fileInfo, iSp, jSp, False) -## with open(self.workingDir + "B%d_%d.pic" % (iSp, jSp), 'rb') as infile: -## return pic.load(infile) # ============================================================================================================================== @@ -193,10 +205,10 @@ def Worker_BlastScores(cmd_queue, seqsInfo, fileInfo, nProcesses, nToDo): i, args = cmd_queue.get(True, 1) nDone = i - nProcesses + 1 if nDone >= 0 and divmod(nDone, 10 if nToDo <= 200 else 100 if nToDo <= 2000 else 1000)[1] == 0: - orthofinder.util.PrintTime("Done %d of %d" % (nDone, nToDo)) - B = orthofinder.BlastFileProcessor.GetBLAST6Scores(seqsInfo, fileInfo, *args, qExcludeSelfHits = False) + util.PrintTime("Done %d of %d" % (nDone, nToDo)) + B = BlastFileProcessor.GetBLAST6Scores(seqsInfo, fileInfo, *args, qExcludeSelfHits = False) with open(fileInfo.outputDir + "Bit%d_%d.pic" % args, 'wb') as outfile: - pic.dump(B, outfile, protocol = orthofinder.picProtocol) + pic.dump(B, outfile, protocol = util.picProtocol) except Queue.Empty: return @@ -250,8 +262,8 @@ class DendroBLASTTrees(object): nSeqs = self.NumberOfSequences(self.species) ogMatrices = [np.zeros((n, n)) for n in nGenes] for iiSp, sp1 in enumerate(self.species): - orthofinder.util.PrintTime("Processing species %d" % sp1) - Bs = [orthofinder.LoadMatrix("Bit", self.ogSet.fileInfo, iiSp, jjSp) for jjSp in xrange(len(self.species))] + util.PrintTime("Processing species %d" % sp1) + Bs = [matrices.LoadMatrix("Bit", self.ogSet.fileInfo, iiSp, jjSp) for jjSp in xrange(len(self.species))] mins = np.ones((nSeqs[sp1], 1), dtype=np.float64)*9e99 maxes = np.zeros((nSeqs[sp1], 1), dtype=np.float64) for B, sp2 in zip(Bs, self.species): @@ -264,6 +276,10 @@ class DendroBLASTTrees(object): m[i, j] = 0.5*max(B[gi.iSeq, gj.iSeq], mins[gi.iSeq]) / maxes[gi.iSeq] return ogs, ogMatrices + def DeleteBlastMatrices(self, workingDir): + for f in glob.glob(workingDir + "B*_*.pic"): + if os.path.exists(f): os.remove(f) + def WriteOGMatrices(self, ogs, ogMatrices): newMatrices = [] for iog, (og, m) in enumerate(zip(ogs, ogMatrices)): @@ -347,12 +363,13 @@ class DendroBLASTTrees(object): def RunAnalysis(self): ogs, ogMatrices_partial = self.GetOGMatrices() ogMatrices = self.WriteOGMatrices(ogs, ogMatrices_partial) + D, spPairs = self.SpeciesTreeDistances(ogs, ogMatrices) cmd_spTree, spTreeFN_ids = self.PrepareSpeciesTreeCommand(D, spPairs) cmds_geneTrees = self.PrepareGeneTreeCommand() print("\n3. Inferring gene and species trees") print( "-----------------------------------") - tfo.RunParallelCommandSets(self.nProcesses, [[cmd_spTree]] + cmds_geneTrees, qHideStdout = True) + util.RunParallelOrderedCommandLists(self.nProcesses, [[cmd_spTree]] + cmds_geneTrees, qHideStdout = True) seqDict = self.ogSet.Spec_SeqDict() for iog in xrange(len(self.ogSet.ogs)): self.RenameTreeTaxa(self.treesPatIDs % iog, self.treesPat % iog, seqDict, qFixNegatives=True) @@ -440,31 +457,33 @@ def RunDlcpar(treesPat, ogSet, nOGs, speciesTreeFN, workingDir): dlcCommands = ['dlcpar_search -s %s -S %s -D 1 -C 0.125 %s -O %s' % (speciesTreeFN, geneMapFN, fn, dlcparResultsDir + os.path.splitext(os.path.split(fn)[1])[0]) for fn in filenames] # print(dlcCommands[0]) # use this to run in parallel - tfo.RunParallelCommandSets(nThreads, [[c] for c in dlcCommands], qHideStdout = True) + util.RunParallelOrderedCommandLists(nThreads, [[c] for c in dlcCommands], qHideStdout = True) return dlcparResultsDir # ============================================================================================================================== # Main -def WriteTestDistancesFile(workingDir): - testFN = workingDir + "SimpleTest.phy" +def WriteTestDistancesFile(testFN): with open(testFN, 'wb') as outfile: outfile.write("4\n1_1 0 0 0.2 0.25\n0_2 0 0 0.21 0.28\n3_1 0.21 0.21 0 0\n4_1 0.25 0.28 0 0") return testFN def CanRunDependencies(workingDir): # FastME - testFN = WriteTestDistancesFile(workingDir) + testFN = workingDir + "SimpleTest.phy" + WriteTestDistancesFile(testFN) outFN = workingDir + "SimpleTest.tre" if os.path.exists(outFN): os.remove(outFN) - if not orthofinder.CanRunCommand("fastme -i %s -o %s" % (testFN, outFN), qAllowStderr=False): + if not util.CanRunCommand("fastme -i %s -o %s" % (testFN, outFN), qAllowStderr=False): print("ERROR: Cannot run fastme") print("Please check FastME is installed and that the executables are in the system path\n") return False os.remove(testFN) os.remove(outFN) + fastme_stat_fn = workingDir + "SimpleTest.phy_fastme_stat.txt" + if os.path.exists(fastme_stat_fn): os.remove(fastme_stat_fn) # DLCPar - if not orthofinder.CanRunCommand("dlcpar_search --version", qAllowStderr=False): + if not util.CanRunCommand("dlcpar_search --version", qAllowStderr=False): print("ERROR: Cannot run dlcpar_search") print("Please check DLCpar is installed and that the executables are in the system path\n") return False @@ -484,11 +503,11 @@ def PrintHelp(): print("""-t max_number_of_threads, --threads max_number_of_threads The maximum number of processes to be run simultaneously. The deafult is %d but this - should be increased by the user to the maximum number of cores available.\n""" % orthofinder.nThreadsDefault) + should be increased by the user to the maximum number of cores available.\n""" % util.nThreadsDefault) print("""-h, --help Print this help text""") - orthofinder.PrintCitation() + util.PrintCitation() def GetResultsFilesString(rootedSpeciesTreeFN): st = "" @@ -509,18 +528,18 @@ def GetResultsFilesString(rootedSpeciesTreeFN): def GetOrthologues(orthofinderWorkingDir, orthofinderResultsDir, clustersFilename_pairs, nProcesses): - ogSet = OrthoGroupsSet(orthofinderWorkingDir, clustersFilename_pairs, idExtractor = orthofinder.FirstWordExtractor) + ogSet = OrthoGroupsSet(orthofinderWorkingDir, clustersFilename_pairs, idExtractor = util.FirstWordExtractor) if len(ogSet.speciesToUse) < 4: print("ERROR: Not enough species to infer species tree") - orthofinder.Fail() + util.Fail() print("\n1. Checking required programs are installed") print( "-------------------------------------------") - if not CanRunDependencies(orthofinderWorkingDir): orthofinder.Fail() + if not CanRunDependencies(orthofinderWorkingDir): util.Fail() print("\n2. Reading sequence similarity scores") print( "-------------------------------------") - resultsDir = orthofinder.util.CreateNewWorkingDirectory(orthofinderResultsDir + "Orthologues_") + resultsDir = util.CreateNewWorkingDirectory(orthofinderResultsDir + "Orthologues_") db = DendroBLASTTrees(ogSet, resultsDir, nProcesses) db.ReadAndPickle() @@ -580,13 +599,13 @@ if __name__ == "__main__": if arg == "-t" or arg == "--threads": if len(args) == 0: print("Missing option for command line argument -t") - orthofinder.Fail() + util.Fail() arg = args.pop(0) try: nProcesses = int(arg) except: print("Incorrect argument for number of threads: %s" % arg) - orthofinder.Fail() + util.Fail() else: userDir = arg @@ -596,13 +615,13 @@ if __name__ == "__main__": if nProcesses == None: print("""\nNumber of parallel processes has not been specified, will use the default value. Number of parallel processes can be specified using the -t option.""") - nProcesses = orthofinder.nThreadsDefault + nProcesses = util.nThreadsDefault print("Using %d threads for alignments and trees" % nProcesses) - orthofinderWorkingDir, orthofinderResultsDir, clustersFilename_pairs = tfo.GetOGsFile(userDir) + orthofinderWorkingDir, orthofinderResultsDir, clustersFilename_pairs = util.GetOGsFile(userDir) resultsString = GetOrthologues(orthofinderWorkingDir, orthofinderResultsDir, clustersFilename_pairs, nProcesses) print(resultsString) - orthofinder.PrintCitation() + util.PrintCitation() diff --git a/orthofinder/scripts/matrices.py b/orthofinder/scripts/matrices.py new file mode 100644 index 0000000..0df906a --- /dev/null +++ b/orthofinder/scripts/matrices.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2014 David Emms +# +# This program (OrthoFinder) is distributed under the terms of the GNU General Public License v3 +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# When publishing work that uses OrthoFinder please cite: +# Emms, D.M. and Kelly, S. (2015) OrthoFinder: solving fundamental biases in whole genome comparisons dramatically +# improves orthogroup inference accuracy, Genome Biology 16:157 +# +# For any enquiries send an email to David Emms +# david_emms@hotmail.com + +import cPickle as pic + +import util + +def DumpMatrix(name, m, fileInfo, iSpecies, jSpecies): + with open(fileInfo.outputDir + "%s%d_%d.pic" % (name, iSpecies, jSpecies), 'wb') as picFile: + pic.dump(m, picFile, protocol=util.picProtocol) + +def DumpMatrixArray(name, matrixArray, fileInfo, iSpecies): + for jSpecies, m in enumerate(matrixArray): + DumpMatrix(name, m, fileInfo, iSpecies, jSpecies) + +def LoadMatrix(name, fileInfo, iSpecies, jSpecies): + with open(fileInfo.outputDir + "%s%d_%d.pic" % (name, iSpecies, jSpecies), 'rb') as picFile: + M = pic.load(picFile) + return M + +def LoadMatrixArray(name, fileInfo, seqsInfo, iSpecies, row=True): + matrixArray = [] + for jSpecies in xrange(seqsInfo.nSpecies): + if row == True: + matrixArray.append(LoadMatrix(name, fileInfo, iSpecies, jSpecies)) + else: + matrixArray.append(LoadMatrix(name, fileInfo, jSpecies, iSpecies)) + return matrixArray + +def MatricesAnd_s(Xarr, Yarr): + Zarr = [] + for x, y in zip(Xarr, Yarr): + Zarr.append(x.multiply(y)) + return Zarr + +def MatricesAndTr_s(Xarr, Yarr): + Zarr = [] + for x, y in zip(Xarr, Yarr): + Zarr.append(x.multiply(y.transpose())) + return Zarr diff --git a/orthofinder/scripts/mcl.py b/orthofinder/scripts/mcl.py new file mode 100644 index 0000000..19b09e6 --- /dev/null +++ b/orthofinder/scripts/mcl.py @@ -0,0 +1,105 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2014 David Emms +# +# This program (OrthoFinder) is distributed under the terms of the GNU General Public License v3 +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# When publishing work that uses OrthoFinder please cite: +# Emms, D.M. and Kelly, S. (2015) OrthoFinder: solving fundamental biases in whole genome comparisons dramatically +# improves orthogroup inference accuracy, Genome Biology 16:157 +# +# For any enquiries send an email to David Emms +# david_emms@hotmail.com + +def GetPredictedOGs(clustersFilename): + predictedOGs = [] + nOGsString = "" + qContainsProfiles = False + with open(clustersFilename, 'rb') as clusterFile: + header = True + og = set() + for line in clusterFile: + if header: + if line.count("begin"): + header = False + else: + if line.find(")") != -1: + break + if line[-2] == "$": + line = line[:-3] + if line[0] == " ": + # continuation of group + x = line.split() + y = [x_ for x_ in x if not x_.startswith('Prof')] + og = og.union(y) + else: + # new OG + if len(og) != 0: + predictedOGs.append(og) + nOGsString, line = line.split(" ", 1) + x = line.split() + y = [x_ for x_ in x if not x_.startswith('Prof')] + if len(x) != len(y): + qContainsProfiles = True + og = set(y) + if len(og) > 0: + predictedOGs.append(og) + if not qContainsProfiles: + assert(len(predictedOGs) == int(nOGsString) + 1) + return predictedOGs + +def GetSingleID(speciesStartingIndices, seq, speciesToUse): + iSpecies, iSeq = map(int, seq.split("_")) + offset = speciesStartingIndices[speciesToUse.index(iSpecies)] + return iSeq + offset + +def GetIDPair(speciesStartingIndices, singleID, speciesToUse): + for i, startingIndex in enumerate(speciesStartingIndices): + if startingIndex > singleID: + return "%d_%d" % (speciesToUse[i-1], singleID - speciesStartingIndices[i-1]) + return "%d_%d" % (speciesToUse[-1], singleID - speciesStartingIndices[len(speciesStartingIndices)-1]) + +def ConvertSingleIDsToIDPair(seqsInfo, clustersFilename, newFilename): + with open(clustersFilename, 'rb') as clusterFile, open(newFilename, "wb") as output: + header = True + for line in clusterFile: + appendDollar = False + initialText = "" + idsString = "" + ids = [] + if header: + output.write(line) + if line.count("begin"): + header = False + else: + if line.find(")") != -1: + output.write(line) + break + if line[-2] == "$": + line = line[:-3] + appendDollar = True + if line[0] != " ": + initialText, line = line.split(None, 1) + # continuation of group + ids = line.split() + for id in ids: + idsString += GetIDPair(seqsInfo.seqStartingIndices, int(id), seqsInfo.speciesToUse) + " " + output.write(initialText + " " + idsString) + if appendDollar: + output.write("$\n") + else: + output.write("\n") + diff --git a/newick.py b/orthofinder/scripts/newick.py similarity index 100% rename from newick.py rename to orthofinder/scripts/newick.py diff --git a/process_trees.py b/orthofinder/scripts/orthologues_from_recon_trees.py similarity index 83% rename from process_trees.py rename to orthofinder/scripts/orthologues_from_recon_trees.py index 705f6c6..840eff4 100644 --- a/process_trees.py +++ b/orthofinder/scripts/orthologues_from_recon_trees.py @@ -1,6 +1,31 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2014 David Emms +# +# This program (OrthoFinder) is distributed under the terms of the GNU General Public License v3 +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# When publishing work that uses OrthoFinder please cite: +# Emms, D.M. and Kelly, S. (2015) OrthoFinder: solving fundamental biases in whole genome comparisons dramatically +# improves orthogroup inference accuracy, Genome Biology 16:157 +# +# For any enquiries send an email to David Emms +# david_emms@hotmail.com + import re import os -import sys import csv import glob import itertools @@ -10,7 +35,7 @@ import tree from scipy import sparse from collections import defaultdict -import orthofinder +import util def natural_sort_key(s, _nsre=re.compile('([0-9]+)')): return [int(text) if text.isdigit() else text.lower() for text in re.split(_nsre, s)] @@ -43,8 +68,8 @@ def make_dicts(dlcparResultsDir, outputDir): return Orthologs, picFN def GetSpeciesGenesInfo(ogSet): - speciesLabels = orthofinder.GetSpeciesToUse(ogSet.speciesIDsFN) - seqsInfo = orthofinder.GetSeqsInfo(ogSet.workingDirOF, speciesLabels) + speciesLabels = util.GetSpeciesToUse(ogSet.speciesIDsFN) + seqsInfo = util.GetSeqsInfo(ogSet.workingDirOF, speciesLabels) genenumbers = list(np.diff(seqsInfo.seqStartingIndices)) genenumbers.append(seqsInfo.nSeqs - seqsInfo.seqStartingIndices[-1]) return speciesLabels, genenumbers @@ -54,7 +79,7 @@ def one_to_one_efficient(orthodict, genenumbers, speciesLabels, iSpecies, output try to mostly deal with iSpecies which is the ordinal number not the label it is given """ #Creates all matrices and appends them to matrixlist. - orthofinder.util.PrintTime("Processing orthologues for species %d" % iSpecies) + util.PrintTime("Processing orthologues for species %d" % iSpecies) matrixlist = [] numspecies = len(speciesLabels) speciesLabelsReverse = {label:i for i, label in enumerate(speciesLabels)} @@ -141,17 +166,4 @@ def get_orthologue_lists(ogSet, resultsDir, dlcparResultsDir, workingDir): # -> csv files species_find_all(ogSet.SpeciesDict(), ogSet.SequenceDict(), matrixDir, resultsDir) - -if __name__ == "__main__": - import get_orthologues as go - d = "/home/david/projects/Orthology/OrthoFinder/Development/Orthologues/ExampleDataset_Results/" - orthofinderWorkingDir = d + "WorkingDirectory/" - clustersFilename_pairs = orthofinderWorkingDir + "clusters_OrthoFinder_v0.7.0_I1.5.txt_id_pairs.txt" - resultsDir = d + "Orthologues_Aug18_test2/" - dlcparResultsDir = resultsDir + "/WorkingDirectory/dlcpar/" - workingDir = resultsDir + "/WorkingDirectory/" - - ogSet = go.OrthoGroupsSet(orthofinderWorkingDir, clustersFilename_pairs, idExtractor = orthofinder.FirstWordExtractor) - - get_orthologue_lists(ogSet, resultsDir, dlcparResultsDir, workingDir) diff --git a/root_from_duplications.py b/orthofinder/scripts/root_from_duplications.py similarity index 93% rename from root_from_duplications.py rename to orthofinder/scripts/root_from_duplications.py index 05e91ff..370c8f2 100644 --- a/root_from_duplications.py +++ b/orthofinder/scripts/root_from_duplications.py @@ -1,16 +1,30 @@ +#!/usr/bin/env python # -*- coding: utf-8 -*- -""" -Created on Tue Jul 26 14:34:46 2016 - -@author: david -""" +# +# Copyright 2014 David Emms +# +# This program (OrthoFinder) is distributed under the terms of the GNU General Public License v3 +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# When publishing work that uses OrthoFinder please cite: +# Emms, D.M. and Kelly, S. (2015) OrthoFinder: solving fundamental biases in whole genome comparisons dramatically +# improves orthogroup inference accuracy, Genome Biology 16:157 +# +# For any enquiries send an email to David Emms +# david_emms@hotmail.com -""" -Performance improvements: -- Use an unrooted tree representation so that the trees don't need to be rerooted at all -- convert all the gene names to species names once per tree - -""" import os import glob #import cProfile as profile @@ -21,6 +35,8 @@ import itertools import multiprocessing as mp from collections import Counter +import util + def cmp_equal(exp, act): """exp - expected set of species act - actual set of species @@ -43,8 +59,7 @@ criteria = "crit_all" spTreeFormat = 3 qSerial = False -nProcs = 64 -#nProcs = 16 +nProcs = util.nThreadsDefault class Node(object): """ The class allows the user to get the 'child' nodes in any of the three directions @@ -404,7 +419,6 @@ if __name__ == "__main__": parser.add_argument("-s", "--separator", choices=("dot", "dash", "second_dash", "3rd_dash")) parser.add_argument("-S", "--Species_tree") parser.add_argument("-d", "--directory", action="store_true") - parser.add_argument("-v", "--verbose", action="store_true") args = parser.parse_args() GeneToSpecies = GeneToSpecies_dash @@ -441,10 +455,8 @@ if __name__ == "__main__": print("Best outgroup(s) for species tree:") for r in roots: print(r) - if args.verbose: PlotTree(speciesTree, args.input_tree, clusters) else: root, clusters, _, nSupport = GetRoot(args.Species_tree, args.input_tree, GeneToSpecies, nProcs, treeFmt = 1) for r in root: print(r) speciesTree = tree.Tree(args.Species_tree, format=1) - if args.verbose: PlotTree(speciesTree, args.input_tree, clusters, qSimplePrint=False) diff --git a/tree.py b/orthofinder/scripts/tree.py similarity index 99% rename from tree.py rename to orthofinder/scripts/tree.py index 489b221..1364bc8 100644 --- a/tree.py +++ b/orthofinder/scripts/tree.py @@ -37,7 +37,6 @@ # # #END_LICENSE############################################################# -import os import cPickle import random import copy @@ -159,20 +158,6 @@ class TreeNode(object): #: A list of children nodes children = property(fget=_get_children, fset=_set_children) - def _set_face_areas(self, value): - if isinstance(value, _FaceAreas): - self._faces = value - else: - raise ValueError("[%s] is not a valid FaceAreas instance" %type(value)) - - def _get_face_areas(self): - if not hasattr(self, "_faces"): - self._faces = _FaceAreas() - return self._faces - - faces = property(fget=_get_face_areas, \ - fset=_set_face_areas) - def __init__(self, newick=None, format=0, dist=None, support=None, name=None): self._children = [] diff --git a/orthofinder/scripts/util.py b/orthofinder/scripts/util.py new file mode 100644 index 0000000..cd8a1f3 --- /dev/null +++ b/orthofinder/scripts/util.py @@ -0,0 +1,388 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2014 David Emms +# +# This program (OrthoFinder) is distributed under the terms of the GNU General Public License v3 +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# When publishing work that uses OrthoFinder please cite: +# Emms, D.M. and Kelly, S. (2015) OrthoFinder: solving fundamental biases in whole genome comparisons dramatically +# improves orthogroup inference accuracy, Genome Biology 16:157 +# +# For any enquiries send an email to David Emms +# david_emms@hotmail.com + +nThreadsDefault = 16 +nAlgDefault = 1 + +import os +import sys +import glob +import time +import subprocess +import datetime +import Queue +import multiprocessing as mp +from collections import namedtuple + + +""" +Utilities +------------------------------------------------------------------------------- +""" +SequencesInfo = namedtuple("SequencesInfo", "nSeqs nSpecies speciesToUse seqStartingIndices") +FileInfo = namedtuple("FileInfo", "inputDir outputDir graphFilename") + +picProtocol = 1 +version = "1.0.1" + +def PrintNoNewLine(text): + sys.stdout.write(text) + +def PrintTime(message): + print(str(datetime.datetime.now()).rsplit(".", 1)[0] + " : " + message) + +""" +Command & parallel command management +------------------------------------------------------------------------------- +""" + +def RunCommand(command): + subprocess.call(command) + +def RunOrderedCommandList(commandSet, qHideStdout): + if qHideStdout: + for cmd in commandSet: + subprocess.call(cmd, shell=True, stdout=subprocess.PIPE) + else: + for cmd in commandSet: + subprocess.call(cmd, shell=True) + +def CanRunCommand(command, qAllowStderr = False): + PrintNoNewLine("Test can run \"%s\"" % command) # print without newline + capture = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + stdout = [x for x in capture.stdout] + stderr = [x for x in capture.stderr] + if len(stdout) > 0 and (qAllowStderr or len(stderr) == 0): + print(" - ok") + return True + else: + print(" - failed") + return False + +def Worker_RunCommand(cmd_queue, nProcesses, nToDo): + while True: + try: + i, command = cmd_queue.get(True, 1) + nDone = i - nProcesses + 1 + if nDone >= 0 and divmod(nDone, 10 if nToDo <= 200 else 100 if nToDo <= 2000 else 1000)[1] == 0: + PrintTime("Done %d of %d" % (nDone, nToDo)) + subprocess.call(command) + except Queue.Empty: + return + +def Worker_RunOrderedCommandList(cmd_queue, nProcesses, nToDo, qHideStdout): + """ repeatedly takes items to process from the queue until it is empty at which point it returns. Does not take a new task + if it can't acquire queueLock as this indicates the queue is being rearranged. + + Writes each commands output and stderr to a file + """ + while True: + try: + i, commandSet = cmd_queue.get(True, 1) + nDone = i - nProcesses + 1 + if nDone >= 0 and divmod(nDone, 10 if nToDo <= 200 else 100 if nToDo <= 2000 else 1000)[1] == 0: + PrintTime("Done %d of %d" % (nDone, nToDo)) + RunOrderedCommandList(commandSet, qHideStdout) + except Queue.Empty: + return + +def RunParallelCommands(nProcesses, commands, qHideStdout = False): + """nProcesss - the number of processes to run in parallel + commands - list of commands to be run in parallel + """ + # Setup the workers and run + cmd_queue = mp.Queue() + for i, cmd in enumerate(commands): + cmd_queue.put((i, cmd)) + runningProcesses = [mp.Process(target=Worker_RunCommand, args=(cmd_queue, nProcesses, i+1, qHideStdout)) for i_ in xrange(nProcesses)] + for proc in runningProcesses: + proc.start() + + for proc in runningProcesses: + while proc.is_alive(): + proc.join(10.) + time.sleep(2) + +def RunParallelOrderedCommandLists(nProcesses, commands, qHideStdout = False): + """nProcesss - the number of processes to run in parallel + commands - list of lists of commands where the commands in the inner list are completed in order (the i_th won't run until + the i-1_th has finished). + """ + # Setup the workers and run + cmd_queue = mp.Queue() + for i, cmd in enumerate(commands): + cmd_queue.put((i, cmd)) + runningProcesses = [mp.Process(target=Worker_RunOrderedCommandList, args=(cmd_queue, nProcesses, i+1, qHideStdout)) for i_ in xrange(nProcesses)] + for proc in runningProcesses: + proc.start() + + for proc in runningProcesses: + while proc.is_alive(): + proc.join(10.) + time.sleep(2) + + +def ManageQueue(runningProcesses, cmd_queue): + """Manage a set of runningProcesses working through cmd_queue. + If there is an error the exit all processes as quickly as possible and + exit via Fail() methods. Otherwise return when all work is complete + """ + # set all completed processes to None + qError = False +# dones = [False for _ in runningProcesses] + nProcesses = len(runningProcesses) + while True: + if runningProcesses.count(None) == len(runningProcesses): break + time.sleep(2) +# for proc in runningProcesses: + for i in xrange(nProcesses): + proc = runningProcesses[i] + if proc == None: continue + if not proc.is_alive(): + if proc.exitcode != 0: + qError = True + while True: + try: + cmd_queue.get(True, 1) + except Queue.Empty: + break + runningProcesses[i] = None + if qError: + Fail() +""" +Directory and file management +------------------------------------------------------------------------------- +""" + +def GetDirectoryName(baseDirName, dateString, i): + if i == 0: + return baseDirName + dateString + os.sep + else: + return baseDirName + dateString + ("_%d" % i) + os.sep + +"""Call GetNameForNewWorkingDirectory before a call to CreateNewWorkingDirectory to find out what directory will be created""" +def CreateNewWorkingDirectory(baseDirectoryName): + dateStr = datetime.date.today().strftime("%b%d") + iAppend = 0 + newDirectoryName = GetDirectoryName(baseDirectoryName, dateStr, iAppend) + while os.path.exists(newDirectoryName): + iAppend += 1 + newDirectoryName = GetDirectoryName(baseDirectoryName, dateStr, iAppend) + os.mkdir(newDirectoryName) + return newDirectoryName + +def GetUnusedFilename(baseFilename, ext): + iAppend = 0 + newFilename = baseFilename + ext + while os.path.exists(newFilename): + iAppend += 1 + newFilename = baseFilename + ("_%d" % iAppend) + ext + return newFilename, iAppend + +def SortArrayPairByFirst(useForSortAr, keepAlignedAr, qLargestFirst=False): + sortedTuples = sorted(zip(useForSortAr, keepAlignedAr), reverse=qLargestFirst) + useForSortAr = [i for i, j in sortedTuples] + keepAlignedAr = [j for i, j in sortedTuples] + return useForSortAr, keepAlignedAr + +def SortFastaFilenames(fastaFilenames): + speciesIndices = [] + for f in fastaFilenames: + start = f.rfind("Species") + speciesIndices.append(int(f[start+7:-3])) + indices, sortedFasta = SortArrayPairByFirst(speciesIndices, fastaFilenames) + return sortedFasta + + +# Get Info from seqs IDs file? +def GetSeqsInfo(inputDirectory, speciesToUse): + seqStartingIndices = [0] + nSeqs = 0 + for i, iFasta in enumerate(speciesToUse): + fastaFilename = inputDirectory + "Species%d.fa" % iFasta + with open(fastaFilename) as infile: + for line in infile: + if len(line) > 1 and line[0] == ">": + nSeqs+=1 + seqStartingIndices.append(nSeqs) + seqStartingIndices = seqStartingIndices[:-1] + nSpecies = len(speciesToUse) + return SequencesInfo(nSeqs=nSeqs, nSpecies=nSpecies, speciesToUse=speciesToUse, seqStartingIndices=seqStartingIndices) + +def GetSpeciesToUse(speciesIDsFN): + speciesToUse = [] + with open(speciesIDsFN, 'rb') as speciesF: + for line in speciesF: + if len(line) == 0 or line[0] == "#": continue + speciesToUse.append(int(line.split(":")[0])) + return speciesToUse + +def Fail(): + print("ERROR: An error occurred, please review previous error messages for more information.") + sys.exit() + +""" +IDExtractor +------------------------------------------------------------------------------- +""" + +def GetIDPairFromString(line): + return map(int, line.split("_")) + +class IDExtractor(object): + """IDExtractor deals with the fact that for different datasets a user will + want to extract a unique sequence ID from the fasta file accessions uin different + ways.""" + def GetIDToNameDict(self): + raise NotImplementedError("Should not be implemented") + def GetNameToIDDict(self): + raise NotImplementedError("Should not be implemented") + +class FullAccession(IDExtractor): + def __init__(self, idsFilename): + # only want the first part and nothing else (easy!) + self.idToNameDict = dict() + self.nameToIDDict = dict() + with open(idsFilename, 'rb') as idsFile: + for line in idsFile: + if line.startswith("#"): continue + id, accession = line.rstrip().split(": ", 1) + # Replace problematic characters + accession = accession.replace(":", "_").replace(",", "_").replace("(", "_").replace(")", "_") + if id in self.idToNameDict: + raise RuntimeError("ERROR: A duplicate id was found in the fasta files: % s" % id) + self.idToNameDict[id] = accession + self.nameToIDDict[accession] = id + + def GetIDToNameDict(self): + return self.idToNameDict + + def GetNameToIDDict(self): + return self.nameToIDDict + +class FirstWordExtractor(IDExtractor): + def __init__(self, idsFilename): + # only want the first part and nothing else (easy!) + self.idToNameDict = dict() + self.nameToIDDict = dict() + with open(idsFilename, 'rb') as idsFile: + for line in idsFile: + id, rest = line.split(": ", 1) + accession = rest.split(None, 1)[0] + # Replace problematic characters + accession = accession.replace(":", "_").replace(",", "_").replace("(", "_").replace(")", "_") + if accession in self.nameToIDDict: + raise RuntimeError("A duplicate accession was found using just first part: % s" % accession) + if id in self.idToNameDict: + raise RuntimeError("ERROR: A duplicate id was found in the fasta files: % s" % id) + self.idToNameDict[id] = accession + self.nameToIDDict[accession] = id + + def GetIDToNameDict(self): + return self.idToNameDict + + def GetNameToIDDict(self): + return self.nameToIDDict + +def IsWorkingDirectory(orthofinderWorkingDir): + ok = True + ok = ok and len(glob.glob(orthofinderWorkingDir + "clusters_OrthoFinder_*.txt_id_pairs.txt")) > 0 + ok = ok and len(glob.glob(orthofinderWorkingDir + "Species*.fa")) > 0 + return ok + +""" +Find results of previous run +------------------------------------------------------------------------------- +""" + +def GetOGsFile(userArg): + """returns the WorkingDirectory, ResultsDirectory and clusters_id_pairs filename""" + qSpecifiedResultsFile = False + if userArg == None: + print("ERROR: orthofinder_results_directory has not been specified") + Fail() + if os.path.isfile(userArg): + fn = os.path.split(userArg)[1] + if ("clusters_OrthoFinder_" not in fn) or ("txt_id_pairs.txt" not in fn): + print("ERROR:\n %s\nis neither a directory or a clusters_OrthoFinder_*.txt_id_pairs.txt file." % userArg) + Fail() + qSpecifiedResultsFile = True + # user has specified specific results file + elif userArg[-1] != os.path.sep: + userArg += os.path.sep + + # find required files + if qSpecifiedResultsFile: + orthofinderWorkingDir = os.path.split(userArg)[0] + os.sep + if not IsWorkingDirectory(orthofinderWorkingDir): + print("ERROR: cannot find files from OrthoFinder run in directory:\n %s" % orthofinderWorkingDir) + Fail() + else: + orthofinderWorkingDir = os.path.split(userArg)[0] if qSpecifiedResultsFile else userArg + if not IsWorkingDirectory(orthofinderWorkingDir): + orthofinderWorkingDir = userArg + "WorkingDirectory" + os.sep + if not IsWorkingDirectory(orthofinderWorkingDir): + print("ERROR: cannot find files from OrthoFinder run in directory:\n %s\nor\n %s\n" % (userArg, orthofinderWorkingDir)) + Fail() + + if qSpecifiedResultsFile: + print("Generating trees for orthogroups in file:\n %s" % userArg) + return orthofinderWorkingDir, orthofinderWorkingDir, userArg + else: + # identify orthogroups file + clustersFiles = glob.glob(orthofinderWorkingDir + "clusters_OrthoFinder_*.txt_id_pairs.txt") + orthogroupFiles = glob.glob(orthofinderWorkingDir + "OrthologousGroups*.txt") + if orthofinderWorkingDir != userArg: + orthogroupFiles += glob.glob(userArg + "OrthologousGroups*.txt") + # User may have specified a WorkingDirectory and results could be in directory above + if len(orthogroupFiles) < len(clustersFiles): + orthogroupFiles += glob.glob(userArg + ".." + os.sep + "OrthologousGroups*.txt") + clustersFiles = sorted(clustersFiles) + orthogroupFiles = sorted(orthogroupFiles) + if len(clustersFiles) > 1 or len(orthogroupFiles) > 1: + print("ERROR: Results from multiple OrthoFinder runs found\n") + print("Tab-delimiter OrthologousGroups*.txt files:") + for fn in orthogroupFiles: + print(" " + fn) + print("With corresponding cluster files:") + for fn in clustersFiles: + print(" " + fn) + print("\nPlease run with only one set of results in directories or specifiy the specific clusters_OrthoFinder_*.txt_id_pairs.txt file on the command line") + Fail() + + if len(clustersFiles) != 1 or len(orthogroupFiles) != 1: + print("ERROR: Results not found in <orthofinder_results_directory> or <orthofinder_results_directory>/WorkingDirectory") + print("\nCould not find:\n OrthologousGroups*.txt\nor\n clusters_OrthoFinder_*.txt_id_pairs.txt") + Fail() + + print("Generating trees for orthogroups in file:\n %s" % orthogroupFiles[0]) + print("and corresponding clusters file:\n %s" % clustersFiles[0]) + return orthofinderWorkingDir, userArg, clustersFiles[0] + +def PrintCitation(): + print("""\nWhen publishing work that uses OrthoFinder please cite: + D.M. Emms & S. Kelly (2015), OrthoFinder: solving fundamental biases in whole genome comparisons + dramatically improves orthogroup inference accuracy, Genome Biology 16:157.\n""") diff --git a/trees_for_orthogroups.py b/orthofinder/trees_from_MSA.py similarity index 62% rename from trees_for_orthogroups.py rename to orthofinder/trees_from_MSA.py index 7dbd61d..ba8df38 100755 --- a/trees_for_orthogroups.py +++ b/orthofinder/trees_from_MSA.py @@ -32,26 +32,11 @@ Created on Thu Sep 25 13:15:22 2014 """ import os import sys -import multiprocessing as mp -import time -import subprocess -import Queue import glob -import orthofinder +import scripts.mcl as MCL +import scripts.util as util -version = "1.0.0" - -def RunCommandSet(commandSet, qHideStdout): -# orthofinder.util.PrintTime("Runing command: %s" % commandSet[-1]) - if qHideStdout: - for cmd in commandSet: - subprocess.call(cmd, shell=True, stdout=subprocess.PIPE) - else: - for cmd in commandSet: - subprocess.call(cmd, shell=True) -# orthofinder.util.PrintTime("Finshed command: %s" % commandSet[-1]) - class FastaWriter(object): def __init__(self, fastaFileDir): self.SeqLists = dict() @@ -91,52 +76,12 @@ class FastaWriter(object): def SortSeqs(self, seqs): return sorted(seqs, key=lambda x: map(int, x.split("_"))) - -def Worker_RunCommand(cmd_queue, nProcesses, nToDo, qHideStdout): - """ repeatedly takes items to process from the queue until it is empty at which point it returns. Does not take a new task - if it can't acquire queueLock as this indicates the queue is being rearranged. - - Writes each commands output and stderr to a file - """ - while True: - try: - i, commandSet = cmd_queue.get(True, 1) - nDone = i - nProcesses + 1 - if nDone >= 0 and divmod(nDone, 10 if nToDo <= 200 else 100 if nToDo <= 2000 else 1000)[1] == 0: - orthofinder.util.PrintTime("Done %d of %d" % (nDone, nToDo)) - RunCommandSet(commandSet, qHideStdout) - except Queue.Empty: - return - -def RunParallelCommandSets(nProcesses, commands, qHideStdout = False): - """nProcesss - the number of processes to run in parallel - commands - list of lists of commands where the commands in the inner list are completed in order (the i_th won't run until - the i-1_th has finished). - """ - # Setup the workers and run - cmd_queue = mp.Queue() - for i, cmd in enumerate(commands): - cmd_queue.put((i, cmd)) - runningProcesses = [mp.Process(target=Worker_RunCommand, args=(cmd_queue, nProcesses, i+1, qHideStdout)) for i_ in xrange(nProcesses)] - for proc in runningProcesses: - proc.start() - - for proc in runningProcesses: - while proc.is_alive(): - proc.join(10.) - time.sleep(2) def WriteTestFile(workingDir): testFN = workingDir + "SimpleTest.fa" with open(testFN, 'wb') as outfile: outfile.write(">a\nA\n>b\nA") return testFN - -def IsWorkingDirectory(orthofinderWorkingDir): - ok = True - ok = ok and len(glob.glob(orthofinderWorkingDir + "clusters_OrthoFinder_*.txt_id_pairs.txt")) > 0 - ok = ok and len(glob.glob(orthofinderWorkingDir + "Species*.fa")) > 0 - return ok class TreesForOrthogroups(object): def __init__(self, baseOutputDir, orthofinderWorkingDir): @@ -197,15 +142,15 @@ class TreesForOrthogroups(object): def DoTrees(self, ogs, idDict, nProcesses, nSwitchToMafft=500): testFN = WriteTestFile(self.orthofinderWorkingDir) - if not orthofinder.CanRunCommand("mafft %s" % testFN, qAllowStderr=True): + if not util.CanRunCommand("mafft %s" % testFN, qAllowStderr=True): print("ERROR: Cannot run mafft") print("Please check MAFFT is installed and that the executables are in the system path\n") return False - if not orthofinder.CanRunCommand("mafft %s" % testFN, qAllowStderr=True): + if not util.CanRunCommand("mafft %s" % testFN, qAllowStderr=True): print("ERROR: Cannot run mafft") print("Please check mafft is installed and that the executables are in the system path\n") return False - if not orthofinder.CanRunCommand("FastTree %s" % testFN, qAllowStderr=True): + if not util.CanRunCommand("FastTree %s" % testFN, qAllowStderr=True): print("ERROR: Cannot run FastTree") print("Please check FastTree is installed and that the executables are in the system path\n") return False @@ -241,9 +186,9 @@ class TreesForOrthogroups(object): print(cmd) print("") - RunParallelCommandSets(nProcesses, commandsSet) + util.RunParallelOrderedCommandLists(nProcesses, commandsSet) - orthofinder.PrintCitation() + util.PrintCitation() print("\nFasta files for orthogroups have been written to:\n %s\n" % (self.baseOutputDir + "Sequences/")) print("Multiple sequences alignments have been written to:\n %s\n" % (self.baseOutputDir + "Alignments/")) print("Gene trees have been written to:\n %s\n" % (self.baseOutputDir + "Trees/")) @@ -262,31 +207,31 @@ def PrintHelp(): print("""-t max_number_of_threads, --threads max_number_of_threads The maximum number of processes to be run simultaneously. The deafult is %d but this - should be increased by the user to the maximum number of cores available.\n""" % orthofinder.nThreadsDefault) + should be increased by the user to the maximum number of cores available.\n""" % util.nThreadsDefault) print("""-h, --help Print this help text""") - orthofinder.PrintCitation() + util.PrintCitation() def GetIDsDict(orthofinderWorkingDir): # sequence IDs idsFilename = orthofinderWorkingDir + "SequenceIDs.txt" try: - idExtract = orthofinder.FirstWordExtractor(idsFilename) + idExtract = util.FirstWordExtractor(idsFilename) idDict = idExtract.GetIDToNameDict() except RuntimeError as error: print(error.message) if error.message.startswith("ERROR"): print("ERROR: %s contains a duplicate ID. If %s was prepared manually then please check the IDs are correct. " % (idsFilename, idsFilename)) - orthofinder.Fail() + util.Fail() else: print("Tried to use only the first part of the accession in order to list the sequences in each orthologous group more concisely but these were not unique. Will use the full accession line instead.") try: - idExtract = orthofinder.FullAccession(idsFilename) + idExtract = util.FullAccession(idsFilename) idDict = idExtract.GetIDToNameDict() except: print("ERROR: %s contains a duplicate ID. If %s was prepared manually then please check the IDs are correct. " % (idsFilename, idsFilename)) - orthofinder.Fail() + util.Fail() # species names speciesDict = dict() @@ -299,72 +244,8 @@ def GetIDsDict(orthofinderWorkingDir): idDict = {seqID:speciesDict[seqID.split("_")[0]] + "_" + name for seqID, name in idDict.items()} return idDict -def GetOGsFile(userArg): - """returns the WorkingDirectory, ResultsDirectory and clusters_id_pairs filename""" - qSpecifiedResultsFile = False - if userArg == None: - print("ERROR: orthofinder_results_directory has not been specified") - orthofinder.Fail() - if os.path.isfile(userArg): - fn = os.path.split(userArg)[1] - if ("clusters_OrthoFinder_" not in fn) or ("txt_id_pairs.txt" not in fn): - print("ERROR:\n %s\nis neither a directory or a clusters_OrthoFinder_*.txt_id_pairs.txt file." % userArg) - orthofinder.Fail() - qSpecifiedResultsFile = True - # user has specified specific results file - elif userArg[-1] != os.path.sep: - userArg += os.path.sep - - # find required files - if qSpecifiedResultsFile: - orthofinderWorkingDir = os.path.split(userArg)[0] + os.sep - if not IsWorkingDirectory(orthofinderWorkingDir): - print("ERROR: cannot find files from OrthoFinder run in directory:\n %s" % orthofinderWorkingDir) - orthofinder.Fail() - else: - orthofinderWorkingDir = os.path.split(userArg)[0] if qSpecifiedResultsFile else userArg - if not IsWorkingDirectory(orthofinderWorkingDir): - orthofinderWorkingDir = userArg + "WorkingDirectory" + os.sep - if not IsWorkingDirectory(orthofinderWorkingDir): - print("ERROR: cannot find files from OrthoFinder run in directory:\n %s\nor\n %s\n" % (userArg, orthofinderWorkingDir)) - orthofinder.Fail() - - if qSpecifiedResultsFile: - print("Generating trees for orthogroups in file:\n %s" % userArg) - return orthofinderWorkingDir, orthofinderWorkingDir, userArg - else: - # identify orthogroups file - clustersFiles = glob.glob(orthofinderWorkingDir + "clusters_OrthoFinder_*.txt_id_pairs.txt") - orthogroupFiles = glob.glob(orthofinderWorkingDir + "OrthologousGroups*.txt") - if orthofinderWorkingDir != userArg: - orthogroupFiles += glob.glob(userArg + "OrthologousGroups*.txt") - # User may have specified a WorkingDirectory and results could be in directory above - if len(orthogroupFiles) < len(clustersFiles): - orthogroupFiles += glob.glob(userArg + ".." + os.sep + "OrthologousGroups*.txt") - clustersFiles = sorted(clustersFiles) - orthogroupFiles = sorted(orthogroupFiles) - if len(clustersFiles) > 1 or len(orthogroupFiles) > 1: - print("ERROR: Results from multiple OrthoFinder runs found\n") - print("Tab-delimiter OrthologousGroups*.txt files:") - for fn in orthogroupFiles: - print(" " + fn) - print("With corresponding cluster files:") - for fn in clustersFiles: - print(" " + fn) - print("\nPlease run with only one set of results in directories or specifiy the specific clusters_OrthoFinder_*.txt_id_pairs.txt file on the command line") - orthofinder.Fail() - - if len(clustersFiles) != 1 or len(orthogroupFiles) != 1: - print("ERROR: Results not found in <orthofinder_results_directory> or <orthofinder_results_directory>/WorkingDirectory") - print("\nCould not find:\n OrthologousGroups*.txt\nor\n clusters_OrthoFinder_*.txt_id_pairs.txt") - orthofinder.Fail() - - print("Generating trees for orthogroups in file:\n %s" % orthogroupFiles[0]) - print("and corresponding clusters file:\n %s" % clustersFiles[0]) - return orthofinderWorkingDir, userArg, clustersFiles[0] - if __name__ == "__main__": - print("\nOrthoFinder Alignments and Trees version %s Copyright (C) 2015 David Emms\n" % version) + print("\nOrthoFinder Alignments and Trees version %s Copyright (C) 2015 David Emms\n" % util.version) print(""" This program comes with ABSOLUTELY NO WARRANTY. This is free software, and you are welcome to redistribute it under certain conditions. For details please see the License.md that came with this software.\n""") @@ -372,12 +253,6 @@ if __name__ == "__main__": PrintHelp() sys.exit() - v = map(int, orthofinder.version.split(".")) - v = 100 * v[0] + 10*v[1] + v[2] - if v < 28: - print("ERROR: OrthoFinder program has not been updated, please update 'orthofinder.py' to the version %s\n" % version) - orthofinder.Fail() - # Get arguments userDir = None nProcesses = None @@ -388,26 +263,26 @@ if __name__ == "__main__": if arg == "-t" or arg == "--threads": if len(args) == 0: print("Missing option for command line argument -t") - orthofinder.Fail() + util.Fail() arg = args.pop(0) try: nProcesses = int(arg) except: print("Incorrect argument for number of threads: %s" % arg) - orthofinder.Fail() + util.Fail() else: userDir = arg # Check arguments - orthofinderWorkingDir, orthofinderResultsDir, clustersFilename_pairs = GetOGsFile(userDir) + orthofinderWorkingDir, orthofinderResultsDir, clustersFilename_pairs = util.GetOGsFile(userDir) if nProcesses == None: print("""Number of parallel processes has not been specified, will use the default value. Number of parallel processes can be specified using the -t option\n""") - nProcesses = orthofinder.nThreadsDefault + nProcesses = util.nThreadsDefault print("Using %d threads for alignments and trees\n" % nProcesses) - ogs = orthofinder.MCL.GetPredictedOGs(clustersFilename_pairs) + ogs = MCL.GetPredictedOGs(clustersFilename_pairs) idDict = GetIDsDict(orthofinderWorkingDir) treeGen = TreesForOrthogroups(orthofinderResultsDir, orthofinderWorkingDir) -- GitLab