Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unify auto-acmg methods #110

Merged
merged 3 commits into from
May 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 24 additions & 2 deletions docs/acmg_seqvars_implementation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,36 @@
Implementation of different ACMG criteria for sequence variants
===============================================================

This section contains the internal building blocks for the
This section contains the internal building blocks for the
implementation of the different ACMG criteria for sequence variants.

-----------
PS1 and PM5
-----------

.. automodule:: src.auto_ps1_pm5
.. automodule:: src.criteria.auto_ps1_pm5
:members:
:inherited-members:
:undoc-members:
:show-inheritance:
:private-members:

-----------
PM4 and BP3
-----------

.. automodule:: src.criteria.auto_pm4_bp3
:members:
:inherited-members:
:undoc-members:
:show-inheritance:
:private-members:

------------------
BA1, BS1, BS2, PM2
------------------

.. automodule:: src.criteria.auto_ba1_bs1_bs2_pm2
:members:
:inherited-members:
:undoc-members:
Expand Down
150 changes: 57 additions & 93 deletions src/auto_acmg.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,10 @@

from loguru import logger

from src.atuo_ba1_bs1_bs_2_pm2 import AutoBA1BS1BS2PM2
from src.auto_ps1_pm5 import AutoPS1PM5
from src.core.config import Config
from src.defs.auto_pvs1 import (
PVS1Prediction,
PVS1PredictionPathMapping,
PVS1PredictionSeqVarPath,
PVS1PredictionStrucVarPath,
)
from src.criteria.auto_criteria import AutoACMGCriteria
from src.defs.auto_acmg import ACMGCriteria
from src.defs.auto_pvs1 import PVS1Prediction, PVS1PredictionPathMapping, PVS1PredictionSeqVarPath
from src.defs.exceptions import AutoAcmgBaseException, ParseError
from src.defs.genome_builds import GenomeRelease
from src.defs.seqvar import SeqVar, SeqVarResolver
Expand Down Expand Up @@ -91,13 +86,19 @@ def resolve_variant(self) -> SeqVar | StrucVar | None:
return None

def predict(self):
"""Runs the prediction algorithm to assess the PVS1 criteria for the resolved variant.
"""
Predict ACMG criteria for the specified variant.
This method first resolves the variant, then predicts the PVS1 criterion for sequence
variants and other ACMG criteria.
This method resolves the variant and then, based on the type of variant, predicts its
classification according to the PVS1 criteria. It handles both sequence and structural variants.
Note:
The method can resolve both sequence and structural variants, but currently only
sequence variants are supported for ACMG criteria prediction.
Raises:
Exception: Handles general exceptions that may occur during prediction and logs them.
Exception: Specific exceptions are caught and logged, but generic exceptions may be
raised if the prediction fails.
"""
logger.info("Predicting ACMG criteria for variant: {}", self.variant_name)
variant = self.resolve_variant()
Expand All @@ -116,8 +117,7 @@ def predict(self):
self.seqvar_pvs1_prediction_path: PVS1PredictionSeqVarPath = (
PVS1PredictionSeqVarPath.NotSet
)
self.seqvar_ps1: Optional[bool] = None
self.seqvar_pm5: Optional[bool] = None
self.seqvar_criteria: ACMGCriteria = ACMGCriteria()

# PVS1
try:
Expand All @@ -132,96 +132,60 @@ def predict(self):
self.seqvar_pvs1_prediction = seqvar_prediction
self.seqvar_pvs1_prediction_path = seqvar_prediction_path
logger.info(
"PVS1 prediction for {}: {}.\n" "The prediction path is:\n{}.",
self.seqvar.user_repr,
"PVS1 prediction: {}.\nThe prediction path is:\n{}.",
self.seqvar_pvs1_prediction.name,
PVS1PredictionPathMapping[self.seqvar_pvs1_prediction_path],
)
except AutoAcmgBaseException as e:
logger.error("Failed to predict PVS1 criteria. Error: {}", e)

# PS1 and PM5
try:
logger.info("Predicting PS1 and PM5.")
ps1pm5 = AutoPS1PM5(self.seqvar, self.genome_release, config=self.config)
ps1_pm5_prediction = ps1pm5.predict()
if not ps1_pm5_prediction:
logger.error("Failed to predict PS1&PM5 criteria.")
else:
self.seqvar_ps1, self.seqvar_pm5 = (
ps1_pm5_prediction.PS1,
ps1_pm5_prediction.PM5,
)
logger.info(
"PS1 prediction for {}: {}.\n" "PM5 prediction: {}.",
self.seqvar.user_repr,
self.seqvar_ps1,
self.seqvar_pm5,
)
except AutoAcmgBaseException as e:
logger.error("Failed to predict PS1 and PM5 criteria. Error: {}", e)

# BA1, BS1, BS2, PM2
# Other criteria
try:
logger.info("Predicting BA1, BS1, BS2, and PM2.")
ba1bs1bs2pm2 = AutoBA1BS1BS2PM2(
logger.info("Predicting other ACMG criteria.")
auto_criteria = AutoACMGCriteria(
self.seqvar, self.genome_release, config=self.config
)
ba1bs1bs2pm2_prediction = ba1bs1bs2pm2.predict()
if not ba1bs1bs2pm2_prediction:
logger.error("Failed to predict BA1, BS1, BS2, and PM2 criteria.")
criteria_preciction = auto_criteria.predict()
if criteria_preciction is None:
logger.error("Failed to predict other ACMG criteria.")
else:
self.seqvar_ba1, self.seqvar_bs1, self.seqvar_bs2, self.seqvar_pm2 = (
ba1bs1bs2pm2_prediction.BA1,
ba1bs1bs2pm2_prediction.BS1,
ba1bs1bs2pm2_prediction.BS2,
ba1bs1bs2pm2_prediction.PM2,
)
logger.info(
"BA1 prediction for {}: {}.\n"
"BS1 prediction: {}.\n"
"BS2 prediction: {}.\n"
"PM2 prediction: {}.",
self.seqvar.user_repr,
self.seqvar_ba1,
self.seqvar_bs1,
self.seqvar_bs2,
self.seqvar_pm2,
)
self.seqvar_criteria = criteria_preciction
logger.info("Other ACMG criteria prediction:\n{}", self.seqvar_criteria)
except AutoAcmgBaseException as e:
logger.error("Failed to predict BA1, BS1, BS2, and PM2 criteria. Error: {}", e)
logger.error("Failed to predict other ACMG criteria. Error: {}", e)

elif isinstance(variant, StrucVar):
logger.info("Currently only PVS1 prediction is implemented for structural variants!")
logger.info(
"Classifying ACMG criteria for structural variant {}, genome release: {}.",
variant.user_repr,
self.genome_release.name,
)
# PVS1
try:
logger.info("Predicting PVS1.")
self.strucvar: StrucVar = variant
self.strucvar_prediction: PVS1Prediction = PVS1Prediction.NotSet # type: ignore
self.strucvar_prediction_path: PVS1PredictionStrucVarPath = PVS1PredictionStrucVarPath.NotSet # type: ignore

pvs1 = AutoPVS1(self.strucvar, self.genome_release)
strucvar_prediction, strucvar_prediction_path = pvs1.predict()
if strucvar_prediction is None or strucvar_prediction_path is None:
logger.error("Failed to predict PVS1 criteria.")
return
else:
# Double check if the prediction path is indeed for structural variant
assert isinstance(strucvar_prediction_path, PVS1PredictionStrucVarPath)
self.strucvar_prediction = strucvar_prediction
self.strucvar_prediction_path = strucvar_prediction_path
logger.info(
"PVS1 prediction for {}: {}.\n" "The prediction path is:\n{}.",
self.strucvar.user_repr,
self.strucvar_prediction.name,
PVS1PredictionPathMapping[self.strucvar_prediction_path],
)
except AutoAcmgBaseException as e:
logger.error("Failed to predict PVS1 criteria. Error: {}", e)
return
logger.info("Classification of structural variants is not implemented yet.")
# logger.info("Currently only PVS1 prediction is implemented for structural variants!")
# logger.info(
# "Classifying ACMG criteria for structural variant {}, genome release: {}.",
# variant.user_repr,
# self.genome_release.name,
# )
# # PVS1
# try:
# logger.info("Predicting PVS1.")
# self.strucvar: StrucVar = variant
# self.strucvar_prediction: PVS1Prediction = PVS1Prediction.NotSet # type: ignore
# self.strucvar_prediction_path: PVS1PredictionStrucVarPath = PVS1PredictionStrucVarPath.NotSet # type: ignore

# pvs1 = AutoPVS1(self.strucvar, self.genome_release)
# strucvar_prediction, strucvar_prediction_path = pvs1.predict()
# if strucvar_prediction is None or strucvar_prediction_path is None:
# logger.error("Failed to predict PVS1 criteria.")
# return
# else:
# # Double check if the prediction path is indeed for structural variant
# assert isinstance(strucvar_prediction_path, PVS1PredictionStrucVarPath)
# self.strucvar_prediction = strucvar_prediction
# self.strucvar_prediction_path = strucvar_prediction_path
# logger.info(
# "PVS1 prediction for {}: {}.\n" "The prediction path is:\n{}.",
# self.strucvar.user_repr,
# self.strucvar_prediction.name,
# PVS1PredictionPathMapping[self.strucvar_prediction_path],
# )
# except AutoAcmgBaseException as e:
# logger.error("Failed to predict PVS1 criteria. Error: {}", e)
# return
logger.info("ACMG criteria prediction completed.")
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,18 @@ class AutoBA1BS1BS2PM2:
"""Predicts BA1, BS1, BS2, PM2 criteria for sequence variants."""

def __init__(
self, seqvar: SeqVar, genome_release: GenomeRelease, *, config: Optional[Config] = None
self,
seqvar: SeqVar,
genome_release: GenomeRelease,
variant_info: VariantResult,
*,
config: Optional[Config] = None,
):
#: Configuration to use.
self.config = config or Config()
# Attributes to be set
self.seqvar = seqvar
self.genome_release = genome_release
self.variant_info = variant_info
self.annonars_client = AnnonarsClient(api_base_url=self.config.api_base_url_annonars)
self.prediction: BA1BS1BS2PM2 | None = None

Expand Down Expand Up @@ -149,21 +154,16 @@ def predict(self) -> Optional[BA1BS1BS2PM2]:
BA1BS1BS2PM2: The prediction result.
"""
try:
# Get variant information
variant_info = self._get_variant_info(self.seqvar)
if not variant_info:
raise MissingDataError("No variant information retrieved")

# Instantiate the prediction result
self.prediction = BA1BS1BS2PM2()

# Evaluate each criterion
self.prediction.BA1 = self.evaluate_ba1(self.seqvar, variant_info.result)
self.prediction.BA1 = self.evaluate_ba1(self.seqvar, self.variant_info)
self.prediction.BS1 = self.evaluate_bs1(
self.seqvar, variant_info.result, max_credible_freq=0.01
self.seqvar, self.variant_info, max_credible_freq=0.01
) # Adjust max_credible_freq as needed
self.prediction.BS2 = self.evaluate_bs2(variant_info.result)
self.prediction.PM2 = self.evaluate_pm2(self.seqvar, variant_info.result)
self.prediction.BS2 = self.evaluate_bs2(self.variant_info)
self.prediction.PM2 = self.evaluate_pm2(self.seqvar, self.variant_info)
except AutoAcmgBaseException as e:
logger.error("Error occurred during BA1, BS1, BS2, PM5 prediction. Error: {}", e)
self.prediction = None
Expand Down
97 changes: 97 additions & 0 deletions src/criteria/auto_criteria.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
from typing import Optional

from loguru import logger

from src.api.annonars import AnnonarsClient
from src.core.config import Config
from src.criteria.auto_ba1_bs1_bs2_pm2 import AutoBA1BS1BS2PM2
from src.criteria.auto_pm4_bp3 import AutoPM4BP3
from src.criteria.auto_ps1_pm5 import AutoPS1PM5
from src.defs.annonars_variant import AnnonarsVariantResponse
from src.defs.auto_acmg import ACMGCriteria
from src.defs.exceptions import AutoAcmgBaseException
from src.defs.genome_builds import GenomeRelease
from src.defs.seqvar import SeqVar


class AutoACMGCriteria:
"""Predict ACMG criteria for sequence variant."""

def __init__(
self, seqvar: SeqVar, genome_release: GenomeRelease, *, config: Optional[Config] = None
):
#: Configuration to use.
self.config = config or Config()
# Attributes to be set
self.seqvar = seqvar
self.genome_release = genome_release
self.annonars_client = AnnonarsClient(api_base_url=self.config.api_base_url_annonars)
self.prediction: Optional[ACMGCriteria] = None

def _get_variant_info(self, seqvar: SeqVar) -> Optional[AnnonarsVariantResponse]:
"""Get variant information from Annonars.
Returns:
AnnonarsVariantResponse: Annonars response.
"""
try:
logger.debug("Getting variant information for {}.", seqvar)
return self.annonars_client.get_variant_info(seqvar)
except AutoAcmgBaseException as e:
logger.error("Failed to get variant information. Error: {}", e)
return None

def predict(self) -> Optional[ACMGCriteria]:
"""Predict ACMG criteria for sequence variant."""
self.prediction = ACMGCriteria()

variant_info = self._get_variant_info(self.seqvar)
if not variant_info:
logger.error("Failed to get variant information for {}.", self.seqvar)
return None

# PS1 and PM5
try:
ps1pm5 = AutoPS1PM5(
self.seqvar, self.genome_release, variant_info.result, config=self.config
)
ps1_pm5_prediction = ps1pm5.predict()
if not ps1_pm5_prediction:
logger.error("Failed to predict PS1&PM5 criteria.")
else:
self.prediction.PS1 = ps1_pm5_prediction.PS1
self.prediction.PM5 = ps1_pm5_prediction.PM5
except AutoAcmgBaseException as e:
logger.error("Failed to predict PS1 and PM5 criteria. Error: {}", e)

# PM4 and BP3
try:
pm4bp3 = AutoPM4BP3(
self.seqvar, self.genome_release, variant_info.result, config=self.config
)
pm4_bp3_prediction = pm4bp3.predict()
if not pm4_bp3_prediction:
logger.error("Failed to predict PM4&BP3 criteria.")
else:
self.prediction.PM4 = pm4_bp3_prediction.PM4
self.prediction.BP3 = pm4_bp3_prediction.BP3
except AutoAcmgBaseException as e:
logger.error("Failed to predict PM4 and BP3 criteria. Error: {}", e)

# BA1, BS1, BS2, PM2
try:
ba1bs1bs2pm2 = AutoBA1BS1BS2PM2(
self.seqvar, self.genome_release, variant_info.result, config=self.config
)
ba1bs1bs2pm2_prediction = ba1bs1bs2pm2.predict()
if not ba1bs1bs2pm2_prediction:
logger.error("Failed to predict BA1, BS1, BS2, and PM2 criteria.")
else:
self.prediction.BA1 = ba1bs1bs2pm2_prediction.BA1
self.prediction.BS1 = ba1bs1bs2pm2_prediction.BS1
self.prediction.BS2 = ba1bs1bs2pm2_prediction.BS2
self.prediction.PM2 = ba1bs1bs2pm2_prediction.PM2
except AutoAcmgBaseException as e:
logger.error("Failed to predict BA1, BS1, BS2, and PM2 criteria. Error: {}", e)

return self.prediction
Loading