-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2 from choderalab/plate_reader
Plate_reader
- Loading branch information
Showing
2 changed files
with
271 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,80 @@ | ||
"""Comment.""" | ||
"""Module to parse plate reader outputs.""" | ||
|
||
import logging | ||
from abc import ABC, abstractmethod | ||
from dataclasses import dataclass, field | ||
from typing import Optional | ||
|
||
from bs4 import BeautifulSoup | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
@dataclass | ||
class Measurements(ABC): | ||
"""""" | ||
|
||
@abstractmethod | ||
def read_file(self, filepath: str, *args, **kwargs) -> None: | ||
"""""" | ||
... | ||
|
||
@abstractmethod | ||
def get_well(self, *args, **kwargs) -> dict: | ||
"""""" | ||
... | ||
|
||
@abstractmethod | ||
def get_parameter(self, *args, **kwargs): | ||
"""""" | ||
... | ||
|
||
|
||
@dataclass | ||
class IControlXML(Measurements): | ||
"""""" | ||
|
||
_data: dict = field(default_factory=dict, init=False) | ||
|
||
def read_file(self, filepath: str, filter: Optional[list[str]] = None) -> None: | ||
"""""" | ||
|
||
with open(filepath) as file: | ||
self._data = BeautifulSoup(file, "xml") | ||
|
||
def get_data(self): | ||
"""""" | ||
|
||
return self._data | ||
|
||
def get_well(self, section, well, cycle: Optional[int] = 1): | ||
"""""" | ||
|
||
def fix_type(val): | ||
try: | ||
return float(val) | ||
except: | ||
return float("nan") | ||
|
||
data = { | ||
int(scan["WL"]): fix_type(scan.contents[0]) | ||
for scan in self._data.select( | ||
f'Section[Name="{section}"] > Data[Cycle="{str(cycle)}"] > Well[Pos="{well}"] > Scan' | ||
) | ||
} | ||
|
||
return data | ||
|
||
def get_parameter(self, section, parameter): | ||
|
||
def fix_type(val): | ||
try: | ||
return float(val) | ||
except: | ||
return val | ||
|
||
return fix_type( | ||
self._data.select( | ||
f'Section[Name="{section}"] > Parameters > Parameter[Name="{parameter}"]' | ||
)[0]["Value"] | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,191 @@ | ||
"""Module to plot parsed plate reader ouptputs.""" | ||
|
||
from abc import ABC, abstractmethod | ||
from dataclasses import dataclass, field | ||
from typing import Optional | ||
|
||
import matplotlib | ||
import matplotlib.axes | ||
import matplotlib.pyplot as plt | ||
import numpy as np | ||
|
||
from .plate_reader import IControlXML, Measurements | ||
|
||
|
||
@dataclass | ||
class Plot(ABC): | ||
"""""" | ||
|
||
@abstractmethod | ||
def load_data(self, Measurement: Measurements, *args, **kwargs) -> None: | ||
"""""" | ||
... | ||
|
||
def format_plot( | ||
self, | ||
axes: matplotlib.axes._axes.Axes, | ||
title: Optional[str] = None, | ||
xlim: Optional[list[float]] = None, | ||
ylim: Optional[list[float]] = None, | ||
xlabel: Optional[str] = None, | ||
ylabel: Optional[str] = None, | ||
square: Optional[bool] = False, | ||
): | ||
|
||
if title is not None: | ||
axes.set_title(title) | ||
|
||
if xlim is not None: | ||
axes.set_xlim(xlim) | ||
|
||
if ylim is not None: | ||
axes.set_ylim(ylim) | ||
|
||
if xlabel is not None: | ||
axes.set_xlabel(xlabel) | ||
|
||
if ylabel is not None: | ||
axes.set_ylabel(ylabel) | ||
|
||
if square == True: | ||
axes.set_box_aspect(1) | ||
|
||
|
||
@dataclass | ||
class IControlXMLPlot(Plot): | ||
"""""" | ||
|
||
_plate_read: dict = field(default_factory=dict, init=False) | ||
|
||
def load_data(self, IControlXML: IControlXML) -> None: | ||
|
||
self._plate_read = IControlXML | ||
|
||
def get_wavelength_axis(self, section) -> np.ndarray: | ||
|
||
lmin, lmax, lstep = ( | ||
self._plate_read.get_parameter(section, parameter) | ||
for parameter in [ | ||
"Emission Wavelength Start", | ||
"Emission Wavelength End", | ||
"Emission Wavelength Step Size", | ||
] | ||
) | ||
|
||
return np.arange(lmin, lmax + lstep, lstep) | ||
|
||
def plot_well_spectrum( | ||
self, | ||
axes: matplotlib.axes._axes.Axes, | ||
section: str, | ||
well: str, | ||
cycle: Optional[int] = 1, | ||
color: Optional[tuple] = (0, 0, 0), | ||
label: Optional[str] = None, | ||
) -> None: | ||
|
||
data = np.array(list(self._plate_read.get_well(section, well, cycle).values())) | ||
|
||
ll = self.get_wavelength_axis(section) | ||
|
||
axes.plot(ll, data, color=color, label=label) | ||
|
||
def plot_corrected_spectrum( | ||
self, | ||
axes: matplotlib.axes._axes.Axes, | ||
section: str, | ||
well_foreground: str, | ||
well_background: str, | ||
cycle: Optional[int] = 1, | ||
color: Optional[tuple] = (0, 0, 0), | ||
label: Optional[str] = None, | ||
) -> None: | ||
|
||
foreground = np.array( | ||
list(self._plate_read.get_well(section, well_foreground, cycle).values()) | ||
) | ||
background = np.array( | ||
list(self._plate_read.get_well(section, well_background, cycle).values()) | ||
) | ||
|
||
difference = foreground - background | ||
|
||
ll = self.get_wavelength_axis(section) | ||
|
||
axes.plot(ll, difference, color=color, label=label) | ||
|
||
def plot_dose_response( | ||
self, | ||
axes: matplotlib.axes._axes.Axes, | ||
section: str, | ||
row_foreground: str, | ||
row_background: str, | ||
wavelength: int, | ||
concentrations: list[float], | ||
cycle: Optional[int] = 1, | ||
color: Optional[tuple] = (0, 0, 0), | ||
label: Optional[str] = None, | ||
) -> None: | ||
|
||
differences = [] | ||
|
||
for i in range(len(concentrations)): | ||
|
||
foreground = self._plate_read.get_well( | ||
section, f"{row_foreground}{i+1}", cycle | ||
)[wavelength] | ||
background = self._plate_read.get_well( | ||
section, f"{row_background}{i+1}", cycle | ||
)[wavelength] | ||
|
||
difference = foreground - background | ||
|
||
differences.append(difference) | ||
|
||
axes.plot(concentrations, differences, ".", color=color, label=label) | ||
|
||
def plot_absorption_spectrum( | ||
self, | ||
axes: matplotlib.axes._axes.Axes, | ||
section: str, | ||
well: str, | ||
cycle: Optional[int] = 1, | ||
color: Optional[tuple] = (0, 0, 0), | ||
label: Optional[str] = None, | ||
) -> None: | ||
|
||
data = np.array(list(self._plate_read.get_well(section, well, cycle).values())) | ||
|
||
lmin, lmax, lstep = ( | ||
# hardcoding these values temporarily | ||
# TODO: Undo this! | ||
240, | ||
800, | ||
5, | ||
) | ||
|
||
ll = np.arange(lmin, lmax + lstep, lstep) | ||
|
||
axes.plot(ll, data, color=color, label=label) | ||
|
||
def plot_absorption_across_row( | ||
self, | ||
axes: matplotlib.axes._axes.Axes, | ||
section: str, | ||
row: str, | ||
wavelength: int, | ||
concentrations: list[float], | ||
cycle: Optional[int] = 1, | ||
color: Optional[tuple] = (0, 0, 0), | ||
label: Optional[str] = None, | ||
) -> None: | ||
|
||
values = [] | ||
|
||
for i in range(len(concentrations)): | ||
|
||
value = self._plate_read.get_well(section, f"{row}{i+1}", cycle)[wavelength] | ||
|
||
values.append(value) | ||
|
||
axes.plot(concentrations, values, ".", color=color, label=label) |