Skip to content

Commit

Permalink
Merge pull request #58 from metabolomics-us/QA
Browse files Browse the repository at this point in the history
FIEHNLAB-1820/Calculate all possible adducts for spectra
  • Loading branch information
NolanGuzman97 committed Jul 18, 2022
2 parents 643f923 + f956e35 commit 6ee8dd6
Show file tree
Hide file tree
Showing 33 changed files with 760 additions and 123 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,10 @@ export class SimilaritySearchFormComponent implements OnInit{
minSimilarity: 500,
precursorMZ: null,
precursorTolerancePPM: null,
precursorToleranceDa: null
precursorToleranceDa: null,
algorithm: 'default',
removePrecursorIon: false,
checkAllAdducts: false
};

if (minSimilarity != null && typeof +minSimilarity === 'number') {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export class User {
emailAddress: string;
id: string;
firstName: string;
lastName: string;
institution: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export class AuthenticationService{

pullSubmitterData(credentials: any) {
if (credentials.username === 'admin') {
this.currentUserSubject.next({emailAddress: credentials.username, accessToken: credentials.accessToken,
this.currentUserSubject.next({emailAddress: credentials.username, id: credentials.id, accessToken: credentials.accessToken,
firstName: credentials.username, lastName: '', institution: '', roles: [{authority: 'ADMIN'}]});
this.isAuthenticatedSubject.next(true);
} else {
Expand All @@ -46,7 +46,7 @@ export class AuthenticationService{
};
this.http.get(`${environment.REST_BACKEND_SERVER}/rest/submitters/${credentials.username}`, config).subscribe((res: User) => {
this.currentUserSubject.next({
emailAddress: res.emailAddress, accessToken: credentials.accessToken,
emailAddress: res.emailAddress, id: res.id, accessToken: credentials.accessToken,
firstName: res.firstName, lastName: res.lastName, institution: res.institution, roles: res.roles || []
});
this.isAuthenticatedSubject.next(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,9 @@ class CurationConfig extends LazyLogging {
new CalculateMassAccuracy,
new MassAccuracyValidation,

// Calculate Adducts after other curations complete
new CalculateAllAdducts,

// Add validation metadata
new FinalizeCuration
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package edu.ucdavis.fiehnlab.mona.backend.curation.processor.metadata

import com.typesafe.scalalogging.LazyLogging
import edu.ucdavis.fiehnlab.mona.backend.core.domain.{Compound, MetaData, Spectrum}
import edu.ucdavis.fiehnlab.mona.backend.core.workflow.annotations.Step
import edu.ucdavis.fiehnlab.mona.backend.curation.util.chemical.AdductBuilder
import edu.ucdavis.fiehnlab.mona.backend.curation.util.{CommonMetaData, CurationUtilities}
import org.springframework.batch.item.ItemProcessor

import scala.collection.mutable.ArrayBuffer

@Step(description = "this step will calculate all possible adducts")
class CalculateAllAdducts extends ItemProcessor[Spectrum, Spectrum] with LazyLogging {

private val PRECURSOR_MATCH_TOLERANCE: Double = 0.5

/**
* processes the given spectrum
*
* @param spectrum to be processed
* @return processed spectrum
*/
override def process(spectrum: Spectrum): Spectrum = {
// Get computed total exact mass from the biological compound if it exists
if(spectrum.tags.exists(x => x.text == "GC-MS")) {
logger.info(s"Process is only for LC-MS spectra")
spectrum
} else {
val biologicalIndex: Int =
if (spectrum.compound.exists(_.kind == "biological")) {
spectrum.compound.indexWhere(_.kind == "biological")
} else if (spectrum.compound.nonEmpty) {
0
} else {
-1
}

if (biologicalIndex == -1) {
logger.info(s"${spectrum.id}: Valid compound could not be found!")
spectrum
} else {
// Get total exact mass from biological compound
val theoreticalMass: Double = {
val x: String = CurationUtilities.findMetaDataValue(spectrum.compound(biologicalIndex).metaData, CommonMetaData.TOTAL_EXACT_MASS)

if (x == null) {
-1
} else {
x.toDouble
}
}

val biologicalCompound = spectrum.compound(biologicalIndex)
val adductMap = AdductBuilder.LCMS_POSITIVE_ADDUCTS ++ AdductBuilder.LCMS_NEGATIVE_ADDUCTS

val updatedMetadata: ArrayBuffer[MetaData] = new ArrayBuffer[MetaData]()
biologicalCompound.metaData.foreach(x => updatedMetadata.append(x))

val updatedCompoundSet: ArrayBuffer[Compound] = new ArrayBuffer[Compound]()
spectrum.compound.foreach(x => updatedCompoundSet.append(x))

if (theoreticalMass < 0) {
logger.info(s"${spectrum.id}: Computed exact mass was not found, unable to generate adduct information")
spectrum
} else {
adductMap.foreach {
case (x, f) => updatedMetadata.append(MetaData("theoretical adduct", true, true, x, null, null, null, f(theoreticalMass)))
}

val updatedCompound = biologicalCompound.copy(
metaData = updatedMetadata.toArray
)

updatedCompoundSet.update(biologicalIndex, updatedCompound)

spectrum.copy(
compound = updatedCompoundSet.toArray
)
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,63 +16,29 @@ object AdductBuilder extends LazyLogging {
* http://fiehnlab.ucdavis.edu/staff/kind/Metabolomics/MS-Adduct-Calculator/
*/
final val LCMS_POSITIVE_ADDUCTS: Map[String, Double => Double] = Map(
"[M]+" -> { M: Double => M - E_MASS },
"[M+3H]+" -> { M: Double => M / 3.0 + 1.007276 },
"[M+2H+Na]+" -> { M: Double => M / 3.0 + 8.334590 },
"[M+H+2Na]+" -> { M: Double => M / 3 + 15.7661904 },
"[M+3Na]+" -> { M: Double => M / 3.0 + 22.989218 },
"[M+2H]+" -> { M: Double => M / 2.0 + 1.007276 },
"[M+H+NH4]+" -> { M: Double => M / 2.0 + 9.520550 },
"[M+H+Na]+" -> { M: Double => M / 2.0 + 11.998247 },
"[M+H+K]+" -> { M: Double => M / 2.0 + 19.985217 },
"[M+ACN+2H]+" -> { M: Double => M / 2.0 + 21.520550 },
"[M+2Na]+" -> { M: Double => M / 2.0 + 22.989218 },
"[M+2ACN+2H]+" -> { M: Double => M / 2.0 + 42.033823 },
"[M+3ACN+2H]+" -> { M: Double => M / 2.0 + 62.547097 },
"[M+H]+" -> { M: Double => M + 1.007276 },
"[M+NH4]+" -> { M: Double => M + 18.033823 },
"[M+Na]+" -> { M: Double => M + 22.989218 },
"[M+CH3OH+H]+" -> { M: Double => M + 33.033489 },
"[M+K]+" -> { M: Double => M + 38.963158 },
"[M+ACN+H]+" -> { M: Double => M + 42.033823 },
"[M+2Na-H]+" -> { M: Double => M + 44.971160 },
"[M+IsoProp+H]+" -> { M: Double => M + 61.06534 },
"[M+ACN+Na]+" -> { M: Double => M + 64.015765 },
"[M+2K-H]+" -> { M: Double => M + 76.919040 },
"[M+DMSO+H]+" -> { M: Double => M + 79.02122 },
"[M+2ACN+H]+" -> { M: Double => M + 83.060370 },
"[M+IsoProp+Na+H]+" -> { M: Double => M + 84.05511 },
"[2M+H]+" -> { M: Double => 2 * M + 1.007276 },
"[2M+NH4]+" -> { M: Double => 2 * M + 18.033823 },
"[2M+Na]+" -> { M: Double => 2 * M + 22.989218 },
"[2M+3H2O+2H]+" -> { M: Double => 2 * M + 28.02312 },
"[2M+K]+" -> { M: Double => 2 * M + 38.963158 },
"[2M+ACN+H]+" -> { M: Double => 2 * M + 42.033823 },
"[2M+ACN+Na]+" -> { M: Double => 2 * M + 64.015765 },
"[M-H2O+H]+" -> { M: Double => M - 17.003838 }
"[M+H]+" -> { M: Double => M + 1.00797},
"[2M+H]+" -> { M: Double => 2 * M + 1.00797 },
"[M+NH4]+" -> { M: Double => M + 18.03858 },
"[2M+NH4]+" -> { M: Double => 2 * M +18.03858 },
"[M+K]+" -> { M: Double => M + 39.0983 },
"[2M+K]+" -> { M: Double => 2 * M +39.0983 },
"[M+H-H2O]+" -> { M: Double => M - 17.00737 },
"[2M+H-H2O]+" -> { M: Double => 2 * M - 17.00737 },
"[M+Na]+" -> { M: Double => M + 22.98977 },
"[2M+Na]+" -> { M: Double => 2 * M + 22.98977 }
)

/**
* Definitions of negative mode LC-MS adducts
* http://fiehnlab.ucdavis.edu/staff/kind/Metabolomics/MS-Adduct-Calculator/
*/
final val LCMS_NEGATIVE_ADDUCTS: Map[String, Double => Double] = Map(
"[M]-" -> { M: Double => M + E_MASS },
"[M-3H]-" -> { M: Double => M / 3.0 - 1.007276 },
"[M-2H]-" -> { M: Double => M / 2.0 - 1.007276 },
"[M-H2O-H]-" -> { M: Double => M - 19.01839 },
"[M-H]-" -> { M: Double => M - 1.007276 },
"[M+Na-2H]-" -> { M: Double => M + 20.974666 },
"[M+Cl]-" -> { M: Double => M + 34.969402 },
"[M+K-2H]-" -> { M: Double => M + 36.948606 },
"[M+FA-H]-" -> { M: Double => M + 44.998201 },
"[M+Hac-H]-" -> { M: Double => M + 59.013851 },
"[M+Br]-" -> { M: Double => M + 78.918885 },
"[M+TFA-H]-" -> { M: Double => M + 112.985586 },
"[2M-H]-" -> { M: Double => 2 * M - 1.007276 },
"[2M+FA-H]-" -> { M: Double => 2 * M + 44.998201 },
"[2M+Hac-H]-" -> { M: Double => 2 * M + 59.013851 },
"[3M-H]-" -> { M: Double => 3 * M - 1.007276 }
"[M+Cl]-" -> { M: Double => M + 35.453 },
"[2M+Cl]-" -> { M: Double => 2 * M + 35.453 },
"[M+HAc-H]-" -> { M: Double => M + 59.04403 },
"[2M+HAc-H]-" -> { M: Double => 2 * M + 59.04403 }
)


Expand Down Expand Up @@ -199,4 +165,4 @@ object AdductBuilder extends LazyLogging {

molecule
}
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package edu.ucdavis.fiehnlab.mona.backend.curation

import java.io.InputStreamReader

import edu.ucdavis.fiehnlab.mona.backend.core.auth.jwt.config.JWTAuthenticationConfig
import edu.ucdavis.fiehnlab.mona.backend.core.domain.Spectrum
import edu.ucdavis.fiehnlab.mona.backend.core.domain.io.json.JSONDomainReader
import edu.ucdavis.fiehnlab.mona.backend.core.persistence.rest.client.config.RestClientTestConfig
import edu.ucdavis.fiehnlab.mona.backend.curation.config.RestClientTestConfig
import edu.ucdavis.fiehnlab.mona.backend.curation.config.CurationConfig
import org.junit.runner.RunWith
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec
import org.springframework.batch.item.ItemProcessor
import org.springframework.beans.factory.annotation.Autowired
Expand All @@ -20,7 +20,7 @@ import org.springframework.test.context.junit4.SpringRunner
*/
@RunWith(classOf[SpringRunner])
@SpringBootTest(classes = Array(classOf[RestClientTestConfig], classOf[CurationConfig], classOf[JWTAuthenticationConfig]))
class CurationWorkflowTest extends AnyWordSpec {
class CurationWorkflowTest extends AnyWordSpec with Matchers{

@Autowired
val curationWorkflow: ItemProcessor[Spectrum, Spectrum] = null
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package edu.ucdavis.fiehnlab.mona.backend.curation.config

import com.typesafe.scalalogging.LazyLogging
import edu.ucdavis.fiehnlab.mona.backend.core.auth.jwt.service.MongoLoginService
import edu.ucdavis.fiehnlab.mona.backend.core.domain.service.LoginService
import edu.ucdavis.fiehnlab.mona.backend.core.persistence.rest.server.config.RestServerConfig
import org.springframework.context.annotation.{Bean, Configuration, Import}

/**
* Created by wohlg on 3/11/2016.
*/
@Configuration
@Import(Array(classOf[RestServerConfig]))
class EmbeddedRestServerConfig extends LazyLogging {

/**
* the service which actually does the login for us
*
* @return
*/
@Bean
def loginService: LoginService = new MongoLoginService

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package edu.ucdavis.fiehnlab.mona.backend.curation.config

import edu.ucdavis.fiehnlab.mona.backend.core.auth.jwt.service.MongoLoginService
import edu.ucdavis.fiehnlab.mona.backend.core.auth.rest.config.AuthSecurityConfig
import edu.ucdavis.fiehnlab.mona.backend.core.domain.service.LoginService
import edu.ucdavis.fiehnlab.mona.backend.core.persistence.rest.client.config.RestClientConfig
import edu.ucdavis.fiehnlab.mona.backend.core.persistence.rest.client.service.RestLoginService
import edu.ucdavis.fiehnlab.mona.backend.core.statistics.config.StatisticsRepositoryConfig
import org.springframework.beans.factory.annotation.Value
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.context.annotation.{Bean, Import, Primary}

/**
* Created by wohlgemuth on 3/15/16.
*/
@Import(Array(classOf[RestClientConfig], classOf[EmbeddedRestServerConfig], classOf[AuthSecurityConfig], classOf[StatisticsRepositoryConfig]))
@SpringBootApplication
class RestClientTestConfig {

@Bean
@Primary
def loginService(@Value("${mona.rest.server.host}") host: String, @Value("${mona.rest.server.port}") port: Int): LoginService =
new RestLoginService("localhost", port)

@Bean
def loginServiceDelegate: LoginService = new MongoLoginService
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package edu.ucdavis.fiehnlab.mona.backend.curation.processor.metadata

import com.typesafe.scalalogging.LazyLogging
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec
import edu.ucdavis.fiehnlab.mona.backend.core.domain.Spectrum
import edu.ucdavis.fiehnlab.mona.backend.core.domain.io.json.JSONDomainReader

import java.io.InputStreamReader

class CalculateAllAdductsTest extends AnyWordSpec with Matchers with LazyLogging{

val reader: JSONDomainReader[Spectrum] = JSONDomainReader.create[Spectrum]

"this processor" when {
val processor = new CalculateAllAdducts

val exampleRecord: Spectrum = JSONDomainReader.create[Spectrum].read(new InputStreamReader(getClass.getResourceAsStream("/monaRecord2.json")))
"given a spectra" must {
"correctly calculate adduct list" in {
val processedSpectrum = processor.process(exampleRecord)
logger.info(s"${processedSpectrum.compound(0).metaData.filter(x => x.category == "theoretical adduct")}")
assert(processedSpectrum.compound(0).metaData.filter(x => x.category == "theoretical adduct").length == 16)
}

"spot check calculations" in {
val processedSpectrum = processor.process(exampleRecord)
val sortedMetaData = processedSpectrum.compound(0).metaData.filter(x => x.category == "theoretical adduct")
sortedMetaData.find(x => x.name == "[M+H]+").get.value shouldBe 165.041395368
sortedMetaData.find(x => x.name == "[2M+NH4]+").get.value shouldBe 346.105430736
sortedMetaData.find(x => x.name == "[2M+HAc-H]-").get.value shouldBe 387.110880736
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ class CalculateMassAccuracyTest extends AnyWordSpec with Matchers {

val massAccuracy: Option[MetaData] = processedSpectrum.metaData.find(_.name == CommonMetaData.MASS_ACCURACY)
massAccuracy.isDefined shouldBe true
massAccuracy.get.value.toString.toDouble shouldBe 50.9472 +- 1.0e-4
massAccuracy.get.value.toString.toDouble shouldBe 52.63049236000388 +- 1.0e-4

val massError: Option[MetaData] = processedSpectrum.metaData.find(_.name == CommonMetaData.MASS_ERROR)
massError.isDefined shouldBe true
massError.get.value.toString.toDouble shouldBe -0.021 +- 1.0e-4
massError.get.value.toString.toDouble shouldBe -0.021 +- 1.0e-2
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class JSONFileSpectraReaderTest extends AnyWordSpec with LazyLogging {
spectra = reader.read()

}
assert(counter == 58)
assert(counter == 59)
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import edu.ucdavis.fiehnlab.mona.backend.core.auth.types.{Role, User}
import edu.ucdavis.fiehnlab.mona.backend.core.domain.Spectrum
import edu.ucdavis.fiehnlab.mona.backend.core.domain.io.json.JSONDomainReader
import edu.ucdavis.fiehnlab.mona.backend.core.persistence.rest.client.api.GenericRestClient
import edu.ucdavis.fiehnlab.mona.backend.core.persistence.rest.client.config.RestClientTestConfig
import edu.ucdavis.fiehnlab.mona.backend.curation.TestConfig
import org.junit.runner.RunWith
import org.scalatest.wordspec.AnyWordSpec
Expand All @@ -24,7 +23,7 @@ import scala.jdk.CollectionConverters._
* Created by wohlgemuth on 3/18/16.
*/
@RunWith(classOf[SpringRunner])
@SpringBootTest(classes = Array(classOf[RestClientTestConfig], classOf[TestConfig], classOf[JWTAuthenticationConfig]), webEnvironment = WebEnvironment.DEFINED_PORT)
@SpringBootTest(classes = Array(classOf[TestConfig], classOf[JWTAuthenticationConfig]), webEnvironment = WebEnvironment.DEFINED_PORT)
class RestRepositoryReaderTest extends AnyWordSpec {


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,6 @@ class AdductBuilderTest extends AnyWordSpec with Matchers with LazyLogging {
assert(result._2 == "negative")
}

"find a 3-term adduct in a different order than the definition" in {
val result = AdductBuilder.findAdduct("[M+H+DMSO]+")
assert(result._1 == "[M+DMSO+H]+")
assert(result._2 == "positive")
}

"find 4-term adduct in a different order than the definition" in {
val result = AdductBuilder.findAdduct("[M+Na+H+IsoProp")
assert(result._1 == "[M+IsoProp+Na+H]+")
assert(result._2 == "positive")
}

"find an adduct should handle an invalid adduct" in {
val result = AdductBuilder.findAdduct("M+1")
assert(result._1 == "M+1")
Expand Down
Loading

0 comments on commit 6ee8dd6

Please sign in to comment.