scout-browser 4.85__py3-none-any.whl → 4.86.1__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/mongo/base.py +17 -14
- scout/adapter/mongo/case.py +20 -1
- scout/adapter/mongo/filter.py +36 -1
- scout/adapter/mongo/hgnc.py +15 -15
- scout/adapter/mongo/omics_variant.py +145 -0
- scout/adapter/mongo/query.py +13 -3
- scout/adapter/mongo/variant.py +10 -4
- scout/build/case.py +5 -0
- scout/build/variant/variant.py +1 -0
- scout/constants/__init__.py +3 -1
- scout/constants/case_tags.py +1 -0
- scout/constants/clinvar.py +1 -1
- scout/constants/file_types.py +31 -0
- scout/constants/filters.py +4 -0
- scout/constants/indexes.py +30 -13
- scout/constants/variant_tags.py +3 -0
- scout/demo/643594.clinical.mei.vcf.gz +0 -0
- scout/demo/643594.clinical.mei.vcf.gz.tbi +0 -0
- scout/demo/643594.config.yaml +4 -0
- scout/demo/drop/fraser_top_hits_clinical.tsv +5 -0
- scout/demo/drop/outrider_top_hits_clinical.tsv +10 -0
- scout/load/panel.py +33 -8
- scout/load/setup.py +4 -4
- scout/models/case/case_loading_models.py +25 -2
- scout/models/omics_variant.py +227 -0
- scout/parse/omics_variant/__init__.py +11 -0
- scout/parse/omics_variant/drop.py +19 -0
- scout/parse/variant/callers.py +6 -3
- scout/parse/variant/frequency.py +10 -2
- scout/server/app.py +4 -1
- scout/server/blueprints/alignviewers/controllers.py +35 -24
- scout/server/blueprints/alignviewers/templates/alignviewers/igv_sashimi_viewer.html +19 -15
- scout/server/blueprints/alignviewers/templates/alignviewers/igv_viewer.html +45 -5
- scout/server/blueprints/alignviewers/templates/alignviewers/utils.html +1 -1
- scout/server/blueprints/alignviewers/views.py +10 -2
- scout/server/blueprints/cases/controllers.py +3 -0
- scout/server/blueprints/cases/templates/cases/case.html +27 -9
- scout/server/blueprints/cases/templates/cases/case_report.html +2 -17
- scout/server/blueprints/cases/templates/cases/phenotype.html +8 -5
- scout/server/blueprints/cases/templates/cases/utils.html +26 -3
- scout/server/blueprints/clinvar/controllers.py +9 -3
- scout/server/blueprints/dashboard/controllers.py +44 -13
- scout/server/blueprints/dashboard/static/charts.js +46 -36
- scout/server/blueprints/dashboard/templates/dashboard/dashboard_general.html +2 -2
- scout/server/blueprints/institutes/forms.py +2 -0
- scout/server/blueprints/institutes/templates/overview/cases.html +6 -4
- scout/server/blueprints/institutes/templates/overview/gene_variants.html +40 -27
- scout/server/blueprints/institutes/templates/overview/institute_sidebar.html +1 -1
- scout/server/blueprints/institutes/views.py +5 -12
- scout/server/blueprints/omics_variants/__init__.py +1 -0
- scout/server/blueprints/omics_variants/controllers.py +122 -0
- scout/server/blueprints/omics_variants/templates/omics_variants/outliers.html +262 -0
- scout/server/blueprints/omics_variants/views.py +106 -0
- scout/server/blueprints/panels/controllers.py +1 -7
- scout/server/blueprints/panels/templates/panels/panels.html +12 -4
- scout/server/blueprints/panels/views.py +9 -11
- scout/server/blueprints/variant/templates/variant/buttons.html +7 -2
- scout/server/blueprints/variant/templates/variant/str-variant-reviewer.html +1 -1
- scout/server/blueprints/variant/templates/variant/utils.html +1 -1
- scout/server/blueprints/variant/utils.py +54 -103
- scout/server/blueprints/variant/views.py +1 -0
- scout/server/blueprints/variants/controllers.py +1 -4
- scout/server/blueprints/variants/forms.py +42 -0
- scout/server/blueprints/variants/templates/variants/utils.html +8 -4
- scout/server/blueprints/variants/views.py +28 -7
- scout/server/config.py +4 -0
- scout/server/extensions/clinvar_extension.py +7 -7
- scout/server/links.py +2 -2
- scout/server/templates/bootstrap_global.html +1 -4
- scout/server/templates/utils.html +3 -3
- scout/server/utils.py +4 -1
- {scout_browser-4.85.dist-info → scout_browser-4.86.1.dist-info}/METADATA +10 -10
- {scout_browser-4.85.dist-info → scout_browser-4.86.1.dist-info}/RECORD +78 -68
- {scout_browser-4.85.dist-info → scout_browser-4.86.1.dist-info}/WHEEL +1 -1
- {scout_browser-4.85.dist-info → scout_browser-4.86.1.dist-info}/LICENSE +0 -0
- {scout_browser-4.85.dist-info → scout_browser-4.86.1.dist-info}/entry_points.txt +0 -0
- {scout_browser-4.85.dist-info → scout_browser-4.86.1.dist-info}/top_level.txt +0 -0
@@ -3,7 +3,7 @@ import logging
|
|
3
3
|
from flask import flash, redirect, request, url_for
|
4
4
|
from flask_login import current_user
|
5
5
|
|
6
|
-
from scout.constants import CASE_SEARCH_TERMS
|
6
|
+
from scout.constants import CASE_SEARCH_TERMS, CASE_TAGS
|
7
7
|
from scout.server.extensions import store
|
8
8
|
from scout.server.utils import user_institutes
|
9
9
|
|
@@ -139,8 +139,22 @@ def get_dashboard_info(adapter, data={}, institute_id=None, slice_query=None):
|
|
139
139
|
"count": general_sliced_info["cohort_cases"],
|
140
140
|
"percent": general_sliced_info["cohort_cases"] / total_sliced_cases,
|
141
141
|
},
|
142
|
+
{
|
143
|
+
"title": "Case status tag",
|
144
|
+
"count": general_sliced_info["tagged_cases"],
|
145
|
+
"percent": general_sliced_info["tagged_cases"] / total_sliced_cases,
|
146
|
+
},
|
142
147
|
]
|
143
148
|
|
149
|
+
for stats_tag in CASE_TAGS.keys():
|
150
|
+
overview.append(
|
151
|
+
{
|
152
|
+
"title": CASE_TAGS[stats_tag]["label"] + " status tag",
|
153
|
+
"count": general_sliced_info[stats_tag + "_cases"],
|
154
|
+
"percent": general_sliced_info[stats_tag + "_cases"] / total_sliced_cases,
|
155
|
+
}
|
156
|
+
)
|
157
|
+
|
144
158
|
data["overview"] = overview
|
145
159
|
|
146
160
|
return data
|
@@ -149,6 +163,11 @@ def get_dashboard_info(adapter, data={}, institute_id=None, slice_query=None):
|
|
149
163
|
def get_general_case_info(adapter, institute_id=None, slice_query=None):
|
150
164
|
"""Return general information about cases
|
151
165
|
|
166
|
+
Count e.g. cases with each kind of case tag, as well as cases that have phenotype, causatives or pinned variants
|
167
|
+
marked or are part of cohorts.
|
168
|
+
|
169
|
+
Gather pedigree information - single, duo, trio or many individuals in case.
|
170
|
+
|
152
171
|
Args:
|
153
172
|
adapter(adapter.MongoAdapter)
|
154
173
|
institute_id(str)
|
@@ -168,15 +187,24 @@ def get_general_case_info(adapter, institute_id=None, slice_query=None):
|
|
168
187
|
"suspects": 1,
|
169
188
|
"cohorts": 1,
|
170
189
|
"individuals": 1,
|
190
|
+
"tags": 1,
|
171
191
|
}
|
172
192
|
cases = adapter.cases(
|
173
193
|
owner=institute_id, name_query=name_query, projection=CASE_GENERAL_INFO_PROJECTION
|
174
194
|
)
|
175
195
|
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
196
|
+
case_counter_keys = [
|
197
|
+
"phenotype",
|
198
|
+
"causative",
|
199
|
+
"pinned",
|
200
|
+
"cohort",
|
201
|
+
"tagged",
|
202
|
+
]
|
203
|
+
case_counter_keys.extend(CASE_TAGS.keys())
|
204
|
+
|
205
|
+
case_counter = {}
|
206
|
+
for counter in case_counter_keys:
|
207
|
+
case_counter[counter] = 0
|
180
208
|
|
181
209
|
pedigree = {
|
182
210
|
1: {"title": "Single", "count": 0},
|
@@ -191,13 +219,18 @@ def get_general_case_info(adapter, institute_id=None, slice_query=None):
|
|
191
219
|
for total_cases, case in enumerate(cases, 1):
|
192
220
|
case_ids.add(case["_id"])
|
193
221
|
if case.get("phenotype_terms"):
|
194
|
-
|
222
|
+
case_counter["phenotype"] += 1
|
195
223
|
if case.get("causatives"):
|
196
|
-
|
224
|
+
case_counter["causative"] += 1
|
197
225
|
if case.get("suspects"):
|
198
|
-
|
226
|
+
case_counter["pinned"] += 1
|
199
227
|
if case.get("cohorts"):
|
200
|
-
|
228
|
+
case_counter["cohort"] += 1
|
229
|
+
if case.get("tags"):
|
230
|
+
case_counter["tagged"] += 1
|
231
|
+
case_tags = case.get("tags")
|
232
|
+
for tag in case_tags:
|
233
|
+
case_counter[tag] += 1
|
201
234
|
|
202
235
|
nr_individuals = len(case.get("individuals", []))
|
203
236
|
if nr_individuals == 0:
|
@@ -208,12 +241,10 @@ def get_general_case_info(adapter, institute_id=None, slice_query=None):
|
|
208
241
|
pedigree[nr_individuals]["count"] += 1
|
209
242
|
|
210
243
|
general["total_cases"] = total_cases
|
211
|
-
general["phenotype_cases"] = phenotype_cases
|
212
|
-
general["causative_cases"] = causative_cases
|
213
|
-
general["pinned_cases"] = pinned_cases
|
214
|
-
general["cohort_cases"] = cohort_cases
|
215
244
|
general["pedigree"] = pedigree
|
216
245
|
general["case_ids"] = case_ids
|
246
|
+
for counter in case_counter_keys:
|
247
|
+
general[counter + "_cases"] = case_counter[counter]
|
217
248
|
|
218
249
|
return general
|
219
250
|
|
@@ -15,12 +15,16 @@ function analysisTypeData(analysis_types) {
|
|
15
15
|
"bkgColors": {
|
16
16
|
"WES": "#46bfbd",
|
17
17
|
"WGS": "#fdb45c",
|
18
|
-
"PANEL": "#44449B"
|
18
|
+
"PANEL": "#44449B",
|
19
|
+
"PANEL-UMI": "#949fb1",
|
20
|
+
"WTS": "#f7464A"
|
19
21
|
},
|
20
22
|
"hoverColors": {
|
21
23
|
"WES": "#5ad3d1",
|
22
24
|
"WGS": "#ffc870",
|
23
|
-
"PANEL": "#353578"
|
25
|
+
"PANEL": "#353578",
|
26
|
+
"PANEL-UMI": "#a8b3c5",
|
27
|
+
"WTS": "#ff5a5e"
|
24
28
|
}
|
25
29
|
};
|
26
30
|
var bkg = [];
|
@@ -77,7 +81,7 @@ function casesType(cases) {
|
|
77
81
|
var bkg = [];
|
78
82
|
var hover = [];
|
79
83
|
labels.forEach(function (item, index) {
|
80
|
-
|
84
|
+
bkg.push(bkgColors[index]);
|
81
85
|
hover.push(hoverColors[index]);
|
82
86
|
});
|
83
87
|
var chart_data = {
|
@@ -129,52 +133,58 @@ function casesDetailed(overview, all_cases) {
|
|
129
133
|
var bkg = [];
|
130
134
|
var hover = [];
|
131
135
|
labels.forEach(function (item, index) {
|
132
|
-
|
136
|
+
ncol = bkgColors.length;
|
137
|
+
bkg.push(bkgColors[index % ncol]);
|
133
138
|
});
|
134
139
|
var chart_data = {
|
135
|
-
type: "
|
140
|
+
type: "bar",
|
136
141
|
data: {
|
137
142
|
labels: labels,
|
138
143
|
datasets: [{
|
139
144
|
data: overview.map(function (overview) {
|
140
145
|
return overview.count * 100 / all_cases;
|
141
146
|
}),
|
147
|
+
label: "Case percentage",
|
142
148
|
backgroundColor: bkg,
|
143
149
|
hoverBackgroundColor: hover
|
144
150
|
}]
|
145
151
|
},
|
146
152
|
options: {
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
153
|
+
indexAxis: 'y',
|
154
|
+
tooltips: {
|
155
|
+
callbacks: {
|
156
|
+
label: function label(tooltipItems) {
|
157
|
+
return Math.round(Number(tooltipItems.value) * all_cases / 100);
|
158
|
+
}
|
159
|
+
}
|
160
|
+
},
|
161
|
+
scales: {
|
162
|
+
x: {
|
163
|
+
ticks: {
|
164
|
+
min: 0,
|
165
|
+
max: 100,
|
166
|
+
callback: function callback(value) {
|
167
|
+
return value + "%";
|
168
|
+
}
|
169
|
+
},
|
170
|
+
title: {
|
171
|
+
display: true,
|
172
|
+
text: "Case percentage",
|
173
|
+
font: 20
|
174
|
+
}
|
175
|
+
},
|
176
|
+
y: {
|
177
|
+
ticks: {
|
178
|
+
fontSize: 20
|
179
|
+
}
|
180
|
+
}
|
181
|
+
},
|
182
|
+
plugins:
|
183
|
+
{
|
184
|
+
legend: {
|
185
|
+
display: false
|
186
|
+
}
|
187
|
+
}
|
178
188
|
}
|
179
189
|
};
|
180
190
|
return chart_data;
|
@@ -108,7 +108,7 @@
|
|
108
108
|
{% endmacro %}
|
109
109
|
|
110
110
|
{% macro cases_stats_panels() %}
|
111
|
-
<div class="mt-3" id="cases">
|
111
|
+
<div class="mt-3 mb-1" id="cases">
|
112
112
|
<div class="row">
|
113
113
|
<div class="col-md-8">
|
114
114
|
<canvas id="cases-bar-horiz" height="80"></canvas>
|
@@ -129,7 +129,7 @@
|
|
129
129
|
|
130
130
|
{% block scripts %}
|
131
131
|
{{ super() }}
|
132
|
-
<script src="https://
|
132
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.3/dist/chart.umd.min.js" integrity="sha512-NqRhTU0DQNHNUO0pTx6zPLJ11YhOqj4MRcvv0+amxJk+re07ykGhFuhMmrQpfTRAUx8nQ4EcMuX/m8cz2K8vIQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
133
133
|
<script src="{{ url_for('dashboard.static', filename='charts.js') }}"></script>
|
134
134
|
<script type="text/javascript">
|
135
135
|
|
@@ -17,6 +17,7 @@ from scout.constants import CASE_SEARCH_TERMS, PHENOTYPE_GROUPS
|
|
17
17
|
from scout.models.case import STATUS
|
18
18
|
|
19
19
|
CASE_SEARCH_KEY = [(value["prefix"], value["label"]) for key, value in CASE_SEARCH_TERMS.items()]
|
20
|
+
CATEGORY_CHOICES = [("snv", "SNV"), ("sv", "SV")]
|
20
21
|
|
21
22
|
|
22
23
|
class NonValidatingSelectField(SelectField):
|
@@ -142,6 +143,7 @@ class GeneVariantFiltersForm(FlaskForm):
|
|
142
143
|
"""Base FiltersForm for SNVs"""
|
143
144
|
|
144
145
|
variant_type = SelectMultipleField(choices=[("clinical", "clinical"), ("research", "research")])
|
146
|
+
category = SelectMultipleField(choices=CATEGORY_CHOICES)
|
145
147
|
hgnc_symbols = TagListField(
|
146
148
|
"HGNC Symbols (comma-separated, case sensitive)", validators=[validators.InputRequired()]
|
147
149
|
)
|
@@ -113,19 +113,21 @@
|
|
113
113
|
{% macro case_row(case) %}
|
114
114
|
<tr class="{% if case.status == 'solved' %}causative{% endif %}">
|
115
115
|
<td>
|
116
|
-
<a
|
116
|
+
<a class="me-2"
|
117
|
+
{% if case.individuals|length == 1 %} data-bs-toggle="tooltip" title="{{case.individuals[0].display_name}}" {% endif %}
|
118
|
+
href="{{ url_for('cases.case', institute_id=case.owner, case_name=case.display_name) }}">
|
117
119
|
{{ case.display_name }}
|
118
120
|
</a>
|
119
121
|
{% if case.individuals|length > 1 %}
|
120
|
-
|
121
|
-
|
122
|
+
{% for sample in case.individuals %}
|
123
|
+
<span class="badge {{'bg-danger' if sample.phenotype == 2 else 'bg-primary'}}" style="margin:-3px !important;" data-bs-toggle="tooltip" title="{{sample.display_name}}">
|
122
124
|
{% if case.track == "cancer" %}
|
123
125
|
<span class="fa fa-vial"></span>
|
124
126
|
{% else %} <!-- rare disease case -->
|
125
127
|
<span class="fa fa-user"></span>
|
126
128
|
{% endif %}
|
129
|
+
</span>
|
127
130
|
{% endfor %}
|
128
|
-
</span>
|
129
131
|
{% endif %}
|
130
132
|
{% for user in case.assignees %}
|
131
133
|
<span class="badge bg-secondary">{{ user.name }}</span>
|
@@ -1,5 +1,5 @@
|
|
1
1
|
{% extends "layout.html" %}
|
2
|
-
{% from "variants/components.html" import
|
2
|
+
{% from "variants/components.html" import frequency_cell_general, variant_funct_anno_cell, variant_gene_symbols_cell, variant_region_anno_cell %}
|
3
3
|
{% from "utils.html" import comments_table %}
|
4
4
|
{% from "overview/institute_sidebar.html" import institute_actionbar %}
|
5
5
|
{% from "variants/utils.html" import pagination_footer, pagination_hidden_div %}
|
@@ -39,12 +39,12 @@
|
|
39
39
|
<thead>
|
40
40
|
<tr>
|
41
41
|
<th scope="col">Case : Score</th>
|
42
|
+
<th scope="col">Var</th>
|
42
43
|
<th scope="col">Gene</th>
|
43
44
|
<th scope="col">Pop Freq</th>
|
44
45
|
<th scope="col">CADD</th>
|
45
46
|
<th scope="col">Region</th>
|
46
47
|
<th scope="col">Function</th>
|
47
|
-
<th scope="col">HGVS</th>
|
48
48
|
</tr>
|
49
49
|
</thead>
|
50
50
|
<tbody>
|
@@ -55,20 +55,12 @@
|
|
55
55
|
<tr>
|
56
56
|
{% endif %}
|
57
57
|
<td class="align-middle">{{ cell_rank(variant) }}</td>
|
58
|
-
<td class="align-middle">{{
|
58
|
+
<td class="align-middle">{{ pretty_link_no_gene(variant) }} </td>
|
59
|
+
<td class="align-middle">{{ variant_gene_symbols_cell(variant) }}</td>
|
59
60
|
<td class="align-middle">{{ frequency_cell_general(variant) }}</td>
|
60
61
|
<td class="align-middle">{{ cell_cadd(variant) }}</td>
|
61
|
-
<td class="align-middle">
|
62
|
-
|
63
|
-
<div>{{ annotation }}</div>
|
64
|
-
{% endfor %}
|
65
|
-
</td>
|
66
|
-
<td class="align-middle">
|
67
|
-
{% for annotation in variant.functional_annotations %}
|
68
|
-
<div>{{ annotation }}</div>
|
69
|
-
{% endfor %}
|
70
|
-
</td>
|
71
|
-
<td class="align-middle"> {{ (variant.hgvs or '')|url_decode }}</td>
|
62
|
+
<td class="align-middle">{{ variant_region_anno_cell(variant) }}</td>
|
63
|
+
<td class="align-middle">{{ variant_funct_anno_cell(variant) }}</td>
|
72
64
|
</tr>
|
73
65
|
{% endfor %}
|
74
66
|
{% if form.hgnc_symbols.data == [] %}
|
@@ -80,7 +72,10 @@
|
|
80
72
|
{% elif variants == [] %}
|
81
73
|
<tr>
|
82
74
|
<td colspan=7>
|
83
|
-
No variants matching your query in genes: {{form.hgnc_symbols.data|join(", ")}}
|
75
|
+
No variants matching your query in genes: {{form.hgnc_symbols.data|join(", ")}}.
|
76
|
+
{% if "research" not in form.variant_type.data %}
|
77
|
+
Consider a lower rank score threshold or including research variants.
|
78
|
+
{% endif %}
|
84
79
|
</td>
|
85
80
|
</tr>
|
86
81
|
{% endif %}
|
@@ -92,12 +87,26 @@
|
|
92
87
|
</form>
|
93
88
|
{% endmacro %}
|
94
89
|
|
95
|
-
|
96
|
-
|
97
|
-
|
90
|
+
|
91
|
+
{% macro pretty_link_no_gene(variant) %}
|
92
|
+
{% if variant.category == "sv" %}
|
93
|
+
<a href="{{ url_for('variant.sv_variant', institute_id=variant.institute,
|
98
94
|
case_name=variant.case_display_name, variant_id=variant._id) }}" target="_blank">
|
99
|
-
|
100
|
-
|
95
|
+
{% if hgvs in variant %}
|
96
|
+
{{ variant.hgvs }}
|
97
|
+
{% else %}
|
98
|
+
{{ variant.sub_category|upper }}({{ variant.chromosome }}{{ variant.cytoband_start }}-{{ variant.end_chrom }}{{ variant.cytoband_end }})
|
99
|
+
{% endif %}
|
100
|
+
{% else %}
|
101
|
+
<a href="{{ url_for('variant.variant', institute_id=variant.institute,
|
102
|
+
case_name=variant.case_display_name, variant_id=variant._id) }}" target="_blank">
|
103
|
+
{{ (variant.hgvs or '')|url_decode }}
|
104
|
+
{% endif %}
|
105
|
+
</a>
|
106
|
+
{% endmacro %}
|
107
|
+
|
108
|
+
{% macro cell_rank(variant) %}
|
109
|
+
{{ variant.case_display_name }}
|
101
110
|
:
|
102
111
|
<span class="badge bg-info">{{ variant.rank_score|int }}</span>
|
103
112
|
{% endmacro %}
|
@@ -123,29 +132,33 @@
|
|
123
132
|
{{ form.hgnc_symbols.label(class="control-label") }}
|
124
133
|
{{ form.hgnc_symbols(class="form-control") }}
|
125
134
|
</div>
|
126
|
-
<div class="col col-md-
|
127
|
-
<label class="control-label">Rank Score
|
135
|
+
<div class="col col-md-1">
|
136
|
+
<label class="control-label" for="rank_score">Rank Score</label>
|
128
137
|
<input type="number" class="form-control" id="rank_score" name="rank_score" min="5" value={{form.rank_score.data}}>
|
129
138
|
</div>
|
130
|
-
<div class="col col-md-
|
139
|
+
<div class="col col-md-2">
|
131
140
|
{{ form.variant_type.label(class="control-label") }}
|
132
141
|
{{ form.variant_type(class="form-control", class="selectpicker", data_style="btn-secondary") }}
|
133
142
|
</div>
|
143
|
+
<div class="col col-md-2">
|
144
|
+
{{ form.category.label(class="control-label") }}
|
145
|
+
{{ form.category(class="form-control", class="selectpicker", data_style="btn-secondary") }}
|
146
|
+
</div>
|
134
147
|
</div>
|
135
148
|
<div class="row">
|
136
|
-
<div class="col-md-
|
149
|
+
<div class="col-md-6">
|
137
150
|
{{ form.phenotype_terms.label(class="control-label") }}
|
138
151
|
{{ form.phenotype_terms(class="form-control") }}
|
139
152
|
</div>
|
140
|
-
<div class="col-md-
|
153
|
+
<div class="col-md-2">
|
141
154
|
{{ form.phenotype_groups.label(class="control-label") }}
|
142
155
|
{{ form.phenotype_groups(class="form-control") }}
|
143
156
|
</div>
|
144
|
-
<div class="col-md-
|
157
|
+
<div class="col-md-2">
|
145
158
|
{{ form.cohorts.label(class="control-label") }}
|
146
159
|
{{ form.cohorts(class="form-control") }}
|
147
160
|
</div>
|
148
|
-
<div class="col-md-
|
161
|
+
<div class="col-md-2">
|
149
162
|
{{ form.similar_case.label(class="control-label") }}
|
150
163
|
{{ form.similar_case(class="form-control") }}
|
151
164
|
</div>
|
@@ -31,7 +31,7 @@
|
|
31
31
|
<a href="{{ url_for('overview.gene_variants', institute_id=institute._id) }}" class="bg-dark list-group-item list-group-item-action flex-column align-items-start h-100">
|
32
32
|
<div class="d-flex w-100 justify-content-start align-items-center">
|
33
33
|
<span class="fa fa-search me-3"></span>
|
34
|
-
<span class="menu-collapsed">Search SNVs
|
34
|
+
<span class="menu-collapsed">Search SNVs & SVs</span>
|
35
35
|
</div>
|
36
36
|
</a>
|
37
37
|
|
@@ -2,16 +2,7 @@
|
|
2
2
|
import json
|
3
3
|
import logging
|
4
4
|
|
5
|
-
from flask import
|
6
|
-
Blueprint,
|
7
|
-
current_app,
|
8
|
-
flash,
|
9
|
-
jsonify,
|
10
|
-
redirect,
|
11
|
-
render_template,
|
12
|
-
request,
|
13
|
-
url_for,
|
14
|
-
)
|
5
|
+
from flask import Blueprint, flash, jsonify, redirect, render_template, request
|
15
6
|
from flask_login import current_user
|
16
7
|
from pymongo import DESCENDING
|
17
8
|
|
@@ -138,11 +129,13 @@ def gene_variants(institute_id):
|
|
138
129
|
form = GeneVariantFiltersForm(request.args)
|
139
130
|
else: # POST
|
140
131
|
form = GeneVariantFiltersForm(request.form)
|
132
|
+
|
141
133
|
if form.variant_type.data == []:
|
142
134
|
form.variant_type.data = ["clinical"]
|
143
|
-
|
144
135
|
variant_type = form.data.get("variant_type")
|
145
136
|
|
137
|
+
category = form.data.get("category") or ["snv", "sv"]
|
138
|
+
|
146
139
|
update_form_hgnc_symbols(store=store, case_obj=None, form=form)
|
147
140
|
|
148
141
|
# If no valid gene is provided, redirect to form
|
@@ -153,7 +146,7 @@ def gene_variants(institute_id):
|
|
153
146
|
variants_query = store.build_variant_query(
|
154
147
|
query=form.data,
|
155
148
|
institute_ids=[inst["_id"] for inst in user_institutes(store, current_user)],
|
156
|
-
category=
|
149
|
+
category=category,
|
157
150
|
variant_type=variant_type,
|
158
151
|
) # This is the actual query dictionary, not the cursor with results
|
159
152
|
|
@@ -0,0 +1 @@
|
|
1
|
+
from .views import omics_variants_bp
|
@@ -0,0 +1,122 @@
|
|
1
|
+
from flask import Response
|
2
|
+
from pymongo.cursor import CursorType
|
3
|
+
from werkzeug.datastructures import Headers
|
4
|
+
|
5
|
+
from scout.adapter import MongoAdapter
|
6
|
+
from scout.constants import EXPORTED_VARIANTS_LIMIT
|
7
|
+
from scout.server.blueprints.variant.utils import update_variant_case_panels
|
8
|
+
from scout.server.blueprints.variants.utils import update_case_panels
|
9
|
+
from scout.server.utils import case_has_alignments, case_has_mt_alignments, case_has_rna_tracks
|
10
|
+
|
11
|
+
|
12
|
+
def outliers(
|
13
|
+
store: MongoAdapter,
|
14
|
+
institute_obj: dict,
|
15
|
+
case_obj: dict,
|
16
|
+
omics_variants_query: CursorType,
|
17
|
+
variant_count: int,
|
18
|
+
page: int = 1,
|
19
|
+
per_page: int = 50,
|
20
|
+
):
|
21
|
+
"""Pre-process list of outlier omics variants."""
|
22
|
+
skip_count = per_page * max(page - 1, 0)
|
23
|
+
|
24
|
+
more_variants = variant_count > (skip_count + per_page)
|
25
|
+
variants = []
|
26
|
+
|
27
|
+
update_case_panels(store, case_obj)
|
28
|
+
|
29
|
+
case_has_alignments(case_obj)
|
30
|
+
case_has_mt_alignments(case_obj)
|
31
|
+
case_has_rna_tracks(case_obj)
|
32
|
+
|
33
|
+
for variant_obj in omics_variants_query.skip(skip_count).limit(per_page):
|
34
|
+
parsed_variant = decorate_omics_variant(
|
35
|
+
store,
|
36
|
+
institute_obj,
|
37
|
+
case_obj,
|
38
|
+
variant_obj,
|
39
|
+
)
|
40
|
+
|
41
|
+
variants.append(parsed_variant)
|
42
|
+
|
43
|
+
return {"variants": variants, "more_variants": more_variants}
|
44
|
+
|
45
|
+
|
46
|
+
def decorate_omics_variant(
|
47
|
+
store: MongoAdapter, institute_obj: dict, case_obj: dict, omics_variant_obj: dict
|
48
|
+
):
|
49
|
+
"""Decorate each variant with a limited selection of variant obj level information for display on variantS page."""
|
50
|
+
|
51
|
+
omics_variant_obj["comments"] = store.events(
|
52
|
+
institute_obj,
|
53
|
+
case=case_obj,
|
54
|
+
variant_id=omics_variant_obj["omics_variant_id"],
|
55
|
+
comments=True,
|
56
|
+
)
|
57
|
+
|
58
|
+
update_variant_case_panels(case_obj, omics_variant_obj)
|
59
|
+
|
60
|
+
return omics_variant_obj
|
61
|
+
|
62
|
+
|
63
|
+
def download_omics_variants(case_obj: dict, variant_objs: CursorType):
|
64
|
+
"""Download omics variants in a csv file."""
|
65
|
+
|
66
|
+
def generate(header, lines):
|
67
|
+
yield header + "\n"
|
68
|
+
for line in lines:
|
69
|
+
yield line + "\n"
|
70
|
+
|
71
|
+
DOCUMENT_HEADER = [
|
72
|
+
"Gene",
|
73
|
+
"Gene annotation",
|
74
|
+
"Category",
|
75
|
+
"Sub-category",
|
76
|
+
"Potential impact",
|
77
|
+
"Delta PSI",
|
78
|
+
"L2FC",
|
79
|
+
"P-value",
|
80
|
+
"Fold-change",
|
81
|
+
"Samples/Individuals",
|
82
|
+
"Position",
|
83
|
+
]
|
84
|
+
|
85
|
+
export_lines = []
|
86
|
+
for variant in variant_objs.limit(EXPORTED_VARIANTS_LIMIT):
|
87
|
+
variant_genes: str = (
|
88
|
+
f'"{", ".join(variant.get("hgnc_symbols", variant.get("hgnc_ids", variant.get("gene_name_orig"))))}"'
|
89
|
+
)
|
90
|
+
gene_anno = variant["gene_type"]
|
91
|
+
category = variant["category"]
|
92
|
+
sub_category = variant["sub_category"]
|
93
|
+
|
94
|
+
if sub_category == "splicing":
|
95
|
+
delta_psi = variant["delta_psi"]
|
96
|
+
l2fc = "N/A"
|
97
|
+
potential_impact = f"{variant['potential_impact']} - fs {variant['causes_frameshift']}"
|
98
|
+
fold_change = "N/A"
|
99
|
+
else:
|
100
|
+
delta_psi = "N/A"
|
101
|
+
l2fc = variant["l2fc"]
|
102
|
+
potential_impact = "N/A"
|
103
|
+
fold_change = variant["fold_change"]
|
104
|
+
p_value = "%.3e" % variant["p_value"]
|
105
|
+
samples = f'"{", ".join([sample["display_name"] for sample in variant.get("samples")])}"'
|
106
|
+
position = f"{variant['chromosome']}:{variant['position']}-{variant['end']}"
|
107
|
+
|
108
|
+
variant_line = f"{variant_genes},{gene_anno},{category},{sub_category},{potential_impact},{delta_psi},{l2fc},{p_value},{fold_change},{samples},{position}"
|
109
|
+
export_lines.append(variant_line)
|
110
|
+
|
111
|
+
headers = Headers()
|
112
|
+
headers.add(
|
113
|
+
"Content-Disposition",
|
114
|
+
"attachment",
|
115
|
+
filename=str(case_obj["display_name"]) + "-filtered-omics_variants.csv",
|
116
|
+
)
|
117
|
+
# return a csv with the exported variants
|
118
|
+
return Response(
|
119
|
+
generate(",".join(DOCUMENT_HEADER), export_lines),
|
120
|
+
mimetype="text/csv",
|
121
|
+
headers=headers,
|
122
|
+
)
|