From 8fecac2615b6d33f315160ddf1e1a81415649862 Mon Sep 17 00:00:00 2001 From: Johannes Alneberg Date: Tue, 24 Sep 2024 10:12:44 +0200 Subject: [PATCH 01/56] Improved the element flowcells list --- run_dir/design/element_flowcells.html | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/run_dir/design/element_flowcells.html b/run_dir/design/element_flowcells.html index 1ac353e41..3dd2877ab 100644 --- a/run_dir/design/element_flowcells.html +++ b/run_dir/design/element_flowcells.html @@ -8,8 +8,8 @@

+ NGI Run ID Start date - Run name Run type Side Cycles @@ -21,8 +21,8 @@

+ NGI Run ID Start date - Run name Run type Side Cycles @@ -35,9 +35,8 @@

{% for onefc in element_fcs %} - {{ onefc.key[:10] }} - - {{ onefc.value['RunName'] }} + + {{ onefc.key }} {% if onefc.value.get('Outcome') == 'OutcomeCompleted' %} {% elif onefc.value.get('Outcome') == 'ongoing' %} @@ -48,26 +47,27 @@

{% end %} + {{ onefc.key[:8] }} - {{ onefc.value["RunType"] }} + {{ onefc.value.get("RunType") }} - {{ onefc.value["Side"] }} + {{ onefc.value.get("Side") }} - {{ onefc.value["Cycles"] }} + {{ onefc.value.get("Cycles") }} - {{ onefc.value["ThroughputSelection"] }} + {{ onefc.value.get("ThroughputSelection") }} - {{ onefc.value["KitConfiguration"] }} + {{ onefc.value.get("KitConfiguration") }} - {{ onefc.value["ChemistryVersion"] }} + {{ onefc.value.get("ChemistryVersion") }} - {{ onefc.value["Outcome"] }} + {{ onefc.value.get("Outcome") }} {% end %} From 1f575c94d46ede90b4ba12d36ec795ace57c71ba Mon Sep 17 00:00:00 2001 From: Johannes Alneberg Date: Tue, 24 Sep 2024 15:56:09 +0200 Subject: [PATCH 02/56] Starting to build a page for individual element flowcells --- run_dir/design/element_flowcell.html | 10 +- run_dir/static/js/element_flowcell.js | 286 ++++++++++++++++++++++++++ status/flowcell.py | 5 +- status_app.py | 3 +- 4 files changed, 298 insertions(+), 6 deletions(-) create mode 100644 run_dir/static/js/element_flowcell.js diff --git a/run_dir/design/element_flowcell.html b/run_dir/design/element_flowcell.html index 4b8335dba..6536e4a11 100644 --- a/run_dir/design/element_flowcell.html +++ b/run_dir/design/element_flowcell.html @@ -1,8 +1,12 @@ {% extends 'base.html' %} {% block stuff %} -
-

Element BioSciences (AVITI) run

-
Under construction. This page should contain information about a single element biosciences run.
+
+
+ + + + {% end %} + diff --git a/run_dir/static/js/element_flowcell.js b/run_dir/static/js/element_flowcell.js new file mode 100644 index 000000000..0755cb4c9 --- /dev/null +++ b/run_dir/static/js/element_flowcell.js @@ -0,0 +1,286 @@ + +const vElementApp = { + data() { + return { + ngi_run_id: "some_id", + flowcell: {}, + } + } +} + +const app = Vue.createApp(vElementApp); + + +app.component('v-element-flowcell', { + props: ['ngi_run_id'], + computed: { + flowcell() { + return this.$root.flowcell; + }, + instrument_generated_files () { + // Check that the key exists + if (!this.flowcell.hasOwnProperty("instrument_generated_files")) { + return {}; + } else { + return this.flowcell["instrument_generated_files"]; + } + }, + aviti_run_stats() { + // Check that the key exists + if (!this.instrument_generated_files.hasOwnProperty("AvitiRunStats.json")) { + return {}; + } else { + return this.instrument_generated_files["AvitiRunStats.json"]; + } + }, + run_parameters() { + // Check that the key exists + if (!this.instrument_generated_files.hasOwnProperty("RunParameters.json")) { + return {}; + } else { + return this.instrument_generated_files["RunParameters.json"]; + } + }, + start_time() { + if (this.run_parameters.hasOwnProperty("Date")) { + var date = new Date(this.run_parameters["Date"]) + var date_string = date.toLocaleDateString(); + var year = date_string.split("/")[2]; + var month = date_string.split("/")[1]; + var day = date_string.split("/")[0]; + var date_formatted = year + "-" + month + "-" + day; + return date_formatted + " " + date.toLocaleTimeString(); + + } else { + return "N/A"; + } + }, + flowcell_id() { + if (this.run_parameters.hasOwnProperty("FlowcellID")) { + return this.run_parameters["FlowcellID"]; + } else { + return "N/A"; + } + }, + side() { + if (this.run_parameters.hasOwnProperty("Side")) { + return this.run_parameters["Side"]; + } else { + return "N/A"; + } + }, + instrument_name() { + if (this.run_parameters.hasOwnProperty("InstrumentName")) { + return this.run_parameters["InstrumentName"]; + } else { + return "N/A"; + } + }, + run_setup() { + return ` ${this.chemistry_version} ${this.kit_configuration} (${this.cycles}); ${this.throughput_selection}` + }, + cycles() { + if (this.run_parameters.hasOwnProperty("Cycles")) { + var return_str = ""; + if (this.run_parameters["Cycles"].hasOwnProperty("R1")){ + return_str += "R1: " + this.run_parameters["Cycles"]["R1"]; + } + if (this.run_parameters["Cycles"].hasOwnProperty("R2")){ + return_str += ", R2: " + this.run_parameters["Cycles"]["R2"]; + } + if (this.run_parameters["Cycles"].hasOwnProperty("I1")){ + return_str += ", I1: " + this.run_parameters["Cycles"]["I1"]; + } + if (this.run_parameters["Cycles"].hasOwnProperty("I2")){ + return_str += ", I2: " + this.run_parameters["Cycles"]["I2"]; + } + return return_str; + } else { + return "N/A"; + } + }, + throughput_selection() { + if (this.run_parameters.hasOwnProperty("ThroughputSelection")) { + return this.run_parameters["ThroughputSelection"] + " Throughput"; + } else { + return "N/A"; + } + }, + kit_configuration() { + if (this.run_parameters.hasOwnProperty("KitConfiguration")) { + return this.run_parameters["KitConfiguration"]; + } else { + return "N/A"; + } + }, + preparation_workflow() { + if (this.run_parameters.hasOwnProperty("PreparationWorkflow")) { + return this.run_parameters["PreparationWorkflow"]; + } else { + return "N/A"; + } + }, + chemistry_version() { + if (this.run_parameters.hasOwnProperty("ChemistryVersion")) { + return this.run_parameters["ChemistryVersion"]; + } else { + return "N/A"; + } + } + }, + mounted() { + this.getFlowcell(); + }, + methods: { + getFlowcell() { + axios.get("/api/v1/element_flowcell/" + this.ngi_run_id) + .then(response => { + this.$root.flowcell = response.data; + }) + .catch(error => { + console.log(error); + }); + } + }, + template: ` +
+

Element BioSciences (AVITI) run {{ flowcell["NGI_run_id"]}}

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
NGI Run ID{{ flowcell["NGI_run_id"] }}
Start time{{ this.start_time }}
Flowcell ID{{ flowcell_id }}
Side{{ side }}
Instrument{{ instrument_name }}
Run setup{{ run_setup }}
+
+
+ +

Summary Run Stats

+ + +
+ +
+ +
+
+

Lane Information

+

To be implemented

+ +
+
+

Project Yields

+

To be implemented

+
+
+ + ` +}); + +app.component('v-run-stats', { + computed: { + aviti_run_stats() { + return this.$root.flowcell["instrument_generated_files"]["AvitiRunStats.json"]; + }, + run_stats() { + return this.aviti_run_stats["RunStats"]; + }, + run_stats() { + // Check that the key exists + if (!this.aviti_run_stats.hasOwnProperty("RunStats")) { + return {}; + } else { + return this.aviti_run_stats["RunStats"]; + } + }, + polony_count() { + if (this.run_stats.hasOwnProperty("PolonyCount")) { + return this.run_stats["PolonyCount"]; + } else { + return "N/A"; + } + }, + pf_count() { + if (this.run_stats.hasOwnProperty("PFCount")) { + return this.run_stats["PFCount"]; + } else { + return "N/A"; + } + }, + percent_pf() { + if (this.run_stats.hasOwnProperty("PercentPF")) { + return this.run_stats["PercentPF"]; + } else { + return "N/A"; + } + }, + total_yield() { + if (this.run_stats.hasOwnProperty("TotalYield")) { + return this.run_stats["TotalYield"]; + } else { + return "N/A"; + } + }, + index_assignment() { + if (this.run_stats.hasOwnProperty("IndexAssignment")) { + return this.run_stats["IndexAssignment"]; + } else { + return "N/A"; + } + }, + percent_assigned_reads() { + if (this.index_assignment.hasOwnProperty("PercentAssignedReads")) { + return this.index_assignment["PercentAssignedReads"]; + } else { + return "N/A"; + } + } + }, + template: ` + + + + + + + + + + + + + + + +
Polony Count{{ polony_count }}PF Count{{ pf_count }}% PF{{ percent_pf }}Total Yield{{ total_yield }}% Assigned Reads{{ percent_assigned_reads }}
` +}); + +app.mount("#element_vue_app"); \ No newline at end of file diff --git a/status/flowcell.py b/status/flowcell.py index 2313e9b1d..b89e7a230 100644 --- a/status/flowcell.py +++ b/status/flowcell.py @@ -462,13 +462,14 @@ def get(self, name): t.generate( gs_globals=self.application.gs_globals, user=self.get_current_user(), + ngi_run_id=name, ) ) class ElementFlowcellDataHandler(SafeHandler): def get(self, name): - flowcell = self.application.element_runs_db.get(name) - self.write(flowcell) + rows = self.application.element_runs_db.view('info/id', include_docs=True)[name].rows + self.write(rows[0].doc) class ONTFlowcellHandler(SafeHandler): """Serves a page which shows information for a given ONT flowcell.""" diff --git a/status_app.py b/status_app.py index 3d1351809..cf6868bb9 100644 --- a/status_app.py +++ b/status_app.py @@ -34,7 +34,7 @@ from status.bioinfo_analysis import BioinfoAnalysisHandler from status.data_deliveries_plot import DataDeliveryHandler, DeliveryPlotHandler from status.deliveries import DeliveriesPageHandler -from status.flowcell import FlowcellHandler, ElementFlowcellHandler, ONTFlowcellHandler, ONTReportHandler +from status.flowcell import FlowcellHandler, ElementFlowcellHandler, ElementFlowcellDataHandler, ONTFlowcellHandler, ONTReportHandler from status.flowcells import ( FlowcellDemultiplexHandler, FlowcellLinksDataHandler, @@ -237,6 +237,7 @@ def __init__(self, settings): ), ("/api/v1/draft_cost_calculator", PricingDraftDataHandler), ("/api/v1/draft_sample_requirements", SampleRequirementsDraftDataHandler), + ("/api/v1/element_flowcell/([^/]*$)", ElementFlowcellDataHandler), ("/api/v1/flowcells", FlowcellsDataHandler), ("/api/v1/flowcell_info2/([^/]*)$", FlowcellsInfoDataHandler), ("/api/v1/flowcell_info/([^/]*)$", OldFlowcellsInfoDataHandler), From d94777a26b9fb04bc09763d3b14822d2d2726e7d Mon Sep 17 00:00:00 2001 From: Johannes Alneberg Date: Wed, 25 Sep 2024 16:01:58 +0200 Subject: [PATCH 03/56] DRY:ed up the element flowcell javascript page --- run_dir/static/js/element_flowcell.js | 174 ++++++++------------------ 1 file changed, 53 insertions(+), 121 deletions(-) diff --git a/run_dir/static/js/element_flowcell.js b/run_dir/static/js/element_flowcell.js index 0755cb4c9..224f24fa4 100644 --- a/run_dir/static/js/element_flowcell.js +++ b/run_dir/static/js/element_flowcell.js @@ -5,6 +5,14 @@ const vElementApp = { ngi_run_id: "some_id", flowcell: {}, } + }, + methods: { + getValue(obj, key, defaultValue = "N/A") { + if (obj === null || obj === "N/A") { + return defaultValue; + } + return obj.hasOwnProperty(key) ? obj[key] : defaultValue; + } } } @@ -17,115 +25,70 @@ app.component('v-element-flowcell', { flowcell() { return this.$root.flowcell; }, - instrument_generated_files () { - // Check that the key exists - if (!this.flowcell.hasOwnProperty("instrument_generated_files")) { - return {}; - } else { - return this.flowcell["instrument_generated_files"]; - } + instrument_generated_files() { + return this.$root.getValue(this.flowcell, "instrument_generated_files", {}); }, aviti_run_stats() { - // Check that the key exists - if (!this.instrument_generated_files.hasOwnProperty("AvitiRunStats.json")) { - return {}; - } else { - return this.instrument_generated_files["AvitiRunStats.json"]; - } + return this.$root.getValue(this.instrument_generated_files, "AvitiRunStats.json", {}); }, run_parameters() { - // Check that the key exists - if (!this.instrument_generated_files.hasOwnProperty("RunParameters.json")) { - return {}; - } else { - return this.instrument_generated_files["RunParameters.json"]; - } + return this.$root.getValue(this.instrument_generated_files, "RunParameters.json", {}); }, start_time() { - if (this.run_parameters.hasOwnProperty("Date")) { - var date = new Date(this.run_parameters["Date"]) - var date_string = date.toLocaleDateString(); - var year = date_string.split("/")[2]; - var month = date_string.split("/")[1]; - var day = date_string.split("/")[0]; - var date_formatted = year + "-" + month + "-" + day; - return date_formatted + " " + date.toLocaleTimeString(); - + const dateStr = this.$root.getValue(this.run_parameters, "Date", null); + if (dateStr) { + const date = new Date(dateStr); + const date_string = date.toLocaleDateString(); + const [month, day, year] = date_string.split("/"); + const date_formatted = `${year}-${month}-${day}`; + return `${date_formatted} ${date.toLocaleTimeString()}`; } else { return "N/A"; } }, flowcell_id() { - if (this.run_parameters.hasOwnProperty("FlowcellID")) { - return this.run_parameters["FlowcellID"]; - } else { - return "N/A"; - } + return this.$root.getValue(this.run_parameters, "FlowcellID"); }, side() { - if (this.run_parameters.hasOwnProperty("Side")) { - return this.run_parameters["Side"]; - } else { - return "N/A"; - } + return this.$root.getValue(this.run_parameters, "Side"); }, instrument_name() { - if (this.run_parameters.hasOwnProperty("InstrumentName")) { - return this.run_parameters["InstrumentName"]; - } else { - return "N/A"; - } + return this.$root.getValue(this.run_parameters, "InstrumentName"); }, run_setup() { - return ` ${this.chemistry_version} ${this.kit_configuration} (${this.cycles}); ${this.throughput_selection}` + return `${this.chemistry_version} ${this.kit_configuration} (${this.cycles}); ${this.throughput_selection}`; }, cycles() { - if (this.run_parameters.hasOwnProperty("Cycles")) { - var return_str = ""; - if (this.run_parameters["Cycles"].hasOwnProperty("R1")){ - return_str += "R1: " + this.run_parameters["Cycles"]["R1"]; - } - if (this.run_parameters["Cycles"].hasOwnProperty("R2")){ - return_str += ", R2: " + this.run_parameters["Cycles"]["R2"]; - } - if (this.run_parameters["Cycles"].hasOwnProperty("I1")){ - return_str += ", I1: " + this.run_parameters["Cycles"]["I1"]; - } - if (this.run_parameters["Cycles"].hasOwnProperty("I2")){ - return_str += ", I2: " + this.run_parameters["Cycles"]["I2"]; - } - return return_str; - } else { + const cycles = this.$root.getValue(this.run_parameters, "Cycles", {}); + if (cycles === "N/A") { return "N/A"; } + let return_str = ""; + if (cycles.hasOwnProperty("R1")) { + return_str += "R1: " + cycles["R1"]; + } + if (cycles.hasOwnProperty("R2")) { + return_str += ", R2: " + cycles["R2"]; + } + if (cycles.hasOwnProperty("I1")) { + return_str += ", I1: " + cycles["I1"]; + } + if (cycles.hasOwnProperty("I2")) { + return_str += ", I2: " + cycles["I2"]; + } + return return_str; }, throughput_selection() { - if (this.run_parameters.hasOwnProperty("ThroughputSelection")) { - return this.run_parameters["ThroughputSelection"] + " Throughput"; - } else { - return "N/A"; - } + return this.$root.getValue(this.run_parameters, "ThroughputSelection", "N/A") + " Throughput"; }, kit_configuration() { - if (this.run_parameters.hasOwnProperty("KitConfiguration")) { - return this.run_parameters["KitConfiguration"]; - } else { - return "N/A"; - } + return this.$root.getValue(this.run_parameters, "KitConfiguration"); }, preparation_workflow() { - if (this.run_parameters.hasOwnProperty("PreparationWorkflow")) { - return this.run_parameters["PreparationWorkflow"]; - } else { - return "N/A"; - } + return this.$root.getValue(this.run_parameters, "PreparationWorkflow"); }, chemistry_version() { - if (this.run_parameters.hasOwnProperty("ChemistryVersion")) { - return this.run_parameters["ChemistryVersion"]; - } else { - return "N/A"; - } + return this.$root.getValue(this.run_parameters, "ChemistryVersion"); } }, mounted() { @@ -140,7 +103,8 @@ app.component('v-element-flowcell', { .catch(error => { console.log(error); }); - } + }, + }, template: `
@@ -208,60 +172,28 @@ app.component('v-element-flowcell', { app.component('v-run-stats', { computed: { aviti_run_stats() { - return this.$root.flowcell["instrument_generated_files"]["AvitiRunStats.json"]; - }, - run_stats() { - return this.aviti_run_stats["RunStats"]; + return this.$root.getValue(this.$root.flowcell["instrument_generated_files"], "AvitiRunStats.json", {}); }, run_stats() { - // Check that the key exists - if (!this.aviti_run_stats.hasOwnProperty("RunStats")) { - return {}; - } else { - return this.aviti_run_stats["RunStats"]; - } + return this.$root.getValue(this.aviti_run_stats, "RunStats", {}); }, polony_count() { - if (this.run_stats.hasOwnProperty("PolonyCount")) { - return this.run_stats["PolonyCount"]; - } else { - return "N/A"; - } + return this.$root.getValue(this.run_stats, "PolonyCount"); }, pf_count() { - if (this.run_stats.hasOwnProperty("PFCount")) { - return this.run_stats["PFCount"]; - } else { - return "N/A"; - } + return this.$root.getValue(this.run_stats, "PFCount"); }, percent_pf() { - if (this.run_stats.hasOwnProperty("PercentPF")) { - return this.run_stats["PercentPF"]; - } else { - return "N/A"; - } + return this.$root.getValue(this.run_stats, "PercentPF"); }, total_yield() { - if (this.run_stats.hasOwnProperty("TotalYield")) { - return this.run_stats["TotalYield"]; - } else { - return "N/A"; - } + return this.$root.getValue(this.run_stats, "TotalYield"); }, index_assignment() { - if (this.run_stats.hasOwnProperty("IndexAssignment")) { - return this.run_stats["IndexAssignment"]; - } else { - return "N/A"; - } + return this.$root.getValue(this.run_stats, "IndexAssignment", {}); }, percent_assigned_reads() { - if (this.index_assignment.hasOwnProperty("PercentAssignedReads")) { - return this.index_assignment["PercentAssignedReads"]; - } else { - return "N/A"; - } + return this.$root.getValue(this.index_assignment, "PercentAssignedReads"); } }, template: ` From e4a4667cff8188ef4da2dd38b8c192d2ec776d6d Mon Sep 17 00:00:00 2001 From: Johannes Alneberg Date: Thu, 26 Sep 2024 10:55:58 +0200 Subject: [PATCH 04/56] Added some element lane stats, along with some bugfixes --- run_dir/design/element_flowcell.html | 3 + run_dir/static/js/element_flowcell.js | 204 +++++++++++++++++++------- 2 files changed, 150 insertions(+), 57 deletions(-) diff --git a/run_dir/design/element_flowcell.html b/run_dir/design/element_flowcell.html index 6536e4a11..7c0b0bec6 100644 --- a/run_dir/design/element_flowcell.html +++ b/run_dir/design/element_flowcell.html @@ -3,6 +3,9 @@
+{% end %} + +{% block js %} diff --git a/run_dir/static/js/element_flowcell.js b/run_dir/static/js/element_flowcell.js index 224f24fa4..9e19b61dc 100644 --- a/run_dir/static/js/element_flowcell.js +++ b/run_dir/static/js/element_flowcell.js @@ -4,14 +4,26 @@ const vElementApp = { return { ngi_run_id: "some_id", flowcell: {}, + flowcell_fetched: false, } }, methods: { getValue(obj, key, defaultValue = "N/A") { - if (obj === null || obj === "N/A") { + if (obj === null || obj == undefined || obj === "N/A") { return defaultValue; } return obj.hasOwnProperty(key) ? obj[key] : defaultValue; + }, + formatNumberBases(number) { + if (number === "N/A") { + return "N/A"; + } else if (number < 1000000) { + return number + " bp"; + } else if (number < 1000000000) { + return (number / 1000000).toFixed(2) + " Mbp"; + } else { + return (number / 1000000000).toFixed(2) + " Gbp"; + } } } } @@ -99,6 +111,7 @@ app.component('v-element-flowcell', { axios.get("/api/v1/element_flowcell/" + this.ngi_run_id) .then(response => { this.$root.flowcell = response.data; + this.$root.flowcell_fetched = true; }) .catch(error => { console.log(error); @@ -107,69 +120,76 @@ app.component('v-element-flowcell', { }, template: ` -
-

Element BioSciences (AVITI) run {{ flowcell["NGI_run_id"]}}

-
- - - - - - - - - - - - - - - - - - - - - - - - - -
NGI Run ID{{ flowcell["NGI_run_id"] }}
Start time{{ this.start_time }}
Flowcell ID{{ flowcell_id }}
Side{{ side }}
Instrument{{ instrument_name }}
Run setup{{ run_setup }}
-
+
+
+ Loading... +
+ Loading...
+
+
+

Element BioSciences (AVITI) run {{ flowcell["NGI_run_id"]}}

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
NGI Run ID{{ flowcell["NGI_run_id"] }}
Start time{{ this.start_time }}
Flowcell ID{{ flowcell_id }}
Side{{ side }}
Instrument{{ instrument_name }}
Run setup{{ run_setup }}
+
+
-

Summary Run Stats

- - -
- -
+

Summary Run Stats

+ -
-
-

Lane Information

-

To be implemented

+
+ +
-
-
-

Project Yields

-

To be implemented

-
+
+
+

Lane Information

+ +
+
+

Project Yields

+

To be implemented

+
+
` }); -app.component('v-run-stats', { +app.component('v-element-run-stats', { computed: { aviti_run_stats() { return this.$root.getValue(this.$root.flowcell["instrument_generated_files"], "AvitiRunStats.json", {}); @@ -189,6 +209,9 @@ app.component('v-run-stats', { total_yield() { return this.$root.getValue(this.run_stats, "TotalYield"); }, + total_yield_formatted() { + return this.$root.formatNumberBases(this.$root.getValue(this.run_stats, "TotalYield")); + }, index_assignment() { return this.$root.getValue(this.run_stats, "IndexAssignment", {}); }, @@ -200,14 +223,16 @@ app.component('v-run-stats', { + + + + - - @@ -215,4 +240,69 @@ app.component('v-run-stats', {
Total Yield{{ total_yield_formatted }} Polony Count {{ polony_count }} PF Count {{ pf_count }} % PF {{ percent_pf }}Total Yield{{ total_yield }} % Assigned Reads {{ percent_assigned_reads }}
` }); +app.component('v-element-lane-stats', { + computed: { + instrument_generated_files() { + return this.$root.getValue(this.$root.flowcell, "instrument_generated_files", {}); + }, + aviti_run_stats() { + return this.$root.getValue(this.instrument_generated_files, "AvitiRunStats.json", {}); + }, + lane_stats() { + return this.$root.getValue(this.aviti_run_stats, "LaneStats", {}); + }, + lanes() { + return this.$root.getValue(this.lane_stats, "Lanes", []); + }, + }, + methods: { + total_lane_yield(lane) { + return this.$root.formatNumberBases(this.$root.getValue(lane, "TotalYield")); + }, + total_lane_yield_formatted(lane) { + return this.$root.formatNumberBases(this.$root.getValue(lane, "TotalYield")); + } + }, + template: ` +
+

Lane {{ lane["Lane"] }}

+ + + + + + + + + + + + + +
Total Yield + {{ total_lane_yield_formatted(lane) }} + Polony Count {{ lane["PolonyCount"] }}PF Count {{ lane["PFCount"] }}% PF {{ lane["PercentPF"] }}% Assigned Reads {{ lane["PercentAssignedReads"] }}
+
+ ` + +}); + +app.component('v-element-tooltip', { + props: ['title'], + mounted() { + this.$nextTick(function() { + this.tooltip = new bootstrap.Tooltip(this.$el) + }) + }, + template: ` + + + + ` +}); + app.mount("#element_vue_app"); \ No newline at end of file From 944d4122362bc70242d1278e850e268c11e1237a Mon Sep 17 00:00:00 2001 From: Johannes Alneberg Date: Thu, 26 Sep 2024 10:59:03 +0200 Subject: [PATCH 05/56] Removed outdated comment --- run_dir/design/pricing_preview.html | 1 - 1 file changed, 1 deletion(-) diff --git a/run_dir/design/pricing_preview.html b/run_dir/design/pricing_preview.html index 2e44966aa..f76805635 100644 --- a/run_dir/design/pricing_preview.html +++ b/run_dir/design/pricing_preview.html @@ -10,7 +10,6 @@ {% block js %} - From cbe47bffa58b9a3af31ed562d88479be1011e6f7 Mon Sep 17 00:00:00 2001 From: Johannes Alneberg Date: Thu, 26 Sep 2024 12:10:23 +0200 Subject: [PATCH 06/56] Basic index assignments for element flowcell page --- .devcontainer/devcontainer.json | 3 +- run_dir/static/js/element_flowcell.js | 60 +++++++++++++++++++++++++-- 2 files changed, 59 insertions(+), 4 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index b08bcdeaf..e2749339e 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -12,7 +12,8 @@ "customizations": { "vscode": { "extensions": [ - "ms-python.python" + "ms-python.python", + "vscode-es6-string-html" ] } }, diff --git a/run_dir/static/js/element_flowcell.js b/run_dir/static/js/element_flowcell.js index 9e19b61dc..382d790a3 100644 --- a/run_dir/static/js/element_flowcell.js +++ b/run_dir/static/js/element_flowcell.js @@ -51,7 +51,7 @@ app.component('v-element-flowcell', { if (dateStr) { const date = new Date(dateStr); const date_string = date.toLocaleDateString(); - const [month, day, year] = date_string.split("/"); + const [day, month, year] = date_string.split("/"); const date_formatted = `${year}-${month}-${day}`; return `${date_formatted} ${date.toLocaleTimeString()}`; } else { @@ -261,10 +261,19 @@ app.component('v-element-lane-stats', { }, total_lane_yield_formatted(lane) { return this.$root.formatNumberBases(this.$root.getValue(lane, "TotalYield")); + }, + index_assignments(lane) { + return this.$root.getValue(lane, "IndexAssignment", {}); + }, + index_samples(lane) { + return this.$root.getValue(this.index_assignments(lane), "IndexSamples", {}); + }, + unassigned_sequences(lane) { + return this.$root.getValue(this.index_assignments(lane), "UnassignedSequences", {}); } }, - template: ` -
+ template: /*html*/` +

Lane {{ lane["Lane"] }}

@@ -282,6 +291,51 @@ app.component('v-element-lane-stats', {
+ +

Index Assignments

+ + + + + + + + + + + + + + + + + +
Project NameSample Name% Assigned Reads% Assigned With Mismatches
{{ sample["ProjectName"] }}{{ sample["SampleName"] }}{{ sample["PercentAssignedReads"] }}{{ sample["PercentMismatch"] }}
+ + +
+
+
+
+ + + + + + + + + + + + + +
Sequence% Occurence
{{ unassigned_item["I1"] }}+{{ unassigned_item["I2"]}}{{ unassigned_item["PercentOcurrence"] }}
+
+
+
` From 140edecc44f3242e4c328af6dfca8c75b5b0d5ad Mon Sep 17 00:00:00 2001 From: Johannes Alneberg Date: Thu, 26 Sep 2024 12:11:07 +0200 Subject: [PATCH 07/56] Bugfix for element flowcell page --- run_dir/static/js/element_flowcell.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/run_dir/static/js/element_flowcell.js b/run_dir/static/js/element_flowcell.js index 382d790a3..0f48f4438 100644 --- a/run_dir/static/js/element_flowcell.js +++ b/run_dir/static/js/element_flowcell.js @@ -265,6 +265,9 @@ app.component('v-element-lane-stats', { index_assignments(lane) { return this.$root.getValue(lane, "IndexAssignment", {}); }, + percent_assigned_reads(lane) { + return this.$root.getValue(this.index_assignments(lane), "PercentAssignedReads", {}); + }, index_samples(lane) { return this.$root.getValue(this.index_assignments(lane), "IndexSamples", {}); }, @@ -287,7 +290,7 @@ app.component('v-element-lane-stats', { Polony Count {{ lane["PolonyCount"] }} PF Count {{ lane["PFCount"] }} % PF {{ lane["PercentPF"] }} - % Assigned Reads {{ lane["PercentAssignedReads"] }} + % Assigned Reads {{ percent_assigned_reads(lane) }} From 54eab64d0e43b6ce9595d89908b9973eb81ed5bd Mon Sep 17 00:00:00 2001 From: Johannes Alneberg Date: Thu, 26 Sep 2024 14:23:39 +0200 Subject: [PATCH 08/56] Managed to get a plot for element flowcell --- run_dir/design/element_flowcell.html | 1 + run_dir/static/js/element_flowcell.js | 107 ++++++++++++++++++++++++-- 2 files changed, 102 insertions(+), 6 deletions(-) diff --git a/run_dir/design/element_flowcell.html b/run_dir/design/element_flowcell.html index 7c0b0bec6..b3c0f8f47 100644 --- a/run_dir/design/element_flowcell.html +++ b/run_dir/design/element_flowcell.html @@ -9,6 +9,7 @@ + {% end %} diff --git a/run_dir/static/js/element_flowcell.js b/run_dir/static/js/element_flowcell.js index 0f48f4438..808625a35 100644 --- a/run_dir/static/js/element_flowcell.js +++ b/run_dir/static/js/element_flowcell.js @@ -7,6 +7,17 @@ const vElementApp = { flowcell_fetched: false, } }, + computed: { + instrument_generated_files() { + return this.getValue(this.flowcell, "instrument_generated_files", {}); + }, + aviti_run_stats() { + return this.getValue(this.instrument_generated_files, "AvitiRunStats.json", {}); + }, + run_stats() { + return this.getValue(this.aviti_run_stats, "RunStats", {}); + }, + }, methods: { getValue(obj, key, defaultValue = "N/A") { if (obj === null || obj == undefined || obj === "N/A") { @@ -38,10 +49,10 @@ app.component('v-element-flowcell', { return this.$root.flowcell; }, instrument_generated_files() { - return this.$root.getValue(this.flowcell, "instrument_generated_files", {}); + return this.$root.instrument_generated_files; }, aviti_run_stats() { - return this.$root.getValue(this.instrument_generated_files, "AvitiRunStats.json", {}); + return this.$root.aviti_run_stats; }, run_parameters() { return this.$root.getValue(this.instrument_generated_files, "RunParameters.json", {}); @@ -119,7 +130,7 @@ app.component('v-element-flowcell', { }, }, - template: ` + template: /*html*/`
Loading... @@ -192,10 +203,10 @@ app.component('v-element-flowcell', { app.component('v-element-run-stats', { computed: { aviti_run_stats() { - return this.$root.getValue(this.$root.flowcell["instrument_generated_files"], "AvitiRunStats.json", {}); + return this.$root.aviti_run_stats; }, run_stats() { - return this.$root.getValue(this.aviti_run_stats, "RunStats", {}); + return this.$root.run_stats; }, polony_count() { return this.$root.getValue(this.run_stats, "PolonyCount"); @@ -237,7 +248,91 @@ app.component('v-element-run-stats', { {{ percent_assigned_reads }} - ` + + + + ` +}); + +app.component('v-element-summary-graph', { + data() { + return { + lanes: [], + } + }, + computed: { + flowcell() { + return this.$root.flowcell; + }, + reads() { + return this.$root.getValue(this.$root.run_stats, "Reads", []); + }, + R1_reads() { + /* filter the reads list to only include R1 reads */ + return this.reads.filter(read => read['Read'] == 'R1'); + }, + data() { + return this.$root.getValue(this.R1_reads[0], 'Cycles', 'N/A'); + }, + }, + methods: { + summary_graph() { + + const cycles = this.data || []; + if (cycles.length === 0) { + return; + } + const categories = cycles.map(cycle => `Cycle ${cycle.Cycle}`); + + const percentQ30 = cycles.map(cycle => cycle.PercentQ30); + const percentQ40 = cycles.map(cycle => cycle.PercentQ40); + const averageQScore = cycles.map(cycle => cycle.AverageQScore); + + Highcharts.chart('SummaryPlot', { + chart: { + type: 'line' + }, + title: { + text: 'Summary Plot' + }, + xAxis: { + categories: categories + }, + yAxis: { + title: { + text: 'Values' + }, + min: 0, + }, + series: [ + { + name: 'Percent Q30', + data: percentQ30 + }, + { + name: 'Percent Q40', + data: percentQ40 + }, + { + name: 'Average Q Score', + data: averageQScore + } + ] + }); + }, + }, + mounted() { + this.$nextTick(function() { + if (this.$root.flowcell_fetched) { + this.summary_graph(); + } + }); + }, + template: /*html*/` +

Summary Plot

+
+

To be implemented

+
` }); app.component('v-element-lane-stats', { From ed01f3360a4a62f71798185b81c78f5684cb6120 Mon Sep 17 00:00:00 2001 From: Johannes Alneberg Date: Thu, 26 Sep 2024 15:34:21 +0200 Subject: [PATCH 09/56] Keep working on the summary graph for element flowcell --- run_dir/static/js/element_flowcell.js | 120 +++++++++++++++++++------- 1 file changed, 89 insertions(+), 31 deletions(-) diff --git a/run_dir/static/js/element_flowcell.js b/run_dir/static/js/element_flowcell.js index 808625a35..e3ce4e8d4 100644 --- a/run_dir/static/js/element_flowcell.js +++ b/run_dir/static/js/element_flowcell.js @@ -134,8 +134,8 @@ app.component('v-element-flowcell', {
Loading... -
- Loading... +
+ Loading...
@@ -257,7 +257,9 @@ app.component('v-element-run-stats', { app.component('v-element-summary-graph', { data() { return { - lanes: [], + include_R1: true, + include_R2: true, + graph_warnings: [], } }, computed: { @@ -267,26 +269,90 @@ app.component('v-element-summary-graph', { reads() { return this.$root.getValue(this.$root.run_stats, "Reads", []); }, - R1_reads() { - /* filter the reads list to only include R1 reads */ - return this.reads.filter(read => read['Read'] == 'R1'); + R1_read_cycles() { + let filtered_values = this.reads.filter(read => read['Read'] == 'R1') + if (filtered_values.length === 0) { + return []; + } + + return this.$root.getValue(filtered_values[0], 'Cycles', []) }, - data() { - return this.$root.getValue(this.R1_reads[0], 'Cycles', 'N/A'); + R2_read_cycles() { + let filtered_values = this.reads.filter(read => read['Read'] == 'R2') + if (filtered_values.length === 0) { + return []; + } + + return this.$root.getValue(filtered_values[0], 'Cycles', []) + }, + categories() { + // Check if R1 is in end_filter + var categories_R1 = this.R1_read_cycles.map(cycle => `Cycle ${cycle.Cycle}`); + + var categories_R2 = this.R2_read_cycles.map(cycle => `Cycle ${cycle.Cycle}`); + + if (categories_R1 !== categories_R2) { + this.graph_warnings.push("Warning! R1 and R2 x-axis are note identical, using R1 axis"); + } + + return categories_R1; }, }, methods: { summary_graph() { - - const cycles = this.data || []; - if (cycles.length === 0) { + console.log("Creating summary graph"); + if (this.categories.length === 0) { return; } - const categories = cycles.map(cycle => `Cycle ${cycle.Cycle}`); - const percentQ30 = cycles.map(cycle => cycle.PercentQ30); - const percentQ40 = cycles.map(cycle => cycle.PercentQ40); - const averageQScore = cycles.map(cycle => cycle.AverageQScore); + let R1_percentQ30 = []; + let R1_percentQ40 = []; + let R1_averageQScore = []; + let series = []; + + if (this.include_R1) { + R1_percentQ30 = this.R1_read_cycles.map(cycle => cycle.PercentQ30); + R1_percentQ40 = this.R1_read_cycles.map(cycle => cycle.PercentQ40); + R1_averageQScore = this.R1_read_cycles.map(cycle => cycle.AverageQScore); + + series.push({ + name: 'R1 Percent Q30', + data: R1_percentQ30 + }) + series.push( + { + name: 'R1 Percent Q40', + data: R1_percentQ40 + }) + series.push( + { + name: 'R1 Average Q Score', + data: R1_averageQScore + }) + } + + let R2_percentQ30 = []; + let R2_percentQ40 = []; + let R2_averageQScore = []; + + if (this.include_R2) { + R2_percentQ30 = this.R2_read_cycles.map(cycle => cycle.PercentQ30); + R2_percentQ40 = this.R2_read_cycles.map(cycle => cycle.PercentQ40); + R2_averageQScore = this.R2_read_cycles.map(cycle => cycle.AverageQScore); + + series.push( { + name: 'R2 Percent Q30', + data: R2_percentQ30 + }) + series.push( { + name: 'R2 Percent Q40', + data: R2_percentQ40 + }) + series.push( { + name: 'R2 Average Q Score', + data: R2_averageQScore + }) + } Highcharts.chart('SummaryPlot', { chart: { @@ -296,7 +362,7 @@ app.component('v-element-summary-graph', { text: 'Summary Plot' }, xAxis: { - categories: categories + categories: this.categories }, yAxis: { title: { @@ -304,20 +370,7 @@ app.component('v-element-summary-graph', { }, min: 0, }, - series: [ - { - name: 'Percent Q30', - data: percentQ30 - }, - { - name: 'Percent Q40', - data: percentQ40 - }, - { - name: 'Average Q Score', - data: averageQScore - } - ] + series: series }); }, }, @@ -329,7 +382,12 @@ app.component('v-element-summary-graph', { }); }, template: /*html*/` -

Summary Plot

+

Summary Plot

+

To be implemented

` From 172613d043767960242521f1bd272c1f7a93ea19 Mon Sep 17 00:00:00 2001 From: Johannes Alneberg Date: Thu, 26 Sep 2024 15:40:27 +0200 Subject: [PATCH 10/56] Better comparison of x-axis for element flowcell plot --- run_dir/static/js/element_flowcell.js | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/run_dir/static/js/element_flowcell.js b/run_dir/static/js/element_flowcell.js index e3ce4e8d4..d02484caa 100644 --- a/run_dir/static/js/element_flowcell.js +++ b/run_dir/static/js/element_flowcell.js @@ -291,7 +291,22 @@ app.component('v-element-summary-graph', { var categories_R2 = this.R2_read_cycles.map(cycle => `Cycle ${cycle.Cycle}`); - if (categories_R1 !== categories_R2) { + if (categories_R1.length !== categories_R2.length) { + console.log("The lengths of categories_R1 and categories_R2 are different."); + console.log("Length of categories_R1:", categories_R1.length); + console.log("Length of categories_R2:", categories_R2.length); + } + var categories_differ = false; + for (let i = 0; i < Math.max(categories_R1.length, categories_R2.length); i++) { + if (categories_R1[i] !== categories_R2[i]) { + categories_differ = true; + console.log(`Difference found at index ${i}:`); + console.log(`categories_R1[${i}]:`, categories_R1[i]); + console.log(`categories_R2[${i}]:`, categories_R2[i]); + } + } + + if (categories_differ) { this.graph_warnings.push("Warning! R1 and R2 x-axis are note identical, using R1 axis"); } From 471e0902860cbfa2e2547fcde0c5f202d4815495 Mon Sep 17 00:00:00 2001 From: Johannes Alneberg Date: Thu, 26 Sep 2024 16:08:59 +0200 Subject: [PATCH 11/56] Added filters to the graph on element flowcell --- run_dir/static/js/element_flowcell.js | 114 +++++++++++++++++++++++--- 1 file changed, 101 insertions(+), 13 deletions(-) diff --git a/run_dir/static/js/element_flowcell.js b/run_dir/static/js/element_flowcell.js index d02484caa..7e4cfdfb5 100644 --- a/run_dir/static/js/element_flowcell.js +++ b/run_dir/static/js/element_flowcell.js @@ -259,7 +259,10 @@ app.component('v-element-summary-graph', { return { include_R1: true, include_R2: true, + include_average_quality: false, graph_warnings: [], + filter_first_cycles: 1, + filter_last_cycles: 1, } }, computed: { @@ -319,6 +322,14 @@ app.component('v-element-summary-graph', { if (this.categories.length === 0) { return; } + /* Filter the first categories */ + this.categories = this.categories.slice(this.filter_first_cycles); + + /* filter the last categories */ + /* The last value seems to be weird for quality */ + if (this.filter_last_cycles > 0) { + this.categories = this.categories.slice(0, -this.filter_last_cycles); + } let R1_percentQ30 = []; let R1_percentQ40 = []; @@ -330,6 +341,18 @@ app.component('v-element-summary-graph', { R1_percentQ40 = this.R1_read_cycles.map(cycle => cycle.PercentQ40); R1_averageQScore = this.R1_read_cycles.map(cycle => cycle.AverageQScore); + /* Filter the first values */ + R1_percentQ30 = R1_percentQ30.slice(this.filter_first_cycles); + R1_percentQ40 = R1_percentQ40.slice(this.filter_first_cycles); + R1_averageQScore = R1_averageQScore.slice(this.filter_first_cycles); + + /* Filter the last values */ + if (this.filter_last_cycles > 0) { + R1_percentQ30 = R1_percentQ30.slice(0, -this.filter_last_cycles); + R1_percentQ40 = R1_percentQ40.slice(0, -this.filter_last_cycles); + R1_averageQScore = R1_averageQScore.slice(0, -this.filter_last_cycles); + } + series.push({ name: 'R1 Percent Q30', data: R1_percentQ30 @@ -339,11 +362,14 @@ app.component('v-element-summary-graph', { name: 'R1 Percent Q40', data: R1_percentQ40 }) - series.push( - { - name: 'R1 Average Q Score', - data: R1_averageQScore - }) + + if (this.include_average_quality) { + series.push( + { + name: 'R1 Average Q Score', + data: R1_averageQScore + }) + } } let R2_percentQ30 = []; @@ -355,6 +381,17 @@ app.component('v-element-summary-graph', { R2_percentQ40 = this.R2_read_cycles.map(cycle => cycle.PercentQ40); R2_averageQScore = this.R2_read_cycles.map(cycle => cycle.AverageQScore); + /* Filter the first values */ + R2_percentQ30 = R2_percentQ30.slice(this.filter_first_cycles); + R2_percentQ40 = R2_percentQ40.slice(this.filter_first_cycles); + R2_averageQScore = R2_averageQScore.slice(this.filter_first_cycles); + + /* Filter the last values */ + if (this.filter_last_cycles > 0) { + R2_percentQ30 = R2_percentQ30.slice(0, -this.filter_last_cycles); + R2_percentQ40 = R2_percentQ40.slice(0, -this.filter_last_cycles); + R2_averageQScore = R2_averageQScore.slice(0, -this.filter_last_cycles); + } series.push( { name: 'R2 Percent Q30', data: R2_percentQ30 @@ -363,15 +400,17 @@ app.component('v-element-summary-graph', { name: 'R2 Percent Q40', data: R2_percentQ40 }) - series.push( { - name: 'R2 Average Q Score', - data: R2_averageQScore - }) + if (this.include_average_quality) { + series.push( { + name: 'R2 Average Q Score', + data: R2_averageQScore + }) + } } Highcharts.chart('SummaryPlot', { chart: { - type: 'line' + type: 'spline' }, title: { text: 'Summary Plot' @@ -383,7 +422,6 @@ app.component('v-element-summary-graph', { title: { text: 'Values' }, - min: 0, }, series: series }); @@ -396,16 +434,66 @@ app.component('v-element-summary-graph', { } }); }, + watch: { + include_R1: function() { + this.summary_graph(); + }, + include_R2: function() { + this.summary_graph(); + }, + include_average_quality: function() { + this.summary_graph(); + }, + filter_first_cycles: function() { + this.summary_graph(); + }, + filter_last_cycles: function() { + this.summary_graph(); + } + }, template: /*html*/` +

Summary Plot

+
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+
+

Filter Cycles

+ + + + + +
+
-

To be implemented

-
` +
+
` }); app.component('v-element-lane-stats', { From 69893845564355946e9de473671235e126fb48fd Mon Sep 17 00:00:00 2001 From: Johannes Alneberg Date: Fri, 27 Sep 2024 13:21:15 +0200 Subject: [PATCH 12/56] Correct link from the element list --- run_dir/design/element_flowcells.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run_dir/design/element_flowcells.html b/run_dir/design/element_flowcells.html index 3dd2877ab..5d1e46645 100644 --- a/run_dir/design/element_flowcells.html +++ b/run_dir/design/element_flowcells.html @@ -36,7 +36,7 @@

{% for onefc in element_fcs %} - {{ onefc.key }} + {{ onefc.key }} {% if onefc.value.get('Outcome') == 'OutcomeCompleted' %} {% elif onefc.value.get('Outcome') == 'ongoing' %} From 30d2c024e6b1a04012c92f79dd707f26562e44e3 Mon Sep 17 00:00:00 2001 From: Johannes Alneberg Date: Fri, 27 Sep 2024 13:36:53 +0200 Subject: [PATCH 13/56] More filtering options for the element run stats plot --- run_dir/static/js/element_flowcell.js | 99 ++++++++++++++++++--------- 1 file changed, 66 insertions(+), 33 deletions(-) diff --git a/run_dir/static/js/element_flowcell.js b/run_dir/static/js/element_flowcell.js index 7e4cfdfb5..eb0e515b3 100644 --- a/run_dir/static/js/element_flowcell.js +++ b/run_dir/static/js/element_flowcell.js @@ -260,6 +260,8 @@ app.component('v-element-summary-graph', { include_R1: true, include_R2: true, include_average_quality: false, + include_percent_q30: true, + include_percent_q40: true, graph_warnings: [], filter_first_cycles: 1, filter_last_cycles: 1, @@ -318,17 +320,16 @@ app.component('v-element-summary-graph', { }, methods: { summary_graph() { - console.log("Creating summary graph"); if (this.categories.length === 0) { return; } /* Filter the first categories */ - this.categories = this.categories.slice(this.filter_first_cycles); + var filtered_categories = this.categories.slice(this.filter_first_cycles); /* filter the last categories */ /* The last value seems to be weird for quality */ if (this.filter_last_cycles > 0) { - this.categories = this.categories.slice(0, -this.filter_last_cycles); + filtered_categories = filtered_categories.slice(0, -this.filter_last_cycles); } let R1_percentQ30 = []; @@ -353,15 +354,20 @@ app.component('v-element-summary-graph', { R1_averageQScore = R1_averageQScore.slice(0, -this.filter_last_cycles); } - series.push({ - name: 'R1 Percent Q30', - data: R1_percentQ30 - }) - series.push( - { - name: 'R1 Percent Q40', - data: R1_percentQ40 - }) + if (this.include_percent_q30) { + series.push({ + name: 'R1 Percent Q30', + data: R1_percentQ30 + }) + } + + if (this.include_percent_q40) { + series.push( + { + name: 'R1 Percent Q40', + data: R1_percentQ40 + }) + } if (this.include_average_quality) { series.push( @@ -392,14 +398,18 @@ app.component('v-element-summary-graph', { R2_percentQ40 = R2_percentQ40.slice(0, -this.filter_last_cycles); R2_averageQScore = R2_averageQScore.slice(0, -this.filter_last_cycles); } - series.push( { - name: 'R2 Percent Q30', - data: R2_percentQ30 - }) - series.push( { - name: 'R2 Percent Q40', - data: R2_percentQ40 - }) + if (this.include_percent_q30) { + series.push( { + name: 'R2 Percent Q30', + data: R2_percentQ30 + }) + } + if (this.include_percent_q40) { + series.push( { + name: 'R2 Percent Q40', + data: R2_percentQ40 + }) + } if (this.include_average_quality) { series.push( { name: 'R2 Average Q Score', @@ -408,7 +418,7 @@ app.component('v-element-summary-graph', { } } - Highcharts.chart('SummaryPlot', { + Highcharts.chart('SummaryPlotQuality', { chart: { type: 'spline' }, @@ -416,14 +426,19 @@ app.component('v-element-summary-graph', { text: 'Summary Plot' }, xAxis: { - categories: this.categories + categories: filtered_categories }, yAxis: { title: { text: 'Values' }, }, - series: series + series: series.map(s => ({ + ...s, + marker: { + enabled: false, // Set to false to hide markers + } + })) }); }, }, @@ -444,6 +459,12 @@ app.component('v-element-summary-graph', { include_average_quality: function() { this.summary_graph(); }, + include_percent_q30: function() { + this.summary_graph(); + }, + include_percent_q40: function() { + this.summary_graph(); + }, filter_first_cycles: function() { this.summary_graph(); }, @@ -452,14 +473,14 @@ app.component('v-element-summary-graph', { } }, template: /*html*/` -
-

Summary Plot

- -
+

Summary Plot

+ +
+
@@ -475,12 +496,24 @@ app.component('v-element-summary-graph', {
-
+
+
+ + +
+
+ + +

Filter Cycles

@@ -491,7 +524,7 @@ app.component('v-element-summary-graph', {
-
+
` }); From c4e3efb86f747041bf81587f69142189d2e0efe6 Mon Sep 17 00:00:00 2001 From: Johannes Alneberg Date: Fri, 4 Oct 2024 15:57:32 +0200 Subject: [PATCH 14/56] Got some plots in place for aviti flowcell page --- run_dir/static/js/element_flowcell.js | 193 ++++++++++++++------------ 1 file changed, 104 insertions(+), 89 deletions(-) diff --git a/run_dir/static/js/element_flowcell.js b/run_dir/static/js/element_flowcell.js index eb0e515b3..71bc19e18 100644 --- a/run_dir/static/js/element_flowcell.js +++ b/run_dir/static/js/element_flowcell.js @@ -17,6 +17,18 @@ const vElementApp = { run_stats() { return this.getValue(this.aviti_run_stats, "RunStats", {}); }, + demultiplex_stats() { + return this.getValue( + this.getValue(this.flowcell, "Element", {}), + "Demultiplex_Stats", {} + ); + }, + index_assignment_demultiplex() { + return this.getValue(this.demultiplex_stats, "Index_Assignment", {}); + }, + index_assignment_instrument() { + return this.getValue(this.run_stats, "IndexAssignment", {}); + } }, methods: { getValue(obj, key, defaultValue = "N/A") { @@ -182,6 +194,9 @@ app.component('v-element-flowcell', { +
@@ -194,6 +209,8 @@ app.component('v-element-flowcell', {

Project Yields

To be implemented

+
+
@@ -224,7 +241,7 @@ app.component('v-element-run-stats', { return this.$root.formatNumberBases(this.$root.getValue(this.run_stats, "TotalYield")); }, index_assignment() { - return this.$root.getValue(this.run_stats, "IndexAssignment", {}); + return this.$root.index_assignment; }, percent_assigned_reads() { return this.$root.getValue(this.index_assignment, "PercentAssignedReads"); @@ -249,19 +266,14 @@ app.component('v-element-run-stats', { - - ` }); -app.component('v-element-summary-graph', { +app.component('v-element-quality-graph', { data() { return { include_R1: true, include_R2: true, - include_average_quality: false, - include_percent_q30: true, - include_percent_q40: true, graph_warnings: [], filter_first_cycles: 1, filter_last_cycles: 1, @@ -336,6 +348,7 @@ app.component('v-element-summary-graph', { let R1_percentQ40 = []; let R1_averageQScore = []; let series = []; + let avg_series = []; if (this.include_R1) { R1_percentQ30 = this.R1_read_cycles.map(cycle => cycle.PercentQ30); @@ -354,28 +367,22 @@ app.component('v-element-summary-graph', { R1_averageQScore = R1_averageQScore.slice(0, -this.filter_last_cycles); } - if (this.include_percent_q30) { - series.push({ - name: 'R1 Percent Q30', - data: R1_percentQ30 - }) - } + series.push({ + name: 'R1 Percent Q30', + data: R1_percentQ30, + }) - if (this.include_percent_q40) { - series.push( - { - name: 'R1 Percent Q40', - data: R1_percentQ40 - }) - } + series.push( + { + name: 'R1 Percent Q40', + data: R1_percentQ40 + }) - if (this.include_average_quality) { - series.push( - { - name: 'R1 Average Q Score', - data: R1_averageQScore - }) - } + avg_series.push( + { + name: 'R1 Average Q Score', + data: R1_averageQScore + }) } let R2_percentQ30 = []; @@ -398,42 +405,71 @@ app.component('v-element-summary-graph', { R2_percentQ40 = R2_percentQ40.slice(0, -this.filter_last_cycles); R2_averageQScore = R2_averageQScore.slice(0, -this.filter_last_cycles); } - if (this.include_percent_q30) { - series.push( { - name: 'R2 Percent Q30', - data: R2_percentQ30 - }) - } - if (this.include_percent_q40) { - series.push( { - name: 'R2 Percent Q40', - data: R2_percentQ40 - }) - } - if (this.include_average_quality) { - series.push( { - name: 'R2 Average Q Score', - data: R2_averageQScore - }) - } + + series.push({ + name: 'R2 Percent Q30', + data: R2_percentQ30, + dashStyle: 'Dash' // Set dash style for R2 series + }); + + series.push({ + name: 'R2 Percent Q40', + data: R2_percentQ40, + dashStyle: 'Dash' // Set dash style for R2 series + }); + + avg_series.push({ + name: 'R2 Average Q Score', + data: R2_averageQScore, + dashStyle: 'Dash' // Set dash style for R2 series + }); } - Highcharts.chart('SummaryPlotQuality', { + Highcharts.chart('SummaryPlotPercentQuality', { + chart: { + type: 'spline' + }, + title: { + text: '% Quality' + }, + xAxis: { + categories: filtered_categories + }, + yAxis: [{ + title: { + text: 'Percent Q30/Q40' + }, + opposite: false // Primary y-axis on the left + }, { + title: { + text: 'Average Q Score' + }, + opposite: true // Secondary y-axis on the right + }], + series: series.map(s => ({ + ...s, + marker: { + enabled: false, // Set to false to hide markers + } + })) + }) + + Highcharts.chart('SummaryPlotAvgQuality', { chart: { type: 'spline' }, title: { - text: 'Summary Plot' + text: 'Average Quality Score' }, xAxis: { categories: filtered_categories }, yAxis: { title: { - text: 'Values' + text: 'Average Q Score' }, }, - series: series.map(s => ({ + series: avg_series.map(s => ({ ...s, marker: { enabled: false, // Set to false to hide markers @@ -456,15 +492,6 @@ app.component('v-element-summary-graph', { include_R2: function() { this.summary_graph(); }, - include_average_quality: function() { - this.summary_graph(); - }, - include_percent_q30: function() { - this.summary_graph(); - }, - include_percent_q40: function() { - this.summary_graph(); - }, filter_first_cycles: function() { this.summary_graph(); }, @@ -473,13 +500,22 @@ app.component('v-element-summary-graph', { } }, template: /*html*/` -

Summary Plot

- -
+
+ +
+
+
+
+
+
+
+
+
+
@@ -496,27 +532,6 @@ app.component('v-element-summary-graph', {
-
- - -
-
- - -
-
- - -
-
-
-

Filter Cycles

@@ -524,9 +539,9 @@ app.component('v-element-summary-graph', {
-
-
-
` +
+ + ` }); app.component('v-element-lane-stats', { From 78948782fef127c2cc7da261b1d10f7c9feb22e7 Mon Sep 17 00:00:00 2001 From: Johannes Alneberg Date: Mon, 7 Oct 2024 11:54:35 +0200 Subject: [PATCH 15/56] Lane stats in slightly better state --- run_dir/static/js/element_flowcell.js | 409 +++++++++++++++++--------- 1 file changed, 265 insertions(+), 144 deletions(-) diff --git a/run_dir/static/js/element_flowcell.js b/run_dir/static/js/element_flowcell.js index 71bc19e18..021c76e5f 100644 --- a/run_dir/static/js/element_flowcell.js +++ b/run_dir/static/js/element_flowcell.js @@ -28,6 +28,9 @@ const vElementApp = { }, index_assignment_instrument() { return this.getValue(this.run_stats, "IndexAssignment", {}); + }, + unassiged_sequences_demultiplex() { + return this.getValue(this.demultiplex_stats, "Unassigned_Sequences", {}); } }, methods: { @@ -186,31 +189,36 @@ app.component('v-element-flowcell', {
-
-

Lane Information

- -
-
-

Project Yields

-

To be implemented

-
-
- +
+ +
+
+ +
+
+

Project Yields

+

To be implemented

+
+
+ +
@@ -269,6 +277,241 @@ app.component('v-element-run-stats', { ` }); +app.component('v-element-lane-stats', { + computed: { + lane_stats() { + const groupedByLane = {}; + if (this.$root.index_assignment_demultiplex && this.$root.index_assignment_demultiplex.length > 0) { + this.$root.index_assignment_demultiplex.forEach(sample => { + const lane = sample["Lane"]; + if (!groupedByLane[lane]) { + groupedByLane[lane] = []; + } + groupedByLane[lane].push(sample); + }); + } + + return groupedByLane; + }, + unassigned_lane_stats() { + const groupedByLane = {}; + + if (this.$root.unassiged_sequences_demultiplex && this.$root.unassiged_sequences_demultiplex.length > 0) { + this.$root.unassiged_sequences_demultiplex.forEach(sample => { + const lane = sample["Lane"]; + if (!groupedByLane[lane]) { + groupedByLane[lane] = []; + } + groupedByLane[lane].push(sample); + }); + } + return groupedByLane; + } + }, + methods: { + barcode(sample) { + var barcode_str = ""; + if (sample.hasOwnProperty("I1") && sample["I1"] !== "") { + barcode_str += sample["I1"]; + } else { + barcode_str += "N/A" + } + + if (sample.hasOwnProperty("I2") && sample["I2"] !== ""){ + barcode_str += "+" + sample["I2"]; + } + return barcode_str; + } + }, + template: /*html*/` +
+

+ Lane {{ laneKey }} +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Sample NameYield (Gb)Num Polonies Assigned% Q30% Q40Barcode(s)% Assigned Reads% Assigned With MismatchesSub Demux CountQuality Score Mean
{{ sample["SampleName"] }}{{ sample["Yield(Gb)"] }}{{ sample["NumPoloniesAssigned"] }}{{ parseFloat(sample["PercentQ30"]).toFixed(2) }}{{ parseFloat(sample["PercentQ40"]).toFixed(2) }}{{ barcode(sample) }}{{ parseFloat(sample["PercentPoloniesAssigned"]).toFixed(2) }}{{ parseFloat(sample["PercentMismatch"]).toFixed(2) }}{{ sample["sub_demux_count"] }}{{ parseFloat(sample["QualityScoreMean"]).toFixed(2) }}
+ + +
+
+
+
+ + + + + + + + + + + + + + + +
Sequence% OccurenceCount
{{ barcode(unassigned_item)}}{{ unassigned_item["% Polonies"] }}{{ unassigned_item["Count"] }}
+
+
+
+
+
+ ` + +}); + +app.component('v-element-lane-stats-pre-demultiplex', { + computed: { + lane_stats() { + return this.$root.getValue(this.$root.aviti_run_stats, "LaneStats", {}); + }, + lanes() { + return this.$root.getValue(this.lane_stats, "Lanes", []); + }, + }, + methods: { + total_lane_yield(lane) { + return this.$root.formatNumberBases(this.$root.getValue(lane, "TotalYield")); + }, + total_lane_yield_formatted(lane) { + return this.$root.formatNumberBases(this.$root.getValue(lane, "TotalYield")); + }, + index_assignments(lane) { + return this.$root.getValue(lane, "IndexAssignment", {}); + }, + percent_assigned_reads(lane) { + return this.$root.getValue(this.index_assignments(lane), "PercentAssignedReads", {}); + }, + index_samples(lane) { + return this.$root.getValue(this.index_assignments(lane), "IndexSamples", {}); + }, + unassigned_sequences(lane) { + return this.$root.getValue(this.index_assignments(lane), "UnassignedSequences", {}); + } + }, + template: /*html*/` +
+

+ Lane {{ lane["Lane"] }} + Pre-demultiplexing +

+ + + + + + + + + + + + + +
Total Yield + {{ total_lane_yield_formatted(lane) }} + Polony Count {{ lane["PolonyCount"] }}PF Count {{ lane["PFCount"] }}% PF {{ lane["PercentPF"] }}% Assigned Reads {{ percent_assigned_reads(lane) }}
+ +

Index Assignments

+ + + + + + + + + + + + + + + + + +
Project NameSample Name% Assigned Reads% Assigned With Mismatches
{{ sample["ProjectName"] }}{{ sample["SampleName"] }}{{ sample["PercentAssignedReads"] }}{{ sample["PercentMismatch"] }}
+ + + +
+
+
+
+ + + + + + + + + + + + + +
Sequence% Occurence
{{ unassigned_item["I1"] }}+{{ unassigned_item["I2"]}}{{ unassigned_item["PercentOcurrence"] }}
+
+
+
+
+ ` + +}); + +app.component('v-element-tooltip', { + props: ['title'], + mounted() { + this.$nextTick(function() { + this.tooltip = new bootstrap.Tooltip(this.$el) + }) + }, + template: ` + + + + ` +}); + app.component('v-element-quality-graph', { data() { return { @@ -544,126 +787,4 @@ app.component('v-element-quality-graph', { ` }); -app.component('v-element-lane-stats', { - computed: { - instrument_generated_files() { - return this.$root.getValue(this.$root.flowcell, "instrument_generated_files", {}); - }, - aviti_run_stats() { - return this.$root.getValue(this.instrument_generated_files, "AvitiRunStats.json", {}); - }, - lane_stats() { - return this.$root.getValue(this.aviti_run_stats, "LaneStats", {}); - }, - lanes() { - return this.$root.getValue(this.lane_stats, "Lanes", []); - }, - }, - methods: { - total_lane_yield(lane) { - return this.$root.formatNumberBases(this.$root.getValue(lane, "TotalYield")); - }, - total_lane_yield_formatted(lane) { - return this.$root.formatNumberBases(this.$root.getValue(lane, "TotalYield")); - }, - index_assignments(lane) { - return this.$root.getValue(lane, "IndexAssignment", {}); - }, - percent_assigned_reads(lane) { - return this.$root.getValue(this.index_assignments(lane), "PercentAssignedReads", {}); - }, - index_samples(lane) { - return this.$root.getValue(this.index_assignments(lane), "IndexSamples", {}); - }, - unassigned_sequences(lane) { - return this.$root.getValue(this.index_assignments(lane), "UnassignedSequences", {}); - } - }, - template: /*html*/` -
-

Lane {{ lane["Lane"] }}

- - - - - - - - - - - - - -
Total Yield - {{ total_lane_yield_formatted(lane) }} - Polony Count {{ lane["PolonyCount"] }}PF Count {{ lane["PFCount"] }}% PF {{ lane["PercentPF"] }}% Assigned Reads {{ percent_assigned_reads(lane) }}
- -

Index Assignments

- - - - - - - - - - - - - - - - - -
Project NameSample Name% Assigned Reads% Assigned With Mismatches
{{ sample["ProjectName"] }}{{ sample["SampleName"] }}{{ sample["PercentAssignedReads"] }}{{ sample["PercentMismatch"] }}
- - -
-
-
-
- - - - - - - - - - - - - -
Sequence% Occurence
{{ unassigned_item["I1"] }}+{{ unassigned_item["I2"]}}{{ unassigned_item["PercentOcurrence"] }}
-
-
-
-
- ` - -}); - -app.component('v-element-tooltip', { - props: ['title'], - mounted() { - this.$nextTick(function() { - this.tooltip = new bootstrap.Tooltip(this.$el) - }) - }, - template: ` - - - - ` -}); - app.mount("#element_vue_app"); \ No newline at end of file From bb366049b47db3ce8ec309ef3b5b627822a7179c Mon Sep 17 00:00:00 2001 From: Johannes Alneberg Date: Mon, 7 Oct 2024 16:47:49 +0200 Subject: [PATCH 16/56] Handle phiX and Unassigned separately --- run_dir/static/js/element_flowcell.js | 113 +++++++++++++++++++++++--- 1 file changed, 102 insertions(+), 11 deletions(-) diff --git a/run_dir/static/js/element_flowcell.js b/run_dir/static/js/element_flowcell.js index 021c76e5f..7be1343b4 100644 --- a/run_dir/static/js/element_flowcell.js +++ b/run_dir/static/js/element_flowcell.js @@ -305,6 +305,70 @@ app.component('v-element-lane-stats', { groupedByLane[lane].push(sample); }); } + return groupedByLane; + }, + phiX_lane_stats_combined() { + /* Sum % Polonies and Count for each lane */ + const groupedByLane = {}; + + if (this.$root.index_assignment_demultiplex && this.$root.index_assignment_demultiplex.length > 0) { + this.$root.index_assignment_demultiplex.forEach((sample) => { + if (sample["SampleName"] === "PhiX") { + const lane = sample["Lane"]; + if (!groupedByLane[lane]) { + groupedByLane[lane] = { + "SampleName": "PhiX", + "NumPoloniesAssigned": 0, + "PercentPoloniesAssigned": 0, + "Yield(Gb)": 0, + "Lane": lane, + "sub_demux_count": new Set(), + "PercentMismatch": [], + "PercentQ30": [], + "PercentQ40": [], + "QualityScoreMean": [] + } + } + groupedByLane[lane]["NumPoloniesAssigned"] += parseFloat(sample["NumPoloniesAssigned"]); + groupedByLane[lane]["PercentPoloniesAssigned"] += parseFloat(sample["PercentPoloniesAssigned"]); + groupedByLane[lane]["Yield(Gb)"] += parseFloat(sample["Yield(Gb)"]); + groupedByLane[lane]["sub_demux_count"].add(sample["sub_demux_count"]); + groupedByLane[lane]["PercentMismatch"].push(sample["PercentMismatch"] * sample["NumPoloniesAssigned"]); + groupedByLane[lane]["PercentQ30"].push(sample["PercentQ30"] * sample["NumPoloniesAssigned"]); + groupedByLane[lane]["PercentQ40"].push(sample["PercentQ40"] * sample["NumPoloniesAssigned"]); + groupedByLane[lane]["QualityScoreMean"].push(sample["QualityScoreMean"] * sample["NumPoloniesAssigned"]); + } + }) + } + + + // Calculate the summary stat + Object.entries(groupedByLane).forEach(([laneKey, lane]) => { + lane["PercentMismatch"] = lane["PercentMismatch"].reduce((a, b) => a + b, 0) / lane["NumPoloniesAssigned"]; + lane["PercentQ30"] = lane["PercentQ30"].reduce((a, b) => a + b, 0) / lane["NumPoloniesAssigned"]; + lane["PercentQ40"] = lane["PercentQ40"].reduce((a, b) => a + b, 0) / lane["NumPoloniesAssigned"]; + lane["QualityScoreMean"] = lane["QualityScoreMean"].reduce((a, b) => a + b, 0) / lane["NumPoloniesAssigned"]; + }) + + return groupedByLane; + }, + unassigned_lane_stats_combined() { + /* Sum % Polonies and Count for each lane */ + const groupedByLane = {}; + if (this.$root.unassiged_sequences_demultiplex && this.$root.unassiged_sequences_demultiplex.length > 0) { + this.$root.unassiged_sequences_demultiplex.forEach(unassigned_index => { + const lane = unassigned_index["Lane"]; + if (!groupedByLane[lane]) { + groupedByLane[lane] = { + "% Polonies": 0, + "Count": 0 + }; + } + groupedByLane[lane]["% Polonies"] += parseFloat(unassigned_index["% Polonies"]); + groupedByLane[lane]["Count"] += parseFloat(unassigned_index["Count"]); + }); + } + return groupedByLane; } }, @@ -329,6 +393,7 @@ app.component('v-element-lane-stats', { Lane {{ laneKey }}

+ @@ -346,16 +411,42 @@ app.component('v-element-lane-stats', { - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + +
{{ sample["SampleName"] }}{{ sample["Yield(Gb)"] }}{{ sample["NumPoloniesAssigned"] }}{{ parseFloat(sample["PercentQ30"]).toFixed(2) }}{{ parseFloat(sample["PercentQ40"]).toFixed(2) }}{{ barcode(sample) }}{{ parseFloat(sample["PercentPoloniesAssigned"]).toFixed(2) }}{{ parseFloat(sample["PercentMismatch"]).toFixed(2) }}{{ sample["sub_demux_count"] }}{{ parseFloat(sample["QualityScoreMean"]).toFixed(2) }}
PhiX{{ phiX_lane_stats_combined[laneKey]["Yield(Gb)"] }}{{ phiX_lane_stats_combined[laneKey]["NumPoloniesAssigned"] }}{{ parseFloat(phiX_lane_stats_combined[laneKey]["PercentQ30"]).toFixed(2) }}{{ parseFloat(phiX_lane_stats_combined[laneKey]["PercentQ40"]).toFixed(2) }}PhiX{{ parseFloat(phiX_lane_stats_combined[laneKey]["PercentPoloniesAssigned"]).toFixed(2) }}{{ parseFloat(phiX_lane_stats_combined[laneKey]["PercentMismatch"]).toFixed(2) }}{{ phiX_lane_stats_combined[laneKey]["sub_demux_count"] }}{{ parseFloat(phiX_lane_stats_combined[laneKey]["QualityScoreMean"]).toFixed(2) }}
UnassignedN/A{{ unassigned_lane_stats_combined[laneKey]["Count"] }}N/AN/AN/A{{ parseFloat(unassigned_lane_stats_combined[laneKey]["% Polonies"]).toFixed(2) }}N/AN/AN/A
@@ -372,7 +463,7 @@ app.component('v-element-lane-stats', { Sequence % Occurence - Count + Count From 6841f30d1dc07f5a65a8068f96c56b01f3e47675 Mon Sep 17 00:00:00 2001 From: Johannes Alneberg Date: Mon, 7 Oct 2024 17:29:15 +0200 Subject: [PATCH 17/56] DRY:ed the lane stats a bit --- run_dir/static/js/element_flowcell.js | 231 ++++++++++++++------------ 1 file changed, 126 insertions(+), 105 deletions(-) diff --git a/run_dir/static/js/element_flowcell.js b/run_dir/static/js/element_flowcell.js index 7be1343b4..79def0dfb 100644 --- a/run_dir/static/js/element_flowcell.js +++ b/run_dir/static/js/element_flowcell.js @@ -34,6 +34,19 @@ const vElementApp = { } }, methods: { + barcode(sample) { + var barcode_str = ""; + if (sample.hasOwnProperty("I1") && sample["I1"] !== "") { + barcode_str += sample["I1"]; + } else { + barcode_str += "N/A" + } + + if (sample.hasOwnProperty("I2") && sample["I2"] !== ""){ + barcode_str += "+" + sample["I2"]; + } + return barcode_str; + }, getValue(obj, key, defaultValue = "N/A") { if (obj === null || obj == undefined || obj === "N/A") { return defaultValue; @@ -50,6 +63,12 @@ const vElementApp = { } else { return (number / 1000000000).toFixed(2) + " Gbp"; } + }, + formatNumberFloat(value) { + if (value === "N/A") { + return "N/A"; + } + return parseFloat(value).toFixed(2); } } } @@ -278,71 +297,74 @@ app.component('v-element-run-stats', { }); app.component('v-element-lane-stats', { + data() { + return { + show_phiX_details: false + } + }, computed: { lane_stats() { const groupedByLane = {}; - if (this.$root.index_assignment_demultiplex && this.$root.index_assignment_demultiplex.length > 0) { - this.$root.index_assignment_demultiplex.forEach(sample => { - const lane = sample["Lane"]; - if (!groupedByLane[lane]) { - groupedByLane[lane] = []; - } - groupedByLane[lane].push(sample); - }); - } + this.$root.index_assignment_demultiplex.forEach(sample => { + const lane = sample["Lane"]; + if (!groupedByLane[lane]) { + groupedByLane[lane] = []; + } + groupedByLane[lane].push(sample); + }); return groupedByLane; }, unassigned_lane_stats() { const groupedByLane = {}; - if (this.$root.unassiged_sequences_demultiplex && this.$root.unassiged_sequences_demultiplex.length > 0) { - this.$root.unassiged_sequences_demultiplex.forEach(sample => { - const lane = sample["Lane"]; - if (!groupedByLane[lane]) { - groupedByLane[lane] = []; - } - groupedByLane[lane].push(sample); - }); - } + this.$root.unassiged_sequences_demultiplex.forEach(sample => { + const lane = sample["Lane"]; + if (!groupedByLane[lane]) { + groupedByLane[lane] = []; + } + groupedByLane[lane].push(sample); + }); + return groupedByLane; }, phiX_lane_stats_combined() { /* Sum % Polonies and Count for each lane */ const groupedByLane = {}; - if (this.$root.index_assignment_demultiplex && this.$root.index_assignment_demultiplex.length > 0) { - this.$root.index_assignment_demultiplex.forEach((sample) => { - if (sample["SampleName"] === "PhiX") { - const lane = sample["Lane"]; - if (!groupedByLane[lane]) { - groupedByLane[lane] = { - "SampleName": "PhiX", - "NumPoloniesAssigned": 0, - "PercentPoloniesAssigned": 0, - "Yield(Gb)": 0, - "Lane": lane, - "sub_demux_count": new Set(), - "PercentMismatch": [], - "PercentQ30": [], - "PercentQ40": [], - "QualityScoreMean": [] - } + this.$root.index_assignment_demultiplex.forEach((sample) => { + if (sample["SampleName"] === "PhiX") { + const lane = sample["Lane"]; + if (!groupedByLane[lane]) { + groupedByLane[lane] = { + "SampleName": "PhiX", + "NumPoloniesAssigned": 0, + "PercentPoloniesAssigned": 0, + "Yield(Gb)": 0, + "Lane": lane, + "sub_demux_count": new Set(), + "PercentMismatch": [], + "PercentQ30": [], + "PercentQ40": [], + "QualityScoreMean": [] } - groupedByLane[lane]["NumPoloniesAssigned"] += parseFloat(sample["NumPoloniesAssigned"]); - groupedByLane[lane]["PercentPoloniesAssigned"] += parseFloat(sample["PercentPoloniesAssigned"]); - groupedByLane[lane]["Yield(Gb)"] += parseFloat(sample["Yield(Gb)"]); - groupedByLane[lane]["sub_demux_count"].add(sample["sub_demux_count"]); - groupedByLane[lane]["PercentMismatch"].push(sample["PercentMismatch"] * sample["NumPoloniesAssigned"]); - groupedByLane[lane]["PercentQ30"].push(sample["PercentQ30"] * sample["NumPoloniesAssigned"]); - groupedByLane[lane]["PercentQ40"].push(sample["PercentQ40"] * sample["NumPoloniesAssigned"]); - groupedByLane[lane]["QualityScoreMean"].push(sample["QualityScoreMean"] * sample["NumPoloniesAssigned"]); } - }) - } + // Summable values + groupedByLane[lane]["NumPoloniesAssigned"] += parseFloat(sample["NumPoloniesAssigned"]); + groupedByLane[lane]["PercentPoloniesAssigned"] += parseFloat(sample["PercentPoloniesAssigned"]); + groupedByLane[lane]["Yield(Gb)"] += parseFloat(sample["Yield(Gb)"]); + // List unique values + groupedByLane[lane]["sub_demux_count"].add(sample["sub_demux_count"]); + // Prepare for mean value-calculations + groupedByLane[lane]["PercentMismatch"].push(sample["PercentMismatch"] * sample["NumPoloniesAssigned"]); + groupedByLane[lane]["PercentQ30"].push(sample["PercentQ30"] * sample["NumPoloniesAssigned"]); + groupedByLane[lane]["PercentQ40"].push(sample["PercentQ40"] * sample["NumPoloniesAssigned"]); + groupedByLane[lane]["QualityScoreMean"].push(sample["QualityScoreMean"] * sample["NumPoloniesAssigned"]); + } + }) - // Calculate the summary stat + // Calculate mean values Object.entries(groupedByLane).forEach(([laneKey, lane]) => { lane["PercentMismatch"] = lane["PercentMismatch"].reduce((a, b) => a + b, 0) / lane["NumPoloniesAssigned"]; lane["PercentQ30"] = lane["PercentQ30"].reduce((a, b) => a + b, 0) / lane["NumPoloniesAssigned"]; @@ -360,33 +382,26 @@ app.component('v-element-lane-stats', { const lane = unassigned_index["Lane"]; if (!groupedByLane[lane]) { groupedByLane[lane] = { - "% Polonies": 0, - "Count": 0 - }; + "SampleName": "Unassigned", + "NumPoloniesAssigned": 0, + "PercentPoloniesAssigned": 0, + "Yield(Gb)": "N/A", + "Lane": lane, + "sub_demux_count": "N/A", + "PercentMismatch": "N/A", + "PercentQ30": "N/A", + "PercentQ40": "N/A", + "QualityScoreMean": "N/A" + } } - groupedByLane[lane]["% Polonies"] += parseFloat(unassigned_index["% Polonies"]); - groupedByLane[lane]["Count"] += parseFloat(unassigned_index["Count"]); + groupedByLane[lane]["PercentPoloniesAssigned"] += parseFloat(unassigned_index["% Polonies"]); + groupedByLane[lane]["NumPoloniesAssigned"] += parseFloat(unassigned_index["Count"]); }); } return groupedByLane; } }, - methods: { - barcode(sample) { - var barcode_str = ""; - if (sample.hasOwnProperty("I1") && sample["I1"] !== "") { - barcode_str += sample["I1"]; - } else { - barcode_str += "N/A" - } - - if (sample.hasOwnProperty("I2") && sample["I2"] !== ""){ - barcode_str += "+" + sample["I2"]; - } - return barcode_str; - } - }, template: /*html*/`

@@ -410,44 +425,19 @@ app.component('v-element-lane-stats', { - - - - - PhiX - {{ phiX_lane_stats_combined[laneKey]["Yield(Gb)"] }} - {{ phiX_lane_stats_combined[laneKey]["NumPoloniesAssigned"] }} - {{ parseFloat(phiX_lane_stats_combined[laneKey]["PercentQ30"]).toFixed(2) }} - {{ parseFloat(phiX_lane_stats_combined[laneKey]["PercentQ40"]).toFixed(2) }} - PhiX - {{ parseFloat(phiX_lane_stats_combined[laneKey]["PercentPoloniesAssigned"]).toFixed(2) }} - {{ parseFloat(phiX_lane_stats_combined[laneKey]["PercentMismatch"]).toFixed(2) }} - {{ phiX_lane_stats_combined[laneKey]["sub_demux_count"] }} - {{ parseFloat(phiX_lane_stats_combined[laneKey]["QualityScoreMean"]).toFixed(2) }} - - - Unassigned - N/A - {{ unassigned_lane_stats_combined[laneKey]["Count"] }} - N/A - N/A - N/A - {{ parseFloat(unassigned_lane_stats_combined[laneKey]["% Polonies"]).toFixed(2) }} - N/A - N/A - N/A - + + + + @@ -468,7 +458,7 @@ app.component('v-element-lane-stats', { - {{ barcode(unassigned_item)}} + {{ this.$root.barcode(unassigned_item)}} {{ unassigned_item["% Polonies"] }} {{ unassigned_item["Count"] }} @@ -478,9 +468,40 @@ app.component('v-element-lane-stats', {

+ + +
` +}); +app.component('v-lane-stats-row', { + props: ['sample', 'laneKey'], + computed: { + sub_demux_count() { + if (this.sample["sub_demux_count"] instanceof Set) { + return Array.from(this.sample["sub_demux_count"]).join(", "); + } else { + return this.sample["sub_demux_count"]; + } + } + }, + template: /*html*/` + + {{ sample["SampleName"] }} + {{ sample["Yield(Gb)"] }} + {{ sample["NumPoloniesAssigned"] }} + {{ this.$root.formatNumberFloat(sample["PercentQ30"]) }} + {{ this.$root.formatNumberFloat(sample["PercentQ40"]) }} + {{ this.$root.barcode(sample) }} + {{ this.$root.formatNumberFloat(sample["PercentPoloniesAssigned"]) }} + {{ this.$root.formatNumberFloat(sample["PercentMismatch"]) }} + {{ this.sub_demux_count }} + {{ this.$root.formatNumberFloat(sample["QualityScoreMean"]) }} + + ` }); app.component('v-element-lane-stats-pre-demultiplex', { From 6a940230adf615e5498863ec999fb9150208e195 Mon Sep 17 00:00:00 2001 From: Johannes Alneberg Date: Mon, 7 Oct 2024 17:33:50 +0200 Subject: [PATCH 18/56] Removed erroneus y-axis label --- run_dir/static/js/element_flowcell.js | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/run_dir/static/js/element_flowcell.js b/run_dir/static/js/element_flowcell.js index 79def0dfb..ef601d0a0 100644 --- a/run_dir/static/js/element_flowcell.js +++ b/run_dir/static/js/element_flowcell.js @@ -790,17 +790,11 @@ app.component('v-element-quality-graph', { xAxis: { categories: filtered_categories }, - yAxis: [{ + yAxis: { title: { text: 'Percent Q30/Q40' - }, - opposite: false // Primary y-axis on the left - }, { - title: { - text: 'Average Q Score' - }, - opposite: true // Secondary y-axis on the right - }], + } + }, series: series.map(s => ({ ...s, marker: { From bf95c9fe0690bf9da6e687a2c5a77bedbfb6552d Mon Sep 17 00:00:00 2001 From: Johannes Alneberg Date: Mon, 7 Oct 2024 19:52:06 +0200 Subject: [PATCH 19/56] Added a plot for PhiX error rate --- run_dir/static/js/element_flowcell.js | 85 +++++++++++++++++++++------ 1 file changed, 67 insertions(+), 18 deletions(-) diff --git a/run_dir/static/js/element_flowcell.js b/run_dir/static/js/element_flowcell.js index ef601d0a0..7d8a0ada9 100644 --- a/run_dir/static/js/element_flowcell.js +++ b/run_dir/static/js/element_flowcell.js @@ -702,24 +702,29 @@ app.component('v-element-quality-graph', { let R1_percentQ30 = []; let R1_percentQ40 = []; let R1_averageQScore = []; + let R1_phiX_error_rate = []; let series = []; let avg_series = []; + let phiX_error_rate_series = []; if (this.include_R1) { R1_percentQ30 = this.R1_read_cycles.map(cycle => cycle.PercentQ30); R1_percentQ40 = this.R1_read_cycles.map(cycle => cycle.PercentQ40); R1_averageQScore = this.R1_read_cycles.map(cycle => cycle.AverageQScore); + R1_phiX_error_rate = this.R1_read_cycles.map(cycle => cycle.PercentPhixErrorRate); /* Filter the first values */ R1_percentQ30 = R1_percentQ30.slice(this.filter_first_cycles); R1_percentQ40 = R1_percentQ40.slice(this.filter_first_cycles); R1_averageQScore = R1_averageQScore.slice(this.filter_first_cycles); + R1_phiX_error_rate = R1_phiX_error_rate.slice(this.filter_first_cycles); /* Filter the last values */ if (this.filter_last_cycles > 0) { R1_percentQ30 = R1_percentQ30.slice(0, -this.filter_last_cycles); R1_percentQ40 = R1_percentQ40.slice(0, -this.filter_last_cycles); R1_averageQScore = R1_averageQScore.slice(0, -this.filter_last_cycles); + R1_phiX_error_rate = R1_phiX_error_rate.slice(0, -this.filter_last_cycles); } series.push({ @@ -738,27 +743,37 @@ app.component('v-element-quality-graph', { name: 'R1 Average Q Score', data: R1_averageQScore }) + + phiX_error_rate_series.push( + { + name: 'Percent PhiX Error Rate', + data: R1_phiX_error_rate + }) } let R2_percentQ30 = []; let R2_percentQ40 = []; let R2_averageQScore = []; + let R2_phiX_error_rate = []; if (this.include_R2) { R2_percentQ30 = this.R2_read_cycles.map(cycle => cycle.PercentQ30); R2_percentQ40 = this.R2_read_cycles.map(cycle => cycle.PercentQ40); R2_averageQScore = this.R2_read_cycles.map(cycle => cycle.AverageQScore); + R2_phiX_error_rate = this.R2_read_cycles.map(cycle => cycle.PercentPhixErrorRate); /* Filter the first values */ R2_percentQ30 = R2_percentQ30.slice(this.filter_first_cycles); R2_percentQ40 = R2_percentQ40.slice(this.filter_first_cycles); R2_averageQScore = R2_averageQScore.slice(this.filter_first_cycles); + R2_phiX_error_rate = R2_phiX_error_rate.slice(this.filter_first_cycles); /* Filter the last values */ if (this.filter_last_cycles > 0) { R2_percentQ30 = R2_percentQ30.slice(0, -this.filter_last_cycles); R2_percentQ40 = R2_percentQ40.slice(0, -this.filter_last_cycles); R2_averageQScore = R2_averageQScore.slice(0, -this.filter_last_cycles); + R2_phiX_error_rate = R2_phiX_error_rate.slice(0, -this.filter_last_cycles); } series.push({ @@ -778,6 +793,12 @@ app.component('v-element-quality-graph', { data: R2_averageQScore, dashStyle: 'Dash' // Set dash style for R2 series }); + + phiX_error_rate_series.push({ + name: 'R2 Percent PhiX Error Rate', + data: R2_phiX_error_rate, + dashStyle: 'Dash' // Set dash style for R2 series + }); } Highcharts.chart('SummaryPlotPercentQuality', { @@ -825,6 +846,29 @@ app.component('v-element-quality-graph', { } })) }); + + Highcharts.chart('SummaryPlotPhiXErrorRate', { + chart: { + type: 'spline' + }, + title: { + text: '% PhiX Error Rate' + }, + xAxis: { + categories: filtered_categories + }, + yAxis: { + title: { + text: '% Error Rate' + }, + }, + series: phiX_error_rate_series.map(s => ({ + ...s, + marker: { + enabled: false, // Set to false to hide markers + } + })) + }); }, }, mounted() { @@ -849,22 +893,6 @@ app.component('v-element-quality-graph', { } }, template: /*html*/` -
- -
-
-
-
-
-
-
-
-
-
@@ -888,8 +916,29 @@ app.component('v-element-quality-graph', {
-
- +
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
` }); From 5db08e60baaac67581a5aad8dba7ba6b58d54e90 Mon Sep 17 00:00:00 2001 From: Johannes Alneberg Date: Mon, 7 Oct 2024 20:17:38 +0200 Subject: [PATCH 20/56] Added an empirical phiX quality plot --- run_dir/static/js/element_flowcell.js | 51 +++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/run_dir/static/js/element_flowcell.js b/run_dir/static/js/element_flowcell.js index 7d8a0ada9..2b2b5494d 100644 --- a/run_dir/static/js/element_flowcell.js +++ b/run_dir/static/js/element_flowcell.js @@ -686,6 +686,9 @@ app.component('v-element-quality-graph', { }, }, methods: { + empirical_quality_score(percent_error_rate) { + return -10 * Math.log10(percent_error_rate/100); + }, summary_graph() { if (this.categories.length === 0) { return; @@ -703,21 +706,25 @@ app.component('v-element-quality-graph', { let R1_percentQ40 = []; let R1_averageQScore = []; let R1_phiX_error_rate = []; + let R1_phiX_empirical_error_rate = []; let series = []; let avg_series = []; let phiX_error_rate_series = []; + let phiX_empirical_error_rate_series = []; if (this.include_R1) { R1_percentQ30 = this.R1_read_cycles.map(cycle => cycle.PercentQ30); R1_percentQ40 = this.R1_read_cycles.map(cycle => cycle.PercentQ40); R1_averageQScore = this.R1_read_cycles.map(cycle => cycle.AverageQScore); R1_phiX_error_rate = this.R1_read_cycles.map(cycle => cycle.PercentPhixErrorRate); + R1_phiX_empirical_error_rate = R1_phiX_error_rate.map(error_rate => this.empirical_quality_score(error_rate)); /* Filter the first values */ R1_percentQ30 = R1_percentQ30.slice(this.filter_first_cycles); R1_percentQ40 = R1_percentQ40.slice(this.filter_first_cycles); R1_averageQScore = R1_averageQScore.slice(this.filter_first_cycles); R1_phiX_error_rate = R1_phiX_error_rate.slice(this.filter_first_cycles); + R1_phiX_empirical_error_rate = R1_phiX_empirical_error_rate.slice(this.filter_first_cycles); /* Filter the last values */ if (this.filter_last_cycles > 0) { @@ -725,6 +732,7 @@ app.component('v-element-quality-graph', { R1_percentQ40 = R1_percentQ40.slice(0, -this.filter_last_cycles); R1_averageQScore = R1_averageQScore.slice(0, -this.filter_last_cycles); R1_phiX_error_rate = R1_phiX_error_rate.slice(0, -this.filter_last_cycles); + R1_phiX_empirical_error_rate = R1_phiX_empirical_error_rate.slice(0, -this.filter_last_cycles); } series.push({ @@ -749,24 +757,33 @@ app.component('v-element-quality-graph', { name: 'Percent PhiX Error Rate', data: R1_phiX_error_rate }) + + phiX_empirical_error_rate_series.push( + { + name: 'PhiX Empirical Quality Score', + data: R1_phiX_empirical_error_rate + }) } let R2_percentQ30 = []; let R2_percentQ40 = []; let R2_averageQScore = []; let R2_phiX_error_rate = []; + let R2_phiX_empirical_error_rate = []; if (this.include_R2) { R2_percentQ30 = this.R2_read_cycles.map(cycle => cycle.PercentQ30); R2_percentQ40 = this.R2_read_cycles.map(cycle => cycle.PercentQ40); R2_averageQScore = this.R2_read_cycles.map(cycle => cycle.AverageQScore); R2_phiX_error_rate = this.R2_read_cycles.map(cycle => cycle.PercentPhixErrorRate); + R2_phiX_empirical_error_rate = R2_phiX_error_rate.map(error_rate => this.empirical_quality_score(error_rate)); /* Filter the first values */ R2_percentQ30 = R2_percentQ30.slice(this.filter_first_cycles); R2_percentQ40 = R2_percentQ40.slice(this.filter_first_cycles); R2_averageQScore = R2_averageQScore.slice(this.filter_first_cycles); R2_phiX_error_rate = R2_phiX_error_rate.slice(this.filter_first_cycles); + R2_phiX_empirical_error_rate = R2_phiX_empirical_error_rate.slice(this.filter_first_cycles); /* Filter the last values */ if (this.filter_last_cycles > 0) { @@ -774,6 +791,7 @@ app.component('v-element-quality-graph', { R2_percentQ40 = R2_percentQ40.slice(0, -this.filter_last_cycles); R2_averageQScore = R2_averageQScore.slice(0, -this.filter_last_cycles); R2_phiX_error_rate = R2_phiX_error_rate.slice(0, -this.filter_last_cycles); + R2_phiX_empirical_error_rate = R2_phiX_empirical_error_rate.slice(0, -this.filter_last_cycles); } series.push({ @@ -799,6 +817,12 @@ app.component('v-element-quality-graph', { data: R2_phiX_error_rate, dashStyle: 'Dash' // Set dash style for R2 series }); + + phiX_empirical_error_rate_series.push({ + name: 'R2 PhiX Empirical Quality Score', + data: R2_phiX_empirical_error_rate, + dashStyle: 'Dash' // Set dash style for R2 series + }); } Highcharts.chart('SummaryPlotPercentQuality', { @@ -869,6 +893,29 @@ app.component('v-element-quality-graph', { } })) }); + + Highcharts.chart('SummaryPlotEmpiricalQuality', { + chart: { + type: 'spline' + }, + title: { + text: 'PhiX Empirical Quality Score' + }, + xAxis: { + categories: filtered_categories + }, + yAxis: { + title: { + text: 'Quality Score' + }, + }, + series: phiX_empirical_error_rate_series.map(s => ({ + ...s, + marker: { + enabled: false, // Set to false to hide markers + } + })) + }); }, }, mounted() { @@ -937,6 +984,10 @@ app.component('v-element-quality-graph', {
+
+
+
+
` From 22a1688e3cc013114371ec5967207c3c49704377 Mon Sep 17 00:00:00 2001 From: Johannes Alneberg Date: Tue, 8 Oct 2024 10:30:58 +0200 Subject: [PATCH 21/56] Added base composition plots --- run_dir/static/js/element_flowcell.js | 160 +++++++++++++++++++++++++- 1 file changed, 157 insertions(+), 3 deletions(-) diff --git a/run_dir/static/js/element_flowcell.js b/run_dir/static/js/element_flowcell.js index 2b2b5494d..2845442a1 100644 --- a/run_dir/static/js/element_flowcell.js +++ b/run_dir/static/js/element_flowcell.js @@ -632,6 +632,7 @@ app.component('v-element-quality-graph', { graph_warnings: [], filter_first_cycles: 1, filter_last_cycles: 1, + use_dynamic_yscale: true, } }, computed: { @@ -707,10 +708,14 @@ app.component('v-element-quality-graph', { let R1_averageQScore = []; let R1_phiX_error_rate = []; let R1_phiX_empirical_error_rate = []; + let R1_base_composition = {}; + let series = []; let avg_series = []; let phiX_error_rate_series = []; let phiX_empirical_error_rate_series = []; + let R1_base_composition_series = []; + let R2_base_composition_series = []; if (this.include_R1) { R1_percentQ30 = this.R1_read_cycles.map(cycle => cycle.PercentQ30); @@ -718,6 +723,10 @@ app.component('v-element-quality-graph', { R1_averageQScore = this.R1_read_cycles.map(cycle => cycle.AverageQScore); R1_phiX_error_rate = this.R1_read_cycles.map(cycle => cycle.PercentPhixErrorRate); R1_phiX_empirical_error_rate = R1_phiX_error_rate.map(error_rate => this.empirical_quality_score(error_rate)); + R1_base_composition['A'] = this.R1_read_cycles.map(cycle => cycle.BaseComposition['A']); + R1_base_composition['C'] = this.R1_read_cycles.map(cycle => cycle.BaseComposition['C']); + R1_base_composition['G'] = this.R1_read_cycles.map(cycle => cycle.BaseComposition['G']); + R1_base_composition['T'] = this.R1_read_cycles.map(cycle => cycle.BaseComposition['T']); /* Filter the first values */ R1_percentQ30 = R1_percentQ30.slice(this.filter_first_cycles); @@ -725,6 +734,10 @@ app.component('v-element-quality-graph', { R1_averageQScore = R1_averageQScore.slice(this.filter_first_cycles); R1_phiX_error_rate = R1_phiX_error_rate.slice(this.filter_first_cycles); R1_phiX_empirical_error_rate = R1_phiX_empirical_error_rate.slice(this.filter_first_cycles); + R1_base_composition['A'] = R1_base_composition['A'].slice(this.filter_first_cycles); + R1_base_composition['C'] = this.R1_read_cycles.map(cycle => cycle.BaseComposition['C']).slice(this.filter_first_cycles); + R1_base_composition['G'] = this.R1_read_cycles.map(cycle => cycle.BaseComposition['G']).slice(this.filter_first_cycles); + R1_base_composition['T'] = this.R1_read_cycles.map(cycle => cycle.BaseComposition['T']).slice(this.filter_first_cycles); /* Filter the last values */ if (this.filter_last_cycles > 0) { @@ -733,6 +746,10 @@ app.component('v-element-quality-graph', { R1_averageQScore = R1_averageQScore.slice(0, -this.filter_last_cycles); R1_phiX_error_rate = R1_phiX_error_rate.slice(0, -this.filter_last_cycles); R1_phiX_empirical_error_rate = R1_phiX_empirical_error_rate.slice(0, -this.filter_last_cycles); + R1_base_composition['A'] = R1_base_composition['A'].slice(0, -this.filter_last_cycles); + R1_base_composition['C'] = this.R1_read_cycles.map(cycle => cycle.BaseComposition['C']).slice(0, -this.filter_last_cycles); + R1_base_composition['G'] = this.R1_read_cycles.map(cycle => cycle.BaseComposition['G']).slice(0, -this.filter_last_cycles); + R1_base_composition['T'] = this.R1_read_cycles.map(cycle => cycle.BaseComposition['T']).slice(0, -this.filter_last_cycles); } series.push({ @@ -754,15 +771,36 @@ app.component('v-element-quality-graph', { phiX_error_rate_series.push( { - name: 'Percent PhiX Error Rate', + name: 'R1 Percent PhiX Error Rate', data: R1_phiX_error_rate }) phiX_empirical_error_rate_series.push( { - name: 'PhiX Empirical Quality Score', + name: 'R1 PhiX Empirical Quality Score', data: R1_phiX_empirical_error_rate }) + + R1_base_composition_series.push( + { + name: 'R1 Base Composition A', + data: R1_base_composition['A'] + }) + R1_base_composition_series.push( + { + name: 'R1 Base Composition C', + data: this.R1_read_cycles.map(cycle => cycle.BaseComposition['C']) + }) + R1_base_composition_series.push( + { + name: 'R1 Base Composition G', + data: this.R1_read_cycles.map(cycle => cycle.BaseComposition['G']) + }) + R1_base_composition_series.push( + { + name: 'R1 Base Composition T', + data: this.R1_read_cycles.map(cycle => cycle.BaseComposition['T']) + }) } let R2_percentQ30 = []; @@ -770,6 +808,7 @@ app.component('v-element-quality-graph', { let R2_averageQScore = []; let R2_phiX_error_rate = []; let R2_phiX_empirical_error_rate = []; + let R2_base_composition = {}; if (this.include_R2) { R2_percentQ30 = this.R2_read_cycles.map(cycle => cycle.PercentQ30); @@ -777,6 +816,10 @@ app.component('v-element-quality-graph', { R2_averageQScore = this.R2_read_cycles.map(cycle => cycle.AverageQScore); R2_phiX_error_rate = this.R2_read_cycles.map(cycle => cycle.PercentPhixErrorRate); R2_phiX_empirical_error_rate = R2_phiX_error_rate.map(error_rate => this.empirical_quality_score(error_rate)); + R2_base_composition['A'] = this.R2_read_cycles.map(cycle => cycle.BaseComposition['A']); + R2_base_composition['C'] = this.R2_read_cycles.map(cycle => cycle.BaseComposition['C']); + R2_base_composition['G'] = this.R2_read_cycles.map(cycle => cycle.BaseComposition['G']); + R2_base_composition['T'] = this.R2_read_cycles.map(cycle => cycle.BaseComposition['T']); /* Filter the first values */ R2_percentQ30 = R2_percentQ30.slice(this.filter_first_cycles); @@ -784,6 +827,10 @@ app.component('v-element-quality-graph', { R2_averageQScore = R2_averageQScore.slice(this.filter_first_cycles); R2_phiX_error_rate = R2_phiX_error_rate.slice(this.filter_first_cycles); R2_phiX_empirical_error_rate = R2_phiX_empirical_error_rate.slice(this.filter_first_cycles); + R2_base_composition['A'] = R2_base_composition['A'].slice(this.filter_first_cycles); + R2_base_composition['C'] = this.R2_read_cycles.map(cycle => cycle.BaseComposition['C']).slice(this.filter_first_cycles); + R2_base_composition['G'] = this.R2_read_cycles.map(cycle => cycle.BaseComposition['G']).slice(this.filter_first_cycles); + R2_base_composition['T'] = this.R2_read_cycles.map(cycle => cycle.BaseComposition['T']).slice(this.filter_first_cycles); /* Filter the last values */ if (this.filter_last_cycles > 0) { @@ -792,6 +839,10 @@ app.component('v-element-quality-graph', { R2_averageQScore = R2_averageQScore.slice(0, -this.filter_last_cycles); R2_phiX_error_rate = R2_phiX_error_rate.slice(0, -this.filter_last_cycles); R2_phiX_empirical_error_rate = R2_phiX_empirical_error_rate.slice(0, -this.filter_last_cycles); + R2_base_composition['A'] = R2_base_composition['A'].slice(0, -this.filter_last_cycles); + R2_base_composition['C'] = this.R2_read_cycles.map(cycle => cycle.BaseComposition['C']).slice(0, -this.filter_last_cycles); + R2_base_composition['G'] = this.R2_read_cycles.map(cycle => cycle.BaseComposition['G']).slice(0, -this.filter_last_cycles); + R2_base_composition['T'] = this.R2_read_cycles.map(cycle => cycle.BaseComposition['T']).slice(0, -this.filter_last_cycles); } series.push({ @@ -823,6 +874,30 @@ app.component('v-element-quality-graph', { data: R2_phiX_empirical_error_rate, dashStyle: 'Dash' // Set dash style for R2 series }); + + R2_base_composition_series.push({ + name: 'R2 Base Composition A', + data: R2_base_composition['A'], + dashStyle: 'Dash' // Set dash style for R2 series + }); + + R2_base_composition_series.push({ + name: 'R2 Base Composition C', + data: R2_base_composition['C'], + dashStyle: 'Dash' // Set dash style for R2 series + }); + + R2_base_composition_series.push({ + name: 'R2 Base Composition G', + data: R2_base_composition['G'], + dashStyle: 'Dash' // Set dash style for R2 series + }); + + R2_base_composition_series.push({ + name: 'R2 Base Composition T', + data: R2_base_composition['T'], + dashStyle: 'Dash' // Set dash style for R2 series + }); } Highcharts.chart('SummaryPlotPercentQuality', { @@ -838,7 +913,9 @@ app.component('v-element-quality-graph', { yAxis: { title: { text: 'Percent Q30/Q40' - } + }, + min: this.use_dynamic_yscale ? null : 0, // Conditionally set the minimum value + max: this.use_dynamic_yscale ? null : 100 // Conditionally set the maximum value }, series: series.map(s => ({ ...s, @@ -862,6 +939,8 @@ app.component('v-element-quality-graph', { title: { text: 'Average Q Score' }, + min: this.use_dynamic_yscale ? null : 0, + max: this.use_dynamic_yscale ? null : 50, }, series: avg_series.map(s => ({ ...s, @@ -885,6 +964,8 @@ app.component('v-element-quality-graph', { title: { text: '% Error Rate' }, + min: this.use_dynamic_yscale ? null : 0, + max: this.use_dynamic_yscale ? null : 20, }, series: phiX_error_rate_series.map(s => ({ ...s, @@ -908,6 +989,8 @@ app.component('v-element-quality-graph', { title: { text: 'Quality Score' }, + min: this.use_dynamic_yscale ? null : 0, + max: this.use_dynamic_yscale ? null : 50, }, series: phiX_empirical_error_rate_series.map(s => ({ ...s, @@ -916,6 +999,56 @@ app.component('v-element-quality-graph', { } })) }); + + Highcharts.chart('SummaryPlotBaseComposition_R1', { + chart: { + type: 'spline' + }, + title: { + text: 'R1 Base Composition' + }, + xAxis: { + categories: filtered_categories + }, + yAxis: { + title: { + text: 'Base Composition' + }, + min: this.use_dynamic_yscale ? null : 0, + max: this.use_dynamic_yscale ? null : 60, + }, + series: R1_base_composition_series.map(s => ({ + ...s, + marker: { + enabled: false, // Set to false to hide markers + } + })) + }); + + Highcharts.chart('SummaryPlotBaseComposition_R2', { + chart: { + type: 'spline' + }, + title: { + text: 'R2 Base Composition' + }, + xAxis: { + categories: filtered_categories + }, + yAxis: { + title: { + text: 'Base Composition' + }, + min: this.use_dynamic_yscale ? null : 0, + max: this.use_dynamic_yscale ? null : 60, + }, + series: R2_base_composition_series.map(s => ({ + ...s, + marker: { + enabled: false, // Set to false to hide markers + } + })) + }); }, }, mounted() { @@ -937,6 +1070,9 @@ app.component('v-element-quality-graph', { }, filter_last_cycles: function() { this.summary_graph(); + }, + use_dynamic_yscale: function() { + this.summary_graph(); } }, template: /*html*/` @@ -954,6 +1090,14 @@ app.component('v-element-quality-graph', { Include R2
+ +
+ + +
+
@@ -989,6 +1133,16 @@ app.component('v-element-quality-graph', {
+
+
+
+
+
+
+
+
+
+
` }); From 03c709ab83fc3682a3860c1e2fbd24e472ac0ea9 Mon Sep 17 00:00:00 2001 From: Johannes Alneberg Date: Tue, 8 Oct 2024 12:09:11 +0200 Subject: [PATCH 22/56] Moved run_parameters to root --- run_dir/static/js/element_flowcell.js | 31 +++++++++++---------------- 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/run_dir/static/js/element_flowcell.js b/run_dir/static/js/element_flowcell.js index 2845442a1..c8337cbc0 100644 --- a/run_dir/static/js/element_flowcell.js +++ b/run_dir/static/js/element_flowcell.js @@ -14,6 +14,9 @@ const vElementApp = { aviti_run_stats() { return this.getValue(this.instrument_generated_files, "AvitiRunStats.json", {}); }, + run_parameters() { + return this.$root.getValue(this.instrument_generated_files, "RunParameters.json", {}); + }, run_stats() { return this.getValue(this.aviti_run_stats, "RunStats", {}); }, @@ -82,17 +85,9 @@ app.component('v-element-flowcell', { flowcell() { return this.$root.flowcell; }, - instrument_generated_files() { - return this.$root.instrument_generated_files; - }, - aviti_run_stats() { - return this.$root.aviti_run_stats; - }, - run_parameters() { - return this.$root.getValue(this.instrument_generated_files, "RunParameters.json", {}); - }, + start_time() { - const dateStr = this.$root.getValue(this.run_parameters, "Date", null); + const dateStr = this.$root.getValue(this.$root.run_parameters, "Date", null); if (dateStr) { const date = new Date(dateStr); const date_string = date.toLocaleDateString(); @@ -104,19 +99,19 @@ app.component('v-element-flowcell', { } }, flowcell_id() { - return this.$root.getValue(this.run_parameters, "FlowcellID"); + return this.$root.getValue(this.$root.run_parameters, "FlowcellID"); }, side() { - return this.$root.getValue(this.run_parameters, "Side"); + return this.$root.getValue(this.$root.run_parameters, "Side"); }, instrument_name() { - return this.$root.getValue(this.run_parameters, "InstrumentName"); + return this.$root.getValue(this.$root.run_parameters, "InstrumentName"); }, run_setup() { return `${this.chemistry_version} ${this.kit_configuration} (${this.cycles}); ${this.throughput_selection}`; }, cycles() { - const cycles = this.$root.getValue(this.run_parameters, "Cycles", {}); + const cycles = this.$root.getValue(this.$root.run_parameters, "Cycles", {}); if (cycles === "N/A") { return "N/A"; } @@ -136,16 +131,16 @@ app.component('v-element-flowcell', { return return_str; }, throughput_selection() { - return this.$root.getValue(this.run_parameters, "ThroughputSelection", "N/A") + " Throughput"; + return this.$root.getValue(this.$root.run_parameters, "ThroughputSelection", "N/A") + " Throughput"; }, kit_configuration() { - return this.$root.getValue(this.run_parameters, "KitConfiguration"); + return this.$root.getValue(this.$root.run_parameters, "KitConfiguration"); }, preparation_workflow() { - return this.$root.getValue(this.run_parameters, "PreparationWorkflow"); + return this.$root.getValue(this.$root.run_parameters, "PreparationWorkflow"); }, chemistry_version() { - return this.$root.getValue(this.run_parameters, "ChemistryVersion"); + return this.$root.getValue(this.$root.run_parameters, "ChemistryVersion"); } }, mounted() { From bb1ffa3204a07fa5771a9e04046cbd6470c8aad6 Mon Sep 17 00:00:00 2001 From: Johannes Alneberg Date: Tue, 8 Oct 2024 14:07:19 +0200 Subject: [PATCH 23/56] Use the root attributes and moved the getFlowcell method --- run_dir/static/js/element_flowcell.js | 47 ++++++++++----------------- 1 file changed, 18 insertions(+), 29 deletions(-) diff --git a/run_dir/static/js/element_flowcell.js b/run_dir/static/js/element_flowcell.js index c8337cbc0..c4e9c5f8a 100644 --- a/run_dir/static/js/element_flowcell.js +++ b/run_dir/static/js/element_flowcell.js @@ -50,6 +50,16 @@ const vElementApp = { } return barcode_str; }, + getFlowcell() { + axios.get("/api/v1/element_flowcell/" + this.ngi_run_id) + .then(response => { + this.flowcell = response.data; + this.flowcell_fetched = true; + }) + .catch(error => { + console.log(error); + }); + }, getValue(obj, key, defaultValue = "N/A") { if (obj === null || obj == undefined || obj === "N/A") { return defaultValue; @@ -144,20 +154,8 @@ app.component('v-element-flowcell', { } }, mounted() { - this.getFlowcell(); - }, - methods: { - getFlowcell() { - axios.get("/api/v1/element_flowcell/" + this.ngi_run_id) - .then(response => { - this.$root.flowcell = response.data; - this.$root.flowcell_fetched = true; - }) - .catch(error => { - console.log(error); - }); - }, - + this.$root.ngi_run_id = this.ngi_run_id; + this.$root.getFlowcell(); }, template: /*html*/`
@@ -241,32 +239,23 @@ app.component('v-element-flowcell', { app.component('v-element-run-stats', { computed: { - aviti_run_stats() { - return this.$root.aviti_run_stats; - }, - run_stats() { - return this.$root.run_stats; - }, polony_count() { - return this.$root.getValue(this.run_stats, "PolonyCount"); + return this.$root.getValue(this.$root.run_stats, "PolonyCount"); }, pf_count() { - return this.$root.getValue(this.run_stats, "PFCount"); + return this.$root.getValue(this.$root.run_stats, "PFCount"); }, percent_pf() { - return this.$root.getValue(this.run_stats, "PercentPF"); + return this.$root.getValue(this.$root.run_stats, "PercentPF"); }, total_yield() { - return this.$root.getValue(this.run_stats, "TotalYield"); + return this.$root.getValue(this.$root.run_stats, "TotalYield"); }, total_yield_formatted() { - return this.$root.formatNumberBases(this.$root.getValue(this.run_stats, "TotalYield")); - }, - index_assignment() { - return this.$root.index_assignment; + return this.$root.formatNumberBases(this.$root.getValue(this.$root.run_stats, "TotalYield")); }, percent_assigned_reads() { - return this.$root.getValue(this.index_assignment, "PercentAssignedReads"); + return this.$root.getValue(this.$root.index_assignment, "PercentAssignedReads"); } }, template: ` From b0bf929257a41bcaa909e949ec1feceae891b8e0 Mon Sep 17 00:00:00 2001 From: Johannes Alneberg Date: Tue, 8 Oct 2024 14:07:50 +0200 Subject: [PATCH 24/56] Tweaking float formatting --- run_dir/static/js/element_flowcell.js | 53 ++++++++++++++++----------- 1 file changed, 32 insertions(+), 21 deletions(-) diff --git a/run_dir/static/js/element_flowcell.js b/run_dir/static/js/element_flowcell.js index c4e9c5f8a..91708d00b 100644 --- a/run_dir/static/js/element_flowcell.js +++ b/run_dir/static/js/element_flowcell.js @@ -77,11 +77,15 @@ const vElementApp = { return (number / 1000000000).toFixed(2) + " Gbp"; } }, - formatNumberFloat(value) { + formatNumberFloat(value, decimalPoints=2) { if (value === "N/A") { return "N/A"; } - return parseFloat(value).toFixed(2); + const number = parseFloat(value); + if (isNaN(number)) { + return "N/A"; + } + return number.toFixed(decimalPoints); } } } @@ -206,13 +210,13 @@ app.component('v-element-flowcell', { Lane stats
@@ -313,11 +317,10 @@ app.component('v-element-lane-stats', { return groupedByLane; }, phiX_lane_stats_combined() { - /* Sum % Polonies and Count for each lane */ const groupedByLane = {}; this.$root.index_assignment_demultiplex.forEach((sample) => { - if (sample["SampleName"] === "PhiX") { + if (! this.is_not_phiX(sample)) { const lane = sample["Lane"]; if (!groupedByLane[lane]) { groupedByLane[lane] = { @@ -359,7 +362,6 @@ app.component('v-element-lane-stats', { return groupedByLane; }, unassigned_lane_stats_combined() { - /* Sum % Polonies and Count for each lane */ const groupedByLane = {}; if (this.$root.unassiged_sequences_demultiplex && this.$root.unassiged_sequences_demultiplex.length > 0) { this.$root.unassiged_sequences_demultiplex.forEach(unassigned_index => { @@ -386,6 +388,11 @@ app.component('v-element-lane-stats', { return groupedByLane; } }, + methods: { + is_not_phiX(sample) { + return sample["SampleName"].indexOf("PhiX") === -1; + }, + }, template: /*html*/`

@@ -411,7 +418,7 @@ app.component('v-element-lane-stats', {