From 63ae074f35559eef2f82c83a938e605c47b23884 Mon Sep 17 00:00:00 2001 From: Mark Polk Date: Sat, 5 Oct 2024 21:41:19 -0400 Subject: [PATCH 01/20] updated pyproject.toml to include packages --- missense_kinase_toolkit/experiments/pyproject.toml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/missense_kinase_toolkit/experiments/pyproject.toml b/missense_kinase_toolkit/experiments/pyproject.toml index a4c1d70..e26c083 100644 --- a/missense_kinase_toolkit/experiments/pyproject.toml +++ b/missense_kinase_toolkit/experiments/pyproject.toml @@ -5,6 +5,11 @@ description = "Tools to facilitate experimental analyses for missense_kinase_too authors = ["Jess White "] license = "MIT" readme = "README.md" +#https://safjan.com/the-importance-of-adding-py-typed-file-to-your-typed-package/ +packages = [ + {include = "missense_kinase_toolkit"}, + {include = "missense_kinase_toolkit/py.typed"} +] [tool.poetry.dependencies] python = "^3.9" From 34004bfaf405b1063b58dee056a9dfe80a3ec1e1 Mon Sep 17 00:00:00 2001 From: Mark Polk Date: Sat, 5 Oct 2024 22:04:40 -0400 Subject: [PATCH 02/20] instantiate Experiment Class --- .../experiments/plate_reader.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py diff --git a/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py b/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py new file mode 100644 index 0000000..2964e3b --- /dev/null +++ b/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py @@ -0,0 +1,15 @@ +class Experiment: + """Class to process Tecan i-control .XML output.""" + + def __init__( + self, + ) -> None: + """Initialize Experiment Class object. + + Parameters + ---------- + + Attributes + ---------- + + """ \ No newline at end of file From b8dce349f529813b0614555af33d11472484f85a Mon Sep 17 00:00:00 2001 From: Mark Polk Date: Sat, 5 Oct 2024 23:30:45 -0400 Subject: [PATCH 03/20] added labels attribute to Experiment Class --- .../experiments/plate_reader.py | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py b/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py index 2964e3b..ec1daa7 100644 --- a/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py +++ b/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py @@ -1,15 +1,34 @@ +import xml.etree.ElementTree as ET + + class Experiment: """Class to process Tecan i-control .XML output.""" def __init__( self, + filepath ) -> None: """Initialize Experiment Class object. Parameters ---------- + filepath : str + Filepath to Tecan i-control .XML output. Attributes ---------- + filepath : str + Filepath to Tecan i-control .XML output. + + """ + self.filepath = filepath + self.labels = [] - """ \ No newline at end of file + if filepath.lower()[-4:] != '.xml': + raise TypeError('Filepath does not point to .xml file') + + tree = ET.parse(filepath) + root = tree.getroot() + + for section in root.iter('Section'): + self.labels.append(section.attrib['Name']) \ No newline at end of file From c99e9d44a53252bd6c8ca34027b6ff84c221012b Mon Sep 17 00:00:00 2001 From: Mark Polk Date: Sat, 5 Oct 2024 23:32:28 -0400 Subject: [PATCH 04/20] instantiate automatically generated poetry.lock file --- missense_kinase_toolkit/experiments/poetry.lock | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 missense_kinase_toolkit/experiments/poetry.lock diff --git a/missense_kinase_toolkit/experiments/poetry.lock b/missense_kinase_toolkit/experiments/poetry.lock new file mode 100644 index 0000000..ac1e080 --- /dev/null +++ b/missense_kinase_toolkit/experiments/poetry.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +package = [] + +[metadata] +lock-version = "2.0" +python-versions = "^3.9" +content-hash = "c595a0588c25d58f3e3834ad7169126836d262b925fe6ca9b5d540dcf301d254" From 63489216c5a055c36cc69030f5cbb12b560a4161 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 6 Oct 2024 03:44:51 +0000 Subject: [PATCH 05/20] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../experiments/plate_reader.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py b/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py index ec1daa7..ca0c763 100644 --- a/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py +++ b/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py @@ -4,10 +4,7 @@ class Experiment: """Class to process Tecan i-control .XML output.""" - def __init__( - self, - filepath - ) -> None: + def __init__(self, filepath) -> None: """Initialize Experiment Class object. Parameters @@ -24,11 +21,11 @@ def __init__( self.filepath = filepath self.labels = [] - if filepath.lower()[-4:] != '.xml': - raise TypeError('Filepath does not point to .xml file') - + if filepath.lower()[-4:] != ".xml": + raise TypeError("Filepath does not point to .xml file") + tree = ET.parse(filepath) root = tree.getroot() - - for section in root.iter('Section'): - self.labels.append(section.attrib['Name']) \ No newline at end of file + + for section in root.iter("Section"): + self.labels.append(section.attrib["Name"]) From b8d25b3afa5081eaf2b3a990519d9e363695f0d4 Mon Sep 17 00:00:00 2001 From: Mark Polk Date: Sun, 6 Oct 2024 00:05:58 -0400 Subject: [PATCH 06/20] instantiate Measurement and Parameters classes --- .../experiments/plate_reader.py | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py b/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py index ca0c763..b75e36f 100644 --- a/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py +++ b/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py @@ -16,6 +16,8 @@ def __init__(self, filepath) -> None: ---------- filepath : str Filepath to Tecan i-control .XML output. + labels: list + List of labels. """ self.filepath = filepath @@ -29,3 +31,34 @@ def __init__(self, filepath) -> None: for section in root.iter("Section"): self.labels.append(section.attrib["Name"]) + +class Measurement: + """Class to store measurement data.""" + + def __init__(self) -> None: + """Initialize Measurement Class object. + + Parameters + ---------- + + Attributes + ---------- + + """ + pass + +class Parameters(Measurement): + """Class to store measurement parameters.""" + + def __init__(self) -> None: + """Initialize Parameters Class object. + + Parameters + ---------- + + Attributes + ---------- + + """ + pass + pass From b17f44a52cfdcc5b32c5e3deaa31e507434248d4 Mon Sep 17 00:00:00 2001 From: Mark Polk Date: Sun, 6 Oct 2024 01:18:39 -0400 Subject: [PATCH 07/20] removed parameters from separate class to Measurement attribute --- .../experiments/plate_reader.py | 46 ++++++++----------- 1 file changed, 20 insertions(+), 26 deletions(-) diff --git a/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py b/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py index b75e36f..3172cfd 100644 --- a/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py +++ b/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py @@ -1,5 +1,7 @@ import xml.etree.ElementTree as ET +# i-control manual: https://bif.wisc.edu/wp-content/uploads/sites/389/2017/11/i-control_Manual.pdf + class Experiment: """Class to process Tecan i-control .XML output.""" @@ -9,19 +11,13 @@ def __init__(self, filepath) -> None: Parameters ---------- - filepath : str - Filepath to Tecan i-control .XML output. Attributes ---------- - filepath : str - Filepath to Tecan i-control .XML output. - labels: list - List of labels. """ self.filepath = filepath - self.labels = [] + self.measurements = [] if filepath.lower()[-4:] != ".xml": raise TypeError("Filepath does not point to .xml file") @@ -29,13 +25,22 @@ def __init__(self, filepath) -> None: tree = ET.parse(filepath) root = tree.getroot() + # TODO: check if i-control allows duplicate labels + for section in root.iter("Section"): - self.labels.append(section.attrib["Name"]) + self.measurements.append(Measurement(section)) + + def get_measurement_by_label(self, label): + + for i in range(len(self.measurements)): + if self.measurements[i].label == label: + return self.measurements[i] + class Measurement: - """Class to store measurement data.""" + """Class to store measurement.""" - def __init__(self) -> None: + def __init__(self, section) -> None: """Initialize Measurement Class object. Parameters @@ -45,20 +50,9 @@ def __init__(self) -> None: ---------- """ - pass - -class Parameters(Measurement): - """Class to store measurement parameters.""" - - def __init__(self) -> None: - """Initialize Parameters Class object. - - Parameters - ---------- + self.label = section.attrib['Name'] + self.parameters = {} - Attributes - ---------- - - """ - pass - pass + for parameters in section.iter("Parameters"): + for parameter in parameters: + self.parameters[parameter.attrib['Name']] = parameter.attrib['Value'] \ No newline at end of file From 7f5e44bef14438b9ec1708e6185a4b903a0db5d9 Mon Sep 17 00:00:00 2001 From: Mark Polk Date: Sun, 6 Oct 2024 01:37:59 -0400 Subject: [PATCH 08/20] added start time, end time, and duration to Measurement class --- .../missense_kinase_toolkit/experiments/plate_reader.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py b/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py index 3172cfd..936cefb 100644 --- a/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py +++ b/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py @@ -1,4 +1,5 @@ import xml.etree.ElementTree as ET +from datetime import datetime # i-control manual: https://bif.wisc.edu/wp-content/uploads/sites/389/2017/11/i-control_Manual.pdf @@ -52,7 +53,13 @@ def __init__(self, section) -> None: """ self.label = section.attrib['Name'] self.parameters = {} + self.time_start = datetime.fromisoformat(section[0].text) + self.time_end = datetime.fromisoformat(section[-1].text) for parameters in section.iter("Parameters"): for parameter in parameters: - self.parameters[parameter.attrib['Name']] = parameter.attrib['Value'] \ No newline at end of file + self.parameters[parameter.attrib['Name']] = parameter.attrib['Value'] + + def get_duration(self): + duration = self.time_end - self.time_start + return duration \ No newline at end of file From 3ef25ce29a4c889ecaaa6b2623b1d2a9e9f526fb Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 6 Oct 2024 05:52:09 +0000 Subject: [PATCH 09/20] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../experiments/plate_reader.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py b/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py index 936cefb..8403359 100644 --- a/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py +++ b/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py @@ -43,23 +43,23 @@ class Measurement: def __init__(self, section) -> None: """Initialize Measurement Class object. - + Parameters ---------- Attributes ---------- - + """ - self.label = section.attrib['Name'] + self.label = section.attrib["Name"] self.parameters = {} self.time_start = datetime.fromisoformat(section[0].text) self.time_end = datetime.fromisoformat(section[-1].text) for parameters in section.iter("Parameters"): for parameter in parameters: - self.parameters[parameter.attrib['Name']] = parameter.attrib['Value'] + self.parameters[parameter.attrib["Name"]] = parameter.attrib["Value"] def get_duration(self): duration = self.time_end - self.time_start - return duration \ No newline at end of file + return duration From fa1754f445d4e280a974a793e60b00c357b5fa89 Mon Sep 17 00:00:00 2001 From: Mark Polk Date: Sun, 6 Oct 2024 02:10:42 -0400 Subject: [PATCH 10/20] added Luminescence_Scan_Data class --- .../experiments/plate_reader.py | 36 +++++++++++++++---- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py b/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py index 936cefb..e2d9c0e 100644 --- a/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py +++ b/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py @@ -7,7 +7,7 @@ class Experiment: """Class to process Tecan i-control .XML output.""" - def __init__(self, filepath) -> None: + def __init__(self, filepath: str) -> None: """Initialize Experiment Class object. Parameters @@ -20,18 +20,18 @@ def __init__(self, filepath) -> None: self.filepath = filepath self.measurements = [] - if filepath.lower()[-4:] != ".xml": - raise TypeError("Filepath does not point to .xml file") + if filepath.lower()[-4:] != '.xml': + raise TypeError('Filepath does not point to .xml file') tree = ET.parse(filepath) root = tree.getroot() # TODO: check if i-control allows duplicate labels - for section in root.iter("Section"): + for section in root.iter('Section'): self.measurements.append(Measurement(section)) - def get_measurement_by_label(self, label): + def get_measurement_by_label(self, label: str): for i in range(len(self.measurements)): if self.measurements[i].label == label: @@ -51,15 +51,37 @@ def __init__(self, section) -> None: ---------- """ + self.section = section self.label = section.attrib['Name'] self.parameters = {} self.time_start = datetime.fromisoformat(section[0].text) self.time_end = datetime.fromisoformat(section[-1].text) - for parameters in section.iter("Parameters"): + # TODO: add units and other info. as applicable + + for parameters in section.iter('Parameters'): for parameter in parameters: self.parameters[parameter.attrib['Name']] = parameter.attrib['Value'] def get_duration(self): duration = self.time_end - self.time_start - return duration \ No newline at end of file + return duration + + def get_data(self, cycle=1): + for data in self.section.iter('Data'): + if int(data.attrib['Cycle']) == cycle: + return data + +class Luminescence_Scan_Data: + """Class to process Luminescence Scan data.""" + + def __init__(self, data) -> None: + """Initialize Luminescence_Scan_Data Class object. + + Parameters + ---------- + + Attributes + ---------- + + """ \ No newline at end of file From c703457ca7706f7eeedd992fbb2600654a5e905d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 6 Oct 2024 06:22:11 +0000 Subject: [PATCH 11/20] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../experiments/plate_reader.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py b/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py index 1b1a022..4badd4d 100644 --- a/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py +++ b/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py @@ -20,15 +20,15 @@ def __init__(self, filepath: str) -> None: self.filepath = filepath self.measurements = [] - if filepath.lower()[-4:] != '.xml': - raise TypeError('Filepath does not point to .xml file') + if filepath.lower()[-4:] != ".xml": + raise TypeError("Filepath does not point to .xml file") tree = ET.parse(filepath) root = tree.getroot() # TODO: check if i-control allows duplicate labels - for section in root.iter('Section'): + for section in root.iter("Section"): self.measurements.append(Measurement(section)) def get_measurement_by_label(self, label: str): @@ -59,10 +59,10 @@ def __init__(self, section) -> None: # TODO: add units and other info. as applicable - for parameters in section.iter('Parameters'): + for parameters in section.iter("Parameters"): for parameter in parameters: self.parameters[parameter.attrib["Name"]] = parameter.attrib["Value"] def get_duration(self): duration = self.time_end - self.time_start - return duration \ No newline at end of file + return duration From d8764c0fe35f60e4671185b501034ca0b3c70e21 Mon Sep 17 00:00:00 2001 From: Mark Polk Date: Mon, 7 Oct 2024 23:26:47 -0400 Subject: [PATCH 12/20] changes made during review --- .../missense_kinase_toolkit/experiments/plate_reader.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py b/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py index 1b1a022..2f90b6f 100644 --- a/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py +++ b/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py @@ -20,6 +20,8 @@ def __init__(self, filepath: str) -> None: self.filepath = filepath self.measurements = [] + # try except parsing an xml file + if filepath.lower()[-4:] != '.xml': raise TypeError('Filepath does not point to .xml file') From 21e1954f8fcb36836784ae001a8e93a2e9609835 Mon Sep 17 00:00:00 2001 From: Mark Polk Date: Tue, 8 Oct 2024 00:59:51 -0400 Subject: [PATCH 13/20] reworked module to use dataclasses and abstract base classes --- .../experiments/plate_reader.py | 116 ++++++++---------- 1 file changed, 51 insertions(+), 65 deletions(-) diff --git a/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py b/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py index db848e7..aee7893 100644 --- a/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py +++ b/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py @@ -1,70 +1,56 @@ +import logging import xml.etree.ElementTree as ET from datetime import datetime +from abc import ABC, abstractmethod +from dataclasses import dataclass # i-control manual: https://bif.wisc.edu/wp-content/uploads/sites/389/2017/11/i-control_Manual.pdf - -class Experiment: - """Class to process Tecan i-control .XML output.""" - - def __init__(self, filepath: str) -> None: - """Initialize Experiment Class object. - - Parameters - ---------- - - Attributes - ---------- - - """ - self.filepath = filepath - self.measurements = [] - - # try except parsing an xml file - - if filepath.lower()[-4:] != ".xml": - raise TypeError("Filepath does not point to .xml file") - - tree = ET.parse(filepath) - root = tree.getroot() - - # TODO: check if i-control allows duplicate labels - - for section in root.iter("Section"): - self.measurements.append(Measurement(section)) - - def get_measurement_by_label(self, label: str): - - for i in range(len(self.measurements)): - if self.measurements[i].label == label: - return self.measurements[i] - - -class Measurement: - """Class to store measurement.""" - - def __init__(self, section) -> None: - """Initialize Measurement Class object. - - Parameters - ---------- - - Attributes - ---------- - - """ - self.section = section - self.label = section.attrib["Name"] - self.parameters = {} - self.time_start = datetime.fromisoformat(section[0].text) - self.time_end = datetime.fromisoformat(section[-1].text) - - # TODO: add units and other info. as applicable - - for parameters in section.iter("Parameters"): - for parameter in parameters: - self.parameters[parameter.attrib["Name"]] = parameter.attrib["Value"] - - def get_duration(self): - duration = self.time_end - self.time_start - return duration +logger = logging.getLogger(__name__) + +@dataclass +class Measurement(ABC): + """Abstract class for measurements.""" + + filepath: str + """str: Filepath to i-control output .XML.""" + label: str + """str: Label parameter in i-control.""" + + def __post_init__(self): + self.parse_xml() + + def parse_xml(self): + try: + tree = ET.parse(self.filepath) + root = tree.getroot() + # TODO: check that i-control does not allow duplicate labels + self.section_element = root.find(f".//Section[@Name='{self.label}']") + if self.section_element is None: + logger.error("Label not found.") + except ET.ParseError as e: + logging.error("Could not parse input file", exc_info=e) + + def get_parameter(self, parameter): + ... + + @abstractmethod + def get_data(self) -> int: + ... + +@dataclass +class Luminescence_Scan(Measurement): + """Class to parse Luminescence Scan measurement.""" + + def get_parameter(self, parameter): + # TODO + pass + + def get_data(self, cycle, temperature, well, wavelength): + data_element = self.section_element.find(f".//Data[@Cycle='{str(cycle)}'][@Temperature='{str(temperature)}']") + if data_element is None: + logger.error("Data not found.") + else: + well_element = data_element.find(f".//Well[@Pos='{well}']") + scan_element = well_element.find(f".//Scan[@WL='{str(wavelength)}']") + return int(scan_element.text) \ No newline at end of file From 94f1b311b61d4cbc95c5bbb2f1d2e50685417dce Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 8 Oct 2024 05:00:03 +0000 Subject: [PATCH 14/20] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../experiments/plate_reader.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py b/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py index aee7893..511c717 100644 --- a/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py +++ b/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py @@ -1,13 +1,14 @@ import logging import xml.etree.ElementTree as ET -from datetime import datetime from abc import ABC, abstractmethod from dataclasses import dataclass +from datetime import datetime # i-control manual: https://bif.wisc.edu/wp-content/uploads/sites/389/2017/11/i-control_Manual.pdf logger = logging.getLogger(__name__) + @dataclass class Measurement(ABC): """Abstract class for measurements.""" @@ -31,12 +32,11 @@ def parse_xml(self): except ET.ParseError as e: logging.error("Could not parse input file", exc_info=e) - def get_parameter(self, parameter): - ... + def get_parameter(self, parameter): ... @abstractmethod - def get_data(self) -> int: - ... + def get_data(self) -> int: ... + @dataclass class Luminescence_Scan(Measurement): @@ -47,10 +47,12 @@ def get_parameter(self, parameter): pass def get_data(self, cycle, temperature, well, wavelength): - data_element = self.section_element.find(f".//Data[@Cycle='{str(cycle)}'][@Temperature='{str(temperature)}']") + data_element = self.section_element.find( + f".//Data[@Cycle='{str(cycle)}'][@Temperature='{str(temperature)}']" + ) if data_element is None: logger.error("Data not found.") else: well_element = data_element.find(f".//Well[@Pos='{well}']") scan_element = well_element.find(f".//Scan[@WL='{str(wavelength)}']") - return int(scan_element.text) \ No newline at end of file + return int(scan_element.text) From 6043b63c0e77609954a0ea4da3ab5d15589bb1ab Mon Sep 17 00:00:00 2001 From: Mark Polk Date: Tue, 8 Oct 2024 01:20:57 -0400 Subject: [PATCH 15/20] added basic get parameter --- .../experiments/plate_reader.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py b/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py index 511c717..bb064e2 100644 --- a/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py +++ b/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py @@ -32,7 +32,10 @@ def parse_xml(self): except ET.ParseError as e: logging.error("Could not parse input file", exc_info=e) - def get_parameter(self, parameter): ... + def get_parameter(self, parameter: str) -> str: + # TODO: Add units handling + parameter_attribute = self.section_element.find(f".//Parameter[@Name='{parameter}']").attrib["Value"] + return parameter_attribute @abstractmethod def get_data(self) -> int: ... @@ -42,11 +45,7 @@ def get_data(self) -> int: ... class Luminescence_Scan(Measurement): """Class to parse Luminescence Scan measurement.""" - def get_parameter(self, parameter): - # TODO - pass - - def get_data(self, cycle, temperature, well, wavelength): + def get_data(self, cycle: int, temperature: int, well: str, wavelength: int): data_element = self.section_element.find( f".//Data[@Cycle='{str(cycle)}'][@Temperature='{str(temperature)}']" ) From 741c257001f67363b40d199bb669501ed2816b1b Mon Sep 17 00:00:00 2001 From: Mark Polk Date: Tue, 8 Oct 2024 01:26:03 -0400 Subject: [PATCH 16/20] added start, end time tracking --- .../missense_kinase_toolkit/experiments/plate_reader.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py b/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py index bb064e2..f468dbb 100644 --- a/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py +++ b/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py @@ -19,9 +19,10 @@ class Measurement(ABC): """str: Label parameter in i-control.""" def __post_init__(self): - self.parse_xml() + self.parse_for_section() + self.parse_for_time() - def parse_xml(self): + def parse_for_section(self): try: tree = ET.parse(self.filepath) root = tree.getroot() @@ -32,6 +33,10 @@ def parse_xml(self): except ET.ParseError as e: logging.error("Could not parse input file", exc_info=e) + def parse_for_time(self): + self.time_start = datetime.fromisoformat(self.section_element.find(f".//Time_Start").text) + self.time_end = datetime.fromisoformat(self.section_element.find(f".//Time_End").text) + def get_parameter(self, parameter: str) -> str: # TODO: Add units handling parameter_attribute = self.section_element.find(f".//Parameter[@Name='{parameter}']").attrib["Value"] From db8aef1dc16e3646ceee04b786e3ea194de4e9b1 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 8 Oct 2024 05:26:31 +0000 Subject: [PATCH 17/20] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../experiments/plate_reader.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py b/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py index f468dbb..666525b 100644 --- a/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py +++ b/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py @@ -34,12 +34,18 @@ def parse_for_section(self): logging.error("Could not parse input file", exc_info=e) def parse_for_time(self): - self.time_start = datetime.fromisoformat(self.section_element.find(f".//Time_Start").text) - self.time_end = datetime.fromisoformat(self.section_element.find(f".//Time_End").text) + self.time_start = datetime.fromisoformat( + self.section_element.find(f".//Time_Start").text + ) + self.time_end = datetime.fromisoformat( + self.section_element.find(f".//Time_End").text + ) def get_parameter(self, parameter: str) -> str: # TODO: Add units handling - parameter_attribute = self.section_element.find(f".//Parameter[@Name='{parameter}']").attrib["Value"] + parameter_attribute = self.section_element.find( + f".//Parameter[@Name='{parameter}']" + ).attrib["Value"] return parameter_attribute @abstractmethod From 38ed2ddae849cc6b3fa2daf7d9e9394c68fcd3e0 Mon Sep 17 00:00:00 2001 From: Mark Polk Date: Tue, 8 Oct 2024 01:29:09 -0400 Subject: [PATCH 18/20] fixed use of f strings --- .../missense_kinase_toolkit/experiments/plate_reader.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py b/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py index 666525b..f0708a3 100644 --- a/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py +++ b/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py @@ -35,10 +35,10 @@ def parse_for_section(self): def parse_for_time(self): self.time_start = datetime.fromisoformat( - self.section_element.find(f".//Time_Start").text + self.section_element.find(".//Time_Start").text ) self.time_end = datetime.fromisoformat( - self.section_element.find(f".//Time_End").text + self.section_element.find(".//Time_End").text ) def get_parameter(self, parameter: str) -> str: From c677a2229243c41ff95be48cd39a2343ac46bac3 Mon Sep 17 00:00:00 2001 From: Jess White <50890758+jessicaw9910@users.noreply.github.com> Date: Tue, 8 Oct 2024 15:57:17 +0000 Subject: [PATCH 19/20] added initial scatterplotting function to Luminescence_Scan --- .../experiments/plate_reader.py | 51 ++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py b/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py index f0708a3..a26c23f 100644 --- a/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py +++ b/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py @@ -31,7 +31,7 @@ def parse_for_section(self): if self.section_element is None: logger.error("Label not found.") except ET.ParseError as e: - logging.error("Could not parse input file", exc_info=e) + logging.error(f"Could not parse input {self.filepath}", exc_info=e) def parse_for_time(self): self.time_start = datetime.fromisoformat( @@ -51,6 +51,12 @@ def get_parameter(self, parameter: str) -> str: @abstractmethod def get_data(self) -> int: ... + @abstractmethod + def plot_data( + self + ) -> Tuple[matplotlib.figure.Figure, plt.Axes] | None: + ... + @dataclass class Luminescence_Scan(Measurement): @@ -66,3 +72,46 @@ def get_data(self, cycle: int, temperature: int, well: str, wavelength: int): well_element = data_element.find(f".//Well[@Pos='{well}']") scan_element = well_element.find(f".//Scan[@WL='{str(wavelength)}']") return int(scan_element.text) + + def plot_data( + cycle: int + temperature: int, + well: str, + header: str, + plot_type: str | None = None, + ): + if plot_type is None: + plot_type = "scatter" + + list_plot_type = ["scatter"] + if plot_type not in list_plot_type: + logging.error(f"Plot type {plot_type} not yet supported...") + return None + + if plot_type == "scatter": + lmin = int(self.get_parameter("Wavelength Start")) + lmax = int(self.get_parameter("Wavelength End")) + lstep = int(self.get_parameter("Wavelength Step Size")) + ll = np.arange(lmin, lmax+lstep, lstep) + + fig, ax = plt.subplots() + + try: + counter = 0 + for l in ll: + ax.plot(l, self.get_data(cycle, temperature, well, l), "b.", alpha=0.5) + counter += 1 + assert counter == int(self.get_parameter("Scan Number")) + print(f"All {counter} points accounted for") + except AssertionError as e: + logging.error(f"{counter} / {int(self.get_parameter('Scan Number'))} points accounted for...") + return None + + ax.set_box_aspect(1) + ax.set_title("Water") # make this a parameter? + ax.set_xlabel("Wavelength (nm)") + ax.set_ylabel("Luminescence (RLU)") + ax.set_xlim([lmin, lmax]) + ax.set_xticks(np.arange(lmin, lmax+50, 50)) # make this a parameter? + + return (fig, ax) \ No newline at end of file From a5565f1407e24385f7813088092b352f7eaed4e3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 8 Oct 2024 15:58:23 +0000 Subject: [PATCH 20/20] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../missense_kinase_toolkit/experiments/plate_reader.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py b/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py index a26c23f..55b6b68 100644 --- a/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py +++ b/missense_kinase_toolkit/experiments/missense_kinase_toolkit/experiments/plate_reader.py @@ -79,7 +79,7 @@ def plot_data( well: str, header: str, plot_type: str | None = None, - ): + ): if plot_type is None: plot_type = "scatter" @@ -87,7 +87,7 @@ def plot_data( if plot_type not in list_plot_type: logging.error(f"Plot type {plot_type} not yet supported...") return None - + if plot_type == "scatter": lmin = int(self.get_parameter("Wavelength Start")) lmax = int(self.get_parameter("Wavelength End")) @@ -114,4 +114,4 @@ def plot_data( ax.set_xlim([lmin, lmax]) ax.set_xticks(np.arange(lmin, lmax+50, 50)) # make this a parameter? - return (fig, ax) \ No newline at end of file + return (fig, ax)