Source code for pyrnaither.visualization.visualizer

import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
from matplotlib import cm, patches
import colorsys
import numpy as np
import matplotlib as mpl
from typing import List, Tuple
import pandas as pd



# Function to get the complementary color
[docs] def get_complementary_color(color): """ Returns the complementary color of the given color. Args: color: The color to get the complementary color of. Returns: The complementary color of the given color. """ # Convert the color from RGB to HLS r, g, b = mcolors.to_rgb(color) # Normalize the RGB values to [0, 1] h, l, s = colorsys.rgb_to_hls(r, g, b) # Shift the hue by 0.5 (equivalent to 180 degrees) to get the complementary color h_complementary = (h + 0.5) % 1.0 # Convert it back to RGB r_complementary, g_complementary, b_complementary = colorsys.hls_to_rgb(h_complementary, l, s) # Return the complementary color in a format that matplotlib can use return (r_complementary, g_complementary, b_complementary)
# Function to plot 96-well plate with color-coded intensity
[docs] def plot_96_well_plate_with_intensity(data, meta=None, title="", scale=1.5, cbar_label=None, ): """ Plots a 96-well plate with color-coded intensity. Args: data: The data to plot. meta: The metadata to use for annotation. title: The title of the plot. scale: The scale of the plot. cbar_label: The label for the colorbar. Returns: None """ fig, ax = plt.subplots(figsize=(12.5/scale, 8/scale)) # 96 well plate dimensions: 8 rows (A-H) and 12 columns (1-12) rowsn = list("ABCDEFGH")[::-1] cols = list(range(1, 13)) # Corrected to have 12 columns (1 to 12) # Normalize the intensity data for color mapping intensities = np.array(list([i for i in data.values() if not isinstance(i, str)])) norm = mpl.colors.Normalize(vmin=np.min(intensities), vmax=np.max(intensities)) cmap = cm.get_cmap('YlOrBr') # You can use other colormaps like 'plasma', 'coolwarm', etc. # Create grid for i, row in enumerate(rowsn): for j, col in enumerate(cols): well_key = f"{row}{col}" intensity = data.get(well_key, np.nan) # Get intensity or NaN if well is empty if not np.isnan(intensity): color = cmap(norm(intensity)) # Get color based on intensity # Plot a circle for each well circle = patches.Circle((j+1, i+1), 0.4, color=color, ec='black', lw=1) ax.add_patch(circle) else: color = 'white' # No data wells will be white circle = patches.Circle((j+1, i+1), 0.4, color=color, ec='gray', lw=0.5, alpha=0.5) ax.add_patch(circle) ax.scatter(j+1, i+1, s=400, c='gray', marker='x', clip_on=False, alpha=0.5) # type: ignore # Add intensity value to the well if not np.isnan(intensity): if (1-intensity) > 0.33 and (1-intensity) < 0.66: color = cmap(1) else: color = cmap(1-intensity) alt = meta.get(well_key, None) if meta is not None else None if alt: circle = patches.Circle((j+1, i+1), 0.4, color=cmap(intensity), ec=cmap(0.5), lw=2) ax.add_patch(circle) ax.text(j+1, i+1.12, f"{alt}", horizontalalignment='center', verticalalignment='center', fontsize=10, color=color) ax.text(j+1, i+0.85, f"{intensity:.2f}", horizontalalignment='center', verticalalignment='center', fontsize=8, color=color) else: ax.text(j+1, i+1, f"{intensity:.2f}", horizontalalignment='center', verticalalignment='center', fontsize=8, color=color) # Set axis limits to ensure all 12 columns are visible ax.set_xlim(0.5, 12.5) # Adjusted to show all 12 columns ax.set_ylim(0.5, 8.5) # Set labels and ticks ax.set_xticks(range(1, 13)) # Ensuring 12 ticks ax.set_xticklabels(list(range(1, 13)) ) ax.set_yticks(range(8, 0, -1)) ax.set_yticklabels(rowsn[::-1]) # Set title and hide axis lines ax.set_title(title) ax.spines['top'].set_visible(False) ax.spines['right'].set_visible(False) ax.spines['left'].set_visible(False) ax.spines['bottom'].set_visible(False) # Turn off the ticks (both major and minor ticks) ax.tick_params(left=False, bottom=False) # Optionally, you can remove the grid if you don't want it ax.grid(False) # Add colorbar sm = cm.ScalarMappable(cmap=cmap, norm=norm) sm.set_array([]) cbar = plt.colorbar(sm, ax=ax, shrink=0.5, fraction=0.01) cbar.set_label(cbar_label) # Show the plot plt.show()
[docs] def spatial_distrib( header: List[str], dataset: pd.DataFrame, plot_title: str, col4plot: str, col4anno: str, show_plot: bool ) -> Tuple[str, Tuple[int,int], Tuple[int,int]]: """ Plot spatial distribution of values per plate and experiment. Args: header: The header of the dataset. dataset: The dataset to plot. plot_title: The title of the plot. col4plot: The column to plot. col4anno: The column to group replicates by. show_plot: Whether to show the plot. Returns: (basic_plot_name, (min_screen, max_screen), (min_plate, max_plate)). """ df = dataset.copy() # mask controls df.loc[df['SpotType'] == -1, col4plot] = np.nan screens = sorted(df['ScreenNb'].dropna().unique().astype(int)) # derive base plot name base = header[0].split(',')[1] if ',' in header[0] else header[0] basic_plot_name = f"{base}_{plot_title}" all_plates = [] for screen in screens: subset = df[df['ScreenNb'] == screen] plates = sorted(subset['LabtekNb'].dropna().unique().astype(int)) all_plates.extend(plates) for plate in plates: sub = subset[subset['LabtekNb'] == plate] # check data if sub[col4plot].notna().any(): # build matrix nrows = int(sub['RowNb'].max()) ncols = int(sub['ColNb'].max()) mat = np.full((nrows, ncols), np.nan) for _, row in sub.iterrows(): r = int(row['RowNb']) - 1 c = int(row['ColNb']) - 1 mat[r, c] = row[col4plot] # plot fig, ax = plt.subplots() cax = ax.imshow(mat, origin='upper', aspect='auto') title = f"{plot_title} plate {plate} Exp. {screen}" ax.set_title(title) # annotate N/P for _, row in sub.iterrows(): if row['SpotType'] in (0, 1): r = int(row['RowNb']) - 1 c = int(row['ColNb']) - 1 label = 'P' if row['SpotType'] == 1 else 'N' ax.text(c, r, label, ha='center', va='center', fontsize=8) fig.colorbar(cax) if show_plot: plt.show() # save PNG fname_png = f"{basic_plot_name}_Exp{screen}_Plate{plate}.png" fig.savefig(fname_png) plt.close(fig) # write simple HTML html_file = f"{basic_plot_name}_Exp{screen}_Plate{plate}.html" with open(html_file, 'w') as f: f.write(f"<html><body><h1>{title}</h1><img src='{fname_png}'/></body></html>") else: # blank plot for no data fig, ax = plt.subplots() ax.text(0.5, 0.75, f"Cannot plot plate {plate} Exp {screen}", ha='center') ax.text(0.5, 0.25, "Only NAs available", ha='center') ax.axis('off') fname_png = f"{basic_plot_name}_Exp{screen}_Plate{plate}.png" fig.savefig(fname_png) plt.close(fig) # determine ranges min_screen, max_screen = (min(screens), max(screens)) if screens else (0, 0) min_plate, max_plate = (min(all_plates), max(all_plates)) if all_plates else (0, 0) return basic_plot_name, (min_screen, max_screen), (min_plate, max_plate)