scout-browser 4.82.1__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 (62) 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/case_bionano.html +3 -24
  32. scout/server/blueprints/cases/templates/cases/case_report.html +5 -3
  33. scout/server/blueprints/cases/templates/cases/case_sma.html +2 -13
  34. scout/server/blueprints/cases/templates/cases/collapsible_actionbar.html +1 -1
  35. scout/server/blueprints/cases/templates/cases/individuals_table.html +2 -12
  36. scout/server/blueprints/cases/templates/cases/phenotype.html +8 -6
  37. scout/server/blueprints/cases/templates/cases/utils.html +20 -3
  38. scout/server/blueprints/cases/views.py +8 -6
  39. scout/server/blueprints/variant/controllers.py +5 -5
  40. scout/server/blueprints/variant/templates/variant/acmg.html +25 -16
  41. scout/server/blueprints/variant/templates/variant/components.html +11 -6
  42. scout/server/blueprints/variant/views.py +5 -2
  43. scout/server/blueprints/variants/controllers.py +3 -5
  44. scout/server/blueprints/variants/templates/variants/str-variants.html +1 -1
  45. scout/server/blueprints/variants/views.py +1 -1
  46. scout/server/config.py +16 -4
  47. scout/server/extensions/__init__.py +4 -2
  48. scout/server/extensions/beacon_extension.py +1 -0
  49. scout/server/extensions/chanjo_extension.py +58 -0
  50. scout/server/extensions/phenopacket_extension.py +1 -0
  51. scout/server/static/bs_styles.css +18 -0
  52. scout/server/utils.py +16 -2
  53. scout/utils/acmg.py +33 -20
  54. scout/utils/track_resources.py +70 -0
  55. {scout_browser-4.82.1.dist-info → scout_browser-4.83.dist-info}/METADATA +1 -1
  56. {scout_browser-4.82.1.dist-info → scout_browser-4.83.dist-info}/RECORD +60 -60
  57. scout/load/case.py +0 -36
  58. scout/utils/cloud_resources.py +0 -61
  59. {scout_browser-4.82.1.dist-info → scout_browser-4.83.dist-info}/LICENSE +0 -0
  60. {scout_browser-4.82.1.dist-info → scout_browser-4.83.dist-info}/WHEEL +0 -0
  61. {scout_browser-4.82.1.dist-info → scout_browser-4.83.dist-info}/entry_points.txt +0 -0
  62. {scout_browser-4.82.1.dist-info → scout_browser-4.83.dist-info}/top_level.txt +0 -0
@@ -29,22 +29,27 @@
29
29
  <form action="{{ url_for('variant.variant_acmg', institute_id=institute._id, case_name=case.display_name, variant_id=variant._id) }}" method="POST">
30
30
  <div class="card panel-default mt-3">
31
31
  <div class="card-body">
32
- {% if not evaluation %}
33
- <div class="mt-3 fixed-bottom bg-light border">
34
- <div class="text-center">
35
- {% for option in ACMG_OPTIONS %}
36
- <a id="acmg-{{ option.code }}" class="btn acmg-preview">{{ option.label }}</a>
37
- {% endfor %}
38
- </div>
39
- </div>
40
- <button class="btn btn-primary form-control">Submit</button>
41
- {% else %}
42
- <h4>
32
+ {% if evaluation %}
33
+ <h4>
43
34
  {{ evaluation.classification.label }}
44
35
  <span class="badge bg-info">{{ evaluation.classification.short }}</span>
45
36
  </h4>
46
37
  By {{ evaluation.user_name }} on {{ evaluation.created_at.date() }}
38
+ {% if edit %}
39
+ <br><br>
40
+ <button class="btn btn-primary form-control" data-bs-toggle="tooltip" title="Editing this classification will result in a new classification">Reclassify</button>
41
+ {% endif %}
42
+ {% elif not evaluation %}
43
+ <button class="btn btn-primary form-control">Submit</button>
47
44
  {% endif %}
45
+ <!-- classification preview in the footer-->
46
+ <div class="mt-3 fixed-bottom bg-light border">
47
+ <div class="text-center">
48
+ {% for option in ACMG_OPTIONS %}
49
+ <a id="acmg-{{ option.code }}" class="btn acmg-preview">{{ option.label }}</a>
50
+ {% endfor %}
51
+ </div>
52
+ </div>
48
53
  </div>
49
54
  </div>
50
55
 
@@ -72,14 +77,14 @@
72
77
  {{ criterion.short }}
73
78
  </div>
74
79
  <div class="form-check form-switch">
75
- <input type="checkbox" class="form-check-input" id="checkbox-{{ criterion_code }}" name="criteria" value="{{ criterion_code }}" {{ 'checked' if evaluation and criterion_code in evaluation.criteria }} {{ 'disabled' if evaluation }}>
80
+ <input type="checkbox" class="form-check-input" id="checkbox-{{ criterion_code }}" name="criteria" value="{{ criterion_code }}" {{ 'checked' if evaluation and criterion_code in evaluation.criteria }} {{ 'disabled' if evaluation and edit is false}}>
76
81
  <label class="form-check-label" for="checkbox-{{ criterion_code }}"></label>
77
82
  </div>
78
83
  </div>
79
84
  <div id="comment-{{ criterion_code }}" class="{{ 'collapse' if not (comment or link or modifier) }} mt-2">
80
85
  {% if criterion.documentation %}<div class="row"><span class="me-1 fw-light text-wrap">{{ criterion.documentation | safe}}</span></div>{% endif %}
81
86
  <div class="row">
82
- <select {{ 'disabled' if evaluation }} id="modifier-{{ criterion_code }}" name="modifier-{{ criterion_code }}" class="form-control form-select">
87
+ <select {{ 'disabled' if evaluation and edit is false}} id="modifier-{{ criterion_code }}" name="modifier-{{ criterion_code }}" class="form-control form-select">
83
88
  <option value="" {% if not modifier %}selected{% endif %}>Strength modifier...</option>
84
89
  {% for level in "Strong", "Moderate", "Supporting" %}
85
90
  {% if(level != evidence) %}
@@ -89,10 +94,10 @@
89
94
  </select>
90
95
  </div>
91
96
  <div class="row">
92
- <textarea {{ 'disabled' if evaluation }} class="form-control"
97
+ <textarea {{ 'disabled' if evaluation and edit is false }} class="form-control"
93
98
  name="comment-{{ criterion_code }}" rows="3" placeholder="Comment (optional)">{{ comment }}</textarea>
94
99
  </div>
95
- <input type="url" {{ 'disabled' if evaluation }} class="form-control" name="link-{{ criterion_code }}"
100
+ <input type="url" {{ 'disabled' if evaluation and edit is false }} class="form-control" name="link-{{ criterion_code }}"
96
101
  placeholder="{{ link or 'Supporting link (optional)' }}" value="">
97
102
  </div>
98
103
  </div>
@@ -120,6 +125,11 @@
120
125
  {{ super() }}
121
126
 
122
127
  <script>
128
+
129
+ window.onload=function() {
130
+ update_classification();
131
+ }
132
+
123
133
  $(function () {
124
134
  $('[data-bs-toggle="tooltip"]').tooltip();
125
135
 
@@ -130,7 +140,6 @@
130
140
  } else {
131
141
  el.collapse('hide');
132
142
  }
133
-
134
143
  update_classification()
135
144
  });
136
145
 
@@ -49,14 +49,19 @@
49
49
  {% endif %}
50
50
  </span>
51
51
  </div>
52
- <small class="text-muted">
53
- <form action="{{ url_for('variant.evaluation', evaluation_id=data._id) }}" method="POST">
52
+ <span>
53
+ <small class="text-muted">
54
54
  {{ data.user_name }} on {{ data.created_at.date() }}
55
55
  {% if current_variant %}
56
- <button class="btn btn-xs btn-link">Delete</button>
56
+ <form action="{{ url_for('variant.evaluation', evaluation_id=data._id) }}" method="POST" style="display: inline-block;">
57
+ <button class="btn btn-xs btn-link" >Delete</button>
58
+ </form>
59
+ {% if data.criteria %}
60
+ <a class="btn btn-xs btn-link" href="{{ url_for('variant.evaluation', evaluation_id=data._id, edit=True) }}" data-bs-toggle="tooltip" title="Editing this classification will result in a new classification">Edit</a>
61
+ {% endif %}
57
62
  {% endif %}
58
- </form>
59
- </small>
63
+ </small>
64
+ </span>
60
65
  </li>
61
66
  {% endmacro %}
62
67
 
@@ -90,7 +95,7 @@
90
95
  {% endif %}
91
96
  </div>
92
97
  {% endfor %}
93
- {% if config.SQLALCHEMY_DATABASE_URI %}
98
+ {% if config.chanjo_report %}
94
99
  <div class="d-flex flex-wrap ms-1">
95
100
  {% for gene in variant.genes %}
96
101
  <a class="btn btn-sm btn-secondary text-white" rel="noopener noreferrer" target="_blank" href="{{ url_for('report.gene', gene_id=gene.hgnc_id, sample_id=variant.samples|map(attribute='sample_id')|list) }}">
@@ -13,7 +13,7 @@ from flask import (
13
13
  from flask_login import current_user
14
14
  from markupsafe import Markup
15
15
 
16
- from scout.constants import ACMG_CRITERIA, ACMG_MAP
16
+ from scout.constants import ACMG_CRITERIA, ACMG_MAP, ACMG_OPTIONS
17
17
  from scout.server.blueprints.variant.controllers import check_reset_variant_classification
18
18
  from scout.server.blueprints.variant.controllers import evaluation as evaluation_controller
19
19
  from scout.server.blueprints.variant.controllers import observations, str_variant_reviewer
@@ -308,7 +308,7 @@ def variant_update(institute_id, case_name, variant_id):
308
308
  @variant_bp.route("/evaluations/<evaluation_id>", methods=["GET", "POST"])
309
309
  @templated("variant/acmg.html")
310
310
  def evaluation(evaluation_id):
311
- """Show or delete an ACMG evaluation."""
311
+ """Show, edit or delete an ACMG evaluation."""
312
312
 
313
313
  evaluation_obj = store.get_evaluation(evaluation_id)
314
314
  if evaluation_obj is None:
@@ -328,12 +328,15 @@ def evaluation(evaluation_id):
328
328
  flash("Cleared ACMG classification.", "info")
329
329
 
330
330
  return redirect(link)
331
+
331
332
  return dict(
332
333
  evaluation=evaluation_obj,
334
+ edit=bool(request.args.get("edit")),
333
335
  institute=evaluation_obj["institute"],
334
336
  case=evaluation_obj["case"],
335
337
  variant=evaluation_obj["variant"],
336
338
  CRITERIA=ACMG_CRITERIA,
339
+ ACMG_OPTIONS=ACMG_OPTIONS,
337
340
  )
338
341
 
339
342
 
@@ -941,7 +941,6 @@ def parse_variant(
941
941
  variant_obj["end_chrom"] = variant_obj["chromosome"]
942
942
 
943
943
  # common motif count for STR variants
944
-
945
944
  variant_obj["str_mc"] = get_str_mc(variant_obj)
946
945
 
947
946
  # variant level links shown on variants page
@@ -970,7 +969,6 @@ def get_str_mc(variant_obj: dict) -> Optional[int]:
970
969
  from the variant FORMAT field, or as a number given in the ALT on the form
971
970
  '<STR123>'.
972
971
  """
973
-
974
972
  alt_mc = None
975
973
  if variant_obj["alternative"] == ".":
976
974
  return alt_mc
@@ -982,9 +980,9 @@ def get_str_mc(variant_obj: dict) -> Optional[int]:
982
980
  if alt_mc:
983
981
  return alt_mc
984
982
 
985
- alt_num = NUM.match(variant_obj["alternative"])
983
+ alt_num = NUM.search(variant_obj["alternative"])
986
984
  if alt_num:
987
- alt_mc = int(alt_num)
985
+ alt_mc = int(alt_num.group())
988
986
  return alt_mc
989
987
 
990
988
  return None
@@ -1998,7 +1996,7 @@ def activate_case(store, institute_obj, case_obj, current_user):
1998
1996
  store.update_status(institute_obj, case_obj, user_obj, "active", case_link)
1999
1997
 
2000
1998
 
2001
- def reset_all_dimissed(store, institute_obj, case_obj):
1999
+ def reset_all_dismissed(store, institute_obj, case_obj):
2002
2000
  """Reset all dismissed variants for a case.
2003
2001
 
2004
2002
  Args:
@@ -93,7 +93,7 @@
93
93
  <td class="str-link">{{ str_locus_info(variant) }}</td>
94
94
  <td class="text-end">{{ variant.str_display_ru or variant.str_ru or variant.reference }}</td>
95
95
  <td class="text-end"><b><span data-bs-toggle="tooltip" title="{{ variant.alternative }}">{{ variant.str_mc }}</span></b></td>
96
- <td class="text-end"><span data-bs-toggle="tooltip" title="{{ variant.reference }}">{{ variant.str_ref or "." }}</span></td>
96
+ <td class="text-end"><span data-bs-toggle="tooltip" title="{{ variant.reference }}">{{ variant.str_ref or "." }}</span></td>
97
97
  <td>{{ str_status(variant) }}</td>
98
98
  <td>{% for sample in variant.samples %}
99
99
  {% if sample.genotype_call != "./." %}
@@ -47,7 +47,7 @@ variants_bp = Blueprint(
47
47
  def reset_dismissed(institute_id, case_name):
48
48
  """Reset all dismissed variants for a case"""
49
49
  institute_obj, case_obj = institute_and_case(store, institute_id, case_name)
50
- controllers.reset_all_dimissed(store, institute_obj, case_obj)
50
+ controllers.reset_all_dismissed(store, institute_obj, case_obj)
51
51
  return redirect(request.referrer)
52
52
 
53
53
 
scout/server/config.py CHANGED
@@ -75,10 +75,9 @@ ACCREDITATION_BADGE = "swedac-1926-iso17025.png"
75
75
  # Connection details for Scout REViewer service
76
76
  # SCOUT_REVIEWER_URL = "http://127.0.0.1:8000/reviewer"
77
77
 
78
- #
79
- # Cloud IGV tracks can be configured here to allow users to enable them on their IGV views.
80
- # A number of publicly-available tracks can be found here: https://trackhubregistry.org/
81
- # CLOUD_IGV_TRACKS = [
78
+ # Custom IGV tracks can be configured here to allow users to enable them on their IGV views.
79
+ # A number of publicly-available tracks can be found here: https://trackhubregistry.org/ or downloaded from
80
+ # CUSTOM_IGV_TRACKS = [
82
81
  # {
83
82
  # "name": "public_tracks",
84
83
  # "access": "public",
@@ -99,6 +98,19 @@ ACCREDITATION_BADGE = "swedac-1926-iso17025.png"
99
98
  # },
100
99
  # ],
101
100
  # },
101
+ # {
102
+ # "name": "local_tracks",
103
+ # "access": "public",
104
+ # "tracks": [
105
+ # {
106
+ # "name": "MANE",
107
+ # "type": "annotation",
108
+ # "format": "bigbed",
109
+ # "build": "38",
110
+ # "url": "scout/resources/custom_igv_tracks/mane.bb",
111
+ # },
112
+ # ],
113
+ # },
102
114
  # ]
103
115
 
104
116
  # Chanjo-Report
@@ -6,10 +6,11 @@ from flask_login import LoginManager
6
6
  from flask_mail import Mail
7
7
 
8
8
  from scout.adapter import MongoAdapter
9
- from scout.utils.cloud_resources import AlignTrackHandler
9
+ from scout.utils.track_resources import AlignTrackHandler
10
10
 
11
11
  from .beacon_extension import Beacon
12
12
  from .bionano_extension import BioNanoAccessAPI
13
+ from .chanjo_extension import ChanjoReport
13
14
  from .clinvar_extension import ClinVarApi
14
15
  from .gens_extension import GensViewer
15
16
  from .ldap_extension import LdapManager
@@ -33,5 +34,6 @@ phenopacketapi = PhenopacketAPI()
33
34
  rerunner = RerunnerService()
34
35
  matchmaker = MatchMaker()
35
36
  beacon = Beacon()
36
- cloud_tracks = AlignTrackHandler()
37
+ config_igv_tracks = AlignTrackHandler()
37
38
  bionano_access = BioNanoAccessAPI()
39
+ chanjo_report = ChanjoReport()
@@ -1,6 +1,7 @@
1
1
  """Scout supports integration with the Clinical Genomics SciLifeLab Beacon
2
2
  cgbeacon2: https://github.com/Clinical-Genomics/cgbeacon2
3
3
  """
4
+
4
5
  import datetime
5
6
  import logging
6
7
 
@@ -0,0 +1,58 @@
1
+ """
2
+ Generate coverage reports using chanjo and chanjo-report. Documentation under -> `docs/admin-guide/chanjo_coverage_integration.md`
3
+ """
4
+
5
+ import logging
6
+
7
+ from flask import current_app, request
8
+ from flask_babel import Babel
9
+ from markupsafe import Markup
10
+
11
+ LOG = logging.getLogger(__name__)
12
+ try:
13
+ from chanjo_report.server.app import configure_template_filters
14
+ from chanjo_report.server.blueprints import report_bp
15
+ from chanjo_report.server.extensions import api as chanjo_api
16
+ except ImportError as error:
17
+ chanjo_api = None
18
+ report_bp = None
19
+ configure_template_filters = None
20
+ LOG.warning("chanjo-report is not properly installed! %s.", error)
21
+
22
+
23
+ class ChanjoReport:
24
+ """Interfaces with chanjo-report. Creates the /reports endpoints in scout domain. Use Babel to set report language."""
25
+
26
+ def init_app(self, app):
27
+ if not chanjo_api:
28
+ raise ImportError(
29
+ "An SQL db path was given, but chanjo-report could not be registered."
30
+ )
31
+
32
+ def get_locale():
33
+ """Determine locale to use for translations."""
34
+ accept_languages = current_app.config.get("ACCEPT_LANGUAGES", ["en"])
35
+
36
+ # first check request args
37
+ session_language = Markup.escape(request.args.get("lang"))
38
+ if session_language in accept_languages:
39
+ current_app.logger.info("using session language: %s", session_language)
40
+ return session_language
41
+
42
+ # language can be forced in config
43
+ user_language = current_app.config.get("REPORT_LANGUAGE")
44
+ if user_language:
45
+ return user_language
46
+
47
+ # try to guess the language from the user accept header that
48
+ # the browser transmits. We support de/fr/en in this example.
49
+ # The best match wins.
50
+ return request.accept_languages.best_match(accept_languages)
51
+
52
+ babel = Babel(app)
53
+ babel.init_app(app, locale_selector=get_locale)
54
+ chanjo_api.init_app(app)
55
+ configure_template_filters(app)
56
+ app.register_blueprint(report_bp, url_prefix="/reports")
57
+ app.config["chanjo_report"] = True
58
+ app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = True if app.debug else False
@@ -3,6 +3,7 @@ Extension to Scout for Phenopacket integration (http://phenopackets.org).
3
3
  Write and read JSON Phenopackets, and interact with phenopacket-api backend
4
4
  (https://github.com/squeezeday/phenopacket-api).
5
5
  """
6
+
6
7
  import json as json_lib
7
8
  import logging
8
9
 
@@ -351,6 +351,24 @@ body {
351
351
  }
352
352
  /* end of sidebar-related stuff */
353
353
 
354
+ /* Closed submenu icon - general case */
355
+ .text-body[aria-expanded="false"] .collapse-icon::after {
356
+ content: " \f0d7";
357
+ font-family: "Font Awesome 5 Free", ui-monospace;
358
+ font-weight: 900;
359
+ display: inline;
360
+ text-align: left;
361
+ }
362
+ /* Opened submenu icon - general case */
363
+ .text-body[aria-expanded="true"] .collapse-icon::after {
364
+ content: " \f0da";
365
+ font-family: "Font Awesome 5 Free", ui-monospace;
366
+ font-weight: 900;
367
+ display: inline;
368
+ text-align: left;
369
+ }
370
+ /* end of sidebar-related stuff */
371
+
354
372
  option {
355
373
  width: 100%;
356
374
  }
scout/server/utils.py CHANGED
@@ -206,12 +206,26 @@ def user_institutes(store, login_user):
206
206
  return institutes
207
207
 
208
208
 
209
+ def case_has_mtdna_report(case_obj: dict):
210
+ """Display mtDNA report on the case sidebar only for some specific cases."""
211
+ if case_obj.get("track", "rare") == "cancer":
212
+ return
213
+ for ind in case_obj.get("individuals", []):
214
+ if ind.get("analysis_type") == "wts":
215
+ continue
216
+ case_obj["mtdna_report"] = True
217
+ return
218
+
219
+
209
220
  def case_has_chanjo_coverage(case_obj: dict):
210
221
  """Set case_obj["chanjo_coverage"] to True if there is an instance of chanjo available and case has coverage stats in chanjo."""
211
222
 
212
- chanjo_instance: bool = bool(current_app.config.get("SQLALCHEMY_DATABASE_URI"))
223
+ chanjo_instance: bool = bool(current_app.config.get("chanjo_report"))
213
224
  if case_obj.get("track", "rare") != "cancer" and chanjo_instance:
214
- case_obj["chanjo_coverage"] = True
225
+ for ind in case_obj.get("individuals", []):
226
+ if ind.get("analysis_type") != "wts":
227
+ case_obj["chanjo_coverage"] = True
228
+ return
215
229
 
216
230
 
217
231
  def case_has_chanjo2_coverage(case_obj: dict):
scout/utils/acmg.py CHANGED
@@ -190,27 +190,40 @@ def get_acmg(acmg_terms):
190
190
  bs_terms = []
191
191
  # Collection of terms with supporting Benign evidence
192
192
  bp_terms = []
193
+
194
+ suffix_map = {
195
+ "_Strong": {"P": ps_terms, "B": bs_terms},
196
+ "_Moderate": {"P": pm_terms},
197
+ "_Supporting": {"P": pp_terms, "B": bp_terms},
198
+ }
199
+
200
+ prefix_map = {
201
+ "PS": ps_terms,
202
+ "PM": pm_terms,
203
+ "PP": pp_terms,
204
+ "BS": bs_terms,
205
+ "BP": bp_terms,
206
+ }
207
+
193
208
  for term in acmg_terms:
194
- if term.endswith("_Strong"):
195
- ps_terms.append(term)
196
- elif term.endswith("_Moderate"):
197
- pm_terms.append(term)
198
- elif term.endswith("_Supporting"):
199
- pp_terms.append(term)
200
- elif term.startswith("PVS"):
201
- pvs = True
202
- elif term.startswith("PS"):
203
- ps_terms.append(term)
204
- elif term.startswith("PM"):
205
- pm_terms.append(term)
206
- elif term.startswith("PP"):
207
- pp_terms.append(term)
208
- elif term.startswith("BA"):
209
- ba = True
210
- elif term.startswith("BS"):
211
- bs_terms.append(term)
212
- elif term.startswith("BP"):
213
- bp_terms.append(term)
209
+ for suffix, prefix_dict in suffix_map.items():
210
+ if term.endswith(suffix):
211
+ for prefix, term_list in prefix_dict.items():
212
+ if term.startswith(prefix):
213
+ term_list.append(term)
214
+ break
215
+ break
216
+ else:
217
+ # Do we match any of the two standalone terms
218
+ if term.startswith("PVS"):
219
+ pvs = True
220
+ elif term.startswith("BA"):
221
+ ba = True
222
+ else: # Check remaining prefixes if no suffix match or standalone criteria match
223
+ for prefix, term_list in prefix_map.items():
224
+ if term.startswith(prefix):
225
+ term_list.append(term)
226
+ break
214
227
 
215
228
  # We need to start by checking for Pathogenecity
216
229
  pathogenic = is_pathogenic(pvs, ps_terms, pm_terms, pp_terms)
@@ -0,0 +1,70 @@
1
+ # coding=UTF-8
2
+ import logging
3
+ import re
4
+ from os.path import abspath, exists, isabs
5
+
6
+ LOG = logging.getLogger(__name__)
7
+
8
+ REQUIRED_FIELDS = ["name", "type", "url"]
9
+ TRACK_KEYS = ["name", "type", "format", "url", "indexURL"]
10
+ URL_PATTERN = re.compile("https?://")
11
+
12
+ from typing import List
13
+
14
+
15
+ class AlignTrackHandler:
16
+ """Class collecting external IGV tracks stored in the cloud"""
17
+
18
+ def init_app(self, app):
19
+ self.tracks = self.set_custom_tracks(
20
+ app.config.get("CUSTOM_IGV_TRACKS") or app.config.get("CLOUD_IGV_TRACKS")
21
+ )
22
+
23
+ def track_template(self, track_info: dict) -> dict:
24
+ """Provides the template for a VCF track object"""
25
+ track = {}
26
+
27
+ for key in TRACK_KEYS:
28
+ if key in track_info:
29
+ track[key] = track_info[key]
30
+
31
+ # Save track only of it contains the minimal required keys
32
+ if all(track.get(key) for key in REQUIRED_FIELDS):
33
+ return track
34
+ else:
35
+ raise FileNotFoundError(
36
+ f"One or more custom tracks specified in the config file could not be used, Please provide all required keys:{REQUIRED_FIELDS}"
37
+ )
38
+
39
+ def set_local_track_path(self, path: str) -> str:
40
+ """Returns the complete path to a local igv track file"""
41
+ if exists(path) and isabs(path):
42
+ return path
43
+ elif exists(path):
44
+ return abspath(path)
45
+ else:
46
+ raise FileNotFoundError(f"Could not verify path to IGV track:{path}")
47
+
48
+ def set_custom_tracks(self, track_dictionaries: List[dict]) -> dict:
49
+ """Return a list of IGV tracks collected from the config settings
50
+
51
+ Args:
52
+ track_dictionaries: a list of cloud bucket dictionaries containing IGV tracks, can be public or private
53
+ """
54
+ custom_tracks = {"37": [], "38": []}
55
+ # Loop over the bucket list and collect all public tracks
56
+ for track_category in track_dictionaries:
57
+ for track in track_category.get("tracks", []):
58
+ build = track.get("build")
59
+ if build not in ["37", "38"]:
60
+ LOG.warning(
61
+ "One or more IGV public tracks could not be used, Please provide a genome build ('37', '38') for it."
62
+ )
63
+ continue
64
+ track_obj = self.track_template(track)
65
+
66
+ if not URL_PATTERN.search(track_obj["url"]): # It's a local file
67
+ track_obj["url"] = self.set_local_track_path(track_obj["url"])
68
+
69
+ custom_tracks[build].append(track_obj)
70
+ return custom_tracks
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: scout-browser
3
- Version: 4.82.1
3
+ Version: 4.83
4
4
  Summary: Clinical DNA variant visualizer and browser.
5
5
  Home-page: https://github.com/Clinical-Genomics/scout
6
6
  Author: Måns Magnusson