scout-browser 4.98.0__py3-none-any.whl → 4.100.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.
Files changed (91) hide show
  1. scout/adapter/mongo/case.py +30 -15
  2. scout/adapter/mongo/clinvar.py +23 -31
  3. scout/adapter/mongo/event.py +14 -4
  4. scout/adapter/mongo/institute.py +42 -55
  5. scout/adapter/mongo/omics_variant.py +14 -1
  6. scout/adapter/mongo/query.py +24 -1
  7. scout/adapter/mongo/variant.py +44 -22
  8. scout/adapter/mongo/variant_loader.py +169 -186
  9. scout/build/individual.py +5 -1
  10. scout/build/variant/variant.py +8 -0
  11. scout/commands/download/ensembl.py +18 -3
  12. scout/commands/load/research.py +2 -3
  13. scout/commands/update/individual.py +3 -0
  14. scout/commands/update/panelapp.py +15 -2
  15. scout/constants/__init__.py +6 -2
  16. scout/constants/clnsig.py +2 -0
  17. scout/constants/file_types.py +12 -0
  18. scout/constants/igv_tracks.py +9 -6
  19. scout/constants/indexes.py +5 -4
  20. scout/constants/panels.py +3 -0
  21. scout/constants/query_terms.py +1 -0
  22. scout/constants/variant_tags.py +6 -6
  23. scout/demo/643594.config.yaml +1 -0
  24. scout/load/panelapp.py +11 -5
  25. scout/models/case/case.py +1 -0
  26. scout/models/case/case_loading_models.py +7 -1
  27. scout/parse/ensembl.py +8 -3
  28. scout/parse/variant/clnsig.py +38 -0
  29. scout/parse/variant/genotype.py +4 -10
  30. scout/parse/variant/models.py +5 -11
  31. scout/parse/variant/rank_score.py +5 -13
  32. scout/parse/variant/variant.py +90 -111
  33. scout/server/app.py +39 -22
  34. scout/server/blueprints/alignviewers/controllers.py +29 -10
  35. scout/server/blueprints/alignviewers/templates/alignviewers/igv_viewer.html +51 -11
  36. scout/server/blueprints/cases/controllers.py +9 -3
  37. scout/server/blueprints/cases/templates/cases/case_report.html +25 -13
  38. scout/server/blueprints/cases/templates/cases/chanjo2_form.html +1 -1
  39. scout/server/blueprints/cases/templates/cases/collapsible_actionbar.html +1 -1
  40. scout/server/blueprints/cases/templates/cases/gene_panel.html +1 -1
  41. scout/server/blueprints/cases/templates/cases/utils.html +25 -6
  42. scout/server/blueprints/clinvar/controllers.py +34 -15
  43. scout/server/blueprints/clinvar/templates/clinvar/clinvar_submissions.html +34 -12
  44. scout/server/blueprints/clinvar/templates/clinvar/multistep_add_variant.html +14 -5
  45. scout/server/blueprints/clinvar/views.py +14 -2
  46. scout/server/blueprints/diagnoses/static/diagnoses.js +8 -1
  47. scout/server/blueprints/institutes/controllers.py +10 -2
  48. scout/server/blueprints/institutes/static/variants_list_scripts.js +9 -1
  49. scout/server/blueprints/institutes/templates/overview/institute_sidebar.html +9 -1
  50. scout/server/blueprints/login/controllers.py +112 -12
  51. scout/server/blueprints/login/views.py +38 -60
  52. scout/server/blueprints/mme/__init__.py +1 -0
  53. scout/server/blueprints/mme/controllers.py +18 -0
  54. scout/server/blueprints/mme/templates/mme/mme_submissions.html +153 -0
  55. scout/server/blueprints/mme/views.py +34 -0
  56. scout/server/blueprints/panels/templates/panels/panel.html +19 -6
  57. scout/server/blueprints/phenotypes/templates/phenotypes/hpo_terms.html +8 -1
  58. scout/server/blueprints/public/templates/public/index.html +5 -1
  59. scout/server/blueprints/variant/controllers.py +19 -10
  60. scout/server/blueprints/variant/templates/variant/acmg.html +15 -2
  61. scout/server/blueprints/variant/templates/variant/cancer-variant.html +1 -1
  62. scout/server/blueprints/variant/templates/variant/components.html +38 -16
  63. scout/server/blueprints/variant/templates/variant/sv-variant.html +2 -2
  64. scout/server/blueprints/variant/templates/variant/utils.html +23 -11
  65. scout/server/blueprints/variant/templates/variant/variant.html +42 -1
  66. scout/server/blueprints/variant/views.py +12 -0
  67. scout/server/blueprints/variants/controllers.py +20 -3
  68. scout/server/blueprints/variants/forms.py +8 -3
  69. scout/server/blueprints/variants/templates/variants/components.html +34 -0
  70. scout/server/blueprints/variants/templates/variants/indicators.html +11 -13
  71. scout/server/blueprints/variants/templates/variants/mei-variants.html +8 -6
  72. scout/server/blueprints/variants/templates/variants/sv-variants.html +9 -7
  73. scout/server/blueprints/variants/templates/variants/utils.html +35 -34
  74. scout/server/blueprints/variants/templates/variants/variants.html +4 -25
  75. scout/server/config.py +8 -0
  76. scout/server/extensions/bionano_extension.py +0 -1
  77. scout/server/extensions/chanjo2_extension.py +54 -13
  78. scout/server/links.py +15 -0
  79. scout/server/static/bs_styles.css +34 -6
  80. scout/server/templates/utils.html +9 -10
  81. scout/server/utils.py +40 -5
  82. scout/utils/acmg.py +25 -26
  83. scout/utils/ensembl_biomart_clients.py +2 -1
  84. scout/utils/ensembl_rest_clients.py +25 -32
  85. scout/utils/hgvs.py +1 -1
  86. scout/utils/scout_requests.py +1 -3
  87. {scout_browser-4.98.0.dist-info → scout_browser-4.100.0.dist-info}/METADATA +10 -14
  88. {scout_browser-4.98.0.dist-info → scout_browser-4.100.0.dist-info}/RECORD +91 -87
  89. {scout_browser-4.98.0.dist-info → scout_browser-4.100.0.dist-info}/WHEEL +0 -0
  90. {scout_browser-4.98.0.dist-info → scout_browser-4.100.0.dist-info}/entry_points.txt +0 -0
  91. {scout_browser-4.98.0.dist-info → scout_browser-4.100.0.dist-info}/licenses/LICENSE +0 -0
@@ -3,13 +3,13 @@ from typing import Any, Dict, List, Optional
3
3
 
4
4
  from cyvcf2 import Variant
5
5
 
6
- from scout.constants import CHR_PATTERN
6
+ from scout.constants import CHR_PATTERN, DNA_SAMPLE_VARIANT_CATEGORIES
7
7
  from scout.exceptions import VcfError
8
8
  from scout.utils.convert import call_safe
9
9
  from scout.utils.dict_utils import remove_nonetype
10
10
 
11
11
  from .callers import parse_callers
12
- from .clnsig import parse_clnsig
12
+ from .clnsig import parse_clnsig, parse_clnsig_onc
13
13
  from .compound import parse_compounds
14
14
  from .conservation import parse_conservations
15
15
  from .coordinates import parse_coordinates
@@ -57,15 +57,39 @@ def parse_variant(
57
57
  # Vep information
58
58
  vep_header = vep_header or []
59
59
 
60
- parsed_variant = {}
61
60
  # Create the ID for the variant
62
61
  case_id = case["_id"]
62
+
63
63
  genmod_key = get_genmod_key(case)
64
64
  chrom_match = CHR_PATTERN.match(variant.CHROM)
65
65
  chrom = chrom_match.group(2)
66
66
 
67
+ ### We always assume split and normalized vcfs!!!
68
+ if len(variant.ALT) > 1:
69
+ raise VcfError("Variants are only allowed to have one alternative")
70
+
67
71
  # Builds a dictionary with the different ids that are used
68
- alt = get_variant_alternative(variant, category)
72
+ parsed_variant = {
73
+ "case_id": case_id,
74
+ "variant_type": variant_type,
75
+ "reference": variant.REF,
76
+ "quality": variant.QUAL, # cyvcf2 will set QUAL to None if '.' in vcf
77
+ "filters": get_filters(variant),
78
+ "chromosome": chrom,
79
+ "compounds": parse_compounds(
80
+ compound_info=variant.INFO.get("Compounds"),
81
+ case_id=genmod_key,
82
+ variant_type=variant_type,
83
+ ),
84
+ "rank_score": parse_rank_score(variant.INFO.get("RankScore", ""), genmod_key) or 0,
85
+ "genetic_models": parse_genetic_models(variant.INFO.get("GeneticModels"), genmod_key),
86
+ "str_swegen_mean": call_safe(float, variant.INFO.get("SweGenMean")),
87
+ "str_swegen_std": call_safe(float, variant.INFO.get("SweGenStd")),
88
+ "somatic_score": call_safe(int, variant.INFO.get("SOMATICSCORE")),
89
+ "custom": parse_custom_data(variant.INFO.get("SCOUT_CUSTOM")),
90
+ }
91
+ category = get_and_set_category(parsed_variant, variant, category)
92
+ alt = get_and_set_variant_alternative(parsed_variant, variant, category)
69
93
 
70
94
  parsed_variant["ids"] = parse_ids(
71
95
  chrom=chrom,
@@ -75,71 +99,17 @@ def parse_variant(
75
99
  case_id=case_id,
76
100
  variant_type=variant_type,
77
101
  )
78
- parsed_variant["case_id"] = case_id
79
- # type can be 'clinical' or 'research'
80
- parsed_variant["variant_type"] = variant_type
81
102
 
82
- category = get_category(category, variant, parsed_variant)
83
- parsed_variant["category"] = category
103
+ set_coordinates(parsed_variant, variant, case, category)
84
104
 
85
- ################# General information #################
86
- parsed_variant["reference"] = variant.REF
105
+ parsed_variant["samples"] = get_samples(variant, individual_positions, case, category)
87
106
 
88
- ### We allways assume splitted and normalized vcfs!!!
89
- if len(variant.ALT) > 1:
90
- raise VcfError("Variants are only allowed to have one alternative")
91
- parsed_variant["alternative"] = alt
92
-
93
- # cyvcf2 will set QUAL to None if '.' in vcf
94
- parsed_variant["quality"] = variant.QUAL
95
-
96
- parsed_variant["filters"] = get_filters(variant)
97
-
98
- # Add the dbsnp ids
99
107
  set_dbsnp_id(parsed_variant, variant.ID)
100
108
 
101
109
  # This is the id of other position in translocations
102
110
  # (only for specific svs)
103
111
  parsed_variant["mate_id"] = None
104
112
 
105
- ################# Position specific #################
106
- parsed_variant["chromosome"] = chrom
107
-
108
- coordinates = parse_coordinates(variant, category, case.get("genome_build"))
109
-
110
- parsed_variant["cytoband_end"] = coordinates["cytoband_end"]
111
- parsed_variant["cytoband_start"] = coordinates["cytoband_start"]
112
- parsed_variant["end"] = coordinates["end"]
113
- parsed_variant["end_chrom"] = coordinates["end_chrom"]
114
- parsed_variant["length"] = coordinates["length"]
115
- parsed_variant["mate_id"] = coordinates["mate_id"]
116
- parsed_variant["position"] = coordinates["position"]
117
- parsed_variant["sub_category"] = coordinates["sub_category"]
118
-
119
- ################# Add rank score #################
120
- # The rank score is central for displaying variants in scout.
121
- # Use RankScore for somatic variations also
122
-
123
- rank_score = parse_rank_score(variant.INFO.get("RankScore", ""), genmod_key)
124
- parsed_variant["rank_score"] = rank_score or 0
125
-
126
- ################# Add gt calls #################
127
- parsed_variant["samples"] = get_samples(variant, individual_positions, case)
128
-
129
- ################# Add compound information #################
130
- compounds = parse_compounds(
131
- compound_info=variant.INFO.get("Compounds"),
132
- case_id=genmod_key,
133
- variant_type=variant_type,
134
- )
135
-
136
- parsed_variant["compounds"] = compounds
137
-
138
- ################# Add inheritance patterns #################
139
- parsed_variant["genetic_models"] = parse_genetic_models(
140
- variant.INFO.get("GeneticModels"), genmod_key
141
- )
142
-
143
113
  ################# Add autozygosity calls if present #################
144
114
  parsed_variant["azlength"] = call_safe(int, variant.INFO.get("AZLENGTH"))
145
115
  parsed_variant["azqual"] = call_safe(float, variant.INFO.get("AZQUAL"))
@@ -148,8 +118,6 @@ def parse_variant(
148
118
  set_str_info(variant, parsed_variant)
149
119
  # STR source dict with display string, source type and entry id
150
120
  set_str_source(parsed_variant, variant)
151
- parsed_variant["str_swegen_mean"] = call_safe(float, variant.INFO.get("SweGenMean"))
152
- parsed_variant["str_swegen_std"] = call_safe(float, variant.INFO.get("SweGenStd"))
153
121
 
154
122
  # MEI variant info
155
123
  set_mei_info(variant, parsed_variant)
@@ -157,18 +125,12 @@ def parse_variant(
157
125
  # Add Fusion info
158
126
  set_fusion_info(variant, parsed_variant)
159
127
 
160
- ################# Add somatic info ##################
161
- parsed_variant["somatic_score"] = call_safe(int, variant.INFO.get("SOMATICSCORE"))
162
-
163
128
  ################# Add mitomap info, from HmtNote #################
164
129
  set_mitomap_associated_diseases(parsed_variant, variant)
165
130
 
166
131
  ################# Add HmtVar variant id, from HmtNote #################
167
132
  add_hmtvar(parsed_variant, variant)
168
133
 
169
- ### Add custom info
170
- parsed_variant["custom"] = parse_custom_data(variant.INFO.get("SCOUT_CUSTOM"))
171
-
172
134
  ### Add gene and transcript information
173
135
  if parsed_variant.get("category") == "fusion":
174
136
  parsed_transcripts = add_gene_and_transcript_info_for_fusions(parsed_variant)
@@ -204,6 +166,25 @@ def parse_variant(
204
166
  return parsed_variant
205
167
 
206
168
 
169
+ def set_coordinates(parsed_variant: dict, variant: dict, case: dict, category: str):
170
+ """
171
+ Parse and set coordinate annotations
172
+ """
173
+ coordinates = parse_coordinates(variant, category, case.get("genome_build"))
174
+ parsed_variant.update(
175
+ {
176
+ "cytoband_end": coordinates["cytoband_end"],
177
+ "cytoband_start": coordinates["cytoband_start"],
178
+ "end": coordinates["end"],
179
+ "end_chrom": coordinates["end_chrom"],
180
+ "length": coordinates["length"],
181
+ "mate_id": coordinates["mate_id"],
182
+ "position": coordinates["position"],
183
+ "sub_category": coordinates["sub_category"],
184
+ }
185
+ )
186
+
187
+
207
188
  def set_mei_specific_annotations(parsed_variant: dict, variant: dict):
208
189
  """Add MEI specific annotations"""
209
190
  if parsed_variant.get("category") in ["mei"]:
@@ -315,20 +296,15 @@ def get_genmod_key(case):
315
296
  return case["_id"]
316
297
 
317
298
 
318
- def get_variant_alternative(variant, category):
319
- """Get variant's ALT
320
-
321
- Args:
322
- variant(cyvcf2.Variant)
323
- category(str)
324
- Return:
325
- alternative variant: Str
326
- """
299
+ def get_and_set_variant_alternative(parsed_variant: dict, variant: Variant, category: str) -> str:
300
+ """Get and set variant's ALT as alternative"""
327
301
 
328
302
  if variant.ALT:
329
- return variant.ALT[0]
303
+ alt = variant.ALT[0]
330
304
  elif not variant.ALT and category == "str":
331
- return "."
305
+ alt = "."
306
+ parsed_variant["alternative"] = alt
307
+ return alt
332
308
 
333
309
 
334
310
  def set_mei_info(variant: Variant, parsed_variant: Dict[str, Any]):
@@ -378,42 +354,46 @@ def get_filters(variant):
378
354
  return ["PASS"]
379
355
 
380
356
 
381
- def get_samples(variant, individual_positions, case):
357
+ def get_samples(variant: Variant, individual_positions: dict, case: dict, category: str) -> List:
382
358
  """Get samples
383
359
 
384
- Args:
385
- variant(cyvcf2.Variant)
386
- individual_positions(dict):
387
- case(dict)
388
- Return:
389
- variant filter
360
+ Add GT calls to individuals.
361
+
362
+ Do not add individuals if they are not wanted based on the analysis type,
363
+ eg a WTS only individual for a DNA SNV variant.
390
364
  """
391
- if individual_positions and case["individuals"]:
392
- return parse_genotypes(variant, case["individuals"], individual_positions)
365
+ invalid_sample_types: List[str] = []
366
+ if category in DNA_SAMPLE_VARIANT_CATEGORIES:
367
+ invalid_sample_types = ["wts"]
368
+
369
+ if individual_positions and bool(case["individuals"]):
370
+ individuals = [
371
+ ind
372
+ for ind in case["individuals"]
373
+ if ind.get("analysis_type") not in invalid_sample_types
374
+ ]
375
+ return parse_genotypes(variant, individuals, individual_positions)
393
376
  return []
394
377
 
395
378
 
396
- def get_category(category, variant, parsed_variant):
397
- """Get category of variant.
398
- Args:
399
- category(str)
400
- variant(cyvcf2.Variant)
401
- parsed_variant(dict)
402
- Return:
403
- category(str)
379
+ def get_and_set_category(parsed_variant: dict, variant: Variant, category: str) -> str:
380
+ """Set category of variant. Convenience return of category.
381
+
382
+ If category not set, use variant type, but convert types SNP or INDEL (or old MNP) to "snv".
404
383
  """
405
384
  if category:
385
+ parsed_variant["category"] = category
406
386
  return category
407
387
 
408
- var_type = variant.var_type
409
- if var_type == "indel":
410
- return "snv"
411
- if var_type == "snp":
412
- return "snv"
413
- if var_type == "mnp":
414
- LOG.warning("Category MNP found: {}".format(parsed_variant["ids"]["display_name"]))
415
- return "snv"
416
- return var_type
388
+ category = variant.var_type
389
+ if category in ["indel", "snp"]:
390
+ category = "snv"
391
+ if category == "mnp":
392
+ category = "snv"
393
+ LOG.warning("Category MNP found for variant. Setting to SNV.")
394
+
395
+ parsed_variant["category"] = category
396
+ return category
417
397
 
418
398
 
419
399
  def set_dbsnp_id(parsed_variant, variant_id):
@@ -660,14 +640,13 @@ def set_clnsig(parsed_variant, variant, parsed_transcripts):
660
640
  variant(cyvcf2.Variant)
661
641
  parsed_transcripts(list)
662
642
  """
663
- # XXX: Why is clnsig_predictions set to emtpy list and then compared?
664
- clnsig_predictions = []
665
- if len(clnsig_predictions) == 0 and len(parsed_transcripts) > 0:
666
- # Parse INFO fielf to collect clnsig info
667
- clnsig_predictions = parse_clnsig(variant, transcripts=parsed_transcripts)
668
-
643
+ clnsig_predictions = parse_clnsig(variant, transcripts=parsed_transcripts)
669
644
  parsed_variant["clnsig"] = clnsig_predictions
670
645
 
646
+ clnsig_onco_predictions = parse_clnsig_onc(variant)
647
+ if clnsig_onco_predictions:
648
+ parsed_variant["clnsig_onc"] = clnsig_onco_predictions
649
+
671
650
 
672
651
  def set_rank_result(parsed_variant, variant, rank_results_header):
673
652
  """Set rank_result in parsed_variant
scout/server/app.py CHANGED
@@ -29,6 +29,7 @@ from .blueprints import (
29
29
  institutes,
30
30
  login,
31
31
  managed_variants,
32
+ mme,
32
33
  omics_variants,
33
34
  panels,
34
35
  phenomodels,
@@ -116,6 +117,10 @@ def configure_extensions(app):
116
117
  extensions.chanjo_report.init_app(app)
117
118
  LOG.info("Chanjo extension enabled")
118
119
 
120
+ if app.config.get("CHANJO2_URL"):
121
+ LOG.info("Chanjo2 extension enabled")
122
+ extensions.chanjo2.init_app(app)
123
+
119
124
  if app.config.get("LOQUSDB_SETTINGS"):
120
125
  LOG.info("LoqusDB enabled")
121
126
  # setup LoqusDB
@@ -149,15 +154,7 @@ def configure_extensions(app):
149
154
  # setup rerunner service
150
155
  extensions.rerunner.init_app(app)
151
156
 
152
- if app.config.get("LDAP_HOST"):
153
- LOG.info("LDAP login enabled")
154
- # setup connection to server
155
- extensions.ldap_manager.init_app(app)
156
-
157
- if app.config.get("GOOGLE"):
158
- LOG.info("Google login enabled")
159
- # setup connection to google oauth2
160
- configure_oauth_login(app)
157
+ set_login_system(app)
161
158
 
162
159
  if app.config.get("CUSTOM_IGV_TRACKS") or app.config.get("CLOUD_IGV_TRACKS"):
163
160
  LOG.info("Collecting IGV tracks from cloud or local resources")
@@ -172,12 +169,28 @@ def configure_extensions(app):
172
169
  extensions.bionano_access.init_app(app)
173
170
 
174
171
 
172
+ def set_login_system(app):
173
+ """Initialize login system: LDAP, Google OAuth, Keycloak. If none of these is set, then simple database user matching is used."""
174
+ if app.config.get("LDAP_HOST"):
175
+ LOG.info("LDAP login enabled")
176
+ extensions.ldap_manager.init_app(app)
177
+
178
+ if app.config.get("GOOGLE"):
179
+ LOG.info("Google login enabled")
180
+ configure_google_login(app)
181
+
182
+ if app.config.get("KEYCLOAK"):
183
+ LOG.info("keycloak login enabled")
184
+ configure_keycloak_login(app)
185
+
186
+
175
187
  def register_blueprints(app):
176
188
  """Register Flask blueprints."""
177
189
  app.register_blueprint(public.public_bp)
178
190
  app.register_blueprint(genes.genes_bp)
179
191
  app.register_blueprint(cases.cases_bp)
180
192
  app.register_blueprint(clinvar.clinvar_bp)
193
+ app.register_blueprint(mme.mme_bp)
181
194
  app.register_blueprint(login.login_bp)
182
195
  app.register_blueprint(variant.variant_bp)
183
196
  app.register_blueprint(variants.variants_bp)
@@ -301,25 +314,29 @@ def register_tests(app):
301
314
  return os.path.exists(path)
302
315
 
303
316
 
304
- def configure_oauth_login(app):
305
- """Register the Google Oauth login client using config settings"""
306
-
307
- google_conf = app.config["GOOGLE"]
308
- discovery_url = google_conf.get("discovery_url")
309
- client_id = google_conf.get("client_id")
310
- client_secret = google_conf.get("client_secret")
311
-
317
+ def configure_oidc_login(app: Flask, provider_name: str, config_key: str):
318
+ """Register an OIDC login client using config settings."""
319
+ provider_conf = app.config[config_key]
312
320
  extensions.oauth_client.init_app(app)
313
-
314
321
  extensions.oauth_client.register(
315
- name="google",
316
- server_metadata_url=discovery_url,
317
- client_id=client_id,
318
- client_secret=client_secret,
322
+ name=provider_name,
323
+ server_metadata_url=provider_conf.get("discovery_url"),
324
+ client_id=provider_conf.get("client_id"),
325
+ client_secret=provider_conf.get("client_secret"),
319
326
  client_kwargs={"scope": "openid email profile"},
320
327
  )
321
328
 
322
329
 
330
+ def configure_google_login(app):
331
+ """Register the Google Oauth login client using config settings"""
332
+ configure_oidc_login(app, "google", "GOOGLE")
333
+
334
+
335
+ def configure_keycloak_login(app):
336
+ """Register a Keycloak OIDC login client using config settings"""
337
+ configure_oidc_login(app, "keycloak", "KEYCLOAK")
338
+
339
+
323
340
  def configure_email_logging(app):
324
341
  """Setup logging of error/exceptions to email."""
325
342
  import logging
@@ -1,7 +1,7 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  import logging
3
3
  import os.path
4
- from typing import Dict, Optional
4
+ from typing import Dict, List, Optional
5
5
 
6
6
  from flask import flash, session
7
7
  from flask_login import current_user
@@ -43,7 +43,9 @@ def set_session_tracks(display_obj: dict):
43
43
 
44
44
  session_tracks = list(display_obj.get("reference_track", {}).values())
45
45
  for key, track_items in display_obj.items():
46
- if key not in ["tracks", "custom_tracks", "sample_tracks", "config_custom_tracks"]:
46
+ if key not in ["tracks", "custom_tracks", "sample_tracks", "config_custom_tracks"] + list(
47
+ CASE_SPECIFIC_TRACKS.keys()
48
+ ):
47
49
  continue
48
50
  for track_item in track_items:
49
51
  session_tracks += list(track_item.values())
@@ -263,11 +265,25 @@ def make_locus_from_gene(variant_obj: Dict, case_obj: Dict, build: str) -> str:
263
265
  return f"{chrom}:{locus_start}-{locus_end}"
264
266
 
265
267
 
266
- def set_tracks(name, file_list):
267
- """Return a dict according to IGV track format."""
268
+ def get_tracks(name_list: list, file_list: list) -> List[Dict]:
269
+ """Return a list of dict according to IGV track format.
270
+
271
+ If an index can be found (e.g. for bam, cram files), use it explicitly.
272
+ If the format is one of those two alignment types, set it explicitly: it will need to be dynamically
273
+ filled into the igv_viewer html template script for igv.js.
274
+ """
268
275
  track_list = []
269
- for track in file_list:
270
- track_list.append({"name": name, "url": track, "min": 0.0, "max": 30.0})
276
+ for name, track in zip(name_list, file_list):
277
+ if track == "missing":
278
+ continue
279
+ track_config = {"name": name, "url": track, "min": 0.0, "max": 30.0}
280
+ index = find_index(track)
281
+ if index:
282
+ track_config["indexURL"] = index
283
+ file_format_ending = track.split(".")[-1]
284
+ if file_format_ending in ["bam", "cram"]:
285
+ track_config["format"] = file_format_ending
286
+ track_list.append(track_config)
271
287
  return track_list
272
288
 
273
289
 
@@ -338,17 +354,20 @@ def set_sample_tracks(display_obj: dict, case_groups: list, chromosome: str):
338
354
 
339
355
 
340
356
  def set_case_specific_tracks(display_obj, case_obj):
341
- """Set up tracks from files that might be present or not at the case level
357
+ """Set up tracks from files that might be present for the focus case samples,
358
+ not fetched for all samples in the case group.
342
359
  (rhocall files, tiddit coverage files, upd regions and sites files)
343
360
  Args:
344
361
  display_obj(dict) dictionary containing all tracks info
345
362
  form(dict) flask request form dictionary
346
363
  """
347
364
  for track, label in CASE_SPECIFIC_TRACKS.items():
348
- if case_obj.get(track) is None:
365
+ if None in [case_obj.get(track), case_obj.get("sample_names")]:
349
366
  continue
350
- track_info = set_tracks(label, case_obj.get(track).split(","))
351
- display_obj[track] = track_info
367
+
368
+ display_obj[track] = get_tracks(
369
+ [f"{label} - {sample}" for sample in case_obj.get("sample_names")], case_obj.get(track)
370
+ )
352
371
 
353
372
 
354
373
  def set_config_custom_tracks(display_obj: dict, build: str):
@@ -86,12 +86,12 @@
86
86
  {% endif %}
87
87
  },
88
88
  {% endfor %}
89
- {% for wtrack in rhocall_wig %}
89
+ {% for wtrack in rhocall_wigs %}
90
90
  {
91
91
  type: "wig",
92
92
  name: '{{ wtrack.name }}',
93
93
  url: '{{ url_for("alignviewers.remote_static", file=wtrack.url) }}',
94
- format: 'wig',
94
+ format: 'bw',
95
95
  {# indexURL: '{{ url_for("alignviewers.remote_static", file=wtrack.url) }}', #}
96
96
  color: "rgb(60, 37, 17)",
97
97
  min: '{{ wtrack.min }}',
@@ -99,12 +99,12 @@
99
99
  sourceType: 'file'
100
100
  },
101
101
  {% endfor %}
102
- {% for btrack in rhocall_bed %}
102
+ {% for btrack in rhocall_beds %}
103
103
  {
104
- type: "bed",
104
+ type: "annotation",
105
105
  name: '{{ btrack.name }}',
106
106
  url: '{{ url_for("alignviewers.remote_static", file=btrack.url) }}',
107
- format: 'bed',
107
+ format: 'bb',
108
108
  color: "rgb(65, 31, 30)",
109
109
  sourceType: 'file'
110
110
  },
@@ -117,33 +117,73 @@
117
117
  indexURL: "{{ url_for('alignviewers.remote_static', file=track.indexURL) }}",
118
118
  sourceType: "file",
119
119
  groupBy: "tag:HP",
120
- colorBy: "basemod2:m",
121
120
  showSoftClips: {{track.show_soft_clips | lower }},
122
121
  format: "{{ track.format }}",
123
122
  height: "{{track.height}}"
124
123
  },
125
124
  {% endfor %}
126
- {% for ttrack in tiddit_coverage_wig %}
125
+ {% for ttrack in tiddit_coverage_wigs %}
127
126
  {
128
127
  type: "wig",
128
+ format: 'bw',
129
129
  name: '{{ ttrack.name }}',
130
130
  url: '{{ url_for("alignviewers.remote_static", file=ttrack.url) }}',
131
131
  color: "rgb(40, 0, 13)",
132
132
  sourceType: 'file'
133
133
  },
134
134
  {% endfor %}
135
- {% for rtrack in upd_regions_bed %}
135
+ {% for maftrack in minor_allele_frequency_wigs %}
136
136
  {
137
- type: "bed",
137
+ type: "wig",
138
+ format: 'bw',
139
+ name: '{{ maftrack.name }}',
140
+ url: '{{ url_for("alignviewers.remote_static", file=maftrack.url) }}',
141
+ color: "rgb(60, 60, 140)",
142
+ sourceType: 'file'
143
+ },
144
+ {% endfor %}
145
+ {% for ptrack in paraphase_alignments %}
146
+ {
147
+ type: "alignment",
148
+ name: "{{ ptrack.name }}",
149
+ url: '{{ url_for("alignviewers.remote_static", file=ptrack.url) }}',
150
+ format: "{{ ptrack.format }}",
151
+ groupBy: "tag:HP",
152
+ indexURL: '{{ url_for("alignviewers.remote_static", file=ptrack.indexURL) }}',
153
+ min: '{{ ptrack.min }}',
154
+ max: '{{ ptrack.max }}',
155
+ sourceType: 'file'
156
+ },
157
+ {% endfor %}
158
+ {% for atrack in assembly_alignments %}
159
+ {
160
+ type: "alignment",
161
+ name: "{{ atrack.name }}",
162
+ url: '{{ url_for("alignviewers.remote_static", file=atrack.url) }}',
163
+ format: "{{ atrack.format }}",
164
+ groupBy: "tag:HP",
165
+ height: 140,
166
+ showCoverage: false,
167
+ indexURL: '{{ url_for("alignviewers.remote_static", file=atrack.indexURL) }}',
168
+ min: '{{ atrack.min }}',
169
+ max: '{{ atrack.max }}',
170
+ sourceType: 'file'
171
+ },
172
+ {% endfor %}
173
+ {% for rtrack in upd_regions_beds %}
174
+ {
175
+ type: 'annotation',
176
+ format: 'bb',
138
177
  name: '{{ rtrack.name }}',
139
178
  url: '{{ url_for("alignviewers.remote_static", file=rtrack.url) }}',
140
179
  color: "rgb(0, 204, 102)",
141
180
  sourceType: 'file'
142
181
  },
143
182
  {% endfor %}
144
- {% for strack in upd_sites_bed %}
183
+ {% for strack in upd_sites_beds %}
145
184
  {
146
- type: "bed",
185
+ type: "annotation",
186
+ format: 'bb',
147
187
  name: '{{ strack.name }}',
148
188
  url: '{{ url_for("alignviewers.remote_static", file=strack.url) }}',
149
189
  color: "rgb(25, 61, 4)",
@@ -40,7 +40,12 @@ from scout.constants.variant_tags import (
40
40
  MANUAL_RANK_OPTIONS,
41
41
  )
42
42
  from scout.export.variant import export_mt_variants
43
- from scout.parse.matchmaker import genomic_features, hpo_terms, omim_terms, parse_matches
43
+ from scout.parse.matchmaker import (
44
+ genomic_features,
45
+ hpo_terms,
46
+ omim_terms,
47
+ parse_matches,
48
+ )
44
49
  from scout.server.blueprints.variant.controllers import variant as variant_decorator
45
50
  from scout.server.blueprints.variants.controllers import get_manual_assessments
46
51
  from scout.server.extensions import (
@@ -61,6 +66,7 @@ from scout.server.utils import (
61
66
  case_has_mt_alignments,
62
67
  case_has_mtdna_report,
63
68
  case_has_rna_tracks,
69
+ get_case_mito_chromosome,
64
70
  institute_and_case,
65
71
  )
66
72
  from scout.utils.acmg import get_acmg_temperature
@@ -788,11 +794,11 @@ def mt_excel_files(store, case_obj, temp_excel_dir):
788
794
 
789
795
  # Check if coverage and MT copy number stats are available via chanjo2 or chanjo
790
796
  if case_obj.get("chanjo2_coverage"):
791
- coverage_stats: Dict[str, dict] = chanjo2.mt_coverage_stats(individuals=samples)
797
+ coverage_stats: Dict[str, dict] = chanjo2.mt_coverage_stats(case_obj=case_obj)
792
798
  elif case_obj.get("chanjo_coverage"):
793
799
  coverage_stats: Dict[str, dict] = chanjo_report.mt_coverage_stats(individuals=samples)
794
800
 
795
- query = {"chrom": "MT"}
801
+ query = {"chrom": get_case_mito_chromosome(case_obj)}
796
802
  mt_variants = list(
797
803
  store.variants(case_id=case_obj["_id"], query=query, nr_of_variants=-1, sort_key="position")
798
804
  )