Qubit results parsing complete.
This commit is contained in:
@@ -0,0 +1,58 @@
|
|||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
import logging
|
||||||
|
from csv import reader
|
||||||
|
from typing import Generator, TYPE_CHECKING
|
||||||
|
from frontend.widgets.results_sample_matcher import ResultsSampleMatcher
|
||||||
|
from backend import Procedure
|
||||||
|
from backend.db.models import ProcedureSampleAssociation
|
||||||
|
from backend.excel.parsers.results_parsers import DefaultResultsInfoParser, DefaultResultsSampleParser
|
||||||
|
from pathlib import Path
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from backend.validators.pydant import PydSample
|
||||||
|
|
||||||
|
logger = logging.getLogger(f"submissions.{__name__}")
|
||||||
|
|
||||||
|
class QubitInfoParser(DefaultResultsInfoParser):
|
||||||
|
|
||||||
|
def __init__(self, filepath: Path | str, procedure=None, **kwargs):
|
||||||
|
self.results_type = "Qubit"
|
||||||
|
self.procedure = procedure
|
||||||
|
super().__init__(filepath=filepath, proceduretype=self.procedure.proceduretype, results_type="Qubit")
|
||||||
|
|
||||||
|
def to_pydantic(self):
|
||||||
|
"""
|
||||||
|
Since there is no overview generated, return blank PydResults object.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
PydResults
|
||||||
|
"""
|
||||||
|
from backend.validators.pydant import PydResults
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class QubitSampleParser(DefaultResultsSampleParser):
|
||||||
|
"""Object to pull data from Design and Analysis PCR export file."""
|
||||||
|
|
||||||
|
def __init__(self, filepath: Path | str, sheet: str | None = None, start_row: int = 1, procedure=None, **kwargs):
|
||||||
|
self.results_type = "Qubit"
|
||||||
|
self.procedure = procedure
|
||||||
|
|
||||||
|
super().__init__(filepath=filepath, proceduretype=self.procedure.proceduretype, results_type="Qubit")
|
||||||
|
self.sample_matcher()
|
||||||
|
|
||||||
|
def sample_matcher(self):
|
||||||
|
# samples = [item for item in self.procedure.proceduresampleassociation]
|
||||||
|
dlg = ResultsSampleMatcher(
|
||||||
|
parent=None,
|
||||||
|
results_var_name="original_sample_conc.",
|
||||||
|
results=self.parsed_info,
|
||||||
|
samples=self.procedure.proceduresampleassociation,
|
||||||
|
procedure=self.procedure,
|
||||||
|
results_type="Qubit"
|
||||||
|
)
|
||||||
|
if dlg.exec():
|
||||||
|
for result in dlg.output:
|
||||||
|
result.save()
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
import logging
|
||||||
|
from io import BytesIO
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
from openpyxl.reader.excel import load_workbook
|
||||||
|
from backend.db.models import Procedure
|
||||||
|
from backend.excel.parsers.results_parsers.qubit_results_parser import QubitSampleParser, QubitInfoParser
|
||||||
|
# from backend.excel.writers.results_writers.pcr_results_writer import QubitInfoWriter, QubitSampleWriter
|
||||||
|
from . import DefaultResultsManager
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from backend.validators.pydant import PydResults
|
||||||
|
|
||||||
|
logger = logging.getLogger(f"submissions.{__name__}")
|
||||||
|
|
||||||
|
class QubitManager(DefaultResultsManager):
|
||||||
|
|
||||||
|
def __init__(self, procedure: Procedure, parent, fname: Path | str | None = None):
|
||||||
|
super().__init__(procedure=procedure, parent=parent, fname=fname, extension="csv")
|
||||||
|
self.parse()
|
||||||
|
|
||||||
|
def parse(self):
|
||||||
|
self.info_parser = QubitInfoParser(filepath=self.fname, procedure=self.procedure)
|
||||||
|
self.sample_parser = QubitSampleParser(filepath=self.fname, procedure=self.procedure, start_row=self.info_parser.end_row)
|
||||||
|
|
||||||
|
def write(self):
|
||||||
|
workbook = load_workbook(BytesIO(self.procedure.proceduretype.template_file))
|
||||||
|
self.info_writer = PCRInfoWriter(pydant_obj=self.procedure.to_pydantic(), proceduretype=self.procedure.proceduretype)
|
||||||
|
workbook = self.info_writer.write_to_workbook(workbook)
|
||||||
|
self.sample_writer = PCRSampleWriter(pydant_obj=self.procedure.to_pydantic(), proceduretype=self.procedure.proceduretype)
|
||||||
96
src/submissions/frontend/widgets/results_sample_matcher.py
Normal file
96
src/submissions/frontend/widgets/results_sample_matcher.py
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import logging, sys
|
||||||
|
from pprint import pformat
|
||||||
|
from typing import List, Generator
|
||||||
|
from PyQt6.QtWidgets import (QDialog, QGridLayout, QDialogButtonBox)
|
||||||
|
from PyQt6.QtWebEngineWidgets import QWebEngineView
|
||||||
|
from PyQt6.QtWebChannel import QWebChannel
|
||||||
|
from PyQt6.QtCore import pyqtSlot
|
||||||
|
from tools import render_details_template, row_keys
|
||||||
|
from backend.db.models import Procedure, ProcedureSampleAssociation, Results
|
||||||
|
|
||||||
|
logger = logging.getLogger(f"submissions.{__name__}")
|
||||||
|
|
||||||
|
class ResultsSampleMatcher(QDialog):
|
||||||
|
|
||||||
|
def __init__(self, parent, results_var_name: str, results: Generator[dict, None, None], samples:List[str],
|
||||||
|
procedure:Procedure, results_type: str):
|
||||||
|
super().__init__(parent=parent)
|
||||||
|
self.procedure = procedure
|
||||||
|
self.results_type = results_type
|
||||||
|
self.results_var_name = results_var_name
|
||||||
|
results = [item for item in results]
|
||||||
|
html = render_details_template("results_sample_match", results=results, results_var_name=self.results_var_name, samples=samples)
|
||||||
|
self.webview = QWebEngineView()
|
||||||
|
self.layout = QGridLayout()
|
||||||
|
self.setLayout(self.layout)
|
||||||
|
self.channel = QWebChannel()
|
||||||
|
self.channel.registerObject('backend', self)
|
||||||
|
self.webview.setHtml(html)
|
||||||
|
self.webview.page().setWebChannel(self.channel)
|
||||||
|
self.layout.addWidget(self.webview)
|
||||||
|
QBtn = QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
|
||||||
|
self.buttonBox = QDialogButtonBox(QBtn)
|
||||||
|
self.buttonBox.accepted.connect(self.accept)
|
||||||
|
self.buttonBox.rejected.connect(self.reject)
|
||||||
|
self.layout.addWidget(self.buttonBox)
|
||||||
|
self.output = []
|
||||||
|
|
||||||
|
@pyqtSlot(bool, str, str, str)
|
||||||
|
def set_match(self, enabled: bool, sample: str, result_text:str, result: str):
|
||||||
|
logger.debug(f"Sample: {sample}")
|
||||||
|
if ":" in sample:
|
||||||
|
sample_id = sample.split(":")[0]
|
||||||
|
well = sample.split(":")[1]
|
||||||
|
row = row_keys[well[0]]
|
||||||
|
column = int(well[1:])
|
||||||
|
else:
|
||||||
|
row = None
|
||||||
|
column = None
|
||||||
|
result = "".join([r for r in result]).replace("\'", "\"")
|
||||||
|
try:
|
||||||
|
result = json.loads(result)
|
||||||
|
except json.decoder.JSONDecoder:
|
||||||
|
logger.error("Could not decode json.")
|
||||||
|
logger.debug(f"Search: {self.procedure}, {sample_id}, {row}, {column}")
|
||||||
|
association = ProcedureSampleAssociation.query(procedure=self.procedure, sample=sample_id, row=row, column=column)
|
||||||
|
if enabled:
|
||||||
|
result = Results(sampleprocedureassociation=association, result=result, result_type=self.results_type)
|
||||||
|
self.output.append(result)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
result = next(
|
||||||
|
(item for item in self.output if str(item.result[self.results_var_name]) == result_text)
|
||||||
|
)
|
||||||
|
except StopIteration:
|
||||||
|
logger.error(f"Couldn't find association for {result_text}")
|
||||||
|
return
|
||||||
|
self.output.remove(result)
|
||||||
|
|
||||||
|
@pyqtSlot(str, str)
|
||||||
|
def update_match(self, sample: str, result_text: str):
|
||||||
|
if ":" in sample:
|
||||||
|
sample_id = sample.split(":")[0]
|
||||||
|
well = sample.split(":")[1]
|
||||||
|
row = row_keys[well[0]]
|
||||||
|
column = int(well[1:])
|
||||||
|
else:
|
||||||
|
row = None
|
||||||
|
column = None
|
||||||
|
logger.debug(f"Search: {self.procedure}, {sample_id}, {row}, {column}")
|
||||||
|
association = ProcedureSampleAssociation.query(procedure=self.procedure, sample=sample_id, row=row, column=column)
|
||||||
|
logger.debug(association)
|
||||||
|
try:
|
||||||
|
result = next(
|
||||||
|
(item for item in self.output if str(item.result[self.results_var_name]) == result_text)
|
||||||
|
)
|
||||||
|
except StopIteration:
|
||||||
|
logger.error(f"Couldn't find association for {result_text}")
|
||||||
|
return
|
||||||
|
result.sampleprocedureassociation = association
|
||||||
|
logger.debug(f"Output: {pformat(self.output)}")
|
||||||
54
src/submissions/templates/results_sample_match.html
Normal file
54
src/submissions/templates/results_sample_match.html
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
{% extends "details.html" %}
|
||||||
|
|
||||||
|
{% block head %}
|
||||||
|
{{ super() }}
|
||||||
|
<title>Matching results</title>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
{% for result in results %}
|
||||||
|
<div class="resultholder" style="border-style: solid; border-width: 2px" data="{{ result }}">
|
||||||
|
|
||||||
|
<input type="checkbox" id="{{ loop.index }}_check" class="checker">
|
||||||
|
<span id="{{ loop.index }}_var", class="variable" data-value="{{ result }}">{{ result[results_var_name] }}</span>
|
||||||
|
<select id="{{ loop.index }}_select" class="selecter" disabled>
|
||||||
|
{% for sample in samples %}
|
||||||
|
{% if sample.well %}
|
||||||
|
<option value="{{ sample.sample.sample_id }}:{{ sample.well }}">{{ sample.sample.sample_id }}:{{ sample.well }}</option>
|
||||||
|
{% else %}
|
||||||
|
<option value="{{ sample.sample.sample_id }}">{{ sample.sample.sample_id }}</option>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block script %}
|
||||||
|
<script>
|
||||||
|
var holders = document.getElementsByClassName("resultholder");
|
||||||
|
for(let i = 0; i < holders.length; i++) {
|
||||||
|
console.log(i);
|
||||||
|
holders[i].getElementsByClassName("checker")[0].addEventListener("change", function(){
|
||||||
|
if ( this.checked ) {
|
||||||
|
holders[i].getElementsByClassName("selecter")[0].disabled = false;
|
||||||
|
} else {
|
||||||
|
holders[i].getElementsByClassName("selecter")[0].disabled = true;
|
||||||
|
}
|
||||||
|
var enabled = this.checked;
|
||||||
|
var sample = holders[i].getElementsByClassName("selecter")[0].value;
|
||||||
|
var result = holders[i].getElementsByClassName("variable")[0].dataset.value;
|
||||||
|
var result_text = holders[i].getElementsByClassName("variable")[0].textContent
|
||||||
|
backend.set_match(enabled, sample, result_text, result);
|
||||||
|
});
|
||||||
|
holders[i].getElementsByClassName("selecter")[0].addEventListener("change", function(){
|
||||||
|
var sample = this.value;
|
||||||
|
var result_text = holders[i].getElementsByClassName("variable")[0].textContent
|
||||||
|
backend.update_match(sample, result_text);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{{ super() }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user