Skip to content

Commit

Permalink
add PlateMapper class
Browse files Browse the repository at this point in the history
  • Loading branch information
TimMonko committed Mar 26, 2024
1 parent 0a01960 commit 8834c40
Show file tree
Hide file tree
Showing 3 changed files with 253 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/napari_ndev/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from ._apoc_container import ApocContainer
from ._apoc_feature_stack import ApocFeatureStack
from ._plate_mapper import PlateMapper
from ._rescale_by import RescaleBy
from ._utilities_container import UtilitiesContainer
from ._workflow_container import WorkflowContainer
Expand All @@ -22,6 +23,7 @@
"ApocContainer",
"ApocFeatureStack",
"RescaleBy",
"PlateMapper",
"get_directory_and_files",
"check_for_missing_files",
"setup_logger",
Expand Down
171 changes: 171 additions & 0 deletions src/napari_ndev/_plate_mapper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
import itertools
import string

import pandas as pd
import seaborn as sns


class PlateMapper:
"""
A class for creating and manipulating plate maps.
Attributes:
- plate_size (int): The size of the plate (e.g., 96, 384).
- wells (dict): A dictionary mapping plate sizes to the number of rows and
columns.
- plate_map (DataFrame): The plate map DataFrame with well labels.
- plate_map_pivot (DataFrame): The wide-formatted plate map DataFrame with
treatments as columns.
Methods:
- __init__(self, plate_size=96): Initializes a PlateMapper object.
- create_empty_plate_map(self): Creates an empty plate map DataFrame for a
given plate size.
- assign_treatments(self, treatments): Assigns treatments to specific wells
in a plate map.
- get_pivoted_plate_map(self, treatment): Pivots a plate map DataFrame to
create a wide-formatted DataFrame with a single treatment as columns.
- get_styled_plate_map(self, treatment, palette='colorblind'): Styles a
plate map DataFrame with different background colors for each unique
value.
"""

def __init__(self, plate_size=96):
"""
Initializes a PlateMapper object.
Parameters:
- plate_size (int, optional): The size of the plate. Defaults to 96.
"""
self.plate_size = plate_size
self.wells = {
6: (2, 3),
12: (3, 4),
24: (4, 6),
48: (6, 8),
96: (8, 12),
384: (16, 24),
}
self.plate_map = self.create_empty_plate_map()
self.plate_map_pivot = None

def create_empty_plate_map(self):
"""
Create an empty plate map DataFrame for a given plate size.
Returns:
- plate_map_df (DataFrame): The empty plate map DataFrame with well
labels.
"""
num_rows, num_columns = self.wells[self.plate_size]

row_labels = list(string.ascii_uppercase)[:num_rows]
column_labels = list(range(1, num_columns + 1))

well_rows = row_labels * num_columns
well_rows.sort() # needed to sort the rows correctly
well_columns = column_labels * num_rows

well_labels_dict = {"Row": well_rows, "Column": well_columns}

plate_map_df = pd.DataFrame(well_labels_dict)

plate_map_df["Well ID"] = plate_map_df["Row"] + plate_map_df[
"Column"
].astype(str)
self.plate_map = plate_map_df
return plate_map_df

def assign_treatments(self, treatments):
"""
Assigns treatments to specific wells in a plate map.
Parameters:
- treatments (dict): A dictionary mapping treatments to conditions and
well ranges.
Returns:
- plate_map (DataFrame): The updated plate map with treatments
assigned to specific wells.
"""
for treatment, conditions in treatments.items():
for condition, wells in conditions.items():
for well in wells:
if ":" in well:
start, end = well.split(":")
start_row, start_col = start[0], int(start[1:])
end_row, end_col = end[0], int(end[1:])
well_condition = (
(self.plate_map["Row"] >= start_row)
& (self.plate_map["Row"] <= end_row)
& (self.plate_map["Column"] >= start_col)
& (self.plate_map["Column"] <= end_col)
)
else:
row, col = well[0], int(well[1:])
well_condition = (self.plate_map["Row"] == row) & (
self.plate_map["Column"] == col
)

self.plate_map.loc[well_condition, treatment] = condition
return self.plate_map

def get_pivoted_plate_map(self, treatment):
"""
Pivot a plate map DataFrame to create a wide-formatted DataFrame with
a single treatment as columns.
Parameters:
- treatment (str): The column name of the treatment variable in the
plate map DataFrame.
Returns:
- plate_map_pivot (DataFrame): The wide-formatted plate map DataFrame
with treatments as columns.
"""
plate_map_pivot = self.plate_map.pivot(
index="Row", columns="Column", values=treatment
)
self.plate_map_pivot = plate_map_pivot
return plate_map_pivot

def get_styled_plate_map(self, treatment, palette="colorblind"):
"""
Style a plate map DataFrame with different background colors for each
unique value.
Parameters:
- treatment (str): The column name of the treatment variable in the
plate map DataFrame.
- palette (str or list, optional): The color palette to use for
styling. Defaults to 'colorblind'.
Returns:
- plate_map_styled (pandas.io.formats.style.Styler): The styled plate
map DataFrame with different background colors for each unique
value.
"""
self.plate_map_pivot = self.get_pivoted_plate_map(treatment)

unique_values = pd.unique(self.plate_map_pivot.values.flatten())
unique_values = unique_values[pd.notna(unique_values)]

color_palette = sns.color_palette(palette).as_hex()
# Create an infinite iterator that cycles through the palette
palette_cycle = itertools.cycle(color_palette)
# Use next() to get the next color
color_dict = {value: next(palette_cycle) for value in unique_values}

def get_background_color(value):
if pd.isna(value):
return ""
else:
return f"background-color: {color_dict[value]}"

plate_map_styled = (
self.plate_map_pivot.style.applymap(get_background_color)
.set_caption(f"{treatment} Plate Map")
.format(lambda x: "" if pd.isna(x) else x)
)

return plate_map_styled
80 changes: 80 additions & 0 deletions src/napari_ndev/_tests/test_plate_mapper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import pandas as pd
import pytest

from napari_ndev._plate_mapper import PlateMapper


@pytest.fixture
def plate_mapper():
return PlateMapper(96)


@pytest.fixture
def treatments():
return {
"Treatment1": {"Condition1": ["A1", "B2"], "Condition2": ["C3"]},
"Treatment2": {"Condition3": ["D4:E5"]},
}


def test_plate_mapper_create_empty_plate_map(plate_mapper):
plate_map_df = plate_mapper.create_empty_plate_map()

assert isinstance(plate_map_df, pd.DataFrame)
assert len(plate_map_df) == 96
assert len(plate_map_df.columns) == 3
assert "Row" in plate_map_df.columns
assert "Column" in plate_map_df.columns
assert "Well ID" in plate_map_df.columns


def test_plate_mapper_assign_treatments(plate_mapper, treatments):
plate_map = plate_mapper.assign_treatments(treatments)

assert isinstance(plate_map, pd.DataFrame)
assert "Treatment1" in plate_map.columns
assert "Treatment2" in plate_map.columns
assert (
plate_map.loc[plate_map["Well ID"] == "A1", "Treatment1"].values[0]
== "Condition1"
)
assert (
plate_map.loc[plate_map["Well ID"] == "B2", "Treatment1"].values[0]
== "Condition1"
)
assert (
plate_map.loc[plate_map["Well ID"] == "C3", "Treatment1"].values[0]
== "Condition2"
)
assert (
plate_map.loc[plate_map["Well ID"] == "D4", "Treatment2"].values[0]
== "Condition3"
)
assert (
plate_map.loc[plate_map["Well ID"] == "E5", "Treatment2"].values[0]
== "Condition3"
)
assert (
plate_map.loc[plate_map["Well ID"] == "E4", "Treatment2"].values[0]
== "Condition3"
)


def test_plate_mapper_get_pivoted_plate_map(plate_mapper, treatments):
plate_mapper.assign_treatments(treatments)
plate_map_pivot = plate_mapper.get_pivoted_plate_map("Treatment1")

assert isinstance(plate_map_pivot, pd.DataFrame)
assert len(plate_map_pivot) == 8
assert len(plate_map_pivot.columns) == 12
assert "A" in plate_map_pivot.index
assert "Condition1" in plate_map_pivot.values
assert "Condition2" in plate_map_pivot.values


def test_plate_mapper_get_styled_plate_map(plate_mapper, treatments):
plate_mapper.assign_treatments(treatments)
plate_map_styled = plate_mapper.get_styled_plate_map("Treatment1")

assert isinstance(plate_map_styled, pd.io.formats.style.Styler)
assert plate_map_styled.caption == "Treatment1 Plate Map"

0 comments on commit 8834c40

Please sign in to comment.