PageRenderTime 53ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/exomate/views/recessive.py

https://bitbucket.org/marcelm/exomate
Python | 180 lines | 97 code | 44 blank | 39 comment | 21 complexity | 9069079f5d8582b95a52b5ecb7d0320e MD5 | raw file
  1. """
  2. The recessive page
  3. """
  4. from exomate.database import get_session
  5. from flask import render_template, request
  6. from exomate.application import app
  7. from exomate.models import Sample, Disease, KnownSource, KnownVariant, Call, Annotation, Variant
  8. from exomate.views.common import get_incomplete_annotation_warning, parse_mutation_query_parameters, Row
  9. from exomate.variants import VARIANT_OUT_NAMES, EXON_VARIANTS
  10. from collections import defaultdict, namedtuple
  11. from sqlalchemy import not_ , or_
  12. from sqlalchemy.orm import aliased
  13. session = get_session()
  14. #@shorten('recessive')
  15. @app.route('/recessive', methods=['GET', 'POST'])
  16. def recessive():
  17. if request.method != 'POST':
  18. samples = session.query(Sample).join(Disease).order_by(Disease.name, Sample.accession).all()
  19. warning = get_incomplete_annotation_warning()
  20. known_sources = session.query(KnownSource).all()
  21. return render_template('recessive-query.html',
  22. samples=samples,
  23. known_sources=known_sources,
  24. warning=warning,
  25. variant=VARIANT_OUT_NAMES,
  26. exon_variants=EXON_VARIANTS)
  27. q = parse_mutation_query_parameters(request.form)
  28. q.known_sources = session.query(KnownSource).filter(KnownSource.id.in_(q.ks)).all() if q.ks else []
  29. affected_sample = session.query(Sample).filter(Sample.accession == q.affected_sample_name).one()
  30. if q.ancestor1 == "father":
  31. anc1_samples = affected_sample.patient.father.samples
  32. else:
  33. anc1_samples = [session.query(Sample).filter(Sample.accession == q.ancestor1).one()]
  34. if q.ancestor2 == "mother":
  35. anc2_samples = affected_sample.patient.mother.samples
  36. else:
  37. anc2_samples = [session.query(Sample).filter(Sample.accession == q.ancestor2).one()]
  38. # check if the sample has parents if recessive mutation is selected
  39. # no_parents = [s.accession for s in affected_samples if s.patient.father == None or s.patient.mother == None]
  40. # if len(no_parents) > 0:
  41. # # error out if parent informations are missing
  42. # return render_template('error.html', message=" ".join(["The following samples have incomplete parent information:"] + no_parents))
  43. rows = defaultdict(Row)
  44. if q.query_mode == 'homozygous':
  45. mutations = get_homozygous(affected_sample, anc1_samples, anc2_samples, q)
  46. else:
  47. mutations = get_compound_heterozygous(affected_sample, anc1_samples, anc2_samples, q)
  48. # grouping (call/annotation)s
  49. CallAnnotation = namedtuple("CallAnnotation", "call annotation bed")
  50. for call, annotation in mutations:
  51. gene = annotation.symbol
  52. unit = call.sample.units[0]
  53. # the key to group the entries
  54. key = (gene, annotation.variant.id)
  55. rows[key].add(CallAnnotation(call, annotation, None))
  56. # filter records with too few common genes and sort by gene name
  57. sorted_rows = sorted([row for row in rows.values()], key=lambda row: row.gene.name)
  58. return render_template('mutations-result.html', rows=sorted_rows, query=q, common_genes=[])
  59. def get_homozygous(index_sample, anc1_samples, anc2_samples, q):
  60. """Return a list of calls which are homozygous in index and heterozygous in at least one parent (to account for new mutations on
  61. the other allele), and which are not homozygous in healthy samples."""
  62. assert len(anc1_samples) > 0
  63. assert len(anc2_samples) > 0
  64. anc1_sample = sorted(anc1_samples, key=lambda s: s.id, reverse=True)[0]
  65. anc2_sample = sorted(anc2_samples, key=lambda s: s.id, reverse=True)[0]
  66. index_homo = session.query(Call, Annotation).join(Variant).join(Annotation).join(Sample)\
  67. .filter(Sample.accession==index_sample.accession)\
  68. .filter(not_(Call.is_heterozygous))\
  69. .filter(Annotation.consequence.op('&')(q.consequences_bits)>0)\
  70. .filter(Call.qual > q.affected_qual)
  71. if q.known_sources:
  72. index_homo = index_homo.outerjoin(KnownVariant)\
  73. .filter(or_(\
  74. KnownVariant.variant_id == None,
  75. KnownVariant.allelefreq <= q.dbsnp_allelefreq,
  76. KnownVariant.allelefreq == None))
  77. AncestorCall = aliased(Call)
  78. x = index_homo.join(AncestorCall, Call.variant_id == AncestorCall.variant_id)\
  79. .filter((AncestorCall.sample == anc1_sample) | (AncestorCall.sample == anc2_sample))\
  80. .filter(AncestorCall.is_heterozygous == True)
  81. healthy_homo = session.query(Call).join(Sample).join(Disease)\
  82. .filter((Disease.name=='healthy') | (Call.sample == anc1_sample) | (Call.sample == anc2_sample))\
  83. .filter(not_(Call.is_heterozygous)).subquery()
  84. x = x.outerjoin(healthy_homo, healthy_homo.c.variant_id == Call.variant_id).filter(healthy_homo.c.variant_id == None)
  85. return x.all()
  86. def get_compound_heterozygous(index_sample, anc1_samples, anc2_samples, q):
  87. """Return a list of calls which are homozygous in index and heterozygous in at least one parent (to account for new mutations on
  88. the other allele), and which are not homozygous in healthy samples."""
  89. assert len(anc1_samples) > 0
  90. assert len(anc2_samples) > 0
  91. anc1_sample = sorted(anc1_samples, key=lambda s: s.id, reverse=True)[0]
  92. anc2_sample = sorted(anc2_samples, key=lambda s: s.id, reverse=True)[0]
  93. # interesting = [ ]
  94. ## interesting = [ func.abs(Annotation.splice_dist) <= q.splice_dist ]
  95. ## if q.show_coding_variants:
  96. ## # coding, but not synonymous
  97. ## interesting.append(and_(Annotation.region == VARIANT_REGIONS.CODING, Annotation.type != VARIANT_TYPES.SYNONYMOUS))
  98. ## if q.show_intron_variants:
  99. ## interesting.append(Annotation.region == VARIANT_REGIONS.INTRON)
  100. ## if q.show_utr_variants:
  101. ## interesting.append(Annotation.region == VARIANT_REGIONS.UTR)
  102. index_hetero = session.query(Call, Annotation)\
  103. .join(Variant)\
  104. .join(Annotation)\
  105. .join(Sample)\
  106. .filter(Sample.accession==index_sample.accession)\
  107. .filter(Call.is_heterozygous == True)\
  108. .filter(Annotation.consequence.op('&')(q.consequences_bits)>0)\
  109. .filter((Annotation.eur_maf == None) | (Annotation.eur_maf < q.allelecount_inhouse))\
  110. .filter(Call.qual > q.affected_qual)
  111. if q.known_sources:
  112. index_hetero = index_hetero.outerjoin(KnownVariant)\
  113. .filter(or_(KnownVariant.variant_id == None, KnownVariant.allelefreq <= q.dbsnp_allelefreq, KnownVariant.allelefreq == None))
  114. # fetch alle calls / annotations / gene occuring in index and Ancestor1
  115. Ancestor1Call = aliased(Call)
  116. x = index_hetero.join(Ancestor1Call, Call.variant_id == Ancestor1Call.variant_id)\
  117. .filter(Ancestor1Call.sample==anc1_sample)\
  118. .filter(Ancestor1Call.is_heterozygous == True).all()
  119. # fetch all calls / annotations / gene occuring in index and Ancestor2
  120. Ancestor2Call = aliased(Call)
  121. y = index_hetero.join(Ancestor2Call, Call.variant_id == Ancestor2Call.variant_id).filter(Ancestor2Call.sample==anc2_sample).filter(Ancestor2Call.is_heterozygous == True).all()
  122. # filter these calls where variant of father = variant of Ancestor. These mutations should not be relevant because in this situation
  123. # index
  124. # ----A------
  125. # -------B---
  126. #
  127. # Ancestor1
  128. # ----A------
  129. # -------B---
  130. # Ancestor2
  131. # ----A------
  132. # -----------
  133. # the Ancestor1 would also have the disease
  134. forbidden_variants = set(call.variant_id for call, annotation in x) & set(call.variant_id for call, annotation in y)
  135. valid_genes = set(annotation.symbol for call, annotation in x if call.variant_id not in forbidden_variants) & set(annotation.symbol for call, annotation in y if call.variant_id not in forbidden_variants)
  136. return [(call, annotation) for call, annotation in x+y if annotation.symbol in valid_genes]