scout-browser 4.101.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 +98 -32
- scout/adapter/mongo/event.py +0 -47
- scout/adapter/mongo/hgnc.py +7 -2
- scout/adapter/mongo/omics_variant.py +8 -0
- scout/adapter/mongo/variant_loader.py +12 -4
- 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 +4 -0
- scout/constants/clinvar.py +10 -0
- scout/constants/igv_tracks.py +6 -2
- scout/constants/phenotype.py +1 -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/load/hpo.py +8 -2
- 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/controllers.py +8 -6
- scout/server/blueprints/alignviewers/templates/alignviewers/igv_viewer.html +4 -0
- scout/server/blueprints/alignviewers/templates/alignviewers/utils.html +1 -1
- scout/server/blueprints/cases/controllers.py +57 -29
- scout/server/blueprints/cases/templates/cases/case_report.html +28 -90
- scout/server/blueprints/cases/templates/cases/matchmaker.html +1 -1
- scout/server/blueprints/cases/templates/cases/phenotype.html +1 -1
- scout/server/blueprints/cases/templates/cases/utils.html +34 -53
- scout/server/blueprints/cases/views.py +32 -33
- scout/server/blueprints/clinvar/controllers.py +235 -54
- 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/diagnoses/controllers.py +4 -8
- scout/server/blueprints/diagnoses/templates/diagnoses/diagnoses.html +1 -1
- scout/server/blueprints/diagnoses/templates/diagnoses/disease_term.html +1 -1
- scout/server/blueprints/diagnoses/views.py +2 -2
- scout/server/blueprints/institutes/controllers.py +148 -75
- scout/server/blueprints/institutes/forms.py +1 -0
- scout/server/blueprints/institutes/templates/overview/cases.html +1 -1
- 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/login/controllers.py +2 -1
- scout/server/blueprints/login/views.py +5 -2
- 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/omics_variants/views.py +2 -2
- scout/server/blueprints/phenotypes/controllers.py +15 -2
- scout/server/blueprints/phenotypes/templates/phenotypes/hpo_terms.html +1 -1
- scout/server/blueprints/variant/controllers.py +11 -12
- 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/utils.html +1 -1
- scout/server/blueprints/variant/templates/variant/variant.html +2 -2
- scout/server/blueprints/variant/templates/variant/variant_details.html +100 -84
- scout/server/blueprints/variant/utils.py +25 -0
- scout/server/blueprints/variants/controllers.py +11 -42
- 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/blueprints/variants/views.py +9 -8
- scout/server/config.py +3 -0
- scout/server/extensions/beacon_extension.py +7 -2
- scout/server/extensions/clinvar_extension.py +2 -2
- scout/server/templates/bootstrap_global.html +11 -1
- scout/server/templates/layout.html +6 -1
- scout/server/utils.py +24 -3
- {scout_browser-4.101.0.dist-info → scout_browser-4.103.0.dist-info}/METADATA +1 -1
- {scout_browser-4.101.0.dist-info → scout_browser-4.103.0.dist-info}/RECORD +87 -81
- {scout_browser-4.101.0.dist-info → scout_browser-4.103.0.dist-info}/WHEEL +0 -0
- {scout_browser-4.101.0.dist-info → scout_browser-4.103.0.dist-info}/entry_points.txt +0 -0
- {scout_browser-4.101.0.dist-info → scout_browser-4.103.0.dist-info}/licenses/LICENSE +0 -0
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")
|
@@ -8,7 +8,11 @@ from flask_login import current_user
|
|
8
8
|
|
9
9
|
from scout.constants import CASE_SPECIFIC_TRACKS, HUMAN_REFERENCE, IGV_TRACKS
|
10
10
|
from scout.server.extensions import config_igv_tracks, store
|
11
|
-
from scout.server.utils import
|
11
|
+
from scout.server.utils import (
|
12
|
+
case_append_alignments,
|
13
|
+
find_index,
|
14
|
+
get_case_genome_build,
|
15
|
+
)
|
12
16
|
from scout.utils.ensembl_rest_clients import EnsemblRestApiClient
|
13
17
|
|
14
18
|
LOG = logging.getLogger(__name__)
|
@@ -88,7 +92,8 @@ def make_igv_tracks(
|
|
88
92
|
display_obj["locus"] = "chr{0}:{1}-{2}".format(chromosome, start, stop)
|
89
93
|
|
90
94
|
# Set genome build for displaying alignments:
|
91
|
-
|
95
|
+
|
96
|
+
if get_case_genome_build(case_obj) == "38" or chromosome == "M":
|
92
97
|
build = "38"
|
93
98
|
else:
|
94
99
|
build = "37"
|
@@ -137,10 +142,7 @@ def make_sashimi_tracks(
|
|
137
142
|
"""
|
138
143
|
|
139
144
|
locus = "All"
|
140
|
-
|
141
|
-
build = "38"
|
142
|
-
if "37" in str(case_obj.get("rna_genome_build", "38")):
|
143
|
-
build = "37"
|
145
|
+
build = "37" if "37" in str(case_obj.get("rna_genome_build", "38")) else "38"
|
144
146
|
|
145
147
|
if variant_id:
|
146
148
|
variant_obj = store.variant(document_id=variant_id)
|
@@ -35,6 +35,8 @@
|
|
35
35
|
$(document).ready(function () {
|
36
36
|
var div = $("#igvDiv")[0],
|
37
37
|
options = {
|
38
|
+
loadDefaultGenomes: false,
|
39
|
+
genomeList: "https://raw.githubusercontent.com/igvteam/igv-data/main/genomes/web/genomes.json",
|
38
40
|
showNavigation: true,
|
39
41
|
showRuler: true,
|
40
42
|
{% if display_center_guide %}
|
@@ -117,6 +119,8 @@
|
|
117
119
|
indexURL: "{{ url_for('alignviewers.remote_static', file=track.indexURL) }}",
|
118
120
|
sourceType: "file",
|
119
121
|
groupBy: "tag:HP",
|
122
|
+
showInsertionText: true,
|
123
|
+
showDeletionText: true,
|
120
124
|
showSoftClips: {{track.show_soft_clips | lower }},
|
121
125
|
format: "{{ track.format }}",
|
122
126
|
height: "{{track.height}}"
|
@@ -1,5 +1,5 @@
|
|
1
1
|
{% macro igv_script() %}
|
2
2
|
<link rel="shortcut icon" href="//igv.org/web/img/favicon.ico">
|
3
3
|
<!-- IGV JS-->
|
4
|
-
<script src="https://cdn.jsdelivr.net/npm/igv@3.
|
4
|
+
<script src="https://cdn.jsdelivr.net/npm/igv@3.3.0/dist/igv.min.js" integrity="sha512-U3drIflKJksG0+kiVuqYrQaI9SsloPhKfISeZr8bzVpO/oRFz7hE1vdJirvDyYIfv51CSucKJgcLAdupqitBcQ==" crossorigin="anonymous"></script>
|
5
5
|
{% endmacro %}
|
@@ -22,6 +22,7 @@ from scout.constants import (
|
|
22
22
|
CUSTOM_CASE_REPORTS,
|
23
23
|
DATE_DAY_FORMATTER,
|
24
24
|
GENOME_REGION,
|
25
|
+
HPO_LINK_URL,
|
25
26
|
INHERITANCE_PALETTE,
|
26
27
|
MITODEL_HEADER,
|
27
28
|
MT_COV_STATS_HEADER,
|
@@ -66,6 +67,7 @@ from scout.server.utils import (
|
|
66
67
|
case_has_mt_alignments,
|
67
68
|
case_has_mtdna_report,
|
68
69
|
case_has_rna_tracks,
|
70
|
+
get_case_genome_build,
|
69
71
|
get_case_mito_chromosome,
|
70
72
|
institute_and_case,
|
71
73
|
)
|
@@ -157,7 +159,12 @@ def coverage_report_contents(base_url, institute_obj, case_obj):
|
|
157
159
|
return html_body_content
|
158
160
|
|
159
161
|
|
160
|
-
def _populate_case_groups(
|
162
|
+
def _populate_case_groups(
|
163
|
+
store: MongoAdapter,
|
164
|
+
case_obj: dict,
|
165
|
+
case_groups: Dict[str, List[str]],
|
166
|
+
case_group_label: Dict[str, str],
|
167
|
+
):
|
161
168
|
"""Case groups allow display of information about user linked cases together on one case.
|
162
169
|
Notably variantS lists show shared annotations and alignment views show all alignments
|
163
170
|
available for the group.
|
@@ -178,20 +185,21 @@ def _populate_case_groups(store, case_obj, case_groups, case_group_label):
|
|
178
185
|
case_group_label[group] = store.case_group_label(group)
|
179
186
|
|
180
187
|
|
181
|
-
def _get_partial_causatives(store: MongoAdapter, case_obj:
|
182
|
-
"""
|
183
|
-
|
184
|
-
store(adapter.MongoAdapter)
|
185
|
-
case_obj(models.Case)
|
186
|
-
Returns:
|
187
|
-
partial(list(dict))
|
188
|
+
def _get_partial_causatives(store: MongoAdapter, institute_obj: dict, case_obj: dict) -> List[Dict]:
|
189
|
+
"""Check for partial causatives and associated phenotypes.
|
190
|
+
Return any partial causatives a case has, populated as causative objs.
|
188
191
|
"""
|
189
192
|
|
190
193
|
partial_causatives = []
|
191
194
|
if case_obj.get("partial_causatives"):
|
192
195
|
for var_id, values in case_obj["partial_causatives"].items():
|
196
|
+
variant_obj = store.variant(var_id)
|
197
|
+
if variant_obj:
|
198
|
+
decorated_variant_obj = _get_decorated_var(
|
199
|
+
store, var_obj=variant_obj, institute_obj=institute_obj, case_obj=case_obj
|
200
|
+
)
|
193
201
|
causative_obj = {
|
194
|
-
"variant":
|
202
|
+
"variant": decorated_variant_obj or var_id,
|
195
203
|
"disease_terms": values.get("diagnosis_phenotypes"),
|
196
204
|
"hpo_terms": values.get("phenotype_terms"),
|
197
205
|
}
|
@@ -287,7 +295,7 @@ def bionano_case(store, institute_obj, case_obj) -> Dict:
|
|
287
295
|
return data
|
288
296
|
|
289
297
|
|
290
|
-
def sma_case(store, institute_obj, case_obj):
|
298
|
+
def sma_case(store: MongoAdapter, institute_obj: dict, case_obj: dict) -> dict:
|
291
299
|
"""Preprocess a case for tabular view, SMA."""
|
292
300
|
|
293
301
|
_populate_case_individuals(case_obj)
|
@@ -299,11 +307,31 @@ def sma_case(store, institute_obj, case_obj):
|
|
299
307
|
"case": case_obj,
|
300
308
|
"comments": store.events(institute_obj, case=case_obj, comments=True),
|
301
309
|
"events": _get_events(store, institute_obj, case_obj),
|
302
|
-
"region": GENOME_REGION[case_obj
|
310
|
+
"region": GENOME_REGION[get_case_genome_build(case_obj)],
|
303
311
|
}
|
304
312
|
return data
|
305
313
|
|
306
314
|
|
315
|
+
def _get_suspects_or_causatives(
|
316
|
+
store: MongoAdapter, institute_obj: dict, case_obj: dict, kind: str = "suspects"
|
317
|
+
) -> list:
|
318
|
+
"""Fetch the variant objects for suspects and causatives and decorate them.
|
319
|
+
If no longer available, append variant_id instead."""
|
320
|
+
|
321
|
+
marked_vars = []
|
322
|
+
for variant_id in case_obj.get(kind, []):
|
323
|
+
variant_obj = store.variant(variant_id)
|
324
|
+
if variant_obj:
|
325
|
+
marked_vars.append(
|
326
|
+
_get_decorated_var(
|
327
|
+
store, var_obj=variant_obj, institute_obj=institute_obj, case_obj=case_obj
|
328
|
+
)
|
329
|
+
)
|
330
|
+
else:
|
331
|
+
marked_vars.append(variant_id)
|
332
|
+
return marked_vars
|
333
|
+
|
334
|
+
|
307
335
|
def case(
|
308
336
|
store: MongoAdapter, institute_obj: dict, case_obj: dict, hide_matching: bool = True
|
309
337
|
) -> dict:
|
@@ -331,25 +359,19 @@ def case(
|
|
331
359
|
case_group_label = {}
|
332
360
|
_populate_case_groups(store, case_obj, case_groups, case_group_label)
|
333
361
|
|
334
|
-
|
335
|
-
suspects = [
|
336
|
-
store.variant(variant_id) or variant_id for variant_id in case_obj.get("suspects", [])
|
337
|
-
]
|
362
|
+
suspects = _get_suspects_or_causatives(store, institute_obj, case_obj, "suspects")
|
338
363
|
_populate_assessments(suspects)
|
339
|
-
|
340
|
-
|
341
|
-
]
|
364
|
+
|
365
|
+
causatives = _get_suspects_or_causatives(store, institute_obj, case_obj, "causatives")
|
342
366
|
_populate_assessments(causatives)
|
343
367
|
|
344
|
-
# get evaluated variants
|
345
368
|
evaluated_variants = store.evaluated_variants(case_obj["_id"], case_obj["owner"])
|
346
369
|
_populate_assessments(evaluated_variants)
|
347
370
|
|
348
|
-
|
349
|
-
partial_causatives = _get_partial_causatives(store, case_obj)
|
371
|
+
partial_causatives = _get_partial_causatives(store, institute_obj, case_obj)
|
350
372
|
_populate_assessments(partial_causatives)
|
351
373
|
|
352
|
-
case_obj["clinvar_variants"] = store.
|
374
|
+
case_obj["clinvar_variants"] = store.case_to_clinvars(case_obj["_id"])
|
353
375
|
|
354
376
|
# check for variants submitted to clinVar but not present in suspects for the case
|
355
377
|
clinvar_variants_not_in_suspects = [
|
@@ -367,9 +389,7 @@ def case(
|
|
367
389
|
for hpo_term in itertools.chain(
|
368
390
|
case_obj.get("phenotype_groups") or [], case_obj.get("phenotype_terms") or []
|
369
391
|
):
|
370
|
-
hpo_term["hpo_link"] = "
|
371
|
-
hpo_term["phenotype_id"]
|
372
|
-
)
|
392
|
+
hpo_term["hpo_link"] = f"{HPO_LINK_URL}{hpo_term['phenotype_id']}"
|
373
393
|
|
374
394
|
_set_rank_model_links(case_obj)
|
375
395
|
|
@@ -471,7 +491,7 @@ def case(
|
|
471
491
|
"tissue_types": SAMPLE_SOURCE,
|
472
492
|
"report_types": CUSTOM_CASE_REPORTS,
|
473
493
|
"mme_nodes": matchmaker.connected_nodes,
|
474
|
-
"gens_info": gens.connection_settings(case_obj
|
494
|
+
"gens_info": gens.connection_settings(get_case_genome_build(case_obj)),
|
475
495
|
"display_rerunner": rerunner.connection_settings.get("display", False),
|
476
496
|
"hide_matching": hide_matching,
|
477
497
|
"audits": store.case_events_by_verb(
|
@@ -676,7 +696,9 @@ def case_report_variants(store: MongoAdapter, case_obj: dict, institute_obj: dic
|
|
676
696
|
add_bayesian_acmg_classification(var_obj)
|
677
697
|
add_bayesian_ccv_classification(var_obj)
|
678
698
|
evaluated_variants_by_type[eval_category].append(
|
679
|
-
_get_decorated_var(
|
699
|
+
_get_decorated_var(
|
700
|
+
store, var_obj=var_obj, institute_obj=institute_obj, case_obj=case_obj
|
701
|
+
)
|
680
702
|
)
|
681
703
|
|
682
704
|
for var_obj in store.evaluated_variants(
|
@@ -689,7 +711,9 @@ def case_report_variants(store: MongoAdapter, case_obj: dict, institute_obj: dic
|
|
689
711
|
data["variants"] = evaluated_variants_by_type
|
690
712
|
|
691
713
|
|
692
|
-
def _get_decorated_var(
|
714
|
+
def _get_decorated_var(
|
715
|
+
store: MongoAdapter, var_obj: dict, institute_obj: dict, case_obj: dict
|
716
|
+
) -> dict:
|
693
717
|
"""Decorate a variant object for display using the variant controller"""
|
694
718
|
return variant_decorator(
|
695
719
|
store=store,
|
@@ -719,7 +743,9 @@ def _append_evaluated_variant_by_type(
|
|
719
743
|
add_bayesian_ccv_classification(var_obj)
|
720
744
|
|
721
745
|
evaluated_variants_by_type[eval_category].append(
|
722
|
-
_get_decorated_var(
|
746
|
+
_get_decorated_var(
|
747
|
+
store, var_obj=var_obj, institute_obj=institute_obj, case_obj=case_obj
|
748
|
+
)
|
723
749
|
)
|
724
750
|
|
725
751
|
|
@@ -765,6 +791,7 @@ def case_report_content(store: MongoAdapter, institute_obj: dict, case_obj: dict
|
|
765
791
|
data["genetic_models"] = dict(GENETIC_MODELS)
|
766
792
|
data["report_created_at"] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
|
767
793
|
data["current_scout_version"] = __version__
|
794
|
+
data["hpo_link_url"] = HPO_LINK_URL
|
768
795
|
|
769
796
|
case_report_variants(store, case_obj, institute_obj, data)
|
770
797
|
|
@@ -1395,6 +1422,7 @@ def matchmaker_matches(request, institute_id, case_name):
|
|
1395
1422
|
pat_matches = parse_matches(patient_id, server_resp["content"]["matches"])
|
1396
1423
|
matches[patient_id] = pat_matches
|
1397
1424
|
|
1425
|
+
data["hpo_link_url"] = HPO_LINK_URL
|
1398
1426
|
data["matches"] = matches
|
1399
1427
|
return data
|
1400
1428
|
|