From a30b08a723ed96c12fc9eb196128f1053fc48b11 Mon Sep 17 00:00:00 2001 From: Olivier Filangi Date: Fri, 1 Apr 2022 14:14:16 +0200 Subject: [PATCH 1/5] Develop backup (#34) * udpate Readme and plugin gpg https://github.com/jodersky/sbt-gpg * downgrade gpg to 0.2.1. 0.2.2 not available ! * remove tagret directory fro repo * remove tagret directory fro repo * update Readme * fix jar publication * covered code for commands * update readme with badge * remove script * change galaxy recipe directory * remove temp file * minor fix for resume * add planemo test * add planemo test * add planemo test * add planemo test * add planemo test * planemo test only master/main/develop * lint planemo + conda test version * lint planemo + conda test version * lint planemo + conda test version * update fix meta.yml * remove conda recipe * clean repo * add sbt-release (#16) * Sbt release (#18) * add sbt-release * valid pom generation * Dockerfile (#20) * target directory for assembly * add dockerized task * add dockerfile * update gitignore * use directory docker * update docker management * fix outputdir assembly * update docker config * remove debug config * Dockerfile (#22) * target directory for assembly * add dockerized task * add dockerfile * update gitignore * use directory docker * update docker management * fix outputdir assembly * update docker config * remove debug config * remove coverage enable * fix dockerfile implementation to run process inside galaxy tools * Uplc tqd to isocor 24 (#26) * update Readme * add MassLynx parser / tests * replace strip with trim * increase coverage. manage bad line def inside array compound * fix coverage * fix coverage * add command/output object and tests * save notes * add galaxy config tools - gcms2isocor openlabcds2csv masslynx2isocor (#27) * add galaxy config tools - gcms2isocor openlabcds2csv masslynx2isocor * fix doc/name bin * downgrade profile galaxy version * fx test masslynx --- .../metabolomics/p2m2/command/MassLynx2IsocorCommandTest.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/scala/fr/inrae/metabolomics/p2m2/command/MassLynx2IsocorCommandTest.scala b/src/test/scala/fr/inrae/metabolomics/p2m2/command/MassLynx2IsocorCommandTest.scala index e1cfcb1..af579af 100644 --- a/src/test/scala/fr/inrae/metabolomics/p2m2/command/MassLynx2IsocorCommandTest.scala +++ b/src/test/scala/fr/inrae/metabolomics/p2m2/command/MassLynx2IsocorCommandTest.scala @@ -13,6 +13,7 @@ object MassLynx2IsocorCommandTest extends TestSuite { Try(MassLynx2IsocorCommand.main(Array(""))) match { case Success(_) => assert(true) case Failure(f) => f.printStackTrace();assert(false) + } } From 00e3683530d80dd089cfccd35fea82373aeb65a3 Mon Sep 17 00:00:00 2001 From: Olivier Filangi Date: Fri, 1 Apr 2022 14:28:33 +0200 Subject: [PATCH 2/5] inverse sample/metabolite in the input isocor file. (#35) --- .../p2m2/converter/MassLynxOutput2IsocorInput.scala | 4 ++-- .../p2m2/converter/MassLynxOutput2IsocorInputTest.scala | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/scala/fr/inrae/metabolomics/p2m2/converter/MassLynxOutput2IsocorInput.scala b/src/main/scala/fr/inrae/metabolomics/p2m2/converter/MassLynxOutput2IsocorInput.scala index 9916cab..67a0e3a 100644 --- a/src/main/scala/fr/inrae/metabolomics/p2m2/converter/MassLynxOutput2IsocorInput.scala +++ b/src/main/scala/fr/inrae/metabolomics/p2m2/converter/MassLynxOutput2IsocorInput.scala @@ -34,16 +34,16 @@ case class MassLynxOutput2IsocorInput( (field: CompoundField) => { (compoundType match { case "M+H" => Some(List( - sample, field.Name, + sample, correspondenceMetabolitesDerivative.getOrElse(field.Name,defaultDerivative), 0, field.Area, resolution.toString ).mkString("\t")) case v if v.startsWith("M+") => Some(List( - sample, field.Name, + sample, correspondenceMetabolitesDerivative.getOrElse(field.Name,defaultDerivative), v.replace("M+", "").toInt, field.Area, diff --git a/src/test/scala/fr/inrae/metabolomics/p2m2/converter/MassLynxOutput2IsocorInputTest.scala b/src/test/scala/fr/inrae/metabolomics/p2m2/converter/MassLynxOutput2IsocorInputTest.scala index a79ce11..caed051 100644 --- a/src/test/scala/fr/inrae/metabolomics/p2m2/converter/MassLynxOutput2IsocorInputTest.scala +++ b/src/test/scala/fr/inrae/metabolomics/p2m2/converter/MassLynxOutput2IsocorInputTest.scala @@ -74,7 +74,7 @@ object MassLynxOutput2IsocorInputTest extends TestSuite { header=OutputMassLynx.Header(), results=MassLynxParser.parseResults(toParse.split("\n").toList) ) - assert( MassLynxOutput2IsocorInput(Map()).transform(entry) == List("M+H\tGlyN15_A_3\tACCQTAG\t0\t96688\t2000")) + assert( MassLynxOutput2IsocorInput(Map()).transform(entry) == List("GlyN15_A_3\tM+H\tACCQTAG\t0\t96688\t2000")) } test("basic run with a sample M+H, resolution=1000") { @@ -90,7 +90,7 @@ object MassLynxOutput2IsocorInputTest extends TestSuite { results=MassLynxParser.parseResults(toParse.split("\n").toList) ) assert( MassLynxOutput2IsocorInput(Map(),resolution=1000).transform(entry) == - List("M+H\tGlyN15_A_3\tACCQTAG\t0\t96688\t1000")) + List("GlyN15_A_3\tM+H\tACCQTAG\t0\t96688\t1000")) } test("basic run with a sample M+H, resolution=1000, defaultDerivative='TOTO'") { val toParse = @@ -106,7 +106,7 @@ object MassLynxOutput2IsocorInputTest extends TestSuite { ) assert( MassLynxOutput2IsocorInput(Map(),resolution=1000, defaultDerivative="TOTO").transform(entry) == - List("M+H\tGlyN15_A_3\tTOTO\t0\t96688\t1000")) + List("GlyN15_A_3\tM+H\tTOTO\t0\t96688\t1000")) } test("basic run with a sample M+H, resolution=1000, map( GlyN15_A_3 => 'TOTO')") { @@ -123,7 +123,7 @@ object MassLynxOutput2IsocorInputTest extends TestSuite { ) assert( MassLynxOutput2IsocorInput(Map("GlyN15_A_3" -> "TOTO"),resolution=1000).transform(entry) == - List("M+H\tGlyN15_A_3\tTOTO\t0\t96688\t1000")) + List("GlyN15_A_3\tM+H\tTOTO\t0\t96688\t1000")) } } } From 2704540d720be9484d9783b659b1088327ee55eb Mon Sep 17 00:00:00 2001 From: Olivier Filangi Date: Thu, 7 Apr 2022 11:52:28 +0200 Subject: [PATCH 3/5] fix (#36) * fix * save * fix coverage * fix typo. increase test precision * increase limit coverage test to 97% ! --- build.sbt | 2 +- .../p2m2/command/MassLynx2IsocorCommand.scala | 10 +--- .../MassLynxOutput2IsocorInput.scala | 56 ++++++++++--------- .../metabolomics/p2m2/parser/GCMSParser.scala | 12 ++-- .../p2m2/parser/MassLynxParser.scala | 10 ++-- .../metabolomics/p2m2/parser/Parser.scala | 5 -- .../tools/format/output/OutputMassLynx.scala | 10 ++-- .../command/MassLynx2IsocorCommandTest.scala | 21 +++---- .../command/OpenLabCDS2CsvCommandTest.scala | 4 +- .../MassLynxOutput2IsocorInputTest.scala | 44 +++++++++++++-- .../p2m2/parser/GCMSParserTest.scala | 56 +++++++++++++++++++ .../p2m2/parser/MassLynxParserTest.scala | 8 +-- 12 files changed, 159 insertions(+), 79 deletions(-) delete mode 100644 src/main/scala/fr/inrae/metabolomics/p2m2/parser/Parser.scala diff --git a/build.sbt b/build.sbt index 16bc66a..21c2a3f 100644 --- a/build.sbt +++ b/build.sbt @@ -26,7 +26,7 @@ libraryDependencies += "com.github.scopt" %% "scopt" % "4.0.1" // Coverage -coverageMinimumStmtTotal := 70 +coverageMinimumStmtTotal := 97 coverageFailOnMinimum := false coverageHighlighting := true diff --git a/src/main/scala/fr/inrae/metabolomics/p2m2/command/MassLynx2IsocorCommand.scala b/src/main/scala/fr/inrae/metabolomics/p2m2/command/MassLynx2IsocorCommand.scala index 9c5275c..892dd8d 100644 --- a/src/main/scala/fr/inrae/metabolomics/p2m2/command/MassLynx2IsocorCommand.scala +++ b/src/main/scala/fr/inrae/metabolomics/p2m2/command/MassLynx2IsocorCommand.scala @@ -14,7 +14,6 @@ case object MassLynx2IsocorCommand extends App { resolution: Int = 2000, derivatives : Option[File] = None, separatorDerivativesFile : String = "[ \t;,]", - defaultDerivative: String = "ACCQTAG", listSampleToRemove : Seq [String] = Seq("NH4"), verbose: Boolean = false, debug: Boolean = false, @@ -49,11 +48,6 @@ case object MassLynx2IsocorCommand extends App { else failure("Value must be >0")) .valueName("") .text("resolution for isocor tool."), - opt[String]("default_derivative") - .optional() - .action({ case (r, c) => c.copy(defaultDerivative = r) }) - .valueName("") - .text("default_derivative value"), opt[String]("list_sample_to_remove") .optional() .action({ case (r, c) => c.copy(listSampleToRemove = r.split(",")) }) @@ -89,7 +83,6 @@ case object MassLynx2IsocorCommand extends App { config.derivatives, config.separatorDerivativesFile, config.resolution, - config.defaultDerivative, config.listSampleToRemove ) case _ => @@ -102,7 +95,6 @@ case object MassLynx2IsocorCommand extends App { derivatives : Option[File], separatorDerivativesFile : String, resolution: Int, - defaultDerivative: String, listSampleToRemove : Seq[String] ): Unit = { @@ -125,7 +117,7 @@ case object MassLynx2IsocorCommand extends App { bw.write("sample\tmetabolite\tderivative\tisotopologue\tarea\tresolution\n") - val pro = MassLynxOutput2IsocorInput(correspondence,resolution, listSampleToRemove,defaultDerivative ) + val pro = MassLynxOutput2IsocorInput(correspondence,resolution, listSampleToRemove ) pro .build(files.map(_.getPath)) diff --git a/src/main/scala/fr/inrae/metabolomics/p2m2/converter/MassLynxOutput2IsocorInput.scala b/src/main/scala/fr/inrae/metabolomics/p2m2/converter/MassLynxOutput2IsocorInput.scala index 67a0e3a..dfb1c69 100644 --- a/src/main/scala/fr/inrae/metabolomics/p2m2/converter/MassLynxOutput2IsocorInput.scala +++ b/src/main/scala/fr/inrae/metabolomics/p2m2/converter/MassLynxOutput2IsocorInput.scala @@ -2,13 +2,12 @@ package fr.inrae.metabolomics.p2m2.converter import fr.inrae.metabolomics.p2m2.parser.MassLynxParser import fr.inrae.metabolomics.p2m2.tools.format.output.OutputMassLynx -import fr.inrae.metabolomics.p2m2.tools.format.output.OutputMassLynx.CompoundField +import fr.inrae.metabolomics.p2m2.tools.format.output.OutputMassLynx.SampleField case class MassLynxOutput2IsocorInput( correspondenceMetabolitesDerivative : Map[String,String], resolution : Int = 2000 , - listSampleToRemove : Seq[String] = Seq("NH4"), - defaultDerivative : String = "ACCQTAG" + listSampleToRemove : Seq[String] = Seq("NH4") ) { def build(inputFiles : Seq[String]) : Seq[OutputMassLynx] = { @@ -25,32 +24,37 @@ case class MassLynxOutput2IsocorInput( massLynx .results .flatMap { - case (sample_and_compType : String , listMetabolites : List[CompoundField]) => { - val sample = sample_and_compType.split(",").head.trim + case (sample_and_compType : String , listMetabolites : List[SampleField]) => { + val metabolite = sample_and_compType.split(",").head.trim val compoundType = sample_and_compType.split(",").last.trim - if (! listSampleToRemove.contains(sample)) + if (! listSampleToRemove.contains(metabolite)) listMetabolites .flatMap( - (field: CompoundField) => { - (compoundType match { - case "M+H" => Some(List( - field.Name, - sample, - correspondenceMetabolitesDerivative.getOrElse(field.Name,defaultDerivative), - 0, - field.Area, - resolution.toString - ).mkString("\t")) - case v if v.startsWith("M+") => Some(List( - field.Name, - sample, - correspondenceMetabolitesDerivative.getOrElse(field.Name,defaultDerivative), - v.replace("M+", "").toInt, - field.Area, - resolution.toString - ).mkString("\t")) - case _ => None - }) + (field: SampleField) => { + correspondenceMetabolitesDerivative.get(metabolite) match { + case Some(fieldName) => { + (compoundType match { + case "M+H" => Some(List( + field.Name, + metabolite, + fieldName, + 0, + field.Area, + resolution.toString + ).mkString("\t")) + case v if v.startsWith("M+") => Some(List( + field.Name, + metabolite, + fieldName, + v.replace("M+", "").toInt, + field.Area, + resolution.toString + ).mkString("\t")) + case _ => None + }) + } + case None => None + } }) else List() } diff --git a/src/main/scala/fr/inrae/metabolomics/p2m2/parser/GCMSParser.scala b/src/main/scala/fr/inrae/metabolomics/p2m2/parser/GCMSParser.scala index af7bf31..fd79e26 100644 --- a/src/main/scala/fr/inrae/metabolomics/p2m2/parser/GCMSParser.scala +++ b/src/main/scala/fr/inrae/metabolomics/p2m2/parser/GCMSParser.scala @@ -45,18 +45,18 @@ object GCMSParser { .slice( lMin_lMax._1+1, lMin_lMax._2 ) .flatMap { case s : String if s.startsWith("""Data File Name""") => - """Data\sFile\sName\s+(.*)""".r.findFirstMatchIn(s) match { - case Some(v) => Some(HeaderField.Data_File_Name -> v.group(1)) + """Data\sFile\sName(\s+.*)""".r.findFirstMatchIn(s) match { + case Some(v) => Some(HeaderField.Data_File_Name -> v.group(1).trim) case None => throw new Exception (s"Can not capture [$category]/Data File Name value") } case s : String if s.startsWith("""Output Date""") => - """Output\sDate\s+(.*)""".r.findFirstMatchIn(s) match { - case Some(v) => Some(HeaderField.Output_Date -> v.group(1)) + """Output\sDate(\s+.*)""".r.findFirstMatchIn(s) match { + case Some(v) => Some(HeaderField.Output_Date -> v.group(1).trim) case None => throw new Exception(s"Can not capture [$category]/Output Date value") } case s : String if s.startsWith("""Output Time""") => - """Output\sTime\s+(.*)""".r.findFirstMatchIn(s) match { - case Some(v) => Some(HeaderField.Output_Time -> v.group(1)) + """Output\sTime(\s+.*)""".r.findFirstMatchIn(s) match { + case Some(v) => Some(HeaderField.Output_Time -> v.group(1).trim) case None => throw new Exception(s"Can not capture [$category]/Output Time value") } case _ => None diff --git a/src/main/scala/fr/inrae/metabolomics/p2m2/parser/MassLynxParser.scala b/src/main/scala/fr/inrae/metabolomics/p2m2/parser/MassLynxParser.scala index 8a88952..5cdd540 100644 --- a/src/main/scala/fr/inrae/metabolomics/p2m2/parser/MassLynxParser.scala +++ b/src/main/scala/fr/inrae/metabolomics/p2m2/parser/MassLynxParser.scala @@ -1,7 +1,7 @@ package fr.inrae.metabolomics.p2m2.parser import fr.inrae.metabolomics.p2m2.tools.format.output.OutputMassLynx -import fr.inrae.metabolomics.p2m2.tools.format.output.OutputMassLynx.{CompoundField, Header, buildCompoundField} +import fr.inrae.metabolomics.p2m2.tools.format.output.OutputMassLynx.{SampleField, Header, buildSampleField} import scala.io.Source @@ -28,7 +28,7 @@ object MassLynxParser { case None => Header() } } - def parseResults( toParse : List[String] ) : List[(String,List[CompoundField])] = { + def parseResults( toParse : List[String] ) : List[(String,List[SampleField])] = { val listCompoundIndexLine = toParse.zipWithIndex.filter( _._1.trim.startsWith("Compound")).map(_._2) listCompoundIndexLine @@ -48,7 +48,7 @@ object MassLynxParser { }) } - def parseArrayCompound( toParse : List[String] ) :List[CompoundField] = { + def parseArrayCompound( toParse : List[String] ) :List[SampleField] = { val header = List("INDEX","Name","Trace","Type","Std. Conc","RT", "Area","uM","%Dev","S/N","Vial","Height/Area","Acq.Date","Height") @@ -63,10 +63,10 @@ object MassLynxParser { val length : Int = something._2 if (length == header.length ) { - Some(buildCompoundField(mapLine + Some(buildSampleField(mapLine .zipWithIndex.map { case (value, index) => header(index) -> value }.toMap)) } else { - System.err.println(" *** bad line def :" + mapLine.mkString(",") + " field number:" + length +" should be :"+header.length) + //System.err.println(" *** bad line def :" + mapLine.mkString(",") + " field number:" + length +" should be :"+header.length) None } }) diff --git a/src/main/scala/fr/inrae/metabolomics/p2m2/parser/Parser.scala b/src/main/scala/fr/inrae/metabolomics/p2m2/parser/Parser.scala deleted file mode 100644 index 6c96016..0000000 --- a/src/main/scala/fr/inrae/metabolomics/p2m2/parser/Parser.scala +++ /dev/null @@ -1,5 +0,0 @@ -package fr.inrae.metabolomics.p2m2.parser - -class Parser { - -} diff --git a/src/main/scala/fr/inrae/metabolomics/p2m2/tools/format/output/OutputMassLynx.scala b/src/main/scala/fr/inrae/metabolomics/p2m2/tools/format/output/OutputMassLynx.scala index 9d74cd8..346999e 100644 --- a/src/main/scala/fr/inrae/metabolomics/p2m2/tools/format/output/OutputMassLynx.scala +++ b/src/main/scala/fr/inrae/metabolomics/p2m2/tools/format/output/OutputMassLynx.scala @@ -1,7 +1,7 @@ package fr.inrae.metabolomics.p2m2.tools.format.output -import fr.inrae.metabolomics.p2m2.tools.format.output.OutputMassLynx.{CompoundField, Header} +import fr.inrae.metabolomics.p2m2.tools.format.output.OutputMassLynx.{SampleField, Header} import java.time.LocalDate import java.time.format.DateTimeFormatterBuilder @@ -22,7 +22,7 @@ object OutputMassLynx { } //Name Trace Type Std. Conc RT Area uM %Dev S/N Vial Height/Area Acq.Date Height - case class CompoundField( + case class SampleField( Name : String, Trace : Int, Type : String, @@ -37,8 +37,8 @@ object OutputMassLynx { `Acq.Date` : String, Height : Int ) - def buildCompoundField(map : Map[String,String] ) : CompoundField = { - CompoundField( + def buildSampleField(map : Map[String,String] ) : SampleField = { + SampleField( map.getOrElse("Name",""), map.getOrElse("Trace","-1").toInt, map.getOrElse("Type",""), @@ -64,7 +64,7 @@ case class OutputMassLynx( origin : String, header : Header, // list of Name Compound/ Area/etc.... - results : List[(String,List[CompoundField])] = List() + results : List[(String,List[SampleField])] = List() ) diff --git a/src/test/scala/fr/inrae/metabolomics/p2m2/command/MassLynx2IsocorCommandTest.scala b/src/test/scala/fr/inrae/metabolomics/p2m2/command/MassLynx2IsocorCommandTest.scala index af579af..2a898db 100644 --- a/src/test/scala/fr/inrae/metabolomics/p2m2/command/MassLynx2IsocorCommandTest.scala +++ b/src/test/scala/fr/inrae/metabolomics/p2m2/command/MassLynx2IsocorCommandTest.scala @@ -3,13 +3,14 @@ package fr.inrae.metabolomics.p2m2.command import utest.{TestSuite, Tests, test} import java.io.File +import scala.io.Source import scala.util.{Failure, Success, Try} object MassLynx2IsocorCommandTest extends TestSuite { val tests = Tests { - test("GCMS2IsocorCommandTest - help") { + test("MassLynx2IsocorCommand - help") { Try(MassLynx2IsocorCommand.main(Array(""))) match { case Success(_) => assert(true) case Failure(f) => f.printStackTrace();assert(false) @@ -17,7 +18,7 @@ object MassLynx2IsocorCommandTest extends TestSuite { } } - test("GCMS2IsocorCommandTest - with args") { + test("MassLynx2IsocorCommand - with args") { val tp = File.createTempFile("out-", ".tsv").getPath Try(MassLynx2IsocorCommand.main( @@ -27,20 +28,21 @@ object MassLynx2IsocorCommandTest extends TestSuite { } } - test("GCMS2IsocorCommandTest - with args derivatives (epty fil)") { - val tp = File.createTempFile("out-", ".tsv").getPath - + test("MassLynx2IsocorCommand - with args derivatives (empty file)") { + val fileTp = File.createTempFile("out-", ".tsv") + val tp = fileTp.getPath + println(tp) Try(MassLynx2IsocorCommand.main( Array( getClass.getResource("/MassLynx/mass_15Ngly.txt").getPath, "--out",tp, "--derivatives",getClass.getResource("/MassLynx/correspondence_derivatives_empty.txt").getPath))) match { - case Success(_) => assert(true) + case Success(a) => assert(Source.fromFile(tp).getLines().length == 1) // only header case Failure(f) => f.printStackTrace();assert(false) } } - test("GCMS2IsocorCommandTest - with args derivatives bad definition. throw an error !") { + test("MassLynx2IsocorCommand - with args derivatives bad definition. throw an error !") { val tp = File.createTempFile("out-", ".tsv").getPath Try(MassLynx2IsocorCommand.main( @@ -53,7 +55,7 @@ object MassLynx2IsocorCommandTest extends TestSuite { } } - test("GCMS2IsocorCommandTest - with args derivatives") { + test("MassLynx2IsocorCommand - with args derivatives") { val tp = File.createTempFile("out-", ".tsv").getPath Try(MassLynx2IsocorCommand.main( @@ -61,10 +63,9 @@ object MassLynx2IsocorCommandTest extends TestSuite { getClass.getResource("/MassLynx/mass_15Ngly.txt").getPath, "--out",tp, "--derivatives",getClass.getResource("/MassLynx/correspondence_derivatives.txt").getPath))) match { - case Success(_) => assert(true) + case Success(_) => assert(Source.fromFile(tp).getLines().length > 1) // header and data case Failure(f) => f.printStackTrace();assert(false) } } - } } diff --git a/src/test/scala/fr/inrae/metabolomics/p2m2/command/OpenLabCDS2CsvCommandTest.scala b/src/test/scala/fr/inrae/metabolomics/p2m2/command/OpenLabCDS2CsvCommandTest.scala index 4a00e37..42b0a98 100644 --- a/src/test/scala/fr/inrae/metabolomics/p2m2/command/OpenLabCDS2CsvCommandTest.scala +++ b/src/test/scala/fr/inrae/metabolomics/p2m2/command/OpenLabCDS2CsvCommandTest.scala @@ -23,8 +23,8 @@ object OpenLabCDS2CsvCommandTest extends TestSuite { ) val tp = File.createTempFile("out-", ".csv").getPath - Try(OpenLabCDS2CsvCommand.main(Array(getClass.getResource("/GCMS/13CPROT1.txt").getPath,"--out",tp))) match { - case Success(_) => assert(true) + Try(OpenLabCDS2CsvCommand.main(infiles ++ Array("--out",tp))) match { + case Success(a) => println(a);assert(true) case Failure(_) => assert(false) } } diff --git a/src/test/scala/fr/inrae/metabolomics/p2m2/converter/MassLynxOutput2IsocorInputTest.scala b/src/test/scala/fr/inrae/metabolomics/p2m2/converter/MassLynxOutput2IsocorInputTest.scala index caed051..ed4987c 100644 --- a/src/test/scala/fr/inrae/metabolomics/p2m2/converter/MassLynxOutput2IsocorInputTest.scala +++ b/src/test/scala/fr/inrae/metabolomics/p2m2/converter/MassLynxOutput2IsocorInputTest.scala @@ -4,7 +4,7 @@ import fr.inrae.metabolomics.p2m2.parser.MassLynxParser import fr.inrae.metabolomics.p2m2.tools.format.output.OutputMassLynx import utest.{TestSuite, Tests, test} -object MassLynxOutput2IsocorInputTest extends TestSuite { +object MassLynxOutput2IsocorInputTest extends TestSuite { val tests = Tests { test("nothing") { val entry = OutputMassLynx( @@ -74,7 +74,7 @@ object MassLynxOutput2IsocorInputTest extends TestSuite { header=OutputMassLynx.Header(), results=MassLynxParser.parseResults(toParse.split("\n").toList) ) - assert( MassLynxOutput2IsocorInput(Map()).transform(entry) == List("GlyN15_A_3\tM+H\tACCQTAG\t0\t96688\t2000")) + assert( MassLynxOutput2IsocorInput(Map("M+H"->"ACCQTAG")).transform(entry) == List("GlyN15_A_3\tM+H\tACCQTAG\t0\t96688\t2000")) } test("basic run with a sample M+H, resolution=1000") { @@ -89,10 +89,26 @@ object MassLynxOutput2IsocorInputTest extends TestSuite { header=OutputMassLynx.Header(), results=MassLynxParser.parseResults(toParse.split("\n").toList) ) - assert( MassLynxOutput2IsocorInput(Map(),resolution=1000).transform(entry) == + assert( MassLynxOutput2IsocorInput(Map("M+H"->"ACCQTAG"),resolution=1000).transform(entry) == List("GlyN15_A_3\tM+H\tACCQTAG\t0\t96688\t1000")) } - test("basic run with a sample M+H, resolution=1000, defaultDerivative='TOTO'") { + test("basic run with a sample M+H, resolution=1000") { + val toParse = + """Compound 1: M+H + | + | Name Trace Type Std. Conc RT Area uM %Dev S/N Vial Height/Area Acq.Date Height + |1 GlyN15_A_3 188 1.78 96688 796 1:A,6 11.911 17-sept-19 1151660""".stripMargin + + val entry = OutputMassLynx( + origin="", + header=OutputMassLynx.Header(), + results=MassLynxParser.parseResults(toParse.split("\n").toList) + ) + + assert( MassLynxOutput2IsocorInput(Map(),resolution=1000).transform(entry) == List()) + } + + test("basic run with a sample M+H, resolution=1000, map( GlyN15_A_3 => 'TOTO')") { val toParse = """Compound 1: M+H | @@ -105,7 +121,7 @@ object MassLynxOutput2IsocorInputTest extends TestSuite { results=MassLynxParser.parseResults(toParse.split("\n").toList) ) - assert( MassLynxOutput2IsocorInput(Map(),resolution=1000, defaultDerivative="TOTO").transform(entry) == + assert( MassLynxOutput2IsocorInput(Map("M+H" -> "TOTO"),resolution=1000).transform(entry) == List("GlyN15_A_3\tM+H\tTOTO\t0\t96688\t1000")) } @@ -122,8 +138,24 @@ object MassLynxOutput2IsocorInputTest extends TestSuite { results=MassLynxParser.parseResults(toParse.split("\n").toList) ) - assert( MassLynxOutput2IsocorInput(Map("GlyN15_A_3" -> "TOTO"),resolution=1000).transform(entry) == + assert( MassLynxOutput2IsocorInput(Map("M+H" -> "TOTO"),resolution=1000).transform(entry) == List("GlyN15_A_3\tM+H\tTOTO\t0\t96688\t1000")) } + + test("basic run with a sample M+H, resolution=1000, map( GlyN15_A_3 => 'TOTO')") { + val toParse = + """Compound 1: TATA + | + | Name Trace Type Std. Conc RT Area uM %Dev S/N Vial Height/Area Acq.Date Height + |1 GlyN15_A_3 188 1.78 96688 796 1:A,6 11.911 17-sept-19 1151660""".stripMargin + + val entry = OutputMassLynx( + origin="", + header=OutputMassLynx.Header(), + results=MassLynxParser.parseResults(toParse.split("\n").toList) + ) + + assert( MassLynxOutput2IsocorInput(Map("TATA" -> "TOTO"),resolution=1000).transform(entry) == List()) + } } } diff --git a/src/test/scala/fr/inrae/metabolomics/p2m2/parser/GCMSParserTest.scala b/src/test/scala/fr/inrae/metabolomics/p2m2/parser/GCMSParserTest.scala index b435446..b363617 100644 --- a/src/test/scala/fr/inrae/metabolomics/p2m2/parser/GCMSParserTest.scala +++ b/src/test/scala/fr/inrae/metabolomics/p2m2/parser/GCMSParserTest.scala @@ -3,6 +3,8 @@ package fr.inrae.metabolomics.p2m2.parser import fr.inrae.metabolomics.p2m2.tools.format.output.OutputGCMS.HeaderField import utest.{TestSuite, Tests, test} +import scala.util.{Failure, Success, Try} + object GCMSParserTest extends TestSuite{ val tests = Tests{ test("getIndexLinesByCategories empty") { @@ -58,6 +60,60 @@ object GCMSParserTest extends TestSuite{ )) } + test("parseHeader - parse exception *Data File Name* attribute malformed (empty) ") { + val toParse = + """ + | + |[Header] + |Data File Name + |Output Date 23/08/2021 + |Output Time 14:09:36 + | + |[U] + |""".stripMargin + + Try(GCMSParser.parseHeader(toParse.split("\n").toList)) match { + case Success(_) => assert(false) + case Failure(_) => assert(true) + } + } + + test("parseHeader - parse exception *Output Date* attribute malformed (empty) ") { + val toParse = + """ + | + |[Header] + |Data File Name test + |Output Date + |Output Time 14:09:36 + | + |[U] + |""".stripMargin + + Try(GCMSParser.parseHeader(toParse.split("\n").toList)) match { + case Success(_) => assert(false) + case Failure(_) => assert(true) + } + } + + test("parseHeader - parse exception *Output Time* attribute malformed (empty) ") { + val toParse = + """ + | + |[Header] + |Data File Name test + |Output Date + |Output Time + | + |[U] + |""".stripMargin + + Try(GCMSParser.parseHeader(toParse.split("\n").toList)) match { + case Success(_) => assert(false) + case Failure(_) => assert(true) + } + } + test("parseMSQuantitativeResults - 1") { val toParse = """ diff --git a/src/test/scala/fr/inrae/metabolomics/p2m2/parser/MassLynxParserTest.scala b/src/test/scala/fr/inrae/metabolomics/p2m2/parser/MassLynxParserTest.scala index e4c11e8..ae200f2 100644 --- a/src/test/scala/fr/inrae/metabolomics/p2m2/parser/MassLynxParserTest.scala +++ b/src/test/scala/fr/inrae/metabolomics/p2m2/parser/MassLynxParserTest.scala @@ -1,7 +1,7 @@ package fr.inrae.metabolomics.p2m2.parser import fr.inrae.metabolomics.p2m2.tools.format.output.OutputMassLynx -import fr.inrae.metabolomics.p2m2.tools.format.output.OutputMassLynx.{CompoundField, Header} +import fr.inrae.metabolomics.p2m2.tools.format.output.OutputMassLynx.{SampleField, Header} import utest.{TestSuite, Tests, test} import scala.util.{Failure, Success, Try} @@ -58,7 +58,7 @@ object MassLynxParserTest extends TestSuite{ assert(MassLynxParser.parseResults(toParse.split("\n").toList) == List(("NH4+", - List(CompoundField("GlyN15_A_3",188,"","",1.78,96688,"","",796,"1:A,6",11.911,"17-sept-19",1151660))))) + List(SampleField("GlyN15_A_3",188,"","",1.78,96688,"","",796,"1:A,6",11.911,"17-sept-19",1151660))))) } test("parse compound with bad line") { @@ -94,10 +94,10 @@ object MassLynxParserTest extends TestSuite{ List( ( "NH4+", - List(CompoundField("GlyN15_A_3",188,"","",1.78,96688,"","",796,"1:A,6",11.911,"17-sept-19",1151660)) + List(SampleField("GlyN15_A_3",188,"","",1.78,96688,"","",796,"1:A,6",11.911,"17-sept-19",1151660)) ),( "NH4+, M+1", - List(CompoundField("SE1_GlyN15_3",189,"","",1.75,16945,"","",58,"1:A,3",12.562,"17-sept-19",212863)) + List(SampleField("SE1_GlyN15_3",189,"","",1.75,16945,"","",58,"1:A,3",12.562,"17-sept-19",212863)) ) )) } From 6cb86cbdfa856deee64864acd26e72c30ae0a733 Mon Sep 17 00:00:00 2001 From: Olivier Filangi Date: Thu, 7 Apr 2022 12:13:55 +0200 Subject: [PATCH 4/5] fix masslynx2isocor. 0.1.7 --- build.sbt | 4 +- .../P2M2Tools-assembly-0.1.6.jar | Bin 6192902 -> 0 bytes galaxy/masslynx2isocor/masslynx2isocor.xml | 2 +- .../test-data/correspondence_derivatives.txt | 27 +- .../test-data/input_isocor.tsv | 14294 ++++++++-------- 5 files changed, 6986 insertions(+), 7341 deletions(-) delete mode 100644 galaxy/masslynx2isocor/P2M2Tools-assembly-0.1.6.jar diff --git a/build.sbt b/build.sbt index 21c2a3f..ac3fe3b 100644 --- a/build.sbt +++ b/build.sbt @@ -16,7 +16,7 @@ scmInfo := Some( versionScheme := Some("early-semver") -val static_version = "0.1.6" +val static_version = "0.1.7" val version_build = scala.util.Properties.envOrElse("PROG_VERSION", static_version ) version := version_build @@ -26,7 +26,7 @@ libraryDependencies += "com.github.scopt" %% "scopt" % "4.0.1" // Coverage -coverageMinimumStmtTotal := 97 +coverageMinimumStmtTotal := 98 coverageFailOnMinimum := false coverageHighlighting := true diff --git a/galaxy/masslynx2isocor/P2M2Tools-assembly-0.1.6.jar b/galaxy/masslynx2isocor/P2M2Tools-assembly-0.1.6.jar deleted file mode 100644 index f1081f47716c3d0d6b006f072b4ed1bb762ab20c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6192902 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