scout-browser 4.102.0__py3-none-any.whl → 4.103.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- scout/adapter/mongo/case.py +26 -122
- scout/adapter/mongo/clinvar.py +91 -25
- scout/adapter/mongo/event.py +0 -47
- scout/adapter/mongo/variant_loader.py +7 -3
- scout/build/variant/variant.py +1 -0
- scout/commands/load/variants.py +1 -1
- scout/commands/update/user.py +87 -49
- scout/constants/__init__.py +3 -0
- scout/constants/clinvar.py +10 -0
- scout/constants/variant_tags.py +18 -0
- scout/demo/NIST.trgt.stranger.vcf.gz +0 -0
- scout/demo/NIST.trgt.stranger.vcf.gz.tbi +0 -0
- scout/demo/__init__.py +1 -0
- scout/models/clinvar.py +86 -0
- scout/parse/variant/coordinates.py +5 -1
- scout/parse/variant/gene.py +5 -9
- scout/parse/variant/genotype.py +66 -42
- scout/parse/variant/variant.py +2 -0
- scout/server/app.py +71 -2
- scout/server/blueprints/alignviewers/templates/alignviewers/igv_viewer.html +2 -0
- scout/server/blueprints/cases/controllers.py +1 -1
- scout/server/blueprints/cases/templates/cases/case_report.html +19 -2
- scout/server/blueprints/cases/templates/cases/utils.html +8 -29
- scout/server/blueprints/clinvar/controllers.py +233 -53
- scout/server/blueprints/clinvar/form.py +38 -1
- scout/server/blueprints/clinvar/static/form_style.css +8 -1
- scout/server/blueprints/clinvar/templates/clinvar/clinvar_onc_submissions.html +200 -0
- scout/server/blueprints/clinvar/templates/clinvar/clinvar_submissions.html +3 -2
- scout/server/blueprints/clinvar/templates/clinvar/components.html +198 -0
- scout/server/blueprints/clinvar/templates/clinvar/multistep_add_onc_variant.html +187 -0
- scout/server/blueprints/clinvar/templates/clinvar/multistep_add_variant.html +9 -348
- scout/server/blueprints/clinvar/templates/clinvar/scripts.html +193 -0
- scout/server/blueprints/clinvar/views.py +90 -13
- scout/server/blueprints/institutes/controllers.py +44 -5
- scout/server/blueprints/institutes/forms.py +1 -0
- scout/server/blueprints/institutes/templates/overview/gene_variants.html +15 -6
- scout/server/blueprints/institutes/templates/overview/institute_sidebar.html +28 -2
- scout/server/blueprints/institutes/templates/overview/utils.html +1 -1
- scout/server/blueprints/institutes/views.py +17 -4
- scout/server/blueprints/mme/templates/mme/mme_submissions.html +2 -2
- scout/server/blueprints/omics_variants/templates/omics_variants/outliers.html +2 -2
- scout/server/blueprints/variant/controllers.py +1 -1
- scout/server/blueprints/variant/templates/variant/cancer-variant.html +2 -1
- scout/server/blueprints/variant/templates/variant/components.html +0 -1
- scout/server/blueprints/variant/templates/variant/sv-variant.html +2 -1
- scout/server/blueprints/variant/templates/variant/variant.html +2 -2
- scout/server/blueprints/variant/templates/variant/variant_details.html +32 -24
- scout/server/blueprints/variants/templates/variants/cancer-variants.html +5 -3
- scout/server/blueprints/variants/templates/variants/str-variants.html +4 -1
- scout/server/blueprints/variants/templates/variants/sv-variants.html +3 -3
- scout/server/blueprints/variants/templates/variants/utils.html +4 -0
- scout/server/blueprints/variants/templates/variants/variants.html +4 -4
- scout/server/extensions/clinvar_extension.py +2 -2
- scout/server/templates/layout.html +1 -1
- {scout_browser-4.102.0.dist-info → scout_browser-4.103.0.dist-info}/METADATA +1 -1
- {scout_browser-4.102.0.dist-info → scout_browser-4.103.0.dist-info}/RECORD +59 -53
- {scout_browser-4.102.0.dist-info → scout_browser-4.103.0.dist-info}/WHEEL +0 -0
- {scout_browser-4.102.0.dist-info → scout_browser-4.103.0.dist-info}/entry_points.txt +0 -0
- {scout_browser-4.102.0.dist-info → scout_browser-4.103.0.dist-info}/licenses/LICENSE +0 -0
scout/constants/__init__.py
CHANGED
@@ -36,6 +36,7 @@ from .clinvar import (
|
|
36
36
|
CONDITION_PREFIX,
|
37
37
|
GERMLINE_CLASSIF_TERMS,
|
38
38
|
MULTIPLE_CONDITION_EXPLANATION,
|
39
|
+
ONCOGENIC_CLASSIF_TERMS,
|
39
40
|
)
|
40
41
|
from .clnsig import CLINSIG_MAP, ONC_CLNSIG, REV_CLINSIG_MAP, TRUSTED_REVSTAT_LEVEL
|
41
42
|
from .disease_parsing import (
|
@@ -100,8 +101,10 @@ from .variant_tags import (
|
|
100
101
|
MANUAL_RANK_OPTIONS,
|
101
102
|
MOSAICISM_OPTIONS,
|
102
103
|
OUTLIER_TYPES,
|
104
|
+
REVEL_SCORE_LABEL_COLOR_MAP,
|
103
105
|
SPIDEX_HUMAN,
|
104
106
|
SPIDEX_LEVELS,
|
107
|
+
SPLICEAI_SCORE_LABEL_COLOR_MAP,
|
105
108
|
SV_TYPES,
|
106
109
|
VARIANT_CALL,
|
107
110
|
VARIANT_FILTERS,
|
scout/constants/clinvar.py
CHANGED
@@ -1,8 +1,12 @@
|
|
1
|
+
from scout.constants.clnsig import ONC_CLNSIG
|
2
|
+
|
1
3
|
CLINVAR_API_URL_DEFAULT = "https://submit.ncbi.nlm.nih.gov/api/v1/submissions/"
|
2
4
|
PRECLINVAR_URL = "https://preclinvar.scilifelab.se"
|
3
5
|
|
4
6
|
ASSERTION_METHOD = "ACMG Guidelines, 2015"
|
5
7
|
ASSERTION_METHOD_CIT = "PMID:25741868"
|
8
|
+
ASSERTION_ONC_ONC_DB = "PubMed"
|
9
|
+
ASSERTION_CRITERIA_ONC_ID = "36063163"
|
6
10
|
NOT_PROVIDED = "not provided"
|
7
11
|
|
8
12
|
# Header used to create the Variant .CSV file for the manual ClinVar submission
|
@@ -95,6 +99,8 @@ GERMLINE_CLASSIF_TERMS = [
|
|
95
99
|
NOT_PROVIDED,
|
96
100
|
]
|
97
101
|
|
102
|
+
ONCOGENIC_CLASSIF_TERMS = ONC_CLNSIG
|
103
|
+
|
98
104
|
REVSTAT_TERMS = {
|
99
105
|
"conflicting_interpretations",
|
100
106
|
"multiple_submitters",
|
@@ -187,6 +193,10 @@ CONDITION_PREFIX = {
|
|
187
193
|
"Orphanet": "ORPHA",
|
188
194
|
}
|
189
195
|
|
196
|
+
CONDITION_DBS_API = ["HP", "MedGen", "MeSH", "MONDO", "OMIM", "Orphanet"]
|
197
|
+
|
190
198
|
CLINVAR_ASSERTION_METHOD_CIT_DB_OPTIONS = {"DOI", "pmc", "PMID"}
|
199
|
+
CITATION_DBS_API = ["PubMed", "BookShelf", "DOI", "pmc"]
|
191
200
|
|
192
201
|
MULTIPLE_CONDITION_EXPLANATION = ["Novel disease", "Uncertain", "Co-occurring"]
|
202
|
+
PRESENCE_IN_NORMAL_TISSUE = ["present", "absent", "not tested"]
|
scout/constants/variant_tags.py
CHANGED
@@ -19,6 +19,22 @@ CONSERVATION = {
|
|
19
19
|
"phylop": {"conserved_min": 2.5, "conserved_max": 100},
|
20
20
|
}
|
21
21
|
|
22
|
+
REVEL_SCORE_LABEL_COLOR_MAP = {
|
23
|
+
(0.932, 1.000): {"label": "Strong pathogenic", "color": "danger"},
|
24
|
+
(0.773, 0.932): {"label": "Moderate pathogenic", "color": "red"},
|
25
|
+
(0.644, 0.773): {"label": "Supporting pathogenic", "color": "orange"},
|
26
|
+
(0.290, 0.644): {"label": "Uncertain significance", "color": "secondary"},
|
27
|
+
(0.183, 0.290): {"label": "Supporting benign", "color": "info"},
|
28
|
+
(0.016, 0.183): {"label": "Moderate benign", "color": "info"},
|
29
|
+
(0.0, 0.016): {"label": "Strong to very strong benign", "color": "success"},
|
30
|
+
}
|
31
|
+
|
32
|
+
SPLICEAI_SCORE_LABEL_COLOR_MAP = {
|
33
|
+
(0.20, 1.00): {"label": "Predicted impact on splicing", "color": "warning"},
|
34
|
+
(0.10, 0.20): {"label": "Not informative", "color": "secondary"},
|
35
|
+
(0.00, 0.10): {"label": "No impact on splicing", "color": "success"},
|
36
|
+
}
|
37
|
+
|
22
38
|
FEATURE_TYPES = (
|
23
39
|
"exonic",
|
24
40
|
"splicing",
|
@@ -535,6 +551,7 @@ CALLERS = {
|
|
535
551
|
{"id": "deepvariant", "name": "DeepVariant"},
|
536
552
|
_FREEBAYES,
|
537
553
|
_GATK,
|
554
|
+
{"id": "mutect2", "name": "MuTect2"},
|
538
555
|
{"id": "samtools", "name": "SAMtools"},
|
539
556
|
],
|
540
557
|
"cancer": [
|
@@ -562,6 +579,7 @@ CALLERS = {
|
|
562
579
|
{"id": "cnvpytor", "name": "CNVpytor"},
|
563
580
|
{"id": "delly", "name": "Delly"},
|
564
581
|
_GATK,
|
582
|
+
{"id": "gcnvcaller", "name": "GATK GermlineCNV"},
|
565
583
|
{"id": "hificnv", "name": "HiFiCNV"},
|
566
584
|
_MANTA,
|
567
585
|
{"id": "severus", "name": "Severus"},
|
Binary file
|
Binary file
|
scout/demo/__init__.py
CHANGED
@@ -25,6 +25,7 @@ gene_fusion_report_path = str(files(BASE_PATH).joinpath("draw-fusions-example.pd
|
|
25
25
|
clinical_snv_path = str(files(BASE_PATH).joinpath("643594.clinical.vcf.gz"))
|
26
26
|
clinical_sv_path = str(files(BASE_PATH).joinpath("643594.clinical.SV.vcf.gz"))
|
27
27
|
clinical_str_path = str(files(BASE_PATH).joinpath("643594.clinical.str.stranger.vcf.gz"))
|
28
|
+
str_trgt_path = str(files(BASE_PATH).joinpath("NIST.trgt.stranger.vcf.gz"))
|
28
29
|
clinical_fusion_path = str(files(BASE_PATH).joinpath("fusion_data.vcf"))
|
29
30
|
customannotation_snv_path = str(files(BASE_PATH).joinpath("customannotations_one.vcf.gz"))
|
30
31
|
vep_97_annotated_path = str(
|
scout/models/clinvar.py
CHANGED
@@ -1,6 +1,12 @@
|
|
1
1
|
from datetime import date, datetime
|
2
|
+
from enum import Enum
|
3
|
+
from typing import List, Literal, Optional
|
2
4
|
|
3
5
|
from bson.objectid import ObjectId
|
6
|
+
from pydantic import BaseModel
|
7
|
+
|
8
|
+
from scout.constants import CHROMOSOMES
|
9
|
+
from scout.constants.clinvar import CITATION_DBS_API, ONCOGENIC_CLASSIF_TERMS
|
4
10
|
|
5
11
|
"""Model of the document that gets saved/updated in the clinvar_submission collection
|
6
12
|
for each institute that has cases with ClinVar submission objects"""
|
@@ -76,3 +82,83 @@ clinvar_casedata = {
|
|
76
82
|
"method_purpose": str, # default: "discovery"
|
77
83
|
"reported_at": date,
|
78
84
|
}
|
85
|
+
|
86
|
+
### Models used for oncogenocity submissions via API
|
87
|
+
|
88
|
+
|
89
|
+
CitationDB = Enum("CitationDB", {db.upper(): db for db in CITATION_DBS_API})
|
90
|
+
OncogenicityClassificationDescription = Enum(
|
91
|
+
"OncogenicityClassificationDescription",
|
92
|
+
{term.upper().replace(" ", "_"): term for term in ONCOGENIC_CLASSIF_TERMS},
|
93
|
+
)
|
94
|
+
|
95
|
+
|
96
|
+
class Citation(BaseModel):
|
97
|
+
db: CitationDB
|
98
|
+
id: str
|
99
|
+
|
100
|
+
|
101
|
+
class OncogenicityClassification(BaseModel):
|
102
|
+
oncogenicityClassificationDescription: OncogenicityClassificationDescription
|
103
|
+
dateLastEvaluated: str
|
104
|
+
comment: Optional[str] = None
|
105
|
+
citation: Optional[List[Citation]] = None
|
106
|
+
|
107
|
+
|
108
|
+
class ObservedIn(BaseModel):
|
109
|
+
alleleOrigin: str
|
110
|
+
affectedStatus: str
|
111
|
+
collectionMethod: str
|
112
|
+
numberOfIndividuals: int
|
113
|
+
presenceOfSomaticVariantInNormalTissue: str
|
114
|
+
somaticVariantAlleleFraction: Optional[float] = None
|
115
|
+
|
116
|
+
|
117
|
+
class Gene(BaseModel):
|
118
|
+
symbol: str
|
119
|
+
|
120
|
+
|
121
|
+
Chromosome = Enum(
|
122
|
+
"Chromosome", {c: c for c in CHROMOSOMES}
|
123
|
+
) # ClinVar API accepts only 'MT' chromosome
|
124
|
+
|
125
|
+
|
126
|
+
class Variant(BaseModel):
|
127
|
+
"""It's defined by either coordinates or hgvs."""
|
128
|
+
|
129
|
+
alternateAllele: Optional[str] = None
|
130
|
+
assembly: Optional[Literal["GRCh37", "GRCh38"]] = None
|
131
|
+
chromosome: Optional[Chromosome] = None
|
132
|
+
gene: Optional[List[Gene]] = None
|
133
|
+
hgvs: Optional[str] = None
|
134
|
+
start: Optional[int] = None
|
135
|
+
stop: Optional[int] = None
|
136
|
+
|
137
|
+
|
138
|
+
class VariantSet(BaseModel):
|
139
|
+
variant: List[Variant]
|
140
|
+
|
141
|
+
|
142
|
+
class Condition(BaseModel):
|
143
|
+
db: Optional[str] = None
|
144
|
+
id: Optional[str] = None
|
145
|
+
name: Optional[str] = None
|
146
|
+
|
147
|
+
|
148
|
+
class ConditionSet(BaseModel):
|
149
|
+
condition: List[Condition]
|
150
|
+
|
151
|
+
|
152
|
+
class OncogenicitySubmissionItem(BaseModel):
|
153
|
+
# Field necessary for the API submissions:
|
154
|
+
recordStatus: str
|
155
|
+
oncogenicityClassification: OncogenicityClassification
|
156
|
+
observedIn: List[ObservedIn]
|
157
|
+
variantSet: VariantSet
|
158
|
+
conditionSet: ConditionSet
|
159
|
+
|
160
|
+
# Fields necessary to map the variant to a variant in Scout:
|
161
|
+
institute_id: str
|
162
|
+
case_id: str
|
163
|
+
case_name: str
|
164
|
+
variant_id: str
|
@@ -1,6 +1,6 @@
|
|
1
1
|
"""Code to parse variant coordinates"""
|
2
2
|
|
3
|
-
from scout.constants import BND_ALT_PATTERN, CHR_PATTERN, CYTOBANDS_37, CYTOBANDS_38
|
3
|
+
from scout.constants import BND_ALT_PATTERN, CHR_PATTERN, CYTOBANDS_37, CYTOBANDS_38, SV_TYPES
|
4
4
|
|
5
5
|
|
6
6
|
def get_cytoband_coordinates(chrom, pos, build):
|
@@ -144,6 +144,10 @@ def parse_coordinates(variant, category, build="37"):
|
|
144
144
|
svtype = variant.INFO.get("SVTYPE")
|
145
145
|
if svtype:
|
146
146
|
svtype = svtype.lower()
|
147
|
+
else:
|
148
|
+
alt_type = alt.lstrip("<").rstrip(">").lower()
|
149
|
+
if alt_type in SV_TYPES:
|
150
|
+
svtype = alt_type
|
147
151
|
sub_category = svtype
|
148
152
|
if sub_category == "bnd":
|
149
153
|
end_chrom = get_end_chrom(alt, chrom)
|
scout/parse/variant/gene.py
CHANGED
@@ -48,10 +48,6 @@ def parse_genes(transcripts):
|
|
48
48
|
# List with all genes and their transcripts
|
49
49
|
genes = []
|
50
50
|
|
51
|
-
hgvs_identifier = None
|
52
|
-
canonical_transcript = None
|
53
|
-
exon = None
|
54
|
-
|
55
51
|
# Loop over all genes
|
56
52
|
for gene_id in genes_to_transcripts:
|
57
53
|
# Get the transcripts for a gene
|
@@ -78,8 +74,7 @@ def parse_genes(transcripts):
|
|
78
74
|
hgnc_symbol = transcript["hgnc_symbol"]
|
79
75
|
if not hgvs_identifier:
|
80
76
|
hgvs_identifier = transcript.get("coding_sequence_name")
|
81
|
-
|
82
|
-
canonical_transcript = transcript["transcript_id"]
|
77
|
+
|
83
78
|
if not exon:
|
84
79
|
exon = transcript["exon"]
|
85
80
|
|
@@ -102,10 +97,11 @@ def parse_genes(transcripts):
|
|
102
97
|
most_severe_spliceai_position = transcript["spliceai_delta_position"]
|
103
98
|
spliceai_prediction = transcript["spliceai_prediction"]
|
104
99
|
|
105
|
-
if transcript["is_canonical"]
|
106
|
-
hgvs_identifier = transcript.get("coding_sequence_name")
|
100
|
+
if transcript["is_canonical"]:
|
107
101
|
canonical_transcript = transcript["transcript_id"]
|
108
|
-
|
102
|
+
if transcript.get("coding_sequence_name"):
|
103
|
+
hgvs_identifier = transcript.get("coding_sequence_name")
|
104
|
+
exon = transcript["exon"]
|
109
105
|
|
110
106
|
gene = {
|
111
107
|
"transcripts": gene_transcripts,
|
scout/parse/variant/genotype.py
CHANGED
@@ -18,6 +18,7 @@ Uses 'DV' to describe number of paired ends that supports the event and
|
|
18
18
|
|
19
19
|
"""
|
20
20
|
|
21
|
+
import ast
|
21
22
|
import logging
|
22
23
|
from typing import Dict, List, Optional, Tuple, Union
|
23
24
|
|
@@ -101,7 +102,7 @@ def parse_genotype(variant, ind, pos):
|
|
101
102
|
(_, mc_alt) = _parse_format_entry_trgt_mc(variant, pos)
|
102
103
|
gt_call["alt_mc"] = mc_alt
|
103
104
|
|
104
|
-
(sd_ref, sd_alt) = _parse_format_entry(variant, pos, "SD",
|
105
|
+
(sd_ref, sd_alt) = _parse_format_entry(variant, pos, "SD", int)
|
105
106
|
(ap_ref, ap_alt) = _parse_format_entry(variant, pos, "AP", float)
|
106
107
|
(am_ref, am_alt) = _parse_format_entry(variant, pos, "AM", float)
|
107
108
|
|
@@ -122,6 +123,7 @@ def parse_genotype(variant, ind, pos):
|
|
122
123
|
spanning_alt,
|
123
124
|
flanking_alt,
|
124
125
|
inrepeat_alt,
|
126
|
+
sd_alt,
|
125
127
|
clip5_alt,
|
126
128
|
clip3_alt,
|
127
129
|
)
|
@@ -135,8 +137,10 @@ def parse_genotype(variant, ind, pos):
|
|
135
137
|
spanning_ref,
|
136
138
|
flanking_ref,
|
137
139
|
inrepeat_ref,
|
140
|
+
sd_ref,
|
138
141
|
spanning_mei_ref,
|
139
142
|
)
|
143
|
+
|
140
144
|
gt_call["ref_depth"] = ref_depth
|
141
145
|
gt_call["read_depth"] = get_read_depth(variant, pos, alt_depth, ref_depth)
|
142
146
|
gt_call["alt_frequency"] = get_alt_frequency(variant, pos)
|
@@ -202,7 +206,7 @@ def get_ffpm_info(variant: cyvcf2.Variant, pos: Dict[str, int]) -> Optional[int]
|
|
202
206
|
pass
|
203
207
|
|
204
208
|
|
205
|
-
def get_paired_ends(variant, pos):
|
209
|
+
def get_paired_ends(variant: cyvcf2.Variant, pos: int) -> tuple:
|
206
210
|
"""Get paired ends"""
|
207
211
|
# SV specific
|
208
212
|
paired_end_alt = None
|
@@ -224,9 +228,10 @@ def get_paired_ends(variant, pos):
|
|
224
228
|
values = variant.format("PR")[pos]
|
225
229
|
try:
|
226
230
|
alt_value = int(values[1])
|
227
|
-
ref_value = int(values[0])
|
228
231
|
if alt_value >= 0:
|
229
232
|
paired_end_alt = alt_value
|
233
|
+
|
234
|
+
ref_value = int(values[0])
|
230
235
|
if ref_value >= 0:
|
231
236
|
paired_end_ref = ref_value
|
232
237
|
except ValueError as _ignore_error:
|
@@ -317,32 +322,30 @@ def get_read_depth(variant, pos, alt_depth, ref_depth):
|
|
317
322
|
|
318
323
|
|
319
324
|
def get_ref_depth(
|
320
|
-
variant,
|
321
|
-
pos,
|
322
|
-
paired_end_ref,
|
323
|
-
split_read_ref,
|
324
|
-
spanning_ref,
|
325
|
-
flanking_ref,
|
326
|
-
inrepeat_ref,
|
327
|
-
|
328
|
-
|
325
|
+
variant: cyvcf2.Variant,
|
326
|
+
pos: int,
|
327
|
+
paired_end_ref: int,
|
328
|
+
split_read_ref: int,
|
329
|
+
spanning_ref: int,
|
330
|
+
flanking_ref: int,
|
331
|
+
inrepeat_ref: int,
|
332
|
+
sd_ref: int,
|
333
|
+
spanning_mei_ref: int,
|
334
|
+
) -> int:
|
329
335
|
"""Get reference read depth"""
|
330
336
|
ref_depth = int(variant.gt_ref_depths[pos])
|
331
337
|
if ref_depth != -1:
|
332
338
|
return ref_depth
|
333
339
|
|
334
340
|
REF_ITEMS_LIST: List[tuple] = [
|
341
|
+
(sd_ref,),
|
335
342
|
(paired_end_ref, split_read_ref),
|
336
343
|
(spanning_ref, flanking_ref, inrepeat_ref),
|
337
344
|
]
|
338
345
|
|
339
|
-
for
|
340
|
-
if
|
341
|
-
|
342
|
-
ref_depth = 0
|
343
|
-
for item in list:
|
344
|
-
if item:
|
345
|
-
ref_depth += item
|
346
|
+
for items in REF_ITEMS_LIST:
|
347
|
+
if any(item is not None for item in items):
|
348
|
+
ref_depth = sum(item for item in items if item)
|
346
349
|
|
347
350
|
if spanning_mei_ref:
|
348
351
|
ref_depth += spanning_mei_ref
|
@@ -350,16 +353,17 @@ def get_ref_depth(
|
|
350
353
|
|
351
354
|
|
352
355
|
def get_alt_depth(
|
353
|
-
variant,
|
354
|
-
pos,
|
355
|
-
paired_end_alt,
|
356
|
-
split_read_alt,
|
357
|
-
spanning_alt,
|
358
|
-
flanking_alt,
|
359
|
-
inrepeat_alt,
|
360
|
-
|
361
|
-
|
362
|
-
|
356
|
+
variant: cyvcf2.Variant,
|
357
|
+
pos: int,
|
358
|
+
paired_end_alt: int,
|
359
|
+
split_read_alt: int,
|
360
|
+
spanning_alt: int,
|
361
|
+
flanking_alt: int,
|
362
|
+
inrepeat_alt: int,
|
363
|
+
sd_alt: int,
|
364
|
+
clip5_alt: int,
|
365
|
+
clip3_alt: int,
|
366
|
+
) -> int:
|
363
367
|
"""Get alternative read depth"""
|
364
368
|
alt_depth = int(variant.gt_alt_depths[pos])
|
365
369
|
if alt_depth != -1:
|
@@ -369,19 +373,15 @@ def get_alt_depth(
|
|
369
373
|
alt_depth = int(variant.format("VD")[pos][0])
|
370
374
|
|
371
375
|
ALT_ITEMS_LIST: List[tuple] = [
|
376
|
+
(sd_alt,),
|
372
377
|
(paired_end_alt, split_read_alt),
|
373
378
|
(clip5_alt, clip3_alt),
|
374
379
|
(spanning_alt, flanking_alt, inrepeat_alt),
|
375
380
|
]
|
376
381
|
|
377
|
-
for
|
378
|
-
if
|
379
|
-
|
380
|
-
alt_depth = 0
|
381
|
-
for item in list:
|
382
|
-
if item:
|
383
|
-
alt_depth += item
|
384
|
-
|
382
|
+
for items in ALT_ITEMS_LIST:
|
383
|
+
if any(item is not None for item in items):
|
384
|
+
alt_depth = sum(item for item in items if item)
|
385
385
|
return alt_depth
|
386
386
|
|
387
387
|
|
@@ -430,7 +430,13 @@ def _parse_format_entry(
|
|
430
430
|
alt = None
|
431
431
|
if format_entry_name in variant.FORMAT:
|
432
432
|
try:
|
433
|
-
|
433
|
+
requested_format_entry = variant.format(format_entry_name)[pos]
|
434
|
+
|
435
|
+
values = (
|
436
|
+
split_values(requested_format_entry)
|
437
|
+
if type(requested_format_entry) is str
|
438
|
+
else requested_format_entry
|
439
|
+
)
|
434
440
|
|
435
441
|
ref_value = None
|
436
442
|
alt_value = None
|
@@ -440,15 +446,31 @@ def _parse_format_entry(
|
|
440
446
|
alt_value = (number_format)(values[1])
|
441
447
|
if len(values) == 1:
|
442
448
|
alt_value = (number_format)(values[0])
|
443
|
-
if ref_value >= 0:
|
449
|
+
if ref_value and ref_value >= 0:
|
444
450
|
ref = ref_value
|
445
|
-
if alt_value >= 0:
|
451
|
+
if alt_value and alt_value >= 0:
|
446
452
|
alt = alt_value
|
447
453
|
except (ValueError, TypeError) as _ignore_error:
|
448
454
|
pass
|
449
455
|
return (ref, alt)
|
450
456
|
|
451
457
|
|
458
|
+
def _get_pathologic_struc(variant: cyvcf2.Variant) -> Optional[list]:
|
459
|
+
"""Check for a PathologicStruc on the variant. If not present, return None.
|
460
|
+
If present, and in string format, convert to a list of ints.
|
461
|
+
If it is already parsed to a list by a later improvement in the parser,
|
462
|
+
simply return it.
|
463
|
+
"""
|
464
|
+
|
465
|
+
pathologic_struc_entry = variant.INFO.get("PathologicStruc", None)
|
466
|
+
if not pathologic_struc_entry:
|
467
|
+
return pathologic_struc_entry
|
468
|
+
if type(pathologic_struc_entry) is str:
|
469
|
+
return ast.literal_eval(pathologic_struc_entry)
|
470
|
+
if type(pathologic_struc_entry) is list:
|
471
|
+
return pathologic_struc_entry
|
472
|
+
|
473
|
+
|
452
474
|
def _parse_format_entry_trgt_mc(variant: cyvcf2.Variant, pos: int):
|
453
475
|
"""Parse genotype entry for TRGT FORMAT MC
|
454
476
|
|
@@ -477,14 +499,16 @@ def _parse_format_entry_trgt_mc(variant: cyvcf2.Variant, pos: int):
|
|
477
499
|
if allele == 0:
|
478
500
|
ref_idx = idx
|
479
501
|
|
480
|
-
pathologic_struc = variant
|
481
|
-
|
502
|
+
pathologic_struc = _get_pathologic_struc(variant)
|
503
|
+
|
482
504
|
for idx, allele in enumerate(mc.split(",")):
|
505
|
+
|
483
506
|
mcs = allele.split("_")
|
484
507
|
|
485
508
|
if len(mcs) > 1:
|
486
509
|
pathologic_mcs = pathologic_struc or range(len(mcs))
|
487
510
|
|
511
|
+
pathologic_counts = 0
|
488
512
|
for index, count in enumerate(mcs):
|
489
513
|
if index in pathologic_mcs:
|
490
514
|
pathologic_counts += int(count)
|
scout/parse/variant/variant.py
CHANGED
@@ -82,6 +82,8 @@ def parse_variant(
|
|
82
82
|
variant_type=variant_type,
|
83
83
|
),
|
84
84
|
"rank_score": parse_rank_score(variant.INFO.get("RankScore", ""), genmod_key) or 0,
|
85
|
+
"norm_rank_score": parse_rank_score(variant.INFO.get("RankScoreNormalized", ""), genmod_key)
|
86
|
+
or 0,
|
85
87
|
"genetic_models": parse_genetic_models(variant.INFO.get("GeneticModels"), genmod_key),
|
86
88
|
"str_swegen_mean": call_safe(float, variant.INFO.get("SweGenMean")),
|
87
89
|
"str_swegen_std": call_safe(float, variant.INFO.get("SweGenStd")),
|
scout/server/app.py
CHANGED
@@ -4,7 +4,7 @@ import logging
|
|
4
4
|
import os
|
5
5
|
import re
|
6
6
|
from datetime import timedelta
|
7
|
-
from typing import Dict, Union
|
7
|
+
from typing import Dict, List, Optional, Union
|
8
8
|
from urllib.parse import parse_qsl, unquote, urlsplit
|
9
9
|
|
10
10
|
from flask import Flask, redirect, request, url_for
|
@@ -14,7 +14,11 @@ from markdown import markdown as python_markdown
|
|
14
14
|
from markupsafe import Markup
|
15
15
|
|
16
16
|
from scout import __version__
|
17
|
-
from scout.constants import
|
17
|
+
from scout.constants import (
|
18
|
+
REVEL_SCORE_LABEL_COLOR_MAP,
|
19
|
+
SPIDEX_HUMAN,
|
20
|
+
SPLICEAI_SCORE_LABEL_COLOR_MAP,
|
21
|
+
)
|
18
22
|
from scout.log import init_log
|
19
23
|
|
20
24
|
from . import extensions
|
@@ -207,6 +211,9 @@ def register_blueprints(app):
|
|
207
211
|
|
208
212
|
|
209
213
|
def register_filters(app):
|
214
|
+
"""Creates methods that can be invoked from jinja2 or views/controllers, given that they are included app.custom_filters."""
|
215
|
+
app.custom_filters = type("CustomFilters", (), {})() # Create an empty object as namespace
|
216
|
+
|
210
217
|
@app.template_filter()
|
211
218
|
def human_longint(value: Union[int, str]) -> str:
|
212
219
|
"""Convert a long integers int or string representation into a human easily readable number."""
|
@@ -226,6 +233,27 @@ def register_filters(app):
|
|
226
233
|
return "medium"
|
227
234
|
return "high"
|
228
235
|
|
236
|
+
@app.template_filter()
|
237
|
+
def get_label_or_color_by_score(
|
238
|
+
score: float,
|
239
|
+
map: str,
|
240
|
+
map_key: str,
|
241
|
+
) -> str:
|
242
|
+
"""Return a label or color for a given score based on predefined score ranges from the provided items_map."""
|
243
|
+
SCORE_ITEM_MAPS = {
|
244
|
+
"revel": REVEL_SCORE_LABEL_COLOR_MAP,
|
245
|
+
"spliceai": SPLICEAI_SCORE_LABEL_COLOR_MAP,
|
246
|
+
}
|
247
|
+
for (low, high), info in SCORE_ITEM_MAPS[map].items():
|
248
|
+
if low <= score <= high:
|
249
|
+
return info[map_key]
|
250
|
+
|
251
|
+
@app.template_filter()
|
252
|
+
def l2fc_2_fc(l2fc: float) -> float:
|
253
|
+
"""Converts Log2 fold change to fold change."""
|
254
|
+
fc = 2 ** abs(l2fc)
|
255
|
+
return fc if l2fc >= 0 else -1 / fc
|
256
|
+
|
229
257
|
@app.template_filter()
|
230
258
|
def human_decimal(number, ndigits=4):
|
231
259
|
"""Return a standard representation of a decimal number.
|
@@ -281,6 +309,33 @@ def register_filters(app):
|
|
281
309
|
return "COSM" + str(cosmicId)
|
282
310
|
return cosmicId
|
283
311
|
|
312
|
+
@app.template_filter()
|
313
|
+
def format_variant_canonical_transcripts(variant: dict) -> List[str]:
|
314
|
+
"""Formats canonical transcripts for all genes in a variant."""
|
315
|
+
|
316
|
+
lines = set()
|
317
|
+
genes = variant.get("genes") or []
|
318
|
+
|
319
|
+
for gene in genes:
|
320
|
+
transcripts = gene.get("transcripts") or []
|
321
|
+
for tx in transcripts:
|
322
|
+
if not tx.get("is_canonical"):
|
323
|
+
continue
|
324
|
+
canonical_tx = tx.get("transcript_id")
|
325
|
+
protein = tx.get("protein_sequence_name")
|
326
|
+
line_components = [f"{canonical_tx} ({gene.get('hgnc_symbol', '')})"]
|
327
|
+
hgvs = gene.get("hgvs_identifier")
|
328
|
+
if hgvs:
|
329
|
+
line_components.append(hgvs)
|
330
|
+
if protein:
|
331
|
+
line_components.append(protein)
|
332
|
+
|
333
|
+
lines.add(" ".join(line_components))
|
334
|
+
|
335
|
+
return list(lines)
|
336
|
+
|
337
|
+
app.custom_filters.format_variant_canonical_transcripts = format_variant_canonical_transcripts
|
338
|
+
|
284
339
|
@app.template_filter()
|
285
340
|
def upper_na(string):
|
286
341
|
"""
|
@@ -306,6 +361,20 @@ def register_filters(app):
|
|
306
361
|
"""Returns a list after removing any None values from it."""
|
307
362
|
return [item for item in in_list if item is not None]
|
308
363
|
|
364
|
+
@app.template_filter()
|
365
|
+
def spliceai_max(values) -> Optional[float]:
|
366
|
+
"""Returns a list of SpliceAI values, extracting floats only from values like 'score:0.23'."""
|
367
|
+
float_values = []
|
368
|
+
for value in values:
|
369
|
+
if isinstance(value, str):
|
370
|
+
if ":" in value: # Variant hits multiple genes
|
371
|
+
value = value.split(":")[1].strip()
|
372
|
+
if value in [None, "-", "None"]:
|
373
|
+
continue
|
374
|
+
float_values.append(float(value))
|
375
|
+
if float_values:
|
376
|
+
return max(float_values)
|
377
|
+
|
309
378
|
|
310
379
|
def register_tests(app):
|
311
380
|
@app.template_test("existing")
|
@@ -119,6 +119,8 @@
|
|
119
119
|
indexURL: "{{ url_for('alignviewers.remote_static', file=track.indexURL) }}",
|
120
120
|
sourceType: "file",
|
121
121
|
groupBy: "tag:HP",
|
122
|
+
showInsertionText: true,
|
123
|
+
showDeletionText: true,
|
122
124
|
showSoftClips: {{track.show_soft_clips | lower }},
|
123
125
|
format: "{{ track.format }}",
|
124
126
|
height: "{{track.height}}"
|
@@ -371,7 +371,7 @@ def case(
|
|
371
371
|
partial_causatives = _get_partial_causatives(store, institute_obj, case_obj)
|
372
372
|
_populate_assessments(partial_causatives)
|
373
373
|
|
374
|
-
case_obj["clinvar_variants"] = store.
|
374
|
+
case_obj["clinvar_variants"] = store.case_to_clinvars(case_obj["_id"])
|
375
375
|
|
376
376
|
# check for variants submitted to clinVar but not present in suspects for the case
|
377
377
|
clinvar_variants_not_in_suspects = [
|
@@ -700,11 +700,28 @@
|
|
700
700
|
<tbody>
|
701
701
|
<tr>
|
702
702
|
<td>{{ variant.sift_predictions|join(', ') or '-'}}</td>
|
703
|
-
<td>
|
703
|
+
<td>
|
704
|
+
{% if variant.revel %}
|
705
|
+
<span class="badge bg-{{variant.revel|get_label_or_color_by_score('revel', 'color')}}">
|
706
|
+
{{ variant.revel }}
|
707
|
+
</span>
|
708
|
+
{% else %}
|
709
|
+
<span class="badge bg-secondary">–</span>
|
710
|
+
{% endif %}
|
711
|
+
</td>
|
704
712
|
<td>{{ variant.revel_score or '-' }}</td>
|
705
713
|
<td>{{ variant.polyphen_predictions|join(', ') or '-' }}</td>
|
706
714
|
<td>{{ variant.spidex|spidex_human if variant.spidex else none|spidex_human }}</td>
|
707
|
-
<td>
|
715
|
+
<td>
|
716
|
+
{% set spliceai_highest = variant.spliceai_scores | spliceai_max %}
|
717
|
+
{% if spliceai_highest %}
|
718
|
+
<span class="badge bg-{{ spliceai_highest | get_label_or_color_by_score('spliceai', 'color') }}">
|
719
|
+
{{ variant.spliceai_scores|join(', ') }}
|
720
|
+
</span>
|
721
|
+
{% else %}
|
722
|
+
<span class="badge bg-secondary">–</span>
|
723
|
+
{% endif %}
|
724
|
+
</td>
|
708
725
|
</tr>
|
709
726
|
</tbody>
|
710
727
|
</table>
|