Plate map for 24 well WW plate.

This commit is contained in:
lwark
2024-06-17 13:13:04 -05:00
parent 00f21abac7
commit 12e552800a
4 changed files with 127 additions and 48 deletions

View File

@@ -1,3 +1,8 @@
## 202406.04
- Adding in tips to Equipment usage.
- New WastewaterArticAssociation will track previously missed sample info.
## 202406.02
- Attached Contact to Submission.

View File

@@ -8,9 +8,9 @@ from getpass import getuser
import logging, uuid, tempfile, re, yaml, base64
from zipfile import ZipFile
from tempfile import TemporaryDirectory
from operator import attrgetter, itemgetter
from operator import itemgetter
from pprint import pformat
from . import BaseClass, Reagent, SubmissionType, KitType, Organization, Contact, Tips, TipRole, SubmissionTipsAssociation
from . import BaseClass, Reagent, SubmissionType, KitType, Organization, Contact, Tips
from sqlalchemy import Column, String, TIMESTAMP, INTEGER, ForeignKey, JSON, FLOAT, case
from sqlalchemy.orm import relationship, validates, Query
from sqlalchemy.orm.attributes import flag_modified
@@ -19,14 +19,14 @@ from sqlalchemy.exc import OperationalError as AlcOperationalError, IntegrityErr
ArgumentError
from sqlite3 import OperationalError as SQLOperationalError, IntegrityError as SQLIntegrityError
import pandas as pd
from openpyxl import Workbook, load_workbook
from openpyxl import Workbook
from openpyxl.worksheet.worksheet import Worksheet
from openpyxl.drawing.image import Image as OpenpyxlImage
from tools import check_not_nan, row_map, setup_lookup, jinja_template_loading, rreplace
from tools import row_map, setup_lookup, jinja_template_loading, rreplace, row_keys
from datetime import datetime, date
from typing import List, Any, Tuple, Literal
from dateutil.parser import parse
from dateutil.parser import ParserError
# from dateutil.parser import ParserError
from pathlib import Path
from jinja2.exceptions import TemplateNotFound
from jinja2 import Template
@@ -225,6 +225,11 @@ class BasicSubmission(BaseClass):
"""
return cls.get_submission_type().construct_sample_map()
@classmethod
def finalize_details(cls, input_dict: dict) -> dict:
del input_dict['id']
return input_dict
def to_dict(self, full_data: bool = False, backup: bool = False, report: bool = False) -> dict:
"""
Constructs dictionary used in submissions summary
@@ -317,7 +322,7 @@ class BasicSubmission(BaseClass):
try:
contact = self.contact.name
except AttributeError as e:
logger.error(f"Problem setting contact: {e}")
# logger.error(f"Problem setting contact: {e}")
contact = "NA"
try:
contact_phone = self.contact.phone
@@ -387,7 +392,8 @@ class BasicSubmission(BaseClass):
output_list = [assoc.to_hitpick() for assoc in self.submission_sample_associations]
return output_list
def make_plate_map(self, plate_rows: int = 8, plate_columns=12) -> str:
@classmethod
def make_plate_map(cls, sample_list: list, plate_rows: int = 8, plate_columns=12) -> str:
"""
Constructs an html based plate map for submission details.
@@ -399,17 +405,6 @@ class BasicSubmission(BaseClass):
Returns:
str: html output string.
"""
# logger.debug("Creating basic hitpick")
sample_list = self.hitpick_plate()
# logger.debug("Setting background colours")
for sample in sample_list:
if sample['positive']:
sample['background_color'] = "#f10f07"
else:
if "colour" in sample.keys():
sample['background_color'] = "#69d84f"
else:
sample['background_color'] = "#80cbc4"
output_samples = []
# logger.debug("Setting locations.")
for column in range(1, plate_columns + 1):
@@ -434,7 +429,8 @@ class BasicSubmission(BaseClass):
return [item.role for item in self.submission_equipment_associations]
@classmethod
def submissions_to_df(cls, submission_type: str | None = None, limit: int = 0, chronologic:bool=True) -> pd.DataFrame:
def submissions_to_df(cls, submission_type: str | None = None, limit: int = 0,
chronologic: bool = True) -> pd.DataFrame:
"""
Convert all submissions to dataframe
@@ -448,7 +444,8 @@ class BasicSubmission(BaseClass):
# logger.debug(f"Querying Type: {submission_type}")
# logger.debug(f"Using limit: {limit}")
# use lookup function to create list of dicts
subs = [item.to_dict() for item in cls.query(submission_type=submission_type, limit=limit, chronologic=chronologic)]
subs = [item.to_dict() for item in
cls.query(submission_type=submission_type, limit=limit, chronologic=chronologic)]
# logger.debug(f"Got {len(subs)} submissions.")
df = pd.DataFrame.from_records(subs)
# logger.debug(f"Column names: {df.columns}")
@@ -456,7 +453,8 @@ class BasicSubmission(BaseClass):
excluded = ['controls', 'extraction_info', 'pcr_info', 'comment', 'comments', 'samples', 'reagents',
'equipment', 'gel_info', 'gel_image', 'dna_core_submission_number', 'gel_controls',
'source_plates', 'pcr_technician', 'ext_technician', 'artic_technician', 'cost_centre',
'signed_by', 'artic_date', 'gel_barcode', 'gel_date', 'ngs_date', 'contact_phone', 'contact', 'tips']
'signed_by', 'artic_date', 'gel_barcode', 'gel_date', 'ngs_date', 'contact_phone', 'contact',
'tips']
for item in excluded:
try:
df = df.drop(item, axis=1)
@@ -1163,6 +1161,7 @@ class BasicSubmission(BaseClass):
writer = pyd.to_writer()
writer.xl.save(filename=fname.with_suffix(".xlsx"))
# Below are the custom submission types
class BacterialCulture(BasicSubmission):
@@ -1397,6 +1396,39 @@ class Wastewater(BasicSubmission):
row = idx.index.to_list()[0]
return row + 1
@classmethod
def get_details_template(cls, base_dict: dict) -> Tuple[dict, Template]:
"""
Get the details jinja template for the correct class. Extends parent
Args:
base_dict (dict): incoming dictionary of Submission fields
Returns:
Tuple[dict, Template]: (Updated dictionary, Template to be rendered)
"""
base_dict, template = super().get_details_template(base_dict=base_dict)
base_dict['excluded'] += ['origin_plate']
return base_dict, template
@classmethod
def finalize_details(cls, input_dict: dict) -> dict:
input_dict = super().finalize_details(input_dict)
dummy_samples = []
for item in input_dict['samples']:
# logger.debug(f"Sample dict: {item}")
thing = item
try:
thing['row'] = thing['source_row']
thing['column'] = thing['source_column']
except KeyError:
logger.error(f"No row or column for sample: {item['submitter_id']}")
continue
thing['tooltip'] = f"Sample Name: {thing['name']}\nWell: {thing['sample_location']}"
dummy_samples.append(thing)
input_dict['origin_plate'] = cls.make_plate_map(sample_list=dummy_samples, plate_rows=4, plate_columns=6)
return input_dict
def custom_context_events(self) -> dict:
events = super().custom_context_events()
events['Link PCR'] = self.link_pcr
@@ -1479,7 +1511,6 @@ class WastewaterArtic(BasicSubmission):
dict: Updated sample dictionary
"""
input_dict = super().custom_info_parser(input_dict)
# workbook = load_workbook(xl.io, data_only=True)
ws = xl['Egel results']
data = [ws.cell(row=ii, column=jj) for jj in range(15, 27) for ii in range(10, 18)]
data = [cell for cell in data if cell.value is not None and "NTC" in cell.value]
@@ -2130,7 +2161,6 @@ class BasicSample(BaseClass):
def get_searchables(cls):
return [dict(label="Submitter ID", field="submitter_id")]
@classmethod
def samples_to_df(cls, sample_type: str | None | BasicSample = None, **kwargs):
# def samples_to_df(cls, sample_type:str|None|BasicSample=None, searchables:dict={}):
@@ -2178,6 +2208,7 @@ class BasicSample(BaseClass):
if dlg.exec():
pass
# Below are the custom sample types
class WastewaterSample(BasicSample):
@@ -2309,6 +2340,7 @@ class BacterialCultureSample(BasicSample):
# logger.debug(f"Done converting to {self} to dict after {time()-start}")
return sample
# Submission to Sample Associations
class SubmissionSampleAssociation(BaseClass):
@@ -2405,11 +2437,19 @@ class SubmissionSampleAssociation(BaseClass):
env = jinja_template_loading()
template = env.get_template("tooltip.html")
tooltip_text = template.render(fields=sample)
try:
control = self.sample.control
except AttributeError:
control = None
if control is not None:
background = "rgb(128, 203, 196)"
else:
background = "rgb(105, 216, 79)"
try:
tooltip_text += sample['tooltip']
except KeyError:
pass
sample.update(dict(Name=self.sample.submitter_id[:10], tooltip=tooltip_text))
sample.update(dict(Name=self.sample.submitter_id[:10], tooltip=tooltip_text, background_color=background))
return sample
@classmethod
@@ -2601,6 +2641,11 @@ class WastewaterAssociation(SubmissionSampleAssociation):
sample = super().to_sub_dict()
sample['ct'] = f"({self.ct_n1}, {self.ct_n2})"
try:
sample['source_row'] = row_keys[self.sample.sample_location[0]]
sample['source_column'] = int(self.sample.sample_location[1:])
except (TypeError, AttributeError) as e:
logger.error(f"Couldn't set sources for {self.sample.rsl_number}. Looks like there isn't data.")
try:
sample['positive'] = any(["positive" in item for item in [self.n1_status, self.n2_status]])
except (TypeError, AttributeError) as e:
@@ -2615,6 +2660,18 @@ class WastewaterAssociation(SubmissionSampleAssociation):
dict: dictionary of sample id, row and column in elution plate
"""
sample = super().to_hitpick()
try:
scaler = max([self.ct_n1, self.ct_n2])
except TypeError:
scaler = 0.0
if scaler == 0.0:
scaler = 45
bg = (45 - scaler) * 17
red = min([128 + bg, 255])
grn = max([255 - bg, 0])
blu = 128
rgb = f"rgb({red}, {grn}, {blu})"
sample['background_color'] = rgb
try:
sample[
'tooltip'] += f"<br>- ct N1: {'{:.2f}'.format(self.ct_n1)} ({self.n1_status})<br>- ct N2: {'{:.2f}'.format(self.ct_n2)} ({self.n2_status})"
@@ -2682,5 +2739,3 @@ class WastewaterArticAssociation(SubmissionSampleAssociation):
except ValueError as e:
logger.error(f"Problem incrementing id: {e}")
return 1

View File

@@ -86,10 +86,11 @@ class SubmissionDetails(QDialog):
self.base_dict = submission.to_dict(full_data=True)
# logger.debug(f"Submission details data:\n{pformat({k:v for k,v in self.base_dict.items() if k != 'samples'})}")
# NOTE: don't want id
del self.base_dict['id']
self.base_dict = submission.finalize_details(self.base_dict)
# del self.base_dict['id']
# logger.debug(f"Creating barcode.")
# logger.debug(f"Making platemap...")
self.base_dict['platemap'] = submission.make_plate_map()
self.base_dict['platemap'] = BasicSubmission.make_plate_map(sample_list=submission.hitpick_plate())
self.base_dict, self.template = submission.get_details_template(base_dict=self.base_dict)
self.html = self.template.render(sub=self.base_dict, signing_permission=is_power_user())
self.webview.setHtml(self.html)

View File

@@ -0,0 +1,18 @@
{% extends "basicsubmission_details.html" %}
<head>
{% block head %}
{{ super() }}
{% endblock %}
</head>
<body>
{% block body %}
{{ super() }}
{% if sub['origin_plate'] %}
<br/>
<h3><u>24 Well Plate:</u></h3>
{{ sub['origin_plate'] }}
{% endif %}
{% endblock %}
</body>