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.
Files changed (57) hide show
  1. scout/__version__.py +1 -1
  2. scout/adapter/client.py +1 -0
  3. scout/adapter/mongo/base.py +0 -1
  4. scout/adapter/mongo/case.py +15 -37
  5. scout/adapter/mongo/case_events.py +98 -2
  6. scout/adapter/mongo/hgnc.py +39 -22
  7. scout/adapter/mongo/institute.py +3 -9
  8. scout/adapter/mongo/panel.py +2 -1
  9. scout/adapter/mongo/variant.py +3 -2
  10. scout/adapter/mongo/variant_loader.py +92 -79
  11. scout/commands/base.py +1 -0
  12. scout/commands/update/case.py +10 -10
  13. scout/commands/update/individual.py +6 -1
  14. scout/constants/file_types.py +4 -0
  15. scout/load/__init__.py +0 -1
  16. scout/load/all.py +3 -4
  17. scout/load/panel.py +8 -4
  18. scout/load/setup.py +1 -0
  19. scout/models/case/case_loading_models.py +6 -16
  20. scout/parse/case.py +0 -1
  21. scout/parse/disease_terms.py +1 -0
  22. scout/parse/omim.py +1 -0
  23. scout/parse/panel.py +40 -15
  24. scout/resources/__init__.py +3 -0
  25. scout/server/app.py +4 -50
  26. scout/server/blueprints/alignviewers/controllers.py +15 -17
  27. scout/server/blueprints/alignviewers/templates/alignviewers/igv_viewer.html +13 -3
  28. scout/server/blueprints/alignviewers/views.py +10 -15
  29. scout/server/blueprints/cases/controllers.py +70 -73
  30. scout/server/blueprints/cases/templates/cases/case.html +37 -21
  31. scout/server/blueprints/cases/templates/cases/collapsible_actionbar.html +1 -1
  32. scout/server/blueprints/cases/templates/cases/phenotype.html +8 -6
  33. scout/server/blueprints/cases/templates/cases/utils.html +3 -3
  34. scout/server/blueprints/cases/views.py +8 -6
  35. scout/server/blueprints/variant/controllers.py +5 -5
  36. scout/server/blueprints/variant/templates/variant/acmg.html +25 -16
  37. scout/server/blueprints/variant/templates/variant/components.html +11 -6
  38. scout/server/blueprints/variant/views.py +5 -2
  39. scout/server/blueprints/variants/controllers.py +1 -1
  40. scout/server/blueprints/variants/views.py +1 -1
  41. scout/server/config.py +16 -4
  42. scout/server/extensions/__init__.py +4 -2
  43. scout/server/extensions/beacon_extension.py +1 -0
  44. scout/server/extensions/chanjo_extension.py +58 -0
  45. scout/server/extensions/phenopacket_extension.py +1 -0
  46. scout/server/static/bs_styles.css +18 -0
  47. scout/server/utils.py +16 -2
  48. scout/utils/acmg.py +33 -20
  49. scout/utils/track_resources.py +70 -0
  50. {scout_browser-4.82.2.dist-info → scout_browser-4.83.dist-info}/METADATA +1 -1
  51. {scout_browser-4.82.2.dist-info → scout_browser-4.83.dist-info}/RECORD +55 -55
  52. scout/load/case.py +0 -36
  53. scout/utils/cloud_resources.py +0 -61
  54. {scout_browser-4.82.2.dist-info → scout_browser-4.83.dist-info}/LICENSE +0 -0
  55. {scout_browser-4.82.2.dist-info → scout_browser-4.83.dist-info}/WHEEL +0 -0
  56. {scout_browser-4.82.2.dist-info → scout_browser-4.83.dist-info}/entry_points.txt +0 -0
  57. {scout_browser-4.82.2.dist-info → scout_browser-4.83.dist-info}/top_level.txt +0 -0
@@ -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 = adapter.case(case_id=case_id, institute_id=institute, display_name=case_name)
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 adapter.institute(collaborator):
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
- adapter.update_case(case_obj)
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 = adapter.case_collection.find_one_and_update(
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
- adapter.delete_variants(case_id, variant_type="clinical", category="sv")
163
- adapter.load_variants(
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
- adapter.delete_variants(case_id, variant_type="research", category="sv")
171
+ store.delete_variants(case_id, variant_type="research", category="sv")
172
172
  if updated_case.get("is_research"):
173
- adapter.load_variants(
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
- adapter.case_variants_count(case_obj["_id"], case_obj["owner"], force_update_case=True)
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
- store.update_case(case_obj)
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
+ )
@@ -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
@@ -1,5 +1,4 @@
1
1
  from .all import load_scout
2
- from .case import load_case
3
2
  from .cytoband import load_cytobands
4
3
  from .exon import load_exons
5
4
  from .hgnc_gene import load_hgnc_genes
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.append(
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, ped=None, update=False):
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
- hgnc_map = adapter.ensembl_to_hgnc_mapping()
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
- hgnc_map=hgnc_map,
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
@@ -5,6 +5,7 @@ This means add a default institute, a user and the internal definitions such as
5
5
  transcripts, hpo terms etc
6
6
 
7
7
  """
8
+
8
9
  import logging
9
10
 
10
11
  import yaml
@@ -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)
@@ -1,4 +1,5 @@
1
1
  """Code for parsing disease terms from OMIM and ORPHA data"""
2
+
2
3
  import logging
3
4
  from typing import Dict, List
4
5
 
scout/parse/omim.py CHANGED
@@ -1,4 +1,5 @@
1
1
  """Code for parsing OMIM formatted files"""
2
+
2
3
  import logging
3
4
  from typing import Any, Dict, Iterable
4
5
 
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, institute="cust000", panel_id="test", panel_type="clinical", genes=None, **kwargs
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(app_gene, hgnc_map, confidence):
272
- """Parse a panel app formatted gene.
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
- hgnc_map.get(ensembl_id) for ensembl_id in ensembl_ids if hgnc_map.get(ensembl_id)
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, hgnc_map, institute="cust000", panel_type="clinical", confidence="green"
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(gene, hgnc_map, confidence)
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
@@ -12,3 +12,6 @@ cytoband_files = {
12
12
  "37": cytobands_37_path,
13
13
  "38": cytobands_38_path,
14
14
  }
15
+
16
+ # Custom IGV tracks
17
+ mane_igv_track_path = str(files(BASE_PATH).joinpath("custom_igv_tracks", "mane.bb"))
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.cloud_tracks.init_app(app)
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 cloud_tracks, store
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", "cloud_public_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
- set_cloud_public_tracks(display_obj, build)
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 set_cloud_public_tracks(display_obj, build):
299
- """Set up custom public tracks stored in a cloud bucket, according to the user preferences
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
- cloud_public_tracks = []
309
- if hasattr(cloud_tracks, "public_tracks"):
310
- build_tracks = cloud_tracks.public_tracks.get(build, [])
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
- cloud_public_tracks.append(track)
316
- if cloud_public_tracks:
317
- display_obj["cloud_public_tracks"] = cloud_public_tracks
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 cloud_public_tracks %}
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
- url: "{{ url_for('alignviewers.remote_cors', remote_url=custTrack.url) }}",
73
- indexURL: "{{ url_for('alignviewers.remote_cors', remote_url=custTrack.indexURL) }}"
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(institute_id, case_name, variant_id=None, chrom=None, start=None, stop=None):
123
- """Visualize BAM alignments using igv.js (https://github.com/igvteam/igv.js)
124
-
125
- Args:
126
- institute_id(str): _id of an institute
127
- case_name(str): dislay_name of a case
128
- variant_id(str/None): variant _id or None
129
- chrom(str/None): requested chromosome [1-22], X, Y, [M-MT]
130
- start(int/None): start of the genomic interval to be displayed
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