Source code for chemFilters.img_render

# -*- coding: utf-8 -*-
"""Contains classes for finding custom fonts & rendering molecules into figures."""

import os
import re
import sys
from io import BytesIO
from pathlib import Path
from typing import Dict, List, Optional, Tuple, Union

import matplotlib.font_manager as fm
import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
import numpy as np
from job_tqdflex import ParallelApplier
from matplotlib import cm
from PIL import Image
from rdkit import Chem
from rdkit.Chem import AllChem, Draw

from .chem.interface import MoleculeHandler
from .logger import logger

if sys.version_info >= (3, 9):
    from importlib.resources import files
else:
    from importlib_resources import files


[docs] class FontManager:
[docs] def __init__(self, wsl: Union[bool, str] = "auto"): """Initialize the FontManager class. It will search for fonts in the system and in matplotlib. Args: wsl: If you're using windows subsystem for linux. Allows for using fonts installed in windows. Defaults to "auto". """ self.wsl = wsl self.fonts = None
@property def operating_system(self): if os.name == "nt": return "windows" elif os.name == "posix": if sys.platform.startswith("linux"): if self.wsl == "auto": # Auto-detect WSL try: wsl_pattern = re.compile(r"wsl|windows", re.IGNORECASE) with open("/proc/version", "r") as f: content = f.read() if wsl_pattern.findall(content): return "wsl" except (FileNotFoundError, PermissionError): pass elif self.wsl: return "wsl" return "linux" elif sys.platform.startswith("darwin"): return "macos" return "Unknown" @property def available_fonts(self) -> Dict[str, Path]: font_dict = dict() font_extensions = ["*.ttf", "*.otf", "*.eot", "*.woff", "*.woff2", "*.svg"] if self.operating_system == "windows": default_font_dir = Path("C:/Windows/Fonts") if default_font_dir.exists(): for ext in font_extensions: font_paths = sorted(list(default_font_dir.glob(ext))) font_dict.update({_path.stem: _path for _path in font_paths}) font_dir = Path.home() / "AppData/Local/Microsoft/Windows/Fonts" if font_dir.exists(): for ext in font_extensions: font_paths = sorted(list(font_dir.glob(ext))) font_dict.update({_path.stem: _path for _path in font_paths}) elif self.operating_system == "wsl": # Search within the default windows path default_font_dir = Path("/mnt/c/Windows/Fonts") if default_font_dir.exists(): for ext in font_extensions: font_paths = sorted(list(default_font_dir.glob(ext))) font_dict.update({_path.stem: _path for _path in font_paths}) user_dir = Path("/mnt/c/Users/") font_dirs = list(user_dir.glob("*/AppData/Local/Microsoft/Windows/Fonts")) if len(font_dirs) > 1: print("More than one user font path found.") for _dir in font_dirs: if _dir.exists(): for ext in font_extensions: font_paths = sorted(list(_dir.glob(ext))) font_dict.update({_path.stem: _path for _path in font_paths}) elif self.operating_system == "linux": font_dirs = [ Path("/usr/share/fonts"), Path("/usr/local/share/fonts"), Path("~/.fonts").expanduser(), ] for _dir in font_dirs: if _dir.exists(): for ext in font_extensions: font_paths = sorted(list(_dir.rglob(ext))) font_dict.update({_path.stem: _path for _path in font_paths}) elif self.operating_system == "macos": font_dirs = [ Path("/System/Library/Fonts"), Path("/Library/Fonts"), Path("~/Library/Fonts").expanduser(), ] for _dir in font_dirs: if _dir.exists(): for ext in font_extensions: font_paths = sorted(list(_dir.rglob(ext))) font_dict.update({_path.stem: _path for _path in font_paths}) # Add fonts from matplotlib try: font_dict.update( {font.name: Path(font.fname) for font in fm.fontManager.ttflist} ) except Exception as e: print(f"Warning: Could not load matplotlib fonts: {e}") # Add fonts from RDKit try: from rdkit import RDPaths rdkit_data_dir = Path(RDPaths.RDDataDir) rdkit_font_dir = rdkit_data_dir / "Fonts" if rdkit_font_dir.exists(): font_paths = sorted(list(rdkit_font_dir.glob("*.ttf"))) font_dict.update({_path.stem: _path for _path in font_paths}) except Exception as e: print(f"Warning: Could not load RDKit fonts: {e}") return font_dict
[docs] def get_font_path(self, font_name: str) -> Union[Path, None]: """Get the path to a specific font by name. Args: font_name: Name of the font to find Returns: Path to the font file or None if not found """ fonts = self.available_fonts return fonts.get(font_name)
[docs] def list_available_fonts(self) -> list: """Return a list of all available font names.""" return list(self.available_fonts.keys())
[docs] def has_font(self, font_name: str) -> bool: """Check if a font is available. Args: font_name: Name of the font to check Returns: True if font is available, False otherwise """ return font_name in self.available_fonts
[docs] class MolPlotter(MoleculeHandler): """MolPlotter class to render molecules into images. It uses RDKit's DrawMolecule method to generate the images. The images can be rendered with labels and/or substructure matches. Attributes: _from_smi: output images directly from smiles. Defaults to False. _cmap: colormap for `render_with_color` method. Defaults to "rainbow". _size: size of the output images. Defaults to (300, 300). _mol_font_size: custom font size on the rendered molecules. Defaults to None. _label_font_size: custom font size for the label of the molecules, if any. Defaults to None. _annotation_font_scale: scale factor for the font size of the annotations. _font_name: : font name found by img_render.FondManager class. Defaults to "DejaVu Sans". _n_jobs: number of jobs to run in parallel. Defaults to 1. _bg_transparent: render the background as transparent. Defaults to False. d2d_params: dictionary with the parameters to be passed to get_d2d method. available_fonts: dictionary with fonts available in the system and in matplotlib """
[docs] def __init__( self, from_smi: bool = False, size: tuple = (300, 300), cmap: str = "rainbow", font_name: str = "Telex-Regular", label_font_size: int = None, mol_font_size: int = None, n_jobs: int = 1, chunk_size: int = None, bond_line_width: float = 2.0, no_atom_labels: bool = False, add_atom_indices: bool = False, add_bond_indices: bool = False, annotation_font_scale: float = None, explicit_methyl: bool = False, unspecified_stereo_unknown: bool = False, bg_transparent: bool = False, bw: bool = False, **kwargs, ) -> None: """Initialize the MolPlotter class. The class is used to render molecules into either .sdf or .png files. It uses RDKit's DrawMolecule method to generate the images and `self.d2d_params` to set the parameters for the rendering process. Args: from_smi: if true, methods will treat inputs as smiles. Defaults to False. size: size of the image to de displayed. Defaults to (300, 300). cmap: colormap for the structure matching. Defaults to "rainbow". font_name: font used on molecules' legends. Defaults to "DejaVu Sans". label_font_size: custom font size for the label of the molecules, if any. Defaults to None. mol_font_size: custom font size on the rendered molecules. Defaults to None. n_jobs: number of jobs to run in parallel. Defaults to 1. chunk_size: size of chunks for ParallelApplier. If None, auto-calculated. Defaults to None. bond_line_width (MolDraw2DCairo): width of bond lines. Defaults to 2.0. no_atom_labels (MolDraw2DCairo): do not add atom labels to the rendered mol. add_atom_indices (MolDraw2DCairo): add atom indices to the rendered mols. Defaults to False. add_bond_indices (MolDraw2DCairo): add bond indices to the rendered mols. Defaults to False. annotation_font_scale: scale factor for the font size of the annotations. Defaults to None explicit_methyl (MolDraw2DCairo): explicitly displays a methil as CH3. Defaults to False. unspecified_stereo_unknown (MolDraw2DCairo): unspecified stereo atoms/bonds are drawn as if they were unknown. Defaults to False. bg_transparent: render the background as transparent. Defaults to False. bw: render the molecule as black and white. Defaults to False. kwargs: additional keyword arguments defining extra MolDraw2D attributes, set by passing them to `self.get_d2d` method. """ self._cmap = cmap self._size = size self._mol_font_size = mol_font_size self._label_font_size = label_font_size self._annotation_font_scale = annotation_font_scale self._font_name = font_name self._n_jobs = n_jobs self._chunk_size = chunk_size self._bg_transparent = bg_transparent self.d2d_params = { "bondLineWidth": bond_line_width, "noAtomLabels": no_atom_labels, "addAtomIndices": add_atom_indices, "addBondIndices": add_bond_indices, "explicitMethyl": explicit_methyl, "unspecifiedStereoIsUnknown": unspecified_stereo_unknown, "bw": bw, **kwargs, } self._check_font(font_name) super().__init__(from_smi)
@property def available_fonts(self): fm = FontManager() return fm.available_fonts def _check_font(self, font_name): if self._font_name not in self.available_fonts.keys(): logger.warning( f"Font {self._font_name} not found. Check `available_fonts` attribute." ) return font_name
[docs] @staticmethod def geometric_mean(arr, axis=0): """Calculate the geometric mean of an array. Adapted from SciPy: https://github.com/scipy/blob/v1.10.1/scipy/stats/_stats.py.py#L199=L269""" with np.errstate(divide="ignore"): log_a = np.log(arr) return np.exp(np.average(log_a, axis=axis))
[docs] def get_d2d( self, bondLineWidth: float = 2.0, noAtomLabels: bool = False, addAtomIndices: bool = False, addBondIndices: bool = False, explicitMethyl: bool = False, unspecifiedStereoIsUnknown: bool = False, bw: bool = False, svg: bool = False, **kwargs, ) -> Draw.MolDraw2DCairo: """Function to get the rdkit's MolDraw2DCairo object with the desired options. Args: bondLineWidth: width of the bonds in the drawing. RDKit defaults to 2.0. noAtomLabels: do not add atom labels to the rendered mol. Defaults to False. addAtomIndices: add the atom indices to the rendered mol. Defaults to False. addBondIndices: add the bond indices to the rendered mol. Defaults to False. explicitMethyl: explicitly displays a methil as CH3. Defaults to False. unspecifiedStereoIsUnknown: unspecified stereo atoms/bonds are drawn as if they were unknown. Defaults to False. bw: render the molecule as black and white. Defaults to False. svg: if True, returns a Draw.MolDraw2DSVG object. Defaults to False. kwargs: additional keyword arguments defining extra MolDraw2D attributes. Returns: Draw.MolDraw2DCairo object with the desired options. """ # TODO: for every kwarg passed here, take the attribute and set the value. if svg: d2d = Draw.MolDraw2DSVG(*self._size) else: d2d = Draw.MolDraw2DCairo(*self._size) dopts = d2d.drawOptions() if self._mol_font_size is not None: dopts.fixedFontSize = self._mol_font_size if self._label_font_size is not None: dopts.legendFontSize = self._label_font_size if self._annotation_font_scale is not None: dopts.annotationFontScale = self._annotation_font_scale if self._bg_transparent: dopts.setBackgroundColour((0, 0, 0, 0)) dopts.bondLineWidth = bondLineWidth if noAtomLabels: dopts.noAtomLabels = True if addAtomIndices: dopts.addAtomIndices = True if addBondIndices: dopts.addBondIndices = True if explicitMethyl: dopts.explicitMethyl = True if unspecifiedStereoIsUnknown: dopts.unspecifiedStereoIsUnknown = True if bw: dopts.useBWAtomPalette() if kwargs: for key, value in kwargs.items(): logger.trace(f"Setting {key} to {value} in MolDraw2D options.") setattr(dopts, key, value) font_path = self.available_fonts.get(self._font_name) dopts.fontFile = str(font_path) return d2d
def _stdin_to_mol(self, stdin): if self._from_smi: return self._output_mol(stdin) else: return stdin def _substructure_palette( self, n_substruct: int, cmap: str, alpha: float = 1.0 ) -> List[Tuple[float, float, float, float]]: """ """ qualitative_cmaps = [ "Pastel1", "Pastel2", "Paired", "Accent", "Dark2", "Set1", "Set2", "Set3", "tab10", "tab20", "tab20b", "tab20c", ] if cmap in qualitative_cmaps: # Unpack colors and add alpha colors = [tuple([*col] + [alpha]) for col in cm.get_cmap(cmap).colors] else: scalar_mappable = cm.ScalarMappable(cmap=cmap) colors = scalar_mappable.to_rgba(range(n_substruct), alpha=alpha).tolist() return colors def _images_to_grid(self, images: List[Image.Image], n_cols: int) -> Image.Image: """Helper function to organize a list of images into a single image, as a grid. Args: images: Images to be added into the grid. n_cols: number of columns of the grid. Returns: The image collage as a PIL.Image.Image object. """ def list_split(list_a, chunk_size): """ Returns a generator with a determined chunk size over list_a. Used to organize the figures in the correct `n_cols` format. """ for i in range(0, len(list_a), chunk_size): yield list_a[i : i + chunk_size] # Splitting the list of images into a list of lists with n_cols if n_cols is None: n_cols = len(images) images = list(list_split(images, n_cols)) # Appending blank figures so we have the correct vector shapes while len(images[-1]) < len(images[0]): images[-1].append(Image.new("RGB", self._size, color=(255, 255, 255))) list_of_hstacked = list() # Creating list of horizontally stacked arrays for sublist in images: list_of_hstacked.append(np.hstack([np.asarray(img) for img in sublist])) # Vertically stacking horizontal arrays for _item in list_of_hstacked: final_img = np.vstack([hstack for hstack in list_of_hstacked]) # Creating and returning image from array final_img = Image.fromarray(final_img) return final_img
[docs] def render_ACS1996( self, mol: Chem.Mol, mean_bond_length: float = None, label: Optional[str] = None, return_svg=False, **kwargs, ) -> Union[Image.Image, str]: """Render molecules using the ACS 1996 style. This is a mode which is designed to produce images compatible with the drawing standards for American Chemical Society (ACS) journals. Args: mol: rdkit mol object. mean_bond_length: the mean bond length of the molecule. Defaults to None. label: the label to be added to the molecule. Defaults to "". return_svg: whether to return an svg image instead. Defaults to False. kwargs: additional keyword arguments to be passed to the DrawMolecule method Returns: a PIL.Image.Image object or a string with the SVG representation in case return_svg is True. """ if label is None: label = "" mol = self._stdin_to_mol(mol) d2d = self.get_d2d(svg=return_svg, **self.d2d_params) Chem.rdDepictor.Compute2DCoords(mol) if mean_bond_length is None: mean_bond_length = Draw.MeanBondLength(mol) dopts = d2d.drawOptions() Draw.SetACS1996Mode(dopts, mean_bond_length) if self._bg_transparent: dopts.setBackgroundColour((0, 0, 0, 0)) d2d.DrawMolecule(mol, legend=label, **kwargs) d2d.FinishDrawing() if not return_svg: img = Image.open(BytesIO(d2d.GetDrawingText())) else: img = d2d.GetDrawingText() return img
[docs] def render_mol( self, mol: Chem.Mol, label: Optional[str] = None, match_pose: Optional[Union[str, Chem.Mol]] = None, return_svg: bool = False, **kwargs, ) -> Union[Image.Image, str]: """Plot molecules using rdMolDraw2D.MolDraw2DCairo. Keyword arguments provided will be passed into DrawMolecule method of the same class. Args: mol: rdkit mol object. label: the label to be added to the molecule. Defaults to None. match_pose: if desired, input a SMILES, a SMARTS or a rdkit mol object to render `mol` into a matching position. Defaults to None. return_svg: if you'd like to return an svg image instead. Defaults to False. kwargs: additional keyword arguments to be passed to the DrawMolecule method Raises: ValueError: _description_ Returns: a PIL.Image.Image object or a string with the SVG representation in case return_svg is True. """ if label is None: label = "" mol = self._stdin_to_mol(mol) if match_pose is not None: if isinstance(match_pose, Chem.rdchem.Mol): AllChem.Compute2DCoords(match_pose) else: if "#" in match_pose: logger.debug('Trying "match_pose" from SMARTS...') ref_mol = Chem.MolFromSmarts(match_pose) if ref_mol is None: raise ValueError( 'Error: "match_pose" from SMARTS RDKit Mol is invalid.' ) else: logger.debug('Trying "match_pose" from SMILES...') ref_mol = Chem.MolFromSmiles(match_pose) if ref_mol is None: logger.warning( 'Error: "match_pose" invalid from SMARTS & SMILES.' ) match_pose = ref_mol AllChem.Compute2DCoords(match_pose) AllChem.GenerateDepictionMatching2DStructure( mol, match_pose, acceptFailure=True ) d2d = self.get_d2d(**self.d2d_params, svg=return_svg) d2d.DrawMolecule(mol, legend=label, **kwargs) d2d.FinishDrawing() if return_svg: img = d2d.GetDrawingText() else: img = Image.open(BytesIO(d2d.GetDrawingText())) return img
[docs] def render_with_matches( self, mol: Chem.Mol, substructs, label: Optional[str] = None, match_pose: Optional[Union[str, Chem.Mol]] = None, **kwargs, ) -> Image.Image: """Render the substructures on the molecules using default RDKit coloring. Args: mol: molecule to be rendered. substructs: substructures output from RDKitFilters.filter_mols. label: if desired, add a label to the molecule. Defaults to None. match_pose: a common structure between `smiles` used to generate the matching 2D images. If None, 2D rendering won't match. Defaults to None. kwargs: additional keyword arguments to be passed to the DrawMolecule method Returns: PIL.Image.Image with the molecule and the highlighted substructures. """ mol = self._output_mol(mol) hit_bonds = [] hit_atoms = [] if "__iter__" not in dir(substructs): substructs = [substructs] for patt in substructs: smarts = Chem.MolToSmarts(patt) pttrn = Chem.MolFromSmarts(smarts) hit_ats = list(mol.GetSubstructMatch(pttrn)) if hit_ats == []: continue hit_atoms = hit_atoms + hit_ats for bond in pttrn.GetBonds(): aid1 = hit_ats[bond.GetBeginAtomIdx()] aid2 = hit_ats[bond.GetEndAtomIdx()] hit_bonds.append(mol.GetBondBetweenAtoms(aid1, aid2).GetIdx()) img = self.render_mol( mol, highlightAtoms=hit_atoms, highlightBonds=hit_bonds, label=label, match_pose=match_pose, **kwargs, ) return img
[docs] def render_with_colored_matches( self, mol: Chem.Mol, descriptions: List[str], substructs: List[Chem.Mol], label: Optional[str] = None, cmap: str = "rainbow", alpha: float = 0.5, match_pose: Optional[Union[str, Chem.Mol]] = None, **kwargs, ): """Take descriptions and substructures output from RDKitFilters.filter_mols and renders them on the molecular structure, coloring the substructures and adding labels according to the descriptions. Note: in case of overlap between colors, the final color corresponds to the their respective geometric mean. Args: mol: rdkit molecule object. descriptions: descriptions (`output[0]`) from RDKitFilters.filter_mols. substructs: substructures (`output[1]`) from RDKitFilters.filter_mols. label: if desired, add a label to the molecule. Defaults to None. cmap: colormap for the substructures. Defaults to "viridis". alpha: transparency of the colors. Defaults to 0.5. match_pose: a common structure between `smiles` used to generate the matching 2D images. If None, 2D rendering won't match. Defaults to None. kwargs: additional keyword arguments to be passed to the DrawMolecule method Returns: matplotlib.figure.Figure and Axis with the molecule and the highlighted substructures. """ mol = self._output_mol(mol) smarts = [Chem.MolToSmarts(sub) for sub in substructs] unique_smarts, indices = np.unique(smarts, return_index=True) unique_descrip = [descriptions[i] for i in indices] color_dict = {} colors = self._substructure_palette(len(unique_smarts), cmap=cmap, alpha=alpha) for smarts, _descr, color in zip(unique_smarts, unique_descrip, colors): # Create the patches that are used for the labels pttrn = Chem.MolFromSmarts(smarts) matches = mol.GetSubstructMatch(pttrn) for match in matches: if match in color_dict.keys(): color_dict.update({match: np.vstack([color_dict[match], color])}) else: color_dict.update({match: np.array(color)}) color_dict = { k: tuple(self.geometric_mean(v)) if v.ndim > 1 else tuple(v) for k, v in color_dict.items() } # should be tuples img = self.render_mol( mol, label=label, highlightAtoms=color_dict.keys(), highlightAtomColors=color_dict, match_pose=match_pose, **kwargs, ) return img
[docs] def colored_matches_legend( self, descriptions, substructures, cmap="rainbow", alpha=0.5, ax=None ): if ax is not None: ax = plt.gca() else: fig, ax = plt.subplots(figsize=(5, 5)) patches = [] colors = self._substructure_palette(len(substructures), cmap=cmap, alpha=alpha) for _smarts, descr, color in zip(substructures, descriptions, colors): patches.append( mpatches.Patch(facecolor=color, label=descr, edgecolor="black") ) ax.legend( handles=patches, bbox_to_anchor=(1.05, 0.25), loc="lower left", borderaxespad=0, frameon=False, ) ax.set_axis_off() if ax is None: return fig, ax
[docs] class MolGridPlotter(MolPlotter): """MolGridPlotter class to render molecules into grids of images. It uses RDKit's DrawMolecule method to generate the images and PIL to patch images together. The images can be rendered with labels and/or substructure matches, similar to its parent class. Attributes: _from_smi: output images directly from smiles. Defaults to False. _cmap: colormap for `render_with_color` method. Defaults to "rainbow". _size: size of the output images. Defaults to (300, 300). _mol_font_size: custom font size on the rendered molecules. Defaults to None. _label_font_size: custom font size for the label of the molecules, if any. Defaults to None. _annotation_font_scale: scale factor for the font size of the annotations. _font_name: : font name found by img_render.FondManager class. Defaults to "DejaVu Sans". _n_jobs: number of jobs to run in parallel. Defaults to 1. _bg_transparent: render the background as transparent. Defaults to False. d2d_params: dictionary with the parameters to be passed to get_d2d method. available_fonts: dictionary with fonts available in the system and in matplotlib """
[docs] def __init__( self, from_smi: bool = False, size: tuple = (300, 300), cmap: str = "rainbow", font_name: str = "Telex-Regular", label_font_size: int = None, mol_font_size: int = None, n_jobs: int = 1, chunk_size: int = None, bond_line_width: float = 2.0, no_atom_labels: bool = False, add_atom_indices: bool = False, add_bond_indices: bool = False, annotation_font_scale: float = None, explicit_methyl: bool = False, unspecified_stereo_unknown: bool = False, bg_transparent: bool = False, bw: bool = False, **kwargs, ) -> None: """initialize the MolGridPlotter class. The class is used to render molecules into either .sdf or .png files. It uses RDKit's DrawMolecule method to generate the images and `self.d2d_params` to set the parameters for the rendering process Args: from_smi: if true, methods will treat inputs as smiles. Defaults to False. size: size of the image to de displayed. Defaults to (300, 300). cmap: colormap for the structure matching. Defaults to "rainbow". font_name: font used on molecules' legends. Defaults to "DejaVu Sans". label_font_size: custom font size for the label of the molecules, if any. Defaults to None. mol_font_size: custom font size on the rendered molecules. Defaults to None. n_jobs: number of jobs to run in parallel. Defaults to 1. chunk_size: size of chunks for ParallelApplier. If None, auto-calculated. Defaults to None. bond_line_width (MolDraw2DCairo): width of bond lines. Defaults to 2.0. no_atom_labels (MolDraw2DCairo): do not add atom labels to the rendered mol. add_atom_indices (MolDraw2DCairo): add atom indices to the rendered mols. Defaults to False. add_bond_indices (MolDraw2DCairo): add bond indices to the rendered mols. Defaults to False. annotation_font_scale: scale factor for the font size of the annotations. Defaults to None explicit_methyl (MolDraw2DCairo): explicitly displays a methil as CH3. Defaults to False. unspecified_stereo_unknown (MolDraw2DCairo): unspecified stereo atoms/bonds are drawn as if they were unknown. Defaults to False. bg_transparent: render the background as transparent. Defaults to False. bw: render the molecule as black and white. Defaults to False. kwargs: additional keyword arguments defining extra MolDraw2D attributes, set by passing them to `self.get_d2d` method. """ super().__init__( from_smi, size, cmap, font_name, label_font_size, mol_font_size, n_jobs, chunk_size, bond_line_width, no_atom_labels, add_atom_indices, add_bond_indices, annotation_font_scale, explicit_methyl, unspecified_stereo_unknown, bg_transparent, bw, **kwargs, )
[docs] def mol_grid_png( self, mols: list, labels: Optional[list] = None, n_cols: int = None, match_pose: Optional[Union[str, Chem.Mol]] = None, **kwargs, ) -> Image.Image: """ Args: mols: list of molecules to display. labels: list of labels to be written on the mol images. Defaults to None n_cols: N columns with molecules. If None, n_cols = len(smiles). Defaults to None. match_pose: a common structure between `smiles` used to generate the matching 2D images. If None, 2D rendering won't match. Defaults to None. kwargs: additional keyword arguments to be passed to the DrawMolecule method Returns: PIL.Image.Image """ if labels is not None: assert len(labels) == len(mols), "Labels and mols must have the same length" variables = list(zip(mols, labels)) else: variables = list(zip(mols)) def _unpack_render_mol(args): """Wrapper to unpack tuple arguments for ParallelApplier.""" return self.render_mol(*args, match_pose=match_pose, **kwargs) try: applier = ParallelApplier( _unpack_render_mol, variables, n_jobs=self._n_jobs, show_progress=False, backend="loky", custom_desc="Rendering molecule grid", chunk_size=self._chunk_size, ) images = applier() except RuntimeError as e: logger.error( "Runtime errors can occur when the molecule is too big for " "the canvas size set by the user. Consider changing the `self.size` " f"parameter. Error message:\n{e}" ) raise e image = self._images_to_grid(images, n_cols) return image
[docs] def mol_structmatch_grid_png( self, mols: list, substructs: list, labels: Optional[list] = None, n_cols: int = None, match_pose: Optional[Union[str, Chem.Mol]] = None, **kwargs, ) -> Image.Image: """ Args: mols: list of molecules to display. substructs: list of substructures to be highlighted on the mol images. labels: list of labels to be written on the mol images. Defaults to None n_cols: N columns with molecules. If None, n_cols = len(smiles). Defaults to None. match_pose: a common structure between `smiles` used to generate the matching 2D images. If None, 2D rendering won't match. Defaults to None. kwargs: additional keyword arguments to be passed to the DrawMolecule method Returns: PIL.Image.Image """ if labels is not None: assert len(labels) == len(mols), "Labels and mols must have the same length" variables = list(zip(mols, substructs, labels)) else: variables = list(zip(mols, substructs)) def _unpack_render_with_matches(args): """Wrapper to unpack tuple arguments for ParallelApplier.""" return self.render_with_matches(*args, match_pose=match_pose, **kwargs) try: applier = ParallelApplier( _unpack_render_with_matches, variables, n_jobs=self._n_jobs, show_progress=False, backend="loky", custom_desc="Rendering molecules with highlighted substructures", chunk_size=self._chunk_size, ) images = applier() except RuntimeError as e: logger.error( "Runtime errors can occur when the molecule is too big for " "the canvas size set by the user. Consider changing the `self.size` " f"parameter. Error message:\n{e}" ) raise e image = self._images_to_grid(images, n_cols) return image
[docs] def mol_structmatch_color_grid_png( self, mols: list, descriptions: List[str], substructs: List[Chem.Mol], labels: Optional[list] = None, n_cols: int = None, match_pose: Optional[Union[str, Chem.Mol]] = None, **kwargs, ) -> Image.Image: """ Args: mols: list of molecules to display. descriptions: descriptions (`output[0]`) from RDKitFilters.filter_mols substructs: substructures (`output[1]`) from RDKitFilters.filter_mols. labels: list of labels to be written on the mol images. Defaults to None n_cols: N columns with molecules. If None, n_cols = len(smiles). Defaults to None. match_pose: a common structure between `smiles` used to generate the matching 2D images. If None, 2D rendering won't match. Defaults to None. kwargs: additional keyword arguments to be passed to the DrawMolecule method Returns: PIL.Image.Image """ mols = self._stdin_to_mol(mols) if labels is not None: assert len(labels) == len(mols), "Labels and mols must have the same length" variables = list(zip(mols, descriptions, substructs, labels)) else: variables = list(zip(mols, descriptions, substructs)) def _unpack_render_with_colored_matches(args): """Wrapper to unpack tuple arguments for ParallelApplier.""" return self.render_with_colored_matches( *args, match_pose=match_pose, **kwargs ) try: applier = ParallelApplier( _unpack_render_with_colored_matches, variables, n_jobs=self._n_jobs, show_progress=False, backend="loky", custom_desc="Rendering molecules with colored matches", chunk_size=self._chunk_size, ) images = applier() except RuntimeError as e: logger.error( "Runtime errors can occur when the molecule is too big for " "the canvas size set by the user. Consider changing the `self.size` " f"parameter. Error message:\n{e}" ) raise e image = self._images_to_grid(images, n_cols) return image