scout-browser 4.90.1__py3-none-any.whl → 4.91__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.
Files changed (62) hide show
  1. scout/__version__.py +1 -1
  2. scout/adapter/mongo/case.py +27 -38
  3. scout/commands/export/variant.py +14 -4
  4. scout/commands/load/panel.py +2 -1
  5. scout/commands/update/panelapp.py +11 -3
  6. scout/commands/view/case.py +2 -2
  7. scout/constants/__init__.py +1 -2
  8. scout/constants/acmg.py +15 -15
  9. scout/constants/case_tags.py +0 -46
  10. scout/constants/clnsig.py +2 -1
  11. scout/constants/gene_tags.py +0 -6
  12. scout/constants/panels.py +16 -0
  13. scout/constants/variants_export.py +2 -0
  14. scout/demo/__init__.py +4 -1
  15. scout/demo/panelapp_panel.json +463 -0
  16. scout/demo/panelapp_panels_reduced.json +37 -0
  17. scout/load/panel.py +3 -142
  18. scout/load/panelapp.py +138 -0
  19. scout/models/case/case_loading_models.py +5 -4
  20. scout/parse/panel.py +3 -117
  21. scout/parse/panelapp.py +112 -0
  22. scout/parse/variant/clnsig.py +26 -21
  23. scout/parse/variant/genotype.py +6 -5
  24. scout/server/blueprints/alignviewers/controllers.py +7 -5
  25. scout/server/blueprints/alignviewers/templates/alignviewers/utils.html +1 -1
  26. scout/server/blueprints/cases/templates/cases/case_sma.html +49 -42
  27. scout/server/blueprints/cases/templates/cases/collapsible_actionbar.html +27 -12
  28. scout/server/blueprints/cases/views.py +18 -7
  29. scout/server/blueprints/clinvar/templates/clinvar/clinvar_submissions.html +7 -7
  30. scout/server/blueprints/clinvar/templates/clinvar/multistep_add_variant.html +2 -2
  31. scout/server/blueprints/dashboard/controllers.py +128 -165
  32. scout/server/blueprints/dashboard/forms.py +3 -13
  33. scout/server/blueprints/dashboard/templates/dashboard/dashboard_general.html +17 -22
  34. scout/server/blueprints/institutes/forms.py +1 -2
  35. scout/server/blueprints/institutes/templates/overview/cases.html +2 -133
  36. scout/server/blueprints/institutes/templates/overview/utils.html +135 -0
  37. scout/server/blueprints/omics_variants/templates/omics_variants/outliers.html +5 -0
  38. scout/server/blueprints/panels/templates/panels/panel.html +5 -1
  39. scout/server/blueprints/panels/templates/panels/panel_pdf_simple.html +5 -1
  40. scout/server/blueprints/variant/controllers.py +6 -1
  41. scout/server/blueprints/variant/templates/variant/buttons.html +11 -10
  42. scout/server/blueprints/variant/templates/variant/components.html +63 -44
  43. scout/server/blueprints/variant/templates/variant/str-variant-reviewer.html +1 -1
  44. scout/server/blueprints/variant/templates/variant/utils.html +38 -10
  45. scout/server/blueprints/variant/templates/variant/variant.html +1 -1
  46. scout/server/blueprints/variants/controllers.py +9 -4
  47. scout/server/blueprints/variants/templates/variants/cancer-sv-variants.html +9 -5
  48. scout/server/blueprints/variants/templates/variants/cancer-variants.html +6 -17
  49. scout/server/blueprints/variants/templates/variants/str-variants.html +2 -2
  50. scout/server/blueprints/variants/templates/variants/sv-variants.html +8 -1
  51. scout/server/blueprints/variants/templates/variants/utils.html +14 -0
  52. scout/server/extensions/__init__.py +2 -0
  53. scout/server/extensions/panelapp_extension.py +75 -0
  54. scout/server/links.py +19 -1
  55. scout/server/utils.py +25 -33
  56. {scout_browser-4.90.1.dist-info → scout_browser-4.91.dist-info}/METADATA +1 -1
  57. {scout_browser-4.90.1.dist-info → scout_browser-4.91.dist-info}/RECORD +61 -57
  58. {scout_browser-4.90.1.dist-info → scout_browser-4.91.dist-info}/WHEEL +1 -1
  59. scout/demo/panelapp_test_panel.json +0 -79
  60. {scout_browser-4.90.1.dist-info → scout_browser-4.91.dist-info}/LICENSE +0 -0
  61. {scout_browser-4.90.1.dist-info → scout_browser-4.91.dist-info}/entry_points.txt +0 -0
  62. {scout_browser-4.90.1.dist-info → scout_browser-4.91.dist-info}/top_level.txt +0 -0
scout/load/panelapp.py ADDED
@@ -0,0 +1,138 @@
1
+ import logging
2
+ import math
3
+ from datetime import datetime
4
+ from typing import Dict, List, Set
5
+
6
+ from click import Abort, progressbar
7
+
8
+ from scout.adapter import MongoAdapter
9
+ from scout.constants.panels import PRESELECTED_PANELAPP_PANEL_TYPE_SLUGS
10
+ from scout.parse.panelapp import parse_panelapp_panel
11
+ from scout.server.extensions import panelapp
12
+
13
+ LOG = logging.getLogger(__name__)
14
+ PANEL_NAME = "PANELAPP-GREEN"
15
+
16
+
17
+ def load_panelapp_panel(
18
+ adapter: MongoAdapter,
19
+ panel_id: str = None,
20
+ institute: str = "cust000",
21
+ confidence: str = "green",
22
+ ):
23
+ """Load PanelApp panels into scout database."""
24
+
25
+ panel_ids = [panel_id]
26
+
27
+ if not panel_id:
28
+ LOG.info("Fetching all panel app panels")
29
+ panel_ids: List[str] = panelapp.get_panel_ids(signed_off=False)
30
+
31
+ ensembl_id_to_hgnc_id_map: Dict[str, int] = adapter.ensembl_to_hgnc_id_mapping()
32
+ hgnc_symbol_to_ensembl_id_map: Dict[int, str] = adapter.hgnc_symbol_ensembl_id_mapping()
33
+
34
+ for _ in panel_ids:
35
+ panel_info: dict = panelapp.get_panel(panel_id)
36
+ parsed_panel = parse_panelapp_panel(
37
+ panel_info=panel_info,
38
+ ensembl_id_to_hgnc_id_map=ensembl_id_to_hgnc_id_map,
39
+ hgnc_symbol_to_ensembl_id_map=hgnc_symbol_to_ensembl_id_map,
40
+ institute=institute,
41
+ confidence=confidence,
42
+ )
43
+
44
+ if len(parsed_panel["genes"]) == 0:
45
+ LOG.warning("Panel %s is missing genes. Skipping.", parsed_panel["display_name"])
46
+ continue
47
+
48
+ adapter.load_panel(parsed_panel=parsed_panel, replace=True)
49
+
50
+
51
+ def get_panelapp_genes(
52
+ adapter: MongoAdapter, institute: str, panel_ids: List[str], types_filter: List[str]
53
+ ) -> Set[tuple]:
54
+ """Parse and collect genes from one or more panelApp panels."""
55
+
56
+ genes = set()
57
+ ensembl_id_to_hgnc_id_map: Dict[str, int] = adapter.ensembl_to_hgnc_id_mapping()
58
+ hgnc_symbol_to_ensembl_id_map: Dict[int, str] = adapter.hgnc_symbol_ensembl_id_mapping()
59
+
60
+ with progressbar(panel_ids, label="Parsing panels", length=len(panel_ids)) as panel_ids:
61
+ for panel_id in panel_ids:
62
+ panel_dict: dict = panelapp.get_panel(panel_id)
63
+ panel_type_slugs = [type["slug"] for type in panel_dict.get("types")]
64
+ # Parse panel only if it's of the expect type(s)
65
+ if not set(types_filter).intersection(panel_type_slugs):
66
+ continue
67
+
68
+ parsed_panel = parse_panelapp_panel(
69
+ panel_info=panel_dict,
70
+ ensembl_id_to_hgnc_id_map=ensembl_id_to_hgnc_id_map,
71
+ hgnc_symbol_to_ensembl_id_map=hgnc_symbol_to_ensembl_id_map,
72
+ institute=institute,
73
+ confidence="green",
74
+ )
75
+ genes.update(
76
+ {(gene["hgnc_id"], gene["hgnc_symbol"]) for gene in parsed_panel.get("genes")}
77
+ )
78
+
79
+ return genes
80
+
81
+
82
+ def load_panelapp_green_panel(adapter: MongoAdapter, institute: str, force: bool, signed_off: bool):
83
+ """Load/Update the panel containing all Panelapp Green genes."""
84
+
85
+ def parse_types_filter(types_filter: str, available_types: List[str]) -> List[str]:
86
+ """Translate panel type input from users to panel type slugs."""
87
+ if not types_filter:
88
+ return PRESELECTED_PANELAPP_PANEL_TYPE_SLUGS
89
+ index_list = [int(typeint) - 1 for typeint in types_filter.replace(" ", "").split(",")]
90
+ return [available_types[i] for i in index_list]
91
+
92
+ # check and set panel version
93
+ old_panel = adapter.gene_panel(panel_id=PANEL_NAME)
94
+ green_panel = {
95
+ "panel_name": PANEL_NAME,
96
+ "display_name": "PanelApp Green Genes",
97
+ "institute": institute,
98
+ "version": float(math.floor(old_panel["version"]) + 1) if old_panel else 1.0,
99
+ "date": datetime.now(),
100
+ }
101
+
102
+ LOG.info("Fetching all PanelApp panels")
103
+
104
+ panel_ids = panelapp.get_panel_ids(signed_off=signed_off)
105
+ LOG.info(f"\n\nQuery returned {len(panel_ids)} panels\n")
106
+ LOG.info("Panels have the following associated types:")
107
+ available_types: List[str] = panelapp.get_panel_types()
108
+ for number, type in enumerate(available_types, 1):
109
+ LOG.info(f"{number}: {type}")
110
+ preselected_options_idx: List[str] = [
111
+ str(available_types.index(presel) + 1)
112
+ for presel in PRESELECTED_PANELAPP_PANEL_TYPE_SLUGS
113
+ if presel in available_types
114
+ ]
115
+ types_filter: str = input(
116
+ f"Please provide a comma-separated list of types you'd like to use to build your panel (leave blank to use the following types:{', '.join(preselected_options_idx)}): "
117
+ )
118
+ types_filter: List[str] = parse_types_filter(
119
+ types_filter=types_filter, available_types=available_types
120
+ )
121
+ LOG.info(f"Collecting green genes from panels of type: {types_filter}")
122
+ green_panel["description"] = (
123
+ f"This panel contains green genes from {'signed off ' if signed_off else ''}panels of the following types: {', '.join(types_filter)}"
124
+ )
125
+ genes: Set[tuple] = get_panelapp_genes(
126
+ adapter=adapter, institute=institute, panel_ids=panel_ids, types_filter=types_filter
127
+ )
128
+ green_panel["genes"] = [{"hgnc_id": tup[0], "hgnc_symbol": tup[1]} for tup in genes]
129
+
130
+ # Do not update panel if new version contains less genes and force flag is False
131
+ if old_panel and len(old_panel.get("genes")) > len(green_panel["genes"]):
132
+ LOG.warning(
133
+ f"This new version of PANELAPP-GREEN contains less genes (n={len(green_panel['genes'])}) than the previous one (n={len(old_panel['genes'])})"
134
+ )
135
+ if force is False:
136
+ LOG.error("Aborting. Please use the force flag -f to update the panel anyway")
137
+ return
138
+ adapter.load_panel(parsed_panel=green_panel, replace=True)
@@ -557,10 +557,11 @@ class CaseLoader(BaseModel):
557
557
  images=custom_images.str_variants_images
558
558
  )
559
559
 
560
- for key, images in custom_images.case_images.items():
561
- custom_images.case_images[key] = set_custom_images(
562
- images=custom_images.case_images[key]
563
- )
560
+ if custom_images.case_images:
561
+ for key, images in custom_images.case_images.items():
562
+ custom_images.case_images[key] = set_custom_images(
563
+ images=custom_images.case_images[key]
564
+ )
564
565
 
565
566
  return custom_images
566
567
 
scout/parse/panel.py CHANGED
@@ -2,15 +2,9 @@
2
2
 
3
3
  import logging
4
4
  from datetime import datetime
5
- from typing import Dict, List, Optional
6
-
7
- from scout.constants import (
8
- INCOMPLETE_PENETRANCE_MAP,
9
- MODELS_MAP,
10
- PANEL_GENE_INFO_MODELS,
11
- PANEL_GENE_INFO_TRANSCRIPTS,
12
- PANELAPP_CONFIDENCE_EXCLUDE,
13
- )
5
+ from typing import List
6
+
7
+ from scout.constants import PANEL_GENE_INFO_MODELS, PANEL_GENE_INFO_TRANSCRIPTS
14
8
  from scout.utils.date import get_date
15
9
  from scout.utils.handle import get_file_handle
16
10
 
@@ -266,114 +260,6 @@ def parse_gene_panel(
266
260
  return gene_panel
267
261
 
268
262
 
269
- def parse_panel_app_gene(
270
- app_gene: dict,
271
- ensembl_gene_hgnc_id_map: Dict[str, int],
272
- hgnc_symbol_ensembl_gene_map: Dict[str, str],
273
- confidence: str,
274
- ) -> dict:
275
- """Parse a panel app-formatted gene."""
276
-
277
- gene_info = {}
278
- confidence_level = app_gene["LevelOfConfidence"]
279
- # Return empty gene if not confident gene
280
- if confidence_level in PANELAPP_CONFIDENCE_EXCLUDE[confidence]:
281
- return gene_info
282
-
283
- hgnc_symbol = app_gene["GeneSymbol"]
284
-
285
- ensembl_ids = app_gene["EnsembleGeneIds"]
286
-
287
- if not ensembl_ids: # This gene is probably tagged as ensembl_ids_known_missing on PanelApp
288
- if hgnc_symbol in hgnc_symbol_ensembl_gene_map:
289
- LOG.warning(
290
- f"PanelApp gene {hgnc_symbol} does not contain Ensembl IDs. Using Ensembl IDs from internal gene collection instead."
291
- )
292
- ensembl_ids = [hgnc_symbol_ensembl_gene_map[hgnc_symbol]]
293
- else:
294
- LOG.warning(
295
- f"PanelApp gene {hgnc_symbol} does not contain Ensembl IDs and gene symbol does not correspond to a gene in scout."
296
- )
297
-
298
- hgnc_ids = set(
299
- ensembl_gene_hgnc_id_map.get(ensembl_id)
300
- for ensembl_id in ensembl_ids
301
- if ensembl_gene_hgnc_id_map.get(ensembl_id)
302
- )
303
- if not hgnc_ids:
304
- LOG.warning("Gene %s does not exist in database. Skipping gene...", hgnc_symbol)
305
- return gene_info
306
-
307
- if len(hgnc_ids) > 1:
308
- LOG.warning("Gene %s has unclear identifier. Choose random id", hgnc_symbol)
309
-
310
- gene_info["hgnc_symbol"] = hgnc_symbol
311
- for hgnc_id in hgnc_ids:
312
- gene_info["hgnc_id"] = hgnc_id
313
-
314
- gene_info["reduced_penetrance"] = INCOMPLETE_PENETRANCE_MAP.get(app_gene["Penetrance"])
315
-
316
- inheritance_models = []
317
- for model in MODELS_MAP.get(app_gene["ModeOfInheritance"], []):
318
- inheritance_models.append(model)
319
-
320
- gene_info["inheritance_models"] = inheritance_models
321
-
322
- return gene_info
323
-
324
-
325
- def parse_panel_app_panel(
326
- panel_info: dict,
327
- ensembl_gene_hgnc_id_map: Dict[str, int],
328
- hgnc_symbol_ensembl_gene_map: Dict[str, str],
329
- institute: Optional[str] = "cust000",
330
- panel_type: Optional[str] = "clinical",
331
- confidence: Optional[str] = "green",
332
- ) -> dict:
333
- """Parse a PanelApp panel
334
-
335
- Args:
336
- panel_info(dict)
337
- hgnc_map(dict): Map from symbol to hgnc ids
338
- institute(str)
339
- panel_type(str)
340
- confidence(str): enum green|amber|red
341
-
342
- Returns:
343
- gene_panel(dict)
344
- """
345
- date_format = "%Y-%m-%dT%H:%M:%S.%f"
346
-
347
- gene_panel = {}
348
- gene_panel["version"] = float(panel_info["version"])
349
- gene_panel["date"] = get_date(panel_info["Created"][:-1], date_format=date_format)
350
- gene_panel["display_name"] = " - ".join(
351
- [panel_info["SpecificDiseaseName"], f"[{confidence.upper()}]"]
352
- )
353
- gene_panel["institute"] = institute
354
- gene_panel["panel_type"] = panel_type
355
-
356
- LOG.info("Parsing panel %s", gene_panel["display_name"])
357
-
358
- gene_panel["genes"] = []
359
-
360
- nr_excluded = 0
361
- nr_genes = 0
362
- for nr_genes, gene in enumerate(panel_info["Genes"], 1):
363
- gene_info = parse_panel_app_gene(
364
- gene, ensembl_gene_hgnc_id_map, hgnc_symbol_ensembl_gene_map, confidence
365
- )
366
- if not gene_info:
367
- nr_excluded += 1
368
- continue
369
- gene_panel["genes"].append(gene_info)
370
-
371
- LOG.info("Number of genes in panel %s", nr_genes)
372
- LOG.info("Number of genes exluded due to confidence threshold: %s", nr_excluded)
373
-
374
- return gene_panel
375
-
376
-
377
263
  def get_omim_panel_genes(genemap2_lines: list, mim2gene_lines: list, alias_genes: dict):
378
264
  """Return all genes that should be included in the OMIM-AUTO panel
379
265
  Return the hgnc symbols
@@ -0,0 +1,112 @@
1
+ """Code to parse panel information"""
2
+
3
+ import logging
4
+ from typing import Dict, List, Optional
5
+
6
+ from scout.constants import INCOMPLETE_PENETRANCE_MAP, MODELS_MAP, PANELAPP_CONFIDENCE_EXCLUDE
7
+ from scout.utils.date import get_date
8
+
9
+ LOG = logging.getLogger(__name__)
10
+
11
+
12
+ def parse_panel_app_gene(
13
+ panelapp_gene: dict,
14
+ ensembl_gene_hgnc_id_map: Dict[str, int],
15
+ hgnc_symbol_ensembl_gene_map: Dict[str, str],
16
+ confidence: str,
17
+ ) -> dict:
18
+ """Parse a panel app-formatted gene."""
19
+ gene_info = {}
20
+ confidence_level = panelapp_gene["confidence_level"]
21
+ # Return empty gene if not confident gene
22
+ if confidence_level in PANELAPP_CONFIDENCE_EXCLUDE[confidence]:
23
+ return gene_info
24
+
25
+ hgnc_symbol = panelapp_gene["gene_data"]["gene_symbol"]
26
+ ensembl_ids = [
27
+ version["ensembl_id"]
28
+ for genome in panelapp_gene["gene_data"]["ensembl_genes"].values()
29
+ for version in genome.values()
30
+ ]
31
+
32
+ if not ensembl_ids: # This gene is probably tagged as ensembl_ids_known_missing on PanelApp
33
+ if hgnc_symbol in hgnc_symbol_ensembl_gene_map:
34
+ LOG.warning(
35
+ f"PanelApp gene {hgnc_symbol} does not contain Ensembl IDs. Using Ensembl IDs from internal gene collection instead."
36
+ )
37
+ ensembl_ids = [hgnc_symbol_ensembl_gene_map[hgnc_symbol]]
38
+ else:
39
+ LOG.warning(
40
+ f"PanelApp gene {hgnc_symbol} does not contain Ensembl IDs and gene symbol does not correspond to a gene in scout."
41
+ )
42
+
43
+ hgnc_ids = set(
44
+ ensembl_gene_hgnc_id_map.get(ensembl_id)
45
+ for ensembl_id in ensembl_ids
46
+ if ensembl_gene_hgnc_id_map.get(ensembl_id)
47
+ )
48
+ if not hgnc_ids:
49
+ LOG.warning("Gene %s does not exist in database. Skipping gene...", hgnc_symbol)
50
+ return gene_info
51
+
52
+ if len(hgnc_ids) > 1:
53
+ LOG.warning("Gene %s has unclear identifier. Choose random id", hgnc_symbol)
54
+
55
+ gene_info["hgnc_symbol"] = hgnc_symbol
56
+ for hgnc_id in hgnc_ids:
57
+ gene_info["hgnc_id"] = hgnc_id
58
+
59
+ gene_info["reduced_penetrance"] = INCOMPLETE_PENETRANCE_MAP.get(panelapp_gene["penetrance"])
60
+
61
+ inheritance_models = []
62
+ for model in MODELS_MAP.get(panelapp_gene["mode_of_inheritance"], []):
63
+ inheritance_models.append(model)
64
+
65
+ gene_info["inheritance_models"] = inheritance_models
66
+
67
+ return gene_info
68
+
69
+
70
+ def parse_panelapp_panel(
71
+ panel_info: dict,
72
+ ensembl_id_to_hgnc_id_map: Dict[str, int],
73
+ hgnc_symbol_to_ensembl_id_map: Dict[str, str],
74
+ institute: Optional[str] = "cust000",
75
+ confidence: Optional[str] = "green",
76
+ ) -> dict:
77
+ """Parse a PanelApp panel"""
78
+
79
+ date_format = "%Y-%m-%dT%H:%M:%S.%f"
80
+
81
+ gene_panel = {}
82
+ panel_id = str(panel_info["id"])
83
+ if confidence != "green":
84
+ gene_panel["panel_id"] = "_".join([panel_id, confidence])
85
+ else: # This way the old green panels will be overwritten, instead of creating 2 sets of green panels, old and new
86
+ gene_panel["panel_id"] = panel_id
87
+
88
+ gene_panel["version"] = float(panel_info["version"])
89
+ gene_panel["date"] = get_date(panel_info["version_created"][:-1], date_format=date_format)
90
+ gene_panel["display_name"] = " - ".join([panel_info["name"], f"[{confidence.upper()}]"])
91
+ gene_panel["institute"] = institute
92
+ gene_panel["panel_type"] = ("clinical",)
93
+
94
+ LOG.info("Parsing panel %s", gene_panel["display_name"])
95
+
96
+ gene_panel["genes"] = []
97
+
98
+ nr_excluded = 0
99
+ nr_genes = 0
100
+ for nr_genes, gene in enumerate(panel_info["genes"], 1):
101
+ gene_info = parse_panel_app_gene(
102
+ gene, ensembl_id_to_hgnc_id_map, hgnc_symbol_to_ensembl_id_map, confidence
103
+ )
104
+ if not gene_info:
105
+ nr_excluded += 1
106
+ continue
107
+ gene_panel["genes"].append(gene_info)
108
+
109
+ LOG.info("Number of genes in panel %s", nr_genes)
110
+ LOG.info("Number of genes excluded due to confidence threshold: %s", nr_excluded)
111
+
112
+ return gene_panel
@@ -1,9 +1,14 @@
1
1
  import logging
2
+ from typing import Dict, List, Optional, Union
3
+
4
+ import cyvcf2
2
5
 
3
6
  LOG = logging.getLogger(__name__)
4
7
 
5
8
 
6
- def parse_clnsig(variant, transcripts=None):
9
+ def parse_clnsig(
10
+ variant: cyvcf2.Variant, transcripts: Optional[dict] = None
11
+ ) -> List[Dict[str, Union[str, int]]]:
7
12
  """Get the clnsig information
8
13
 
9
14
  The clinvar format has changed several times and this function will try to parse all of them.
@@ -15,12 +20,7 @@ def parse_clnsig(variant, transcripts=None):
15
20
  sometimes with CLNVID.
16
21
  This function parses also Clinvar annotations performed by VEP (CSQ field, parsed transcripts required)
17
22
 
18
- Args:
19
- variant(cyvcf2.Variant)
20
- transcripts(iterable(dict))
21
-
22
- Returns:
23
- clnsig_accsessions(list(dict)): A list with clnsig accessions
23
+ Returns a list with clnsig accessions
24
24
  """
25
25
  transcripts = transcripts or []
26
26
  acc = variant.INFO.get("CLNACC", variant.INFO.get("CLNVID", ""))
@@ -87,33 +87,42 @@ def parse_clnsig(variant, transcripts=None):
87
87
  return clnsig_accessions
88
88
 
89
89
 
90
- def is_pathogenic(variant):
90
+ def is_pathogenic(variant: cyvcf2.Variant):
91
91
  """Check if a variant has the clinical significance to be loaded
92
92
 
93
93
  We want to load all variants that are in any of the predefined categories regardless of rank
94
94
  scores etc.
95
95
 
96
- Args:
97
- variant(cyvcf2.Variant)
96
+ To avoid parsing much, we first quickly check if we have a string match to substrings in
97
+ all relevant categories, that are somewhat rare in the CSQ strings in general. If not,
98
+ we check more carefully.
99
+
100
+ We include the old style numerical categories as well for backwards compatibility.
98
101
 
99
102
  Returns:
100
103
  bool: If variant should be loaded based on clinvar or not
101
104
  """
102
105
 
103
- load_categories = {
104
- "pathogenic",
105
- "likely_pathogenic",
106
- "conflicting_interpretations_of_pathogenicity",
107
- "conflicting_interpretations",
108
- }
106
+ quick_load_categories = {"pathogenic", "conflicting_"}
109
107
 
110
108
  # check if VEP-annotated field contains clinvar pathogenicity info
111
109
  vep_info = variant.INFO.get("CSQ")
112
110
  if vep_info:
113
- for category in load_categories:
111
+ for category in quick_load_categories:
114
112
  if category in vep_info.lower():
115
113
  return True
116
114
 
115
+ load_categories: set = {
116
+ "pathogenic",
117
+ "likely_pathogenic",
118
+ "conflicting_classifications_of_pathogenicity",
119
+ "conflicting_interpretations_of_pathogenicity",
120
+ "conflicting_interpretations",
121
+ 4,
122
+ 5,
123
+ 8,
124
+ }
125
+
117
126
  # Otherwise check if clinvar pathogenicity status is in INFO field
118
127
  clnsig_accessions = parse_clnsig(variant)
119
128
 
@@ -121,8 +130,4 @@ def is_pathogenic(variant):
121
130
  clnsig = annotation["value"]
122
131
  if clnsig in load_categories:
123
132
  return True
124
- if isinstance(clnsig, int):
125
- if clnsig == 4 or clnsig == 5:
126
- return True
127
-
128
133
  return False
@@ -291,11 +291,12 @@ def get_split_reads(variant, pos):
291
291
 
292
292
 
293
293
  def get_alt_frequency(variant, pos):
294
- """ """
295
- alt_frequency = float(variant.gt_alt_freqs[pos])
296
- if alt_frequency == -1:
297
- if "AF" in variant.FORMAT:
298
- alt_frequency = float(variant.format("AF")[pos][0])
294
+ """AF - prioritise caller AF if set in FORMAT (e.g. DeepVariant does an INFO field)"""
295
+ if "AF" in variant.FORMAT:
296
+ alt_frequency = float(variant.format("AF")[pos][0])
297
+ else:
298
+ alt_frequency = float(variant.gt_alt_freqs[pos])
299
+
299
300
  return alt_frequency
300
301
 
301
302
 
@@ -297,13 +297,13 @@ def set_common_tracks(display_obj, build):
297
297
  display_obj["custom_tracks"].append(track)
298
298
 
299
299
 
300
- def set_sample_tracks(display_obj, case_groups, chromosome):
300
+ def set_sample_tracks(display_obj: dict, case_groups: list, chromosome: str):
301
301
  """Set up individual-specific alignment tracks (bam/cram files)
302
302
 
303
- Args:
304
- display_obj(dict): dictionary containing all tracks info
305
- case_groups(list): a list of case dictionaries
306
- chromosome(str) [1-22],X,Y,M or "All"
303
+ Given a dictionary containing all tracks info (display_obj), a list of case group dictionaries
304
+ A chromosome string argument is used to check if we should look at mt alignment files for MT.
305
+
306
+ A missing file is indicated with the string "missing", and no track is made for such entries.
307
307
  """
308
308
  sample_tracks = []
309
309
 
@@ -321,6 +321,8 @@ def set_sample_tracks(display_obj, case_groups, chromosome):
321
321
  return
322
322
 
323
323
  for count, sample in enumerate(case.get("sample_names")):
324
+ if case[track_items][count] == "missing" or case[track_index_items][count] == "missing":
325
+ continue
324
326
  sample_tracks.append(
325
327
  {
326
328
  "name": sample,
@@ -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.0.5/dist/igv.min.js" integrity="sha512-v/9h2zZ8sRzSmIkMWHq/9Zbs54d2P/wd15/r0peNTqZn7uD/VfrwmeyT4pfBe94nbF9mQhbU5FI+zIXqqnPRpw==" crossorigin="anonymous"></script>
4
+ <script src="https://cdn.jsdelivr.net/npm/igv@3.0.9/dist/igv.min.js" integrity="sha512-vcMdgqBb5jsj6fAIlBMkDe3riA5meRJARkXtDFAl+5/jm5TxL6agAFq8Ek8LzrHtLncs40Qa8s98awdkINjy+Q==" crossorigin="anonymous"></script>
5
5
  {% endmacro %}
@@ -30,61 +30,68 @@
30
30
  <div class="card col-md-12">
31
31
  <h4 class="mt-3">Case: {{case.display_name}}</h4>
32
32
  <div class="card-body">
33
-
34
33
  <div class="row">
35
- <div class="col-xs-12 col-md-12">{{ smn_individuals_table(case, institute, tissue_types) }}</div>
36
- </div> <!-- end of div class row -->
37
- <div class="row">
38
- <div class="col-md-4">
39
- {% if case.madeline_info and case.individuals|length > 1 %}
40
- {{ pedigree_panel() }}
41
- {% else %}
42
- <p>No pedigree picture available.</p>
43
- {% endif %}
44
- </div>
45
- <div class="col-md-8">
46
- {{ synopsis_panel() }}
47
- <div class="panel-default">
48
- {{ comments_panel(institute, case, current_user, comments) }}
49
- </div>
34
+ <div class="col-xs-12 col-md-12">{{ smn_individuals_table(case, institute, tissue_types) }}</div>
35
+ </div> <!-- end of div class row -->
36
+ <div class="row">
37
+ <div class="col-md-4">
38
+ {% if case.madeline_info and case.individuals|length > 1 %}
39
+ {{ pedigree_panel() }}
40
+ {% else %}
41
+ <p>No pedigree picture available.</p>
42
+ {% endif %}
43
+ </div>
44
+ <div class="col-md-8">
45
+ {{ synopsis_panel() }}
46
+ <div class="panel-default">
47
+ {{ comments_panel(institute, case, current_user, comments) }}
50
48
  </div>
51
- </div> <!-- end of div class row -->
49
+ </div>
50
+ </div> <!-- end of div class row -->
52
51
 
53
- <span class="d-flex">
54
- {% if case.vcf_files.vcf_snv %}
55
- <span class="me-3">
56
- <form action="{{url_for('variants.variants', institute_id=institute._id, case_name=case.display_name) }}">
57
- <input type="hidden" id="hgnc_symbols" name="hgnc_symbols" value="SMN1, SMN2"></input>
58
- <input type="hidden" id="gene_panels" name="gene_panels" value="['']"></input>
59
- <span><button type="submit" class="btn btn-secondary btn-sm" target="_blank" rel="noopener" data-bs-toggle="tooltip" title="SNV and INDEL variants view filtered for the genes SMN1 and SMN2">SNVs</button></span>
60
- </form>
61
- </span>
62
- {% endif %}
63
- {% if case.vcf_files.vcf_sv %}
52
+ <span class="d-flex">
53
+ {% if case.vcf_files.vcf_snv %}
54
+ <span class="me-3">
55
+ <form action="{{url_for('variants.variants', institute_id=institute._id, case_name=case.display_name) }}">
56
+ <input type="hidden" id="hgnc_symbols" name="hgnc_symbols" value="SMN1, SMN2"></input>
57
+ <input type="hidden" id="gene_panels" name="gene_panels" value="['']"></input>
58
+ <span><button type="submit" class="btn btn-secondary btn-sm" target="_blank" rel="noopener" data-bs-toggle="tooltip" title="SNV and INDEL variants view filtered for the genes SMN1 and SMN2">SNVs</button></span>
59
+ </form>
60
+ </span>
61
+ {% endif %}
62
+ {% if case.vcf_files.vcf_sv %}
64
63
  <span class="me-3">
65
64
  <form action="{{url_for('variants.sv_variants', institute_id=institute._id, case_name=case.display_name) }}">
66
65
  <input type="hidden" id="hgnc_symbols" name="hgnc_symbols" value="SMN1, SMN2"></input>
67
66
  <input type="hidden" id="gene_panels" name="gene_panels" value="['']"></input>
68
- <button type="submit" class="btn btn-secondary btn-sm" target="_blank" rel="noopener" data-bs-toggle="tooltip" title="Structural variants view filtered for the genes SMN1 and SMN2">SVs</button></span>
67
+ <button type="submit" class="btn btn-secondary btn-sm" target="_blank" rel="noopener" data-bs-toggle="tooltip" title="Structural variants view filtered for the genes SMN1 and SMN2">SVs</button>
69
68
  </form>
70
69
  </span>
71
- {% endif %}
72
- {% if case.bam_files %}
73
- <span class="me-3"><a class="btn btn-secondary btn-sm text-white" href="{{url_for('alignviewers.igv', institute_id=case['owner'], case_name=case['display_name'], chrom=region['smn1']['chrom'], start=region['smn1']['start'], stop=region['smn1']['end'] )}}" target="_blank">IGV viewer SMN1</a></span>
74
- <span class="me-3"><a class="btn btn-secondary btn-sm text-white" href="{{url_for('alignviewers.igv', institute_id=case['owner'], case_name=case['display_name'], chrom=region['smn2']['chrom'], start=region['smn2']['start'], stop=region['smn2']['end'] )}}" target="_blank">IGV viewer SMN2</a></span>
75
- {% else %}
76
- <span class="me-3 text-muted">BAM file(s) missing</span>
77
- {% endif %}
78
- </div>
70
+ {% endif %}
79
71
 
80
- <div class="row">
81
- <div class="col-sm-12">{{activity_panel(events)}}</div>
82
- </div>
72
+ <span class="me-3"
73
+ {% if not case.bam_files %}title="Alignment file(s) missing" data-bs-toggle="tooltip"{% endif %}>
74
+ <a href="{{url_for('alignviewers.igv', institute_id=case['owner'], case_name=case['display_name'], chrom=region['smn1']['chrom'], start=region['smn1']['start'], stop=region['smn1']['end'] )}}" target="_blank"
75
+ class="btn btn-secondary btn-sm text-white{% if not case.bam_files %} disabled{% endif %}">
76
+ IGV DNA SMN1
77
+ </a>
78
+ </span>
79
+ <span class="me-3"
80
+ {% if not case.bam_files %}title="Alignment file(s) missing" data-bs-toggle="tooltip"{% endif %}>
81
+ <a href="{{url_for('alignviewers.igv', institute_id=case['owner'], case_name=case['display_name'], chrom=region['smn2']['chrom'], start=region['smn2']['start'], stop=region['smn2']['end'] )}}" target="_blank"
82
+ class="btn btn-secondary btn-sm text-white{% if not case.bam_files %} disabled{% endif %}">
83
+ IGV DNA SMN2
84
+ </a>
85
+ </span>
86
+ </span>
83
87
 
84
- {{ modal_synopsis() }}
88
+ <div class="row">
89
+ <div class="col-sm-12">{{activity_panel(events)}}</div>
90
+ </div>
91
+
92
+ {{ modal_synopsis() }}
85
93
  </div> <!-- end of card body -->
86
94
  </div> <!-- end of card div-->
87
- </div>
88
95
  </div> <!-- end of div class col -->
89
96
  {% endmacro %}
90
97