cnotebook 1.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,255 @@
1
+ import logging
2
+ from typing import Literal
3
+ from math import floor, ceil
4
+ from openeye import oechem, oedepict
5
+ from collections.abc import Iterable, Sequence
6
+ from .render import (
7
+ CNotebookContext,
8
+ pass_cnotebook_context,
9
+ oemol_to_html,
10
+ oedisp_to_html,
11
+ oeimage_to_html
12
+ )
13
+ from .helpers import create_structure_highlighter
14
+
15
+ # Only register iPython formatters if that is present
16
+ try:
17
+ # noinspection PyProtectedMember,PyPackageRequirements
18
+ from IPython import get_ipython
19
+ ipython_present = True
20
+ except ModuleNotFoundError:
21
+ ipython_present = False
22
+
23
+ log = logging.getLogger("cnotebook")
24
+
25
+
26
+ @pass_cnotebook_context
27
+ def render_molecule_grid(
28
+ mols: Sequence[oechem.OEMolBase],
29
+ nrows: int | None = None,
30
+ ncols: int | None = None,
31
+ max_width: int = 1280,
32
+ max_columns: int = 100,
33
+ min_width: int | None = None,
34
+ min_height: int | None = None,
35
+ align: oechem.OEMolBase | Literal["first"] | None = None,
36
+ smarts: str | Iterable[str] | oechem.OESubSearch | Iterable[oechem.OESubSearch] | None = None,
37
+ color: oechem.OEColor = oechem.OEColor(oechem.OELightBlue),
38
+ style: int = oedepict.OEHighlightStyle_Stick,
39
+ scale: float = 1.0,
40
+ *,
41
+ ctx: CNotebookContext
42
+ ) -> oedepict.OEImage:
43
+ """
44
+ Convenience function to render a molecule grid
45
+ :param ctx: Current OpenEye rendering context
46
+ :param mols: Iterable of OpenEye molecules
47
+ :param nrows: Number of rows to display in the grid
48
+ :param ncols: Number of columns to display in the grid
49
+ :param max_width: Maximum width of the image
50
+ :param max_columns: Maximum number of columns (if ncols is being automatically calculated)
51
+ :param min_width: Minimum image width (prevents tiny images)
52
+ :param min_height: Minimum image height (prevents tiny images)
53
+ :param align: Alignment to the first molecule, or to a reference molecule
54
+ :param smarts: SMARTS substructure highlighting
55
+ :param color: SMARTS highlighting color
56
+ :param style: SMARTS highlighting style
57
+ :param scale: Image scale in the 2D grid
58
+ :return: Image of the molecule grid
59
+ """
60
+ # Re-scale the images
61
+ ctx.display_options.SetScale(ctx.display_options.GetScale() * scale)
62
+
63
+ # ---------------------------------------------------------------
64
+ # Input validation
65
+ # ---------------------------------------------------------------
66
+
67
+ # Handle single molecules
68
+ if isinstance(mols, oechem.OEMolBase):
69
+ mols = [mols]
70
+
71
+ # Make a copy of the molecules (so we do not modify them)
72
+ # We can use OEGraphMol here because we don't care about conformers
73
+ # Filter out None values first
74
+ mols = [oechem.OEGraphMol(mol) for mol in mols if mol is not None]
75
+
76
+ if len(mols) == 0:
77
+ log.warning("No molecules or display objects to render into a grid")
78
+ # Return a minimal 1x1 image instead of 0x0 to avoid OpenEye bug
79
+ return oedepict.OEImage(1, 1)
80
+
81
+ # Get the subset of molecules that will actually be displayed
82
+ valid = []
83
+
84
+ for idx, mol in enumerate(mols):
85
+ if mol is not None:
86
+ if isinstance(mol, oechem.OEMolBase):
87
+ if mol.IsValid():
88
+ valid.append(mol)
89
+ else:
90
+ log.warning(f'Molecule at index {idx} is not valid')
91
+
92
+ else:
93
+ log.warning(f'Object at index is not a molecule but type {type(mol).__name__}')
94
+
95
+ if len(valid) == 0:
96
+ log.warning("No valid molecules or display objects to render into a grid")
97
+ # Return a minimal 1x1 image instead of 0x0 to avoid OpenEye bug
98
+ return oedepict.OEImage(1, 1)
99
+
100
+ # ---------------------------------------------------------------
101
+ # For highlighting SMARTS
102
+ # ---------------------------------------------------------------
103
+
104
+ highlighers = None
105
+
106
+ if smarts is not None:
107
+ highlighers = []
108
+ # Case: Single pattern
109
+ if isinstance(smarts, (str, oechem.OESubSearch)):
110
+ highlighers.append(
111
+ create_structure_highlighter(
112
+ smarts,
113
+ color=color,
114
+ style=style
115
+ )
116
+ )
117
+
118
+ else:
119
+ for pattern in smarts:
120
+ highlighers.append(
121
+ create_structure_highlighter(
122
+ pattern,
123
+ color=color,
124
+ style=style
125
+ )
126
+ )
127
+
128
+ # ---------------------------------------------------------------
129
+ # For substructure alignment
130
+ # ---------------------------------------------------------------
131
+
132
+ align_mcss = None
133
+
134
+ if align is not None:
135
+
136
+ # If we are doing an alignment
137
+ if isinstance(align, bool) and align:
138
+ align_ref = mols[0]
139
+
140
+ elif isinstance(align, oechem.OEMolBase):
141
+ align_ref = align
142
+
143
+ else:
144
+ raise TypeError(f'Cannot initialize MCSS alignment reference from type {type(align).__name__}')
145
+
146
+ # Set up the MCSS
147
+ align_mcss = oechem.OEMCSSearch(oechem.OEMCSType_Approximate)
148
+ align_mcss.Init(align_ref, oechem.OEExprOpts_DefaultAtoms, oechem.OEExprOpts_DefaultBonds)
149
+
150
+ # ---------------------------------------------------------------
151
+ # Create the display objects for each molecule
152
+ # ---------------------------------------------------------------
153
+
154
+ displays = []
155
+ max_disp_width = float('-inf')
156
+ max_disp_height = float('-inf')
157
+
158
+ for mol in valid:
159
+ if align_mcss is not None:
160
+ oedepict.OEPrepareAlignedDepiction(mol, align_mcss)
161
+ else:
162
+ oedepict.OEPrepareDepiction(mol)
163
+
164
+ # Create the display object
165
+ disp = ctx.create_molecule_display(mol, min_width=min_width, min_height=min_height)
166
+ # disp = oedepict.OE2DMolDisplay(mol, ctx.display_options)
167
+
168
+ # Highlight SMARTS patterns
169
+ if highlighers is not None:
170
+ for highlight in highlighers:
171
+ highlight(disp)
172
+
173
+ displays.append(disp)
174
+
175
+ if disp.GetWidth() > max_disp_width:
176
+ max_disp_width = disp.GetWidth()
177
+
178
+ if disp.GetHeight() > max_disp_height:
179
+ max_disp_height = disp.GetHeight()
180
+
181
+ # ---------------------------------------------------------------
182
+ # Figure out the geometry of the full image
183
+ # ---------------------------------------------------------------
184
+ # Case: We have one molecule
185
+ if len(displays) == 1:
186
+ ncols = 1
187
+ nrows = 1
188
+
189
+ # Case: We are computing based on max_width
190
+ elif ncols is None and nrows is None:
191
+
192
+ # Number of columns we can fit into max_width
193
+ ncols = min(floor(max_width / max_disp_width), max_columns, len(displays))
194
+ nrows = ceil(len(displays) / ncols)
195
+
196
+ elif nrows is not None:
197
+ ncols = ceil(len(displays) / nrows)
198
+
199
+ elif ncols is not None:
200
+ nrows = ceil(len(displays) / ncols)
201
+
202
+ else:
203
+ raise RuntimeError("Cannot determine number of rows and columns in molecule grid")
204
+
205
+ # Image width and height
206
+ width = max_disp_width * ncols
207
+ height = max_disp_height * nrows
208
+
209
+ image = oedepict.OEImage(width, height)
210
+ grid = oedepict.OEImageGrid(image, nrows, ncols)
211
+
212
+ # Render the molecules
213
+ for disp, cell in zip(displays, grid.GetCells()):
214
+ oedepict.OERenderMolecule(cell, disp)
215
+
216
+ return image
217
+
218
+
219
+ ########################################################################################################################
220
+ # Register iPython formatters
221
+ ########################################################################################################################
222
+
223
+ if ipython_present:
224
+
225
+ def register_ipython_formatters():
226
+ """
227
+ Register formatters for OpenEye types here that can be rendered. Calls to this function are idempotent.
228
+ """
229
+ ipython_instance = get_ipython()
230
+
231
+ if ipython_instance is not None:
232
+ html_formatter = ipython_instance.display_formatter.formatters['text/html']
233
+
234
+ try:
235
+ _ = html_formatter.lookup(oechem.OEMolBase)
236
+ except KeyError:
237
+ html_formatter.for_type(oechem.OEMolBase, oemol_to_html)
238
+
239
+ try:
240
+ _ = html_formatter.lookup(oedepict.OE2DMolDisplay)
241
+ except KeyError:
242
+ html_formatter.for_type(oedepict.OE2DMolDisplay, oedisp_to_html)
243
+
244
+ try:
245
+ _ = html_formatter.lookup(oedepict.OEImage)
246
+ except KeyError:
247
+ html_formatter.for_type(oedepict.OEImage, oeimage_to_html)
248
+ else:
249
+ log.debug("[cnotebook] iPython installed but not in use - cannot register iPython extension")
250
+
251
+ else:
252
+
253
+ # iPython is not present, so we do not register formatters for OpenEye objects
254
+ def register_ipython_formatters():
255
+ pass
@@ -0,0 +1,34 @@
1
+ import pandas as pd
2
+ from openeye import oechem, oedepict
3
+ from .context import cnotebook_context
4
+ from .render import oemol_to_html, oedisp_to_html, oeimage_to_html
5
+ from .pandas_ext import render_dataframe
6
+
7
+ def _display_mol(self: oechem.OEMolBase):
8
+ ctx = cnotebook_context.get().copy()
9
+ ctx.image_format = "png"
10
+ return "text/html", oemol_to_html(self, ctx=ctx)
11
+
12
+ oechem.OEMolBase._mime_ = _display_mol
13
+
14
+
15
+ def _display_display(self: oedepict.OE2DMolDisplay):
16
+ ctx = cnotebook_context.get().copy()
17
+ ctx.image_format = "png"
18
+ return "text/html", oedisp_to_html(self, ctx=ctx)
19
+
20
+ oedepict.OE2DMolDisplay.__mime__ = _display_display
21
+
22
+
23
+ def _display_image(self: oedepict.OEImage):
24
+ ctx = cnotebook_context.get().copy()
25
+ ctx.image_format = "png"
26
+ return "text/html", oeimage_to_html(self, ctx=ctx)
27
+
28
+ oedepict.OEImage.__mime__ = _display_image
29
+
30
+ def display_dataframe(self: pd.DataFrame):
31
+ ctx = cnotebook_context.get().copy()
32
+ return render_dataframe(df=self, formatters=None, col_space=None)
33
+
34
+ pd.DataFrame.__mime__ = display_dataframe