hitpicking complete, pre-addition of WW-Arctic parsers and models.
This commit is contained in:
@@ -135,6 +135,7 @@ class App(QMainWindow):
|
||||
logger.debug(f"Attempting to open {url}")
|
||||
webbrowser.get('windows-default').open(f"file://{url.__str__()}")
|
||||
|
||||
# All main window functions return a result which is reported here, unless it is None
|
||||
def result_reporter(self, result:dict|None=None):
|
||||
if result != None:
|
||||
msg = AlertPop(message=result['message'], status=result['status'])
|
||||
@@ -147,128 +148,6 @@ class App(QMainWindow):
|
||||
self, result = import_submission_function(self)
|
||||
logger.debug(f"Import result: {result}")
|
||||
self.result_reporter(result)
|
||||
# logger.debug(self.ctx)
|
||||
# # initialize samples
|
||||
# self.samples = []
|
||||
# self.reagents = {}
|
||||
# # set file dialog
|
||||
# home_dir = str(Path(self.ctx["directory_path"]))
|
||||
# fname = Path(QFileDialog.getOpenFileName(self, 'Open file', home_dir)[0])
|
||||
# logger.debug(f"Attempting to parse file: {fname}")
|
||||
# assert fname.exists()
|
||||
# # create sheetparser using excel sheet and context from gui
|
||||
# try:
|
||||
# prsr = SheetParser(fname, **self.ctx)
|
||||
# except PermissionError:
|
||||
# logger.error(f"Couldn't get permission to access file: {fname}")
|
||||
# return
|
||||
# if prsr.sub['rsl_plate_num'] == None:
|
||||
# prsr.sub['rsl_plate_num'] = RSLNamer(fname.__str__()).parsed_name
|
||||
# logger.debug(f"prsr.sub = {prsr.sub}")
|
||||
# # destroy any widgets from previous imports
|
||||
# for item in self.table_widget.formlayout.parentWidget().findChildren(QWidget):
|
||||
# item.setParent(None)
|
||||
# # regex to parser out different variable types for decision making
|
||||
# variable_parser = re.compile(r"""
|
||||
# # (?x)
|
||||
# (?P<extraction_kit>^extraction_kit$) |
|
||||
# (?P<submitted_date>^submitted_date$) |
|
||||
# (?P<submitting_lab>)^submitting_lab$ |
|
||||
# (?P<samples>)^samples$ |
|
||||
# (?P<reagent>^lot_.*$) |
|
||||
# (?P<csv>^csv$)
|
||||
# """, re.VERBOSE)
|
||||
# for item in prsr.sub:
|
||||
# logger.debug(f"Item: {item}")
|
||||
# # attempt to match variable name to regex group
|
||||
# try:
|
||||
# mo = variable_parser.fullmatch(item).lastgroup
|
||||
# except AttributeError:
|
||||
# mo = "other"
|
||||
# logger.debug(f"Mo: {mo}")
|
||||
# match mo:
|
||||
# case 'submitting_lab':
|
||||
# # create label
|
||||
# self.table_widget.formlayout.addWidget(QLabel(item.replace("_", " ").title()))
|
||||
# logger.debug(f"{item}: {prsr.sub[item]}")
|
||||
# # create combobox to hold looked up submitting labs
|
||||
# add_widget = QComboBox()
|
||||
# labs = [item.__str__() for item in lookup_all_orgs(ctx=self.ctx)]
|
||||
# # try to set closest match to top of list
|
||||
# try:
|
||||
# labs = difflib.get_close_matches(prsr.sub[item], labs, len(labs), 0)
|
||||
# except (TypeError, ValueError):
|
||||
# pass
|
||||
# # set combobox values to lookedup values
|
||||
# add_widget.addItems(labs)
|
||||
# case 'extraction_kit':
|
||||
# # create label
|
||||
# self.table_widget.formlayout.addWidget(QLabel(item.replace("_", " ").title()))
|
||||
# # if extraction kit not available, all other values fail
|
||||
# if not check_not_nan(prsr.sub[item]):
|
||||
# msg = AlertPop(message="Make sure to check your extraction kit in the excel sheet!", status="warning")
|
||||
# msg.exec()
|
||||
# # create combobox to hold looked up kits
|
||||
# # add_widget = KitSelector(ctx=self.ctx, submission_type=prsr.sub['submission_type'], parent=self)
|
||||
# add_widget = QComboBox()
|
||||
# # add_widget.currentTextChanged.connect(self.kit_reload)
|
||||
# # lookup existing kits by 'submission_type' decided on by sheetparser
|
||||
# uses = [item.__str__() for item in lookup_kittype_by_use(ctx=self.ctx, used_by=prsr.sub['submission_type'])]
|
||||
# # if len(uses) > 0:
|
||||
# add_widget.addItems(uses)
|
||||
# # else:
|
||||
# # add_widget.addItems(['bacterial_culture'])
|
||||
# if check_not_nan(prsr.sub[item]):
|
||||
# self.ext_kit = prsr.sub[item]
|
||||
# else:
|
||||
# self.ext_kit = add_widget.currentText()
|
||||
# case 'submitted_date':
|
||||
# # create label
|
||||
# self.table_widget.formlayout.addWidget(QLabel(item.replace("_", " ").title()))
|
||||
# # uses base calendar
|
||||
# add_widget = QDateEdit(calendarPopup=True)
|
||||
# # sets submitted date based on date found in excel sheet
|
||||
# try:
|
||||
# add_widget.setDate(prsr.sub[item])
|
||||
# # if not found, use today
|
||||
# except:
|
||||
# add_widget.setDate(date.today())
|
||||
# case 'reagent':
|
||||
# # create label
|
||||
# reg_label = QLabel(item.replace("_", " ").title())
|
||||
# reg_label.setObjectName(f"lot_{item}_label")
|
||||
# self.table_widget.formlayout.addWidget(reg_label)
|
||||
# # create reagent choice widget
|
||||
# add_widget = ImportReagent(ctx=self.ctx, item=item, prsr=prsr)
|
||||
# self.reagents[item] = prsr.sub[item]
|
||||
# case 'samples':
|
||||
# # hold samples in 'self' until form submitted
|
||||
# logger.debug(f"{item}: {prsr.sub[item]}")
|
||||
# self.samples = prsr.sub[item]
|
||||
# add_widget = None
|
||||
# case 'csv':
|
||||
# self.csv = prsr.sub[item]
|
||||
# case _:
|
||||
# # anything else gets added in as a line edit
|
||||
# self.table_widget.formlayout.addWidget(QLabel(item.replace("_", " ").title()))
|
||||
# add_widget = QLineEdit()
|
||||
# logger.debug(f"Setting widget text to {str(prsr.sub[item]).replace('_', ' ')}")
|
||||
# add_widget.setText(str(prsr.sub[item]).replace("_", " "))
|
||||
# try:
|
||||
# add_widget.setObjectName(item)
|
||||
# logger.debug(f"Widget name set to: {add_widget.objectName()}")
|
||||
# self.table_widget.formlayout.addWidget(add_widget)
|
||||
# except AttributeError as e:
|
||||
# logger.error(e)
|
||||
# # compare self.reagents with expected reagents in kit
|
||||
# if hasattr(self, 'ext_kit'):
|
||||
# self.kit_integrity_completion()
|
||||
# # create submission button
|
||||
# # submit_btn = QPushButton("Submit")
|
||||
# # submit_btn.setObjectName("submit_btn")
|
||||
# # self.table_widget.formlayout.addWidget(submit_btn)
|
||||
# # submit_btn.clicked.connect(self.submit_new_sample)
|
||||
# logger.debug(f"Imported reagents: {self.reagents}")
|
||||
|
||||
|
||||
def kit_reload(self):
|
||||
@@ -277,17 +156,7 @@ class App(QMainWindow):
|
||||
"""
|
||||
self, result = kit_reload_function(self)
|
||||
self.result_reporter(result)
|
||||
# for item in self.table_widget.formlayout.parentWidget().findChildren(QWidget):
|
||||
# # item.setParent(None)
|
||||
# if isinstance(item, QLabel):
|
||||
# if item.text().startswith("Lot"):
|
||||
# item.setParent(None)
|
||||
# else:
|
||||
# logger.debug(f"Type of {item.objectName()} is {type(item)}")
|
||||
# if item.objectName().startswith("lot_"):
|
||||
# item.setParent(None)
|
||||
# self.kit_integrity_completion()
|
||||
|
||||
|
||||
|
||||
def kit_integrity_completion(self):
|
||||
"""
|
||||
@@ -297,26 +166,6 @@ class App(QMainWindow):
|
||||
"""
|
||||
self, result = kit_integrity_completion_function(self)
|
||||
self.result_reporter(result)
|
||||
# logger.debug(inspect.currentframe().f_back.f_code.co_name)
|
||||
# kit_widget = self.table_widget.formlayout.parentWidget().findChild(QComboBox, 'extraction_kit')
|
||||
# logger.debug(f"Kit selector: {kit_widget}")
|
||||
# self.ext_kit = kit_widget.currentText()
|
||||
# logger.debug(f"Checking integrity of {self.ext_kit}")
|
||||
# kit = lookup_kittype_by_name(ctx=self.ctx, name=self.ext_kit)
|
||||
# reagents_to_lookup = [item.replace("lot_", "") for item in self.reagents]
|
||||
# logger.debug(f"Reagents for lookup for {kit.name}: {reagents_to_lookup}")
|
||||
# kit_integrity = check_kit_integrity(kit, reagents_to_lookup)
|
||||
# if kit_integrity != None:
|
||||
# msg = AlertPop(message=kit_integrity['message'], status="critical")
|
||||
# msg.exec()
|
||||
# for item in kit_integrity['missing']:
|
||||
# self.table_widget.formlayout.addWidget(QLabel(f"Lot {item.replace('_', ' ').title()}"))
|
||||
# add_widget = ImportReagent(ctx=self.ctx, item=item)
|
||||
# self.table_widget.formlayout.addWidget(add_widget)
|
||||
# submit_btn = QPushButton("Submit")
|
||||
# submit_btn.setObjectName("lot_submit_btn")
|
||||
# self.table_widget.formlayout.addWidget(submit_btn)
|
||||
# submit_btn.clicked.connect(self.submit_new_sample)
|
||||
|
||||
|
||||
def submit_new_sample(self):
|
||||
|
||||
@@ -3,6 +3,8 @@ Contains widgets specific to the submission summary and submission details.
|
||||
'''
|
||||
import base64
|
||||
from datetime import datetime
|
||||
from io import BytesIO
|
||||
import math
|
||||
from PyQt6 import QtPrintSupport
|
||||
from PyQt6.QtWidgets import (
|
||||
QVBoxLayout, QDialog, QTableView,
|
||||
@@ -10,16 +12,18 @@ from PyQt6.QtWidgets import (
|
||||
QMessageBox, QFileDialog, QMenu, QLabel,
|
||||
QDialogButtonBox, QToolBar, QMainWindow
|
||||
)
|
||||
from PyQt6.QtCore import Qt, QAbstractTableModel, QSortFilterProxyModel
|
||||
from PyQt6.QtCore import Qt, QAbstractTableModel, QSortFilterProxyModel, QItemSelectionModel
|
||||
from PyQt6.QtGui import QFontMetrics, QAction, QCursor, QPixmap, QPainter
|
||||
from backend.db import submissions_to_df, lookup_submission_by_id, delete_submission_by_id, lookup_submission_by_rsl_num
|
||||
from backend.db import submissions_to_df, lookup_submission_by_id, delete_submission_by_id, lookup_submission_by_rsl_num, hitpick_plate
|
||||
# from backend.misc import hitpick_plate
|
||||
from backend.excel import make_hitpicks
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
from xhtml2pdf import pisa
|
||||
import sys
|
||||
from pathlib import Path
|
||||
import logging
|
||||
from .pop_ups import QuestionAsker
|
||||
from ..visualizations import make_plate_barcode
|
||||
from .pop_ups import QuestionAsker, AlertPop
|
||||
from ..visualizations import make_plate_barcode, make_plate_map
|
||||
from getpass import getuser
|
||||
|
||||
logger = logging.getLogger(f"submissions.{__name__}")
|
||||
@@ -92,6 +96,7 @@ class SubmissionsSheet(QTableView):
|
||||
self.resizeColumnsToContents()
|
||||
self.resizeRowsToContents()
|
||||
self.setSortingEnabled(True)
|
||||
|
||||
self.doubleClicked.connect(self.show_details)
|
||||
|
||||
def setData(self) -> None:
|
||||
@@ -111,10 +116,8 @@ class SubmissionsSheet(QTableView):
|
||||
pass
|
||||
proxyModel = QSortFilterProxyModel()
|
||||
proxyModel.setSourceModel(pandasModel(self.data))
|
||||
# self.model = pandasModel(self.data)
|
||||
# self.setModel(self.model)
|
||||
self.setModel(proxyModel)
|
||||
# self.resize(800,600)
|
||||
|
||||
|
||||
def show_details(self) -> None:
|
||||
"""
|
||||
@@ -124,7 +127,7 @@ class SubmissionsSheet(QTableView):
|
||||
value = index.sibling(index.row(),0).data()
|
||||
dlg = SubmissionDetails(ctx=self.ctx, id=value)
|
||||
if dlg.exec():
|
||||
pass
|
||||
pass
|
||||
|
||||
def create_barcode(self) -> None:
|
||||
index = (self.selectionModel().currentIndex())
|
||||
@@ -155,14 +158,17 @@ class SubmissionsSheet(QTableView):
|
||||
detailsAction = QAction('Details', self)
|
||||
barcodeAction = QAction("Print Barcode", self)
|
||||
commentAction = QAction("Add Comment", self)
|
||||
hitpickAction = QAction("Hitpicks", self)
|
||||
renameAction.triggered.connect(lambda: self.delete_item(event))
|
||||
detailsAction.triggered.connect(lambda: self.show_details())
|
||||
barcodeAction.triggered.connect(lambda: self.create_barcode())
|
||||
commentAction.triggered.connect(lambda: self.add_comment())
|
||||
hitpickAction.triggered.connect(lambda: self.hit_pick())
|
||||
self.menu.addAction(detailsAction)
|
||||
self.menu.addAction(renameAction)
|
||||
self.menu.addAction(barcodeAction)
|
||||
self.menu.addAction(commentAction)
|
||||
self.menu.addAction(hitpickAction)
|
||||
# add other required actions
|
||||
self.menu.popup(QCursor.pos())
|
||||
|
||||
@@ -185,7 +191,63 @@ class SubmissionsSheet(QTableView):
|
||||
self.setData()
|
||||
|
||||
|
||||
|
||||
def hit_pick(self):
|
||||
"""
|
||||
Extract positive samples from submissions with PCR results and export to csv.
|
||||
NOTE: For this to work for arbitrary samples, positive samples must have 'positive' in their name
|
||||
"""
|
||||
# Get all selected rows
|
||||
indices = self.selectionModel().selectedIndexes()
|
||||
# convert to id numbers
|
||||
indices = [index.sibling(index.row(), 0).data() for index in indices]
|
||||
# biomek can handle 4 plates maximum
|
||||
if len(indices) > 4:
|
||||
logger.error(f"Error: Had to truncate number of plates to 4.")
|
||||
indices = indices[:4]
|
||||
# lookup ids in the database
|
||||
subs = [lookup_submission_by_id(self.ctx, id) for id in indices]
|
||||
# full list of samples
|
||||
dicto = []
|
||||
# list to contain plate images
|
||||
images = []
|
||||
for iii, sub in enumerate(subs):
|
||||
# second check to make sure there aren't too many plates
|
||||
if iii > 3:
|
||||
logger.error(f"Error: Had to truncate number of plates to 4.")
|
||||
continue
|
||||
plate_dicto = hitpick_plate(submission=sub, plate_number=iii+1)
|
||||
if plate_dicto == None:
|
||||
continue
|
||||
image = make_plate_map(plate_dicto)
|
||||
images.append(image)
|
||||
for item in plate_dicto:
|
||||
if len(dicto) < 94:
|
||||
dicto.append(item)
|
||||
else:
|
||||
logger.error(f"We had to truncate the number of samples to 94.")
|
||||
logger.debug(f"We found {len(dicto)} to hitpick")
|
||||
msg = AlertPop(message=f"We found {len(dicto)} samples to hitpick", status="INFORMATION")
|
||||
msg.exec()
|
||||
# convert all samples to dataframe
|
||||
df = make_hitpicks(dicto)
|
||||
logger.debug(f"Size of the dataframe: {df.size}")
|
||||
if df.size == 0:
|
||||
return
|
||||
date = datetime.strftime(datetime.today(), "%Y-%m-%d")
|
||||
# ask for filename and save as csv.
|
||||
home_dir = Path(self.ctx["directory_path"]).joinpath(f"Hitpicks_{date}.csv").resolve().__str__()
|
||||
fname = Path(QFileDialog.getSaveFileName(self, "Save File", home_dir, filter=".csv")[0])
|
||||
if fname.__str__() == ".":
|
||||
logger.debug("Saving csv was cancelled.")
|
||||
return
|
||||
df.to_csv(fname.__str__(), index=False)
|
||||
# show plate maps
|
||||
for image in images:
|
||||
try:
|
||||
image.show()
|
||||
except Exception as e:
|
||||
logger.error(f"Could not show image: {e}.")
|
||||
|
||||
|
||||
class SubmissionDetails(QDialog):
|
||||
"""
|
||||
@@ -239,7 +301,17 @@ class SubmissionDetails(QDialog):
|
||||
Renders submission to html, then creates and saves .pdf file to user selected file.
|
||||
"""
|
||||
template = env.get_template("submission_details.html")
|
||||
# make barcode because, reasons
|
||||
self.base_dict['barcode'] = base64.b64encode(make_plate_barcode(self.base_dict['Plate Number'], width=120, height=30)).decode('utf-8')
|
||||
sub = lookup_submission_by_rsl_num(ctx=self.ctx, rsl_num=self.base_dict['Plate Number'])
|
||||
plate_dicto = hitpick_plate(sub)
|
||||
platemap = make_plate_map(plate_dicto)
|
||||
logger.debug(f"platemap: {platemap}")
|
||||
image_io = BytesIO()
|
||||
platemap.save(image_io, 'JPEG')
|
||||
platemap.save("test.jpg", 'JPEG')
|
||||
self.base_dict['platemap'] = base64.b64encode(image_io.getvalue()).decode('utf-8')
|
||||
logger.debug(self.base_dict)
|
||||
html = template.render(sub=self.base_dict)
|
||||
home_dir = Path(self.ctx["directory_path"]).joinpath(f"Submission_Details_{self.base_dict['Plate Number']}.pdf").resolve().__str__()
|
||||
fname = Path(QFileDialog.getSaveFileName(self, "Save File", home_dir, filter=".pdf")[0])
|
||||
@@ -269,18 +341,10 @@ class BarcodeWindow(QDialog):
|
||||
# creating label
|
||||
self.label = QLabel()
|
||||
self.img = make_plate_barcode(rsl_num)
|
||||
# logger.debug(dir(img), img.contents[0])
|
||||
# fp = BytesIO().read()
|
||||
# img.save(formats=['png'], fnRoot=fp)
|
||||
# pixmap = QPixmap("C:\\Users\\lwark\\Documents\\python\\submissions\\src\\Drawing000.png")
|
||||
self.pixmap = QPixmap()
|
||||
# self.pixmap.loadFromData(self.img.asString("bmp"))
|
||||
self.pixmap.loadFromData(self.img)
|
||||
# adding image to label
|
||||
self.label.setPixmap(self.pixmap)
|
||||
# Optional, resize label to image size
|
||||
# self.label.resize(self.pixmap.width(), self.pixmap.height())
|
||||
# self.label.resize(200, 200)
|
||||
# show all the widgets]
|
||||
QBtn = QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
|
||||
self.buttonBox = QDialogButtonBox(QBtn)
|
||||
@@ -300,8 +364,6 @@ class BarcodeWindow(QDialog):
|
||||
adds items to menu bar
|
||||
"""
|
||||
toolbar = QToolBar("My main toolbar")
|
||||
# self.addToolBar(toolbar)
|
||||
# self.layout.setToolBar(toolbar)
|
||||
toolbar.addAction(self.printAction)
|
||||
|
||||
|
||||
@@ -321,7 +383,6 @@ class BarcodeWindow(QDialog):
|
||||
|
||||
def print_barcode(self):
|
||||
printer = QtPrintSupport.QPrinter()
|
||||
|
||||
dialog = QtPrintSupport.QPrintDialog(printer)
|
||||
if dialog.exec():
|
||||
self.handle_paint_request(printer, self.pixmap.toImage())
|
||||
|
||||
@@ -41,15 +41,11 @@ logger = logging.getLogger(f"submissions.{__name__}")
|
||||
|
||||
def import_submission_function(obj:QMainWindow) -> Tuple[QMainWindow, dict|None]:
|
||||
result = None
|
||||
# from .custom_widgets.misc import ImportReagent
|
||||
# from .custom_widgets.pop_ups import AlertPop
|
||||
logger.debug(obj.ctx)
|
||||
# initialize samples
|
||||
obj.samples = []
|
||||
obj.reagents = {}
|
||||
# set file dialog
|
||||
# home_dir = str(Path(obj.ctx["directory_path"]))
|
||||
# fname = Path(QFileDialog.getOpenFileName(obj, 'Open file', home_dir)[0])
|
||||
fname = select_open_file(obj, extension="xlsx")
|
||||
logger.debug(f"Attempting to parse file: {fname}")
|
||||
if not fname.exists():
|
||||
@@ -69,7 +65,6 @@ def import_submission_function(obj:QMainWindow) -> Tuple[QMainWindow, dict|None]
|
||||
item.setParent(None)
|
||||
# regex to parser out different variable types for decision making
|
||||
variable_parser = re.compile(r"""
|
||||
# (?x)
|
||||
(?P<extraction_kit>^extraction_kit$) |
|
||||
(?P<submitted_date>^submitted_date$) |
|
||||
(?P<submitting_lab>)^submitting_lab$ |
|
||||
@@ -161,13 +156,11 @@ def import_submission_function(obj:QMainWindow) -> Tuple[QMainWindow, dict|None]
|
||||
if hasattr(obj, 'ext_kit'):
|
||||
obj.kit_integrity_completion()
|
||||
logger.debug(f"Imported reagents: {obj.reagents}")
|
||||
|
||||
return obj, result
|
||||
|
||||
def kit_reload_function(obj:QMainWindow) -> QMainWindow:
|
||||
result = None
|
||||
for item in obj.table_widget.formlayout.parentWidget().findChildren(QWidget):
|
||||
# item.setParent(None)
|
||||
if isinstance(item, QLabel):
|
||||
if item.text().startswith("Lot"):
|
||||
item.setParent(None)
|
||||
@@ -180,20 +173,22 @@ def kit_reload_function(obj:QMainWindow) -> QMainWindow:
|
||||
|
||||
def kit_integrity_completion_function(obj:QMainWindow) -> QMainWindow:
|
||||
result = None
|
||||
# from .custom_widgets.misc import ImportReagent
|
||||
# from .custom_widgets.pop_ups import AlertPop
|
||||
logger.debug(inspect.currentframe().f_back.f_code.co_name)
|
||||
# find the widget that contains lit info
|
||||
kit_widget = obj.table_widget.formlayout.parentWidget().findChild(QComboBox, 'extraction_kit')
|
||||
logger.debug(f"Kit selector: {kit_widget}")
|
||||
# get current kit info
|
||||
obj.ext_kit = kit_widget.currentText()
|
||||
logger.debug(f"Checking integrity of {obj.ext_kit}")
|
||||
# get the kit from database using current kit info
|
||||
kit = lookup_kittype_by_name(ctx=obj.ctx, name=obj.ext_kit)
|
||||
# get all reagents stored in the QWindow object
|
||||
reagents_to_lookup = [item.replace("lot_", "") for item in obj.reagents]
|
||||
logger.debug(f"Reagents for lookup for {kit.name}: {reagents_to_lookup}")
|
||||
# make sure kit contains all necessary info
|
||||
kit_integrity = check_kit_integrity(kit, reagents_to_lookup)
|
||||
# if kit integrity comes back with an error, make widgets with missing reagents using default info
|
||||
if kit_integrity != None:
|
||||
# msg = AlertPop(message=kit_integrity['message'], status="critical")
|
||||
# msg.exec()
|
||||
result = dict(message=kit_integrity['message'], status="Warning")
|
||||
for item in kit_integrity['missing']:
|
||||
obj.table_widget.formlayout.addWidget(QLabel(f"Lot {item.replace('_', ' ').title()}"))
|
||||
@@ -207,9 +202,9 @@ def kit_integrity_completion_function(obj:QMainWindow) -> QMainWindow:
|
||||
|
||||
def submit_new_sample_function(obj:QMainWindow) -> QMainWindow:
|
||||
result = None
|
||||
# from .custom_widgets.misc import ImportReagent
|
||||
# from .custom_widgets.pop_ups import AlertPop, QuestionAsker
|
||||
# extract info from the form widgets
|
||||
info = extract_form_info(obj.table_widget.tab1)
|
||||
# seperate out reagents
|
||||
reagents = {k:v for k,v in info.items() if k.startswith("lot_")}
|
||||
info = {k:v for k,v in info.items() if not k.startswith("lot_")}
|
||||
logger.debug(f"Info: {info}")
|
||||
@@ -268,12 +263,9 @@ def submit_new_sample_function(obj:QMainWindow) -> QMainWindow:
|
||||
# reset form
|
||||
for item in obj.table_widget.formlayout.parentWidget().findChildren(QWidget):
|
||||
item.setParent(None)
|
||||
# print(dir(obj))
|
||||
if hasattr(obj, 'csv'):
|
||||
dlg = QuestionAsker("Export CSV?", "Would you like to export the csv file?")
|
||||
if dlg.exec():
|
||||
# home_dir = Path(obj.ctx["directory_path"]).joinpath(f"{base_submission.rsl_plate_num}.csv").resolve().__str__()
|
||||
# fname = Path(QFileDialog.getSaveFileName(obj, "Save File", home_dir, filter=".csv")[0])
|
||||
fname = select_save_file(obj, f"{base_submission.rsl_plate_num}.csv", extension="csv")
|
||||
try:
|
||||
obj.csv.to_csv(fname.__str__(), index=False)
|
||||
@@ -282,8 +274,8 @@ def submit_new_sample_function(obj:QMainWindow) -> QMainWindow:
|
||||
return obj, result
|
||||
|
||||
def generate_report_function(obj:QMainWindow) -> QMainWindow:
|
||||
# from .custom_widgets import ReportDatePicker
|
||||
result = None
|
||||
# ask for date ranges
|
||||
dlg = ReportDatePicker()
|
||||
if dlg.exec():
|
||||
info = extract_form_info(dlg)
|
||||
@@ -324,8 +316,6 @@ def generate_report_function(obj:QMainWindow) -> QMainWindow:
|
||||
def add_kit_function(obj:QMainWindow) -> QMainWindow:
|
||||
result = None
|
||||
# setup file dialog to find yaml flie
|
||||
# home_dir = str(Path(obj.ctx["directory_path"]))
|
||||
# fname = Path(QFileDialog.getOpenFileName(obj, 'Open file', home_dir, filter = "yml(*.yml)")[0])
|
||||
fname = select_open_file(obj, extension="yml")
|
||||
assert fname.exists()
|
||||
# read yaml file
|
||||
@@ -340,19 +330,11 @@ def add_kit_function(obj:QMainWindow) -> QMainWindow:
|
||||
return
|
||||
# send to kit creator function
|
||||
result = create_kit_from_yaml(ctx=obj.ctx, exp=exp)
|
||||
# match result['code']:
|
||||
# case 0:
|
||||
# msg = AlertPop(message=result['message'], status='info')
|
||||
# case 1:
|
||||
# msg = AlertPop(message=result['message'], status='critical')
|
||||
# msg.exec()
|
||||
return obj, result
|
||||
|
||||
def add_org_function(obj:QMainWindow) -> QMainWindow:
|
||||
result = None
|
||||
# setup file dialog to find yaml flie
|
||||
# home_dir = str(Path(obj.ctx["directory_path"]))
|
||||
# fname = Path(QFileDialog.getOpenFileName(obj, 'Open file', home_dir, filter = "yml(*.yml)")[0])
|
||||
fname = select_open_file(obj, extension="yml")
|
||||
assert fname.exists()
|
||||
# read yaml file
|
||||
@@ -367,12 +349,6 @@ def add_org_function(obj:QMainWindow) -> QMainWindow:
|
||||
return obj, result
|
||||
# send to kit creator function
|
||||
result = create_org_from_yaml(ctx=obj.ctx, org=org)
|
||||
# match result['code']:
|
||||
# case 0:
|
||||
# msg = AlertPop(message=result['message'], status='information')
|
||||
# case 1:
|
||||
# msg = AlertPop(message=result['message'], status='critical')
|
||||
# msg.exec()
|
||||
return obj, result
|
||||
|
||||
def controls_getter_function(obj:QMainWindow) -> QMainWindow:
|
||||
@@ -391,7 +367,7 @@ def controls_getter_function(obj:QMainWindow) -> QMainWindow:
|
||||
obj.table_widget.datepicker.start_date.setDate(threemonthsago)
|
||||
obj._controls_getter()
|
||||
return obj, result
|
||||
# convert to python useable date object
|
||||
# convert to python useable date object
|
||||
obj.start_date = obj.table_widget.datepicker.start_date.date().toPyDate()
|
||||
obj.end_date = obj.table_widget.datepicker.end_date.date().toPyDate()
|
||||
obj.con_type = obj.table_widget.control_typer.currentText()
|
||||
@@ -412,8 +388,18 @@ def controls_getter_function(obj:QMainWindow) -> QMainWindow:
|
||||
return obj, result
|
||||
|
||||
def chart_maker_function(obj:QMainWindow) -> QMainWindow:
|
||||
"""
|
||||
create html chart for controls reporting
|
||||
|
||||
Args:
|
||||
obj (QMainWindow): original MainWindow
|
||||
|
||||
Returns:
|
||||
QMainWindow: MainWindow with control display updates
|
||||
"""
|
||||
result = None
|
||||
logger.debug(f"Control getter context: \n\tControl type: {obj.con_type}\n\tMode: {obj.mode}\n\tStart Date: {obj.start_date}\n\tEnd Date: {obj.end_date}")
|
||||
# set the subtype for kraken
|
||||
if obj.table_widget.sub_typer.currentText() == "":
|
||||
obj.subtype = None
|
||||
else:
|
||||
@@ -425,7 +411,7 @@ def chart_maker_function(obj:QMainWindow) -> QMainWindow:
|
||||
if controls == None:
|
||||
fig = None
|
||||
else:
|
||||
# change each control to list of dicts
|
||||
# change each control to list of dictionaries
|
||||
data = [control.convert_by_mode(mode=obj.mode) for control in controls]
|
||||
# flatten data to one dimensional list
|
||||
data = [item for sublist in data for item in sublist]
|
||||
@@ -646,7 +632,8 @@ def import_pcr_results_function(obj:QMainWindow) -> QMainWindow:
|
||||
logger.debug(f"Existing {type(sub.pcr_info)}: {sub.pcr_info}")
|
||||
logger.debug(f"Inserting {type(json.dumps(parser.pcr))}: {json.dumps(parser.pcr)}")
|
||||
obj.ctx["database_session"].commit()
|
||||
logger.debug(f"Got {len(parser.samples)} to update!")
|
||||
logger.debug(f"Got {len(parser.samples)} samples to update!")
|
||||
logger.debug(f"Parser samples: {parser.samples}")
|
||||
for sample in parser.samples:
|
||||
logger.debug(f"Running update on: {sample['sample']}")
|
||||
sample['plate_rsl'] = sub.rsl_plate_num
|
||||
@@ -655,11 +642,3 @@ def import_pcr_results_function(obj:QMainWindow) -> QMainWindow:
|
||||
return obj, result
|
||||
# dlg.exec()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -2,4 +2,5 @@
|
||||
Contains all operations for creating charts, graphs and visual effects.
|
||||
'''
|
||||
from .control_charts import *
|
||||
from .barcode import *
|
||||
from .barcode import *
|
||||
from .plate_map import *
|
||||
80
src/submissions/frontend/visualizations/plate_map.py
Normal file
80
src/submissions/frontend/visualizations/plate_map.py
Normal file
@@ -0,0 +1,80 @@
|
||||
from pathlib import Path
|
||||
import sys
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
import numpy as np
|
||||
from tools import check_if_app
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(f"submissions.{__name__}")
|
||||
|
||||
def make_plate_map(sample_list:list) -> Image:
|
||||
"""
|
||||
Makes a pillow image of a plate from hitpicks
|
||||
|
||||
Args:
|
||||
sample_list (list): list of positive sample dictionaries from the hitpicks
|
||||
|
||||
Returns:
|
||||
Image: Image of the 96 well plate with positive samples in red.
|
||||
"""
|
||||
# If we can't get a plate number, do nothing
|
||||
try:
|
||||
plate_num = sample_list[0]['plate_name']
|
||||
except IndexError as e:
|
||||
logger.error(f"Couldn't get a plate number. Will not make plate.")
|
||||
return None
|
||||
except TypeError as e:
|
||||
logger.error(f"No samples for this plate. Nothing to do.")
|
||||
return None
|
||||
# Make a 8 row, 12 column, 3 color ints array, filled with white by default
|
||||
grid = np.full((8,12,3),255, dtype=np.uint8)
|
||||
# Go through samples and change its row/column to red
|
||||
for sample in sample_list:
|
||||
grid[int(sample['row'])-1][int(sample['column'])-1] = [255,0,0]
|
||||
# Create image from the grid
|
||||
img = Image.fromarray(grid).resize((1200, 800), resample=Image.NEAREST)
|
||||
# create a drawer over the image
|
||||
draw = ImageDraw.Draw(img)
|
||||
# draw grid over the image
|
||||
y_start = 0
|
||||
y_end = img.height
|
||||
step_size = int(img.width / 12)
|
||||
for x in range(0, img.width, step_size):
|
||||
line = ((x, y_start), (x, y_end))
|
||||
draw.line(line, fill=128)
|
||||
x_start = 0
|
||||
x_end = img.width
|
||||
step_size = int(img.height / 8)
|
||||
for y in range(0, img.height, step_size):
|
||||
line = ((x_start, y), (x_end, y))
|
||||
draw.line(line, fill=128)
|
||||
del draw
|
||||
old_size = img.size
|
||||
new_size = (1300, 900)
|
||||
# create a new, larger white image to hold the annotations
|
||||
new_img = Image.new("RGB", new_size, "White")
|
||||
box = tuple((n - o) // 2 for n, o in zip(new_size, old_size))
|
||||
# paste plate map into the new image
|
||||
new_img.paste(img, box)
|
||||
# create drawer over the new image
|
||||
draw = ImageDraw.Draw(new_img)
|
||||
# font = ImageFont.truetype("sans-serif.ttf", 16)
|
||||
if check_if_app():
|
||||
font_path = Path(sys._MEIPASS).joinpath("files", "resources")
|
||||
else:
|
||||
font_path = Path(__file__).parents[2].joinpath('resources').absolute()
|
||||
logger.debug(f"Font path: {font_path}")
|
||||
font = ImageFont.truetype(font_path.joinpath('arial.ttf').__str__(), 32)
|
||||
row_dict = ["A", "B", "C", "D", "E", "F", "G", "H"]
|
||||
# write the plate number on the image
|
||||
draw.text((100, 850),plate_num,(0,0,0),font=font)
|
||||
# write column numbers
|
||||
for num in range(1,13):
|
||||
x = (num * 100) - 10
|
||||
draw.text((x, 0), str(num), (0,0,0),font=font)
|
||||
# write row letters
|
||||
for num in range(1,9):
|
||||
letter = row_dict[num-1]
|
||||
y = (num * 100) - 10
|
||||
draw.text((10, y), letter, (0,0,0),font=font)
|
||||
return new_img
|
||||
Reference in New Issue
Block a user