Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Plate_reader #2

Merged
merged 31 commits into from
Jan 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
f266683
Added i-control XML parsing to plate_reader module.
mark-polk Oct 24, 2024
8a8c8e2
Test commit
mark-polk Oct 24, 2024
c6da4f8
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Oct 24, 2024
6a5d884
Changed from | operator to Optional to fix Python 3.9 failure
mark-polk Oct 24, 2024
ab32e9c
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Oct 24, 2024
65e08a1
Redid IControlXML parsing and removed templating of read_file from ABC.
mark-polk Oct 25, 2024
9086589
Merging
mark-polk Oct 25, 2024
f19f950
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Oct 25, 2024
012683d
Implemented BeautifulSoup4 for XML parsing.
mark-polk Nov 29, 2024
612d6c2
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 29, 2024
4ab4b94
Switched to base Python NaN to remove NumPy dependency.
mark-polk Nov 29, 2024
6632193
Merge branch 'plate_reader' of https://github.com/choderalab/fluoresc…
mark-polk Nov 29, 2024
3397dda
Actually removed NumPy dependency this time.
mark-polk Nov 29, 2024
30e4b43
Added method to get parameter from IControlXML.
mark-polk Nov 29, 2024
0bbe99e
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 29, 2024
487ebb1
Fixed get_parameter.
mark-polk Nov 29, 2024
0161c63
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 29, 2024
958869d
Started plotting module.
mark-polk Nov 29, 2024
5d9ebee
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 29, 2024
5a0fe45
Added placeholder for docstring to IControlXML.
mark-polk Nov 29, 2024
465a49d
Started plotting module
mark-polk Nov 29, 2024
9b64b30
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 29, 2024
3c2f0f2
Added methods to plot corrected spectrum and dose response.
mark-polk Nov 29, 2024
5fd3b36
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 29, 2024
23834f5
Added title to plot formatting.
mark-polk Nov 30, 2024
893c032
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 30, 2024
3fa52cd
Quick implementation of absorption measurement.
mark-polk Dec 3, 2024
18052a0
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Dec 3, 2024
d7de4b4
Temporary fix: hardcoding values
mark-polk Dec 4, 2024
ea62ba4
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Dec 4, 2024
988316a
Fixed hardcoding
mark-polk Dec 4, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 80 additions & 1 deletion fluorescence_assay/plate_reader.py
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"]
)
191 changes: 191 additions & 0 deletions fluorescence_assay/plotting.py
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)
Loading