scout-browser 4.82.2__py3-none-any.whl → 4.83__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/__version__.py +1 -1
- scout/adapter/client.py +1 -0
- scout/adapter/mongo/base.py +0 -1
- scout/adapter/mongo/case.py +15 -37
- scout/adapter/mongo/case_events.py +98 -2
- scout/adapter/mongo/hgnc.py +39 -22
- scout/adapter/mongo/institute.py +3 -9
- scout/adapter/mongo/panel.py +2 -1
- scout/adapter/mongo/variant.py +3 -2
- scout/adapter/mongo/variant_loader.py +92 -79
- scout/commands/base.py +1 -0
- scout/commands/update/case.py +10 -10
- scout/commands/update/individual.py +6 -1
- scout/constants/file_types.py +4 -0
- scout/load/__init__.py +0 -1
- scout/load/all.py +3 -4
- scout/load/panel.py +8 -4
- scout/load/setup.py +1 -0
- scout/models/case/case_loading_models.py +6 -16
- scout/parse/case.py +0 -1
- scout/parse/disease_terms.py +1 -0
- scout/parse/omim.py +1 -0
- scout/parse/panel.py +40 -15
- scout/resources/__init__.py +3 -0
- scout/server/app.py +4 -50
- scout/server/blueprints/alignviewers/controllers.py +15 -17
- scout/server/blueprints/alignviewers/templates/alignviewers/igv_viewer.html +13 -3
- scout/server/blueprints/alignviewers/views.py +10 -15
- scout/server/blueprints/cases/controllers.py +70 -73
- scout/server/blueprints/cases/templates/cases/case.html +37 -21
- scout/server/blueprints/cases/templates/cases/collapsible_actionbar.html +1 -1
- scout/server/blueprints/cases/templates/cases/phenotype.html +8 -6
- scout/server/blueprints/cases/templates/cases/utils.html +3 -3
- scout/server/blueprints/cases/views.py +8 -6
- scout/server/blueprints/variant/controllers.py +5 -5
- scout/server/blueprints/variant/templates/variant/acmg.html +25 -16
- scout/server/blueprints/variant/templates/variant/components.html +11 -6
- scout/server/blueprints/variant/views.py +5 -2
- scout/server/blueprints/variants/controllers.py +1 -1
- scout/server/blueprints/variants/views.py +1 -1
- scout/server/config.py +16 -4
- scout/server/extensions/__init__.py +4 -2
- scout/server/extensions/beacon_extension.py +1 -0
- scout/server/extensions/chanjo_extension.py +58 -0
- scout/server/extensions/phenopacket_extension.py +1 -0
- scout/server/static/bs_styles.css +18 -0
- scout/server/utils.py +16 -2
- scout/utils/acmg.py +33 -20
- scout/utils/track_resources.py +70 -0
- {scout_browser-4.82.2.dist-info → scout_browser-4.83.dist-info}/METADATA +1 -1
- {scout_browser-4.82.2.dist-info → scout_browser-4.83.dist-info}/RECORD +55 -55
- scout/load/case.py +0 -36
- scout/utils/cloud_resources.py +0 -61
- {scout_browser-4.82.2.dist-info → scout_browser-4.83.dist-info}/LICENSE +0 -0
- {scout_browser-4.82.2.dist-info → scout_browser-4.83.dist-info}/WHEEL +0 -0
- {scout_browser-4.82.2.dist-info → scout_browser-4.83.dist-info}/entry_points.txt +0 -0
- {scout_browser-4.82.2.dist-info → scout_browser-4.83.dist-info}/top_level.txt +0 -0
scout/commands/update/case.py
CHANGED
@@ -94,7 +94,6 @@ def case(
|
|
94
94
|
"""
|
95
95
|
Update a case in the database
|
96
96
|
"""
|
97
|
-
adapter = store
|
98
97
|
|
99
98
|
if not case_id:
|
100
99
|
if not (case_name and institute):
|
@@ -104,7 +103,7 @@ def case(
|
|
104
103
|
raise click.Abort()
|
105
104
|
|
106
105
|
# Check if the case exists
|
107
|
-
case_obj =
|
106
|
+
case_obj = store.case(case_id=case_id, institute_id=institute, display_name=case_name)
|
108
107
|
|
109
108
|
if not case_obj:
|
110
109
|
LOG.warning("Case %s could not be found", case_id)
|
@@ -112,7 +111,7 @@ def case(
|
|
112
111
|
|
113
112
|
case_changed = False
|
114
113
|
if collaborator:
|
115
|
-
if not
|
114
|
+
if not store.institute(collaborator):
|
116
115
|
LOG.warning("Institute %s could not be found", collaborator)
|
117
116
|
return
|
118
117
|
if not collaborator in case_obj["collaborators"]:
|
@@ -139,7 +138,8 @@ def case(
|
|
139
138
|
case_changed = True
|
140
139
|
|
141
140
|
if case_changed:
|
142
|
-
|
141
|
+
institute_obj = store.institute(case_obj["owner"])
|
142
|
+
store.update_case_cli(case_obj, institute_obj)
|
143
143
|
|
144
144
|
if reupload_sv:
|
145
145
|
LOG.info("Set needs_check to True for case %s", case_id)
|
@@ -151,7 +151,7 @@ def case(
|
|
151
151
|
if vcf_sv:
|
152
152
|
updates["vcf_files.vcf_sv_research"] = vcf_sv_research
|
153
153
|
|
154
|
-
updated_case =
|
154
|
+
updated_case = store.case_collection.find_one_and_update(
|
155
155
|
{"_id": case_id},
|
156
156
|
{"$set": updates},
|
157
157
|
return_document=pymongo.ReturnDocument.AFTER,
|
@@ -159,8 +159,8 @@ def case(
|
|
159
159
|
rankscore_treshold = rankscore_treshold or updated_case.get("rank_score_threshold", 5)
|
160
160
|
# Delete and reload the clinical SV variants
|
161
161
|
if updated_case["vcf_files"].get("vcf_sv"):
|
162
|
-
|
163
|
-
|
162
|
+
store.delete_variants(case_id, variant_type="clinical", category="sv")
|
163
|
+
store.load_variants(
|
164
164
|
updated_case,
|
165
165
|
variant_type="clinical",
|
166
166
|
category="sv",
|
@@ -168,13 +168,13 @@ def case(
|
|
168
168
|
)
|
169
169
|
# Delete and reload research SV variants
|
170
170
|
if updated_case["vcf_files"].get("vcf_sv_research"):
|
171
|
-
|
171
|
+
store.delete_variants(case_id, variant_type="research", category="sv")
|
172
172
|
if updated_case.get("is_research"):
|
173
|
-
|
173
|
+
store.load_variants(
|
174
174
|
updated_case,
|
175
175
|
variant_type="research",
|
176
176
|
category="sv",
|
177
177
|
rank_threshold=int(rankscore_treshold),
|
178
178
|
)
|
179
179
|
# Update case variants count
|
180
|
-
|
180
|
+
store.case_variants_count(case_obj["_id"], case_obj["owner"], force_update_case=True)
|
@@ -1,5 +1,6 @@
|
|
1
1
|
"""Code for updating information on individuals
|
2
2
|
"""
|
3
|
+
|
3
4
|
from pathlib import Path
|
4
5
|
|
5
6
|
import click
|
@@ -95,4 +96,8 @@ def individual(case_id, ind, key, value):
|
|
95
96
|
|
96
97
|
ind_obj[key] = value
|
97
98
|
|
98
|
-
|
99
|
+
link = f"/{case_obj['owner']}/{case_obj['display_name']}"
|
100
|
+
institute_obj = store.institute(case_obj["owner"])
|
101
|
+
store.update_case_individual(
|
102
|
+
case_obj, user_obj=None, institute_obj=institute_obj, link=link, keep_date=False
|
103
|
+
)
|
scout/constants/file_types.py
CHANGED
@@ -8,9 +8,13 @@ FILE_TYPE_MAP = {
|
|
8
8
|
"vcf_fusion": {"category": "fusion", "variant_type": "clinical"},
|
9
9
|
"vcf_fusion_research": {"category": "fusion", "variant_type": "research"},
|
10
10
|
"vcf_snv": {"category": "snv", "variant_type": "clinical"},
|
11
|
+
"vcf_snv_mt": {"category": "snv", "variant_type": "clinical"},
|
11
12
|
"vcf_snv_research": {"category": "snv", "variant_type": "research"},
|
13
|
+
"vcf_snv_research_mt": {"category": "snv", "variant_type": "research"},
|
12
14
|
"vcf_sv": {"category": "sv", "variant_type": "clinical"},
|
15
|
+
"vcf_sv_mt": {"category": "sv", "variant_type": "clinical"},
|
13
16
|
"vcf_sv_research": {"category": "sv", "variant_type": "research"},
|
17
|
+
"vcf_sv_research_mt": {"category": "sv", "variant_type": "research"},
|
14
18
|
"vcf_str": {"category": "str", "variant_type": "clinical"},
|
15
19
|
"vcf_mei": {"category": "mei", "variant_type": "clinical"},
|
16
20
|
"vcf_mei_research": {"category": "mei", "variant_type": "research"},
|
scout/load/__init__.py
CHANGED
scout/load/all.py
CHANGED
@@ -54,11 +54,11 @@ def load_region(adapter, case_id, hgnc_id=None, chrom=None, start=None, end=None
|
|
54
54
|
start = gene_caption["start"]
|
55
55
|
end = gene_caption["end"]
|
56
56
|
|
57
|
-
case_file_types =
|
57
|
+
case_file_types = set()
|
58
58
|
|
59
59
|
for file_type in FILE_TYPE_MAP:
|
60
60
|
if case_obj.get("vcf_files", {}).get(file_type):
|
61
|
-
case_file_types.
|
61
|
+
case_file_types.add(
|
62
62
|
(FILE_TYPE_MAP[file_type]["variant_type"], FILE_TYPE_MAP[file_type]["category"])
|
63
63
|
)
|
64
64
|
|
@@ -84,13 +84,12 @@ def load_region(adapter, case_id, hgnc_id=None, chrom=None, start=None, end=None
|
|
84
84
|
adapter.case_variants_count(case_obj["_id"], case_obj["owner"], force_update_case=True)
|
85
85
|
|
86
86
|
|
87
|
-
def load_scout(adapter, config,
|
87
|
+
def load_scout(adapter, config, update=False):
|
88
88
|
"""Load a new case from a Scout config.
|
89
89
|
|
90
90
|
Args:
|
91
91
|
adapter(MongoAdapter)
|
92
92
|
config(dict): loading info
|
93
|
-
ped(Iterable(str)): Pedigree ingformation
|
94
93
|
update(bool): If existing case should be updated
|
95
94
|
|
96
95
|
DEPRECATED method, historically used by the CG monolith, which has since switched to call the Scout CLI instead.
|
scout/load/panel.py
CHANGED
@@ -7,6 +7,7 @@ functions to load panels into the database
|
|
7
7
|
import logging
|
8
8
|
import math
|
9
9
|
from datetime import datetime
|
10
|
+
from typing import Dict, List
|
10
11
|
|
11
12
|
from click import Abort
|
12
13
|
from flask.cli import current_app
|
@@ -110,7 +111,7 @@ def load_panel(panel_path, adapter, **kwargs):
|
|
110
111
|
raise err
|
111
112
|
|
112
113
|
|
113
|
-
def _panelapp_panel_ids():
|
114
|
+
def _panelapp_panel_ids() -> List[str]:
|
114
115
|
"""Fetch all PanelApp panel IDs"""
|
115
116
|
json_lines = fetch_resource(PANELAPP_BASE_URL.format("list_panels"), json=True)
|
116
117
|
return [panel_info["Panel_Id"] for panel_info in json_lines.get("result", [])]
|
@@ -129,11 +130,14 @@ def _parse_panelapp_panel(adapter, panel_id, institute, confidence):
|
|
129
130
|
{'version': 3.3, 'date': datetime.datetime(2023, 1, 31, 16, 43, 37, 521719), 'display_name': 'Diabetes - neonatal onset - [GREEN]', 'institute': 'cust000', 'panel_type': 'clinical', 'genes': [list of genes], 'panel_id': '55a9041e22c1fc6711b0c6c0'}
|
130
131
|
|
131
132
|
"""
|
132
|
-
|
133
|
+
ensembl_gene_hgnc_id_map: Dict[str, int] = adapter.ensembl_to_hgnc_id_mapping()
|
134
|
+
hgnc_symbol_ensembl_gene_map: Dict[str, str] = adapter.hgnc_symbol_ensembl_id_mapping()
|
135
|
+
|
133
136
|
json_lines = fetch_resource(PANELAPP_BASE_URL.format("get_panel") + panel_id, json=True)
|
134
137
|
parsed_panel = parse_panel_app_panel(
|
135
138
|
panel_info=json_lines["result"],
|
136
|
-
|
139
|
+
ensembl_gene_hgnc_id_map=ensembl_gene_hgnc_id_map,
|
140
|
+
hgnc_symbol_ensembl_gene_map=hgnc_symbol_ensembl_gene_map,
|
137
141
|
institute=institute,
|
138
142
|
confidence=confidence,
|
139
143
|
)
|
@@ -160,7 +164,7 @@ def load_panelapp_panel(adapter, panel_id=None, institute="cust000", confidence=
|
|
160
164
|
|
161
165
|
if not panel_id:
|
162
166
|
LOG.info("Fetching all panel app panels")
|
163
|
-
panel_ids = _panelapp_panel_ids()
|
167
|
+
panel_ids: List[str] = _panelapp_panel_ids()
|
164
168
|
|
165
169
|
for _ in panel_ids:
|
166
170
|
parsed_panel = _parse_panelapp_panel(adapter, _, institute, confidence)
|
scout/load/setup.py
CHANGED
@@ -15,7 +15,7 @@ except ImportError:
|
|
15
15
|
|
16
16
|
from pydantic import BaseModel, Field, field_validator, model_validator
|
17
17
|
|
18
|
-
from scout.constants import ANALYSIS_TYPES
|
18
|
+
from scout.constants import ANALYSIS_TYPES, FILE_TYPE_MAP
|
19
19
|
from scout.exceptions import PedigreeError
|
20
20
|
from scout.utils.date import get_date
|
21
21
|
|
@@ -58,21 +58,7 @@ CASE_FILE_PATH_CHECKS = [
|
|
58
58
|
"RNAfusion_report_research",
|
59
59
|
]
|
60
60
|
|
61
|
-
VCF_FILE_PATH_CHECKS =
|
62
|
-
"vcf_cancer",
|
63
|
-
"vcf_cancer_research",
|
64
|
-
"vcf_cancer_sv",
|
65
|
-
"vcf_cancer_sv_research",
|
66
|
-
"vcf_fusion",
|
67
|
-
"vcf_fusion_research",
|
68
|
-
"vcf_snv",
|
69
|
-
"vcf_snv_research",
|
70
|
-
"vcf_mei",
|
71
|
-
"vcf_mei_research",
|
72
|
-
"vcf_str",
|
73
|
-
"vcf_sv",
|
74
|
-
"vcf_sv_research",
|
75
|
-
]
|
61
|
+
VCF_FILE_PATH_CHECKS = FILE_TYPE_MAP.keys()
|
76
62
|
|
77
63
|
GENOME_BUILDS = ["37", "38"]
|
78
64
|
TRACKS = ["rare", "cancer"]
|
@@ -110,12 +96,16 @@ class VcfFiles(BaseModel):
|
|
110
96
|
vcf_cancer_sv: Optional[str] = None
|
111
97
|
vcf_cancer_sv_research: Optional[str] = None
|
112
98
|
vcf_snv: Optional[str] = None
|
99
|
+
vcf_snv_mt: Optional[str] = None
|
113
100
|
vcf_snv_research: Optional[str] = None
|
101
|
+
vcf_snv_research_mt: Optional[str] = None
|
114
102
|
vcf_mei: Optional[str] = None
|
115
103
|
vcf_mei_research: Optional[str] = None
|
116
104
|
vcf_str: Optional[str] = None
|
117
105
|
vcf_sv: Optional[str] = None
|
106
|
+
vcf_sv_mt: Optional[str] = None
|
118
107
|
vcf_sv_research: Optional[str] = None
|
108
|
+
vcf_sv_research_mt: Optional[str] = None
|
119
109
|
vcf_fusion: Optional[str] = None
|
120
110
|
vcf_fusion_research: Optional[str] = None
|
121
111
|
|
scout/parse/case.py
CHANGED
@@ -86,7 +86,6 @@ def parse_case_data(**kwargs):
|
|
86
86
|
config_dict["case_id"] = config_dict["family"]
|
87
87
|
|
88
88
|
if config_dict.get("smn_tsv"):
|
89
|
-
LOG.info("Adding SMN info from {}.".format(config_dict["smn_tsv"]))
|
90
89
|
add_smn_info_case(config_dict)
|
91
90
|
|
92
91
|
return remove_none_recursive(config_dict)
|
scout/parse/disease_terms.py
CHANGED
scout/parse/omim.py
CHANGED
scout/parse/panel.py
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
"""Code to parse panel information"""
|
2
|
+
|
2
3
|
import logging
|
3
4
|
from datetime import datetime
|
5
|
+
from typing import Dict, List, Optional
|
4
6
|
|
5
7
|
from scout.constants import (
|
6
8
|
INCOMPLETE_PENETRANCE_MAP,
|
@@ -233,7 +235,12 @@ def parse_genes(gene_lines):
|
|
233
235
|
|
234
236
|
|
235
237
|
def parse_gene_panel(
|
236
|
-
path,
|
238
|
+
path,
|
239
|
+
institute="cust000",
|
240
|
+
panel_id="test",
|
241
|
+
panel_type="clinical",
|
242
|
+
genes=None,
|
243
|
+
**kwargs,
|
237
244
|
):
|
238
245
|
"""Parse the panel info and return a gene panel
|
239
246
|
|
@@ -268,17 +275,14 @@ def parse_gene_panel(
|
|
268
275
|
return gene_panel
|
269
276
|
|
270
277
|
|
271
|
-
def parse_panel_app_gene(
|
272
|
-
|
278
|
+
def parse_panel_app_gene(
|
279
|
+
app_gene: dict,
|
280
|
+
ensembl_gene_hgnc_id_map: Dict[str, int],
|
281
|
+
hgnc_symbol_ensembl_gene_map: Dict[str, str],
|
282
|
+
confidence: str,
|
283
|
+
) -> dict:
|
284
|
+
"""Parse a panel app-formatted gene."""
|
273
285
|
|
274
|
-
Args:
|
275
|
-
app_gene(dict): dict with panel app info, where Ensembl ids are present as a loist with key "EnsembleGeneIds"
|
276
|
-
hgnc_map(dict): a dictionary with Ensembl IDs as keys and HGNC ids as values
|
277
|
-
confidence(str): enum green|amber|red
|
278
|
-
|
279
|
-
Returns:
|
280
|
-
gene_info(dict): Scout infromation
|
281
|
-
"""
|
282
286
|
gene_info = {}
|
283
287
|
confidence_level = app_gene["LevelOfConfidence"]
|
284
288
|
# Return empty gene if not confident gene
|
@@ -288,8 +292,22 @@ def parse_panel_app_gene(app_gene, hgnc_map, confidence):
|
|
288
292
|
hgnc_symbol = app_gene["GeneSymbol"]
|
289
293
|
|
290
294
|
ensembl_ids = app_gene["EnsembleGeneIds"]
|
295
|
+
|
296
|
+
if not ensembl_ids: # This gene is probably tagged as ensembl_ids_known_missing on PanelApp
|
297
|
+
if hgnc_symbol in hgnc_symbol_ensembl_gene_map:
|
298
|
+
LOG.warning(
|
299
|
+
f"PanelApp gene {hgnc_symbol} does not contain Ensembl IDs. Using Ensembl IDs from internal gene collection instead."
|
300
|
+
)
|
301
|
+
ensembl_ids = [hgnc_symbol_ensembl_gene_map[hgnc_symbol]]
|
302
|
+
else:
|
303
|
+
LOG.warning(
|
304
|
+
f"PanelApp gene {hgnc_symbol} does not contain Ensembl IDs and gene symbol does not correspond to a gene in scout."
|
305
|
+
)
|
306
|
+
|
291
307
|
hgnc_ids = set(
|
292
|
-
|
308
|
+
ensembl_gene_hgnc_id_map.get(ensembl_id)
|
309
|
+
for ensembl_id in ensembl_ids
|
310
|
+
if ensembl_gene_hgnc_id_map.get(ensembl_id)
|
293
311
|
)
|
294
312
|
if not hgnc_ids:
|
295
313
|
LOG.warning("Gene %s does not exist in database. Skipping gene...", hgnc_symbol)
|
@@ -314,8 +332,13 @@ def parse_panel_app_gene(app_gene, hgnc_map, confidence):
|
|
314
332
|
|
315
333
|
|
316
334
|
def parse_panel_app_panel(
|
317
|
-
panel_info
|
318
|
-
|
335
|
+
panel_info: dict,
|
336
|
+
ensembl_gene_hgnc_id_map: Dict[str, int],
|
337
|
+
hgnc_symbol_ensembl_gene_map: Dict[str, str],
|
338
|
+
institute: Optional[str] = "cust000",
|
339
|
+
panel_type: Optional[str] = "clinical",
|
340
|
+
confidence: Optional[str] = "green",
|
341
|
+
) -> dict:
|
319
342
|
"""Parse a PanelApp panel
|
320
343
|
|
321
344
|
Args:
|
@@ -346,7 +369,9 @@ def parse_panel_app_panel(
|
|
346
369
|
nr_excluded = 0
|
347
370
|
nr_genes = 0
|
348
371
|
for nr_genes, gene in enumerate(panel_info["Genes"], 1):
|
349
|
-
gene_info = parse_panel_app_gene(
|
372
|
+
gene_info = parse_panel_app_gene(
|
373
|
+
gene, ensembl_gene_hgnc_id_map, hgnc_symbol_ensembl_gene_map, confidence
|
374
|
+
)
|
350
375
|
if not gene_info:
|
351
376
|
nr_excluded += 1
|
352
377
|
continue
|
scout/resources/__init__.py
CHANGED
scout/server/app.py
CHANGED
@@ -8,7 +8,6 @@ from urllib.parse import parse_qsl, unquote, urlsplit
|
|
8
8
|
|
9
9
|
import coloredlogs
|
10
10
|
from flask import Flask, current_app, redirect, request, url_for
|
11
|
-
from flask_babel import Babel
|
12
11
|
from flask_cors import CORS
|
13
12
|
from flask_login import current_user
|
14
13
|
from markdown import markdown as python_markdown
|
@@ -36,16 +35,6 @@ from .blueprints import (
|
|
36
35
|
|
37
36
|
LOG = logging.getLogger(__name__)
|
38
37
|
|
39
|
-
try:
|
40
|
-
from chanjo_report.server.app import configure_template_filters
|
41
|
-
from chanjo_report.server.blueprints import report_bp
|
42
|
-
from chanjo_report.server.extensions import api as chanjo_api
|
43
|
-
except ImportError:
|
44
|
-
chanjo_api = None
|
45
|
-
report_bp = None
|
46
|
-
configure_template_filters = None
|
47
|
-
LOG.info("chanjo report not installed!")
|
48
|
-
|
49
38
|
|
50
39
|
def create_app(config_file=None, config=None):
|
51
40
|
"""Flask app factory function."""
|
@@ -111,8 +100,8 @@ def configure_extensions(app):
|
|
111
100
|
extensions.mail.init_app(app)
|
112
101
|
|
113
102
|
if app.config.get("SQLALCHEMY_DATABASE_URI"):
|
103
|
+
extensions.chanjo_report.init_app(app)
|
114
104
|
LOG.info("Chanjo extension enabled")
|
115
|
-
configure_coverage(app)
|
116
105
|
|
117
106
|
if app.config.get("LOQUSDB_SETTINGS"):
|
118
107
|
LOG.info("LoqusDB enabled")
|
@@ -157,9 +146,9 @@ def configure_extensions(app):
|
|
157
146
|
# setup connection to google oauth2
|
158
147
|
configure_oauth_login(app)
|
159
148
|
|
160
|
-
if app.config.get("CLOUD_IGV_TRACKS"):
|
161
|
-
LOG.info("Collecting IGV tracks from cloud resources")
|
162
|
-
extensions.
|
149
|
+
if app.config.get("CUSTOM_IGV_TRACKS") or app.config.get("CLOUD_IGV_TRACKS"):
|
150
|
+
LOG.info("Collecting IGV tracks from cloud or local resources")
|
151
|
+
extensions.config_igv_tracks.init_app(app)
|
163
152
|
|
164
153
|
if app.config.get("PHENOPACKET_API_URL"):
|
165
154
|
LOG.info("Enable Phenopacket API")
|
@@ -319,38 +308,3 @@ def configure_email_logging(app):
|
|
319
308
|
)
|
320
309
|
)
|
321
310
|
app.logger.addHandler(mail_handler)
|
322
|
-
|
323
|
-
|
324
|
-
def configure_coverage(app):
|
325
|
-
"""Setup coverage related extensions."""
|
326
|
-
# setup chanjo report
|
327
|
-
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = True if app.debug else False
|
328
|
-
if chanjo_api:
|
329
|
-
chanjo_api.init_app(app)
|
330
|
-
configure_template_filters(app)
|
331
|
-
# register chanjo report blueprint
|
332
|
-
app.register_blueprint(report_bp, url_prefix="/reports")
|
333
|
-
|
334
|
-
babel = Babel()
|
335
|
-
|
336
|
-
def get_locale():
|
337
|
-
"""Determine locale to use for translations."""
|
338
|
-
accept_languages = current_app.config.get("ACCEPT_LANGUAGES", ["en"])
|
339
|
-
|
340
|
-
# first check request args
|
341
|
-
session_language = Markup.escape(request.args.get("lang"))
|
342
|
-
if session_language in accept_languages:
|
343
|
-
current_app.logger.info("using session language: %s", session_language)
|
344
|
-
return session_language
|
345
|
-
|
346
|
-
# language can be forced in config
|
347
|
-
user_language = current_app.config.get("REPORT_LANGUAGE")
|
348
|
-
if user_language:
|
349
|
-
return user_language
|
350
|
-
|
351
|
-
# try to guess the language from the user accept header that
|
352
|
-
# the browser transmits. We support de/fr/en in this example.
|
353
|
-
# The best match wins.
|
354
|
-
return request.accept_languages.best_match(accept_languages)
|
355
|
-
|
356
|
-
babel.init_app(app, locale_selector=get_locale)
|
@@ -7,7 +7,7 @@ from flask import flash, session
|
|
7
7
|
from flask_login import current_user
|
8
8
|
|
9
9
|
from scout.constants import CASE_SPECIFIC_TRACKS, HUMAN_REFERENCE, IGV_TRACKS
|
10
|
-
from scout.server.extensions import
|
10
|
+
from scout.server.extensions import config_igv_tracks, store
|
11
11
|
from scout.server.utils import case_append_alignments, find_index
|
12
12
|
from scout.utils.ensembl_rest_clients import EnsemblRestApiClient
|
13
13
|
|
@@ -34,15 +34,16 @@ def check_session_tracks(resource):
|
|
34
34
|
return True
|
35
35
|
|
36
36
|
|
37
|
-
def set_session_tracks(display_obj):
|
37
|
+
def set_session_tracks(display_obj: dict):
|
38
38
|
"""Save igv tracks as a session object. This way it's easy to verify that a user is requesting one of these files from remote_static view endpoint
|
39
39
|
|
40
40
|
Args:
|
41
41
|
display_obj(dict): A display object containing case name, list of genes, locus and tracks
|
42
42
|
"""
|
43
|
+
|
43
44
|
session_tracks = list(display_obj.get("reference_track", {}).values())
|
44
45
|
for key, track_items in display_obj.items():
|
45
|
-
if key not in ["tracks", "custom_tracks", "sample_tracks", "
|
46
|
+
if key not in ["tracks", "custom_tracks", "sample_tracks", "config_custom_tracks"]:
|
46
47
|
continue
|
47
48
|
for track_item in track_items:
|
48
49
|
session_tracks += list(track_item.values())
|
@@ -107,7 +108,7 @@ def make_igv_tracks(case_obj, variant_id, chrom=None, start=None, stop=None):
|
|
107
108
|
set_case_specific_tracks(display_obj, case_obj)
|
108
109
|
|
109
110
|
# Set up custom cloud public tracks, if available
|
110
|
-
|
111
|
+
set_config_custom_tracks(display_obj, build)
|
111
112
|
|
112
113
|
display_obj["display_center_guide"] = True
|
113
114
|
|
@@ -295,23 +296,20 @@ def set_case_specific_tracks(display_obj, case_obj):
|
|
295
296
|
display_obj[track] = track_info
|
296
297
|
|
297
298
|
|
298
|
-
def
|
299
|
-
"""Set up custom public tracks stored in a cloud bucket
|
300
|
-
|
301
|
-
Args:
|
302
|
-
display_obj(dict) dictionary containing all tracks info
|
303
|
-
build(string) "37" or "38"
|
304
|
-
"""
|
299
|
+
def set_config_custom_tracks(display_obj: dict, build: str):
|
300
|
+
"""Set up custom public or private tracks stored in a cloud bucket or locally. These tracks were those specified in the Scout config file.
|
301
|
+
Respect user's preferences."""
|
305
302
|
user_obj = store.user(email=current_user.email)
|
306
303
|
custom_tracks_names = user_obj.get("igv_tracks")
|
307
304
|
|
308
|
-
|
309
|
-
|
310
|
-
|
305
|
+
config_custom_tracks = []
|
306
|
+
|
307
|
+
if hasattr(config_igv_tracks, "tracks"):
|
308
|
+
build_tracks = config_igv_tracks.tracks.get(build, [])
|
311
309
|
for track in build_tracks:
|
312
310
|
# Do not display track if user doesn't want to see it
|
313
311
|
if custom_tracks_names and track["name"] not in custom_tracks_names:
|
314
312
|
continue
|
315
|
-
|
316
|
-
if
|
317
|
-
display_obj["
|
313
|
+
config_custom_tracks.append(track)
|
314
|
+
if config_custom_tracks:
|
315
|
+
display_obj["config_custom_tracks"] = config_custom_tracks
|
@@ -64,13 +64,23 @@
|
|
64
64
|
{% endif %}
|
65
65
|
},
|
66
66
|
{% endfor %}
|
67
|
-
{% for custTrack in
|
67
|
+
{% for custTrack in config_custom_tracks %}
|
68
68
|
{
|
69
69
|
name: "{{ custTrack.name }}",
|
70
70
|
type: "{{ custTrack.type }}",
|
71
71
|
format: "{{ custTrack.format }}",
|
72
|
-
|
73
|
-
|
72
|
+
height: 70,
|
73
|
+
{% if "http" in custTrack.url %}
|
74
|
+
url: "{{ url_for('alignviewers.remote_cors', remote_url=custTrack.url) }}",
|
75
|
+
{% if custTrack.indexURL %}
|
76
|
+
indexURL: "{{ url_for('alignviewers.remote_cors', remote_url=custTrack.indexURL) }}",
|
77
|
+
{% endif %}
|
78
|
+
{% else %}
|
79
|
+
url: "{{ url_for('alignviewers.remote_static', file=custTrack.url) }}",
|
80
|
+
{% if custTrack.indexURL %}
|
81
|
+
indexURL: "{{ url_for('alignviewers.remote_static', file=custTrack.indexURL) }}",
|
82
|
+
{% endif %}
|
83
|
+
{% endif %}
|
74
84
|
},
|
75
85
|
{% endfor %}
|
76
86
|
{% for wtrack in rhocall_wig %}
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# -*- coding: utf-8 -*-
|
2
2
|
import logging
|
3
|
+
from typing import Optional
|
3
4
|
|
4
5
|
import requests
|
5
6
|
from flask import (
|
@@ -11,7 +12,6 @@ from flask import (
|
|
11
12
|
request,
|
12
13
|
session,
|
13
14
|
)
|
14
|
-
from flask_login import current_user
|
15
15
|
|
16
16
|
from scout.server.extensions import store
|
17
17
|
from scout.server.utils import institute_and_case
|
@@ -119,20 +119,15 @@ def sashimi_igv(institute_id, case_name, variant_id=None):
|
|
119
119
|
@alignviewers_bp.route(
|
120
120
|
"/<institute_id>/<case_name>/<variant_id>/<chrom>/<start>/<stop>/igv", methods=["GET"]
|
121
121
|
) # from SV variant page, where you have to pass breakpoints coordinates
|
122
|
-
def igv(
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
stop(int/None): stop of the genomic interval to be displayed
|
132
|
-
|
133
|
-
Returns:
|
134
|
-
a string, corresponging to the HTML rendering of the IGV alignments page
|
135
|
-
"""
|
122
|
+
def igv(
|
123
|
+
institute_id: str,
|
124
|
+
case_name: str,
|
125
|
+
variant_id: Optional[str] = None,
|
126
|
+
chrom: Optional[str] = None,
|
127
|
+
start: Optional[int] = None,
|
128
|
+
stop: Optional[int] = None,
|
129
|
+
) -> Response:
|
130
|
+
"""Visualize BAM alignments using igv.js (https://github.com/igvteam/igv.js)."""
|
136
131
|
_, case_obj = institute_and_case(
|
137
132
|
store, institute_id, case_name
|
138
133
|
) # This function takes care of checking if user is authorized to see resource
|