cnotebook 1.2.0__py3-none-any.whl → 2.1.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.
- cnotebook/__init__.py +365 -67
- cnotebook/align.py +231 -167
- cnotebook/context.py +50 -18
- cnotebook/grid/__init__.py +56 -0
- cnotebook/grid/grid.py +1655 -0
- cnotebook/helpers.py +147 -15
- cnotebook/ipython_ext.py +0 -3
- cnotebook/marimo_ext.py +67 -0
- cnotebook/pandas_ext.py +760 -514
- cnotebook/polars_ext.py +1237 -0
- cnotebook/render.py +0 -195
- cnotebook-2.1.1.dist-info/METADATA +338 -0
- cnotebook-2.1.1.dist-info/RECORD +16 -0
- {cnotebook-1.2.0.dist-info → cnotebook-2.1.1.dist-info}/WHEEL +1 -1
- cnotebook-1.2.0.dist-info/METADATA +0 -280
- cnotebook-1.2.0.dist-info/RECORD +0 -13
- {cnotebook-1.2.0.dist-info → cnotebook-2.1.1.dist-info}/licenses/LICENSE +0 -0
- {cnotebook-1.2.0.dist-info → cnotebook-2.1.1.dist-info}/top_level.txt +0 -0
cnotebook/align.py
CHANGED
|
@@ -132,172 +132,188 @@ def fingerprint_maker(
|
|
|
132
132
|
########################################################################################################################
|
|
133
133
|
|
|
134
134
|
class Aligner(metaclass=ABCMeta):
|
|
135
|
+
"""Abstract base class for 2D molecule aligners.
|
|
136
|
+
|
|
137
|
+
Aligners transform molecule 2D coordinates to align with a reference
|
|
138
|
+
structure or pattern. Subclasses must implement :meth:`validate` and
|
|
139
|
+
:meth:`align` methods.
|
|
140
|
+
|
|
141
|
+
The aligner is callable - calling it with a molecule or display object
|
|
142
|
+
will validate and then align the molecule if validation passes.
|
|
135
143
|
"""
|
|
136
|
-
|
|
137
|
-
"""
|
|
144
|
+
|
|
138
145
|
def __call__(self, mol_or_disp: oechem.OEMolBase | oedepict.OE2DMolDisplay) -> bool:
|
|
139
146
|
|
|
140
147
|
# Get the molecule
|
|
141
148
|
mol = mol_or_disp if isinstance(mol_or_disp, oechem.OEMolBase) else mol_or_disp.GetMolecule()
|
|
142
149
|
|
|
150
|
+
try:
|
|
151
|
+
log.debug("Aligner called for molecule: %s", oechem.OEMolToSmiles(mol) if mol else "None")
|
|
152
|
+
except TypeError:
|
|
153
|
+
log.debug("Aligner called for molecule: %s", mol)
|
|
154
|
+
|
|
143
155
|
# If the molecule validates against the aligner
|
|
144
156
|
if self.validate(mol):
|
|
145
|
-
|
|
157
|
+
result = self.align(mol)
|
|
158
|
+
log.debug("Alignment result: %s", result)
|
|
159
|
+
return result
|
|
146
160
|
|
|
161
|
+
log.debug("Molecule failed validation, skipping alignment")
|
|
147
162
|
return False
|
|
148
163
|
|
|
149
164
|
@abstractmethod
|
|
150
165
|
def align(self, mol: oechem.OEMolBase) -> bool:
|
|
166
|
+
"""Align the molecule to the reference.
|
|
167
|
+
|
|
168
|
+
:param mol: Molecule to align (will be modified in place).
|
|
169
|
+
:returns: True if alignment was successful.
|
|
170
|
+
"""
|
|
151
171
|
raise NotImplementedError
|
|
152
172
|
|
|
153
173
|
@abstractmethod
|
|
154
174
|
def validate(self, mol: oechem.OEMolBase) -> bool:
|
|
175
|
+
"""Validate that the molecule can be aligned.
|
|
176
|
+
|
|
177
|
+
:param mol: Molecule to validate.
|
|
178
|
+
:returns: True if the molecule can be aligned.
|
|
179
|
+
"""
|
|
155
180
|
raise NotImplementedError
|
|
156
181
|
|
|
157
182
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
#
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
#
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
#
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
#
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
#
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
#
|
|
285
|
-
# alignres = oedepict.OEPrepareAlignedDepiction(
|
|
286
|
-
# mol,
|
|
287
|
-
# self.mcss,
|
|
288
|
-
# self.alignment_options
|
|
289
|
-
# )
|
|
290
|
-
#
|
|
291
|
-
# ok = alignres.IsValid()
|
|
292
|
-
#
|
|
293
|
-
# return ok
|
|
183
|
+
class OESubSearchAligner(Aligner):
|
|
184
|
+
"""Aligner using substructure search for 2D molecule alignment."""
|
|
185
|
+
|
|
186
|
+
def __init__(self, ref: oechem.OESubSearch | oechem.OEMolBase | str, **_kwargs):
|
|
187
|
+
"""Create a substructure-based aligner.
|
|
188
|
+
|
|
189
|
+
:param ref: Reference for alignment. Can be:
|
|
190
|
+
|
|
191
|
+
- ``OESubSearch``: Pre-configured substructure search object.
|
|
192
|
+
- ``OEMolBase``: Molecule to use as substructure pattern.
|
|
193
|
+
- ``str``: SMARTS pattern string.
|
|
194
|
+
|
|
195
|
+
:param _kwargs: Additional keyword arguments (ignored, for API compatibility).
|
|
196
|
+
"""
|
|
197
|
+
# Reference molecule with 2D coordinates
|
|
198
|
+
self.refmol = None
|
|
199
|
+
|
|
200
|
+
if isinstance(ref, (oechem.OESubSearch, str)):
|
|
201
|
+
self.ss = oechem.OESubSearch(ref)
|
|
202
|
+
|
|
203
|
+
else:
|
|
204
|
+
self.refmol = oechem.OEGraphMol(ref)
|
|
205
|
+
# Ensure the reference molecule has proper 2D depiction coordinates
|
|
206
|
+
oedepict.OEPrepareDepiction(self.refmol, False)
|
|
207
|
+
self.ss = oechem.OESubSearch(self.refmol, oechem.OEExprOpts_DefaultAtoms, oechem.OEExprOpts_DefaultBonds)
|
|
208
|
+
|
|
209
|
+
def validate(self, mol: oechem.OEMolBase) -> bool:
|
|
210
|
+
"""
|
|
211
|
+
Validate that the molecule has a match to this substructure search.
|
|
212
|
+
|
|
213
|
+
:param mol: Molecule to search.
|
|
214
|
+
:returns: True if there is a match to this substructure search.
|
|
215
|
+
"""
|
|
216
|
+
oechem.OEPrepareSearch(mol, self.ss)
|
|
217
|
+
return self.ss.SingleMatch(mol)
|
|
218
|
+
|
|
219
|
+
def align(self, mol: oechem.OEMolBase) -> bool:
|
|
220
|
+
"""
|
|
221
|
+
Align molecule to the substructure pattern.
|
|
222
|
+
|
|
223
|
+
:param mol: Molecule to align.
|
|
224
|
+
:returns: True if the alignment was successful.
|
|
225
|
+
"""
|
|
226
|
+
oechem.OEPrepareSearch(mol, self.ss)
|
|
227
|
+
alignres = oedepict.OEPrepareAlignedDepiction(mol, self.ss)
|
|
228
|
+
result = alignres.IsValid()
|
|
229
|
+
log.debug("OEPrepareAlignedDepiction (substructure) returned: %s", result)
|
|
230
|
+
return result
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
class OEMCSSearchAligner(Aligner):
|
|
234
|
+
"""Aligner using Maximum Common Substructure (MCS) search for 2D molecule alignment."""
|
|
235
|
+
|
|
236
|
+
def __init__(
|
|
237
|
+
self,
|
|
238
|
+
ref: oechem.OEMCSSearch | oechem.OEMolBase,
|
|
239
|
+
*,
|
|
240
|
+
func: Literal["atoms", "bonds", "atoms_and_cycles", "bonds_and_cycles"] = "bonds_and_cycles",
|
|
241
|
+
min_atoms: int = 1,
|
|
242
|
+
**_kwargs
|
|
243
|
+
):
|
|
244
|
+
"""Create an MCS-based aligner.
|
|
245
|
+
|
|
246
|
+
:param ref: Reference for alignment. Can be:
|
|
247
|
+
|
|
248
|
+
- ``OEMCSSearch``: Pre-configured MCS search object.
|
|
249
|
+
- ``OEMolBase``: Reference molecule for MCS calculation.
|
|
250
|
+
|
|
251
|
+
:param func: MCS evaluation function to use:
|
|
252
|
+
|
|
253
|
+
- ``"atoms"``: Maximize atom count.
|
|
254
|
+
- ``"bonds"``: Maximize bond count.
|
|
255
|
+
- ``"atoms_and_cycles"``: Maximize atoms while preserving complete cycles.
|
|
256
|
+
- ``"bonds_and_cycles"``: Maximize bonds while preserving complete cycles.
|
|
257
|
+
|
|
258
|
+
:param min_atoms: Minimum number of atoms required in the MCS.
|
|
259
|
+
:param _kwargs: Additional keyword arguments (ignored, for API compatibility).
|
|
260
|
+
"""
|
|
261
|
+
self.refmol = None
|
|
262
|
+
|
|
263
|
+
if isinstance(ref, oechem.OEMCSSearch):
|
|
264
|
+
self.mcss = oechem.OEMCSSearch(ref)
|
|
265
|
+
|
|
266
|
+
else:
|
|
267
|
+
self.refmol = ref.CreateCopy()
|
|
268
|
+
# Ensure the reference molecule has proper 2D depiction coordinates
|
|
269
|
+
oedepict.OEPrepareDepiction(self.refmol, False)
|
|
270
|
+
|
|
271
|
+
# Currently just using default parameters
|
|
272
|
+
self.mcss = oechem.OEMCSSearch(oechem.OEMCSType_Approximate)
|
|
273
|
+
self.mcss.Init(self.refmol, oechem.OEExprOpts_DefaultAtoms, oechem.OEExprOpts_DefaultBonds)
|
|
274
|
+
|
|
275
|
+
if func == "atoms":
|
|
276
|
+
self.mcss.SetMCSFunc(oechem.OEMCSMaxAtoms())
|
|
277
|
+
elif func == "bonds":
|
|
278
|
+
self.mcss.SetMCSFunc(oechem.OEMCSMaxBonds())
|
|
279
|
+
elif func == "atoms_and_cycles":
|
|
280
|
+
self.mcss.SetMCSFunc(oechem.OEMCSMaxAtomsCompleteCycles())
|
|
281
|
+
elif func == "bonds_and_cycles":
|
|
282
|
+
self.mcss.SetMCSFunc(oechem.OEMCSMaxBondsCompleteCycles())
|
|
283
|
+
else:
|
|
284
|
+
raise ValueError(f'Unknown MCS evaluation function name: {func}')
|
|
285
|
+
|
|
286
|
+
# Other options
|
|
287
|
+
self.mcss.SetMinAtoms(min_atoms)
|
|
288
|
+
|
|
289
|
+
def validate(self, mol: oechem.OEMolBase) -> bool:
|
|
290
|
+
"""
|
|
291
|
+
Validate that a maximum common substructure exists in a query molecule.
|
|
292
|
+
|
|
293
|
+
:param mol: Molecule to search.
|
|
294
|
+
:returns: True if the molecule contains the maximum common substructure.
|
|
295
|
+
"""
|
|
296
|
+
return self.mcss.SingleMatch(mol)
|
|
297
|
+
|
|
298
|
+
def align(self, mol: oechem.OEMolBase) -> bool:
|
|
299
|
+
"""
|
|
300
|
+
Align molecule using the maximum common substructure.
|
|
301
|
+
|
|
302
|
+
:param mol: Molecule to align.
|
|
303
|
+
:returns: True if the alignment was successful.
|
|
304
|
+
"""
|
|
305
|
+
alignres = oedepict.OEPrepareAlignedDepiction(mol, self.mcss)
|
|
306
|
+
result = alignres.IsValid()
|
|
307
|
+
log.debug("OEPrepareAlignedDepiction (MCS) returned: %s", result)
|
|
308
|
+
return result
|
|
294
309
|
|
|
295
310
|
|
|
296
311
|
class OEFingerprintAligner(Aligner):
|
|
312
|
+
"""Aligner using fingerprint similarity and overlap for 2D molecule alignment.
|
|
313
|
+
|
|
314
|
+
This aligner uses molecular fingerprints to identify common structural
|
|
315
|
+
features between molecules and aligns based on the fingerprint overlap.
|
|
297
316
|
"""
|
|
298
|
-
Fingerprint aligner
|
|
299
|
-
"""
|
|
300
|
-
# Just using the default tree fingerprint type for the alignment
|
|
301
317
|
|
|
302
318
|
def __init__(
|
|
303
319
|
self,
|
|
@@ -311,7 +327,20 @@ class OEFingerprintAligner(Aligner):
|
|
|
311
327
|
atom_type: str | int = oegraphsim.OEFPAtomType_DefaultTreeAtom,
|
|
312
328
|
bond_type: str | int = oegraphsim.OEFPBondType_DefaultTreeBond
|
|
313
329
|
):
|
|
314
|
-
|
|
330
|
+
"""Create a fingerprint-based aligner.
|
|
331
|
+
|
|
332
|
+
:param refmol: Reference molecule for alignment.
|
|
333
|
+
:param threshold: Minimum Tanimoto similarity required to attempt alignment.
|
|
334
|
+
:param fptype: Fingerprint type ("path", "circular", or "tree").
|
|
335
|
+
:param num_bits: Number of bits in the fingerprint.
|
|
336
|
+
:param min_distance: Minimum path/radius distance for fingerprint.
|
|
337
|
+
:param max_distance: Maximum path/radius distance for fingerprint.
|
|
338
|
+
:param atom_type: Atom type for fingerprint generation. Can be an integer
|
|
339
|
+
constant or a string name (e.g., "default", "aromaticity").
|
|
340
|
+
:param bond_type: Bond type for fingerprint generation. Can be an integer
|
|
341
|
+
constant or a string name (e.g., "default", "inring").
|
|
342
|
+
"""
|
|
343
|
+
# Similarity threshold to apply alignment
|
|
315
344
|
self.threshold = threshold
|
|
316
345
|
|
|
317
346
|
# Fingerprint maker
|
|
@@ -326,65 +355,100 @@ class OEFingerprintAligner(Aligner):
|
|
|
326
355
|
)
|
|
327
356
|
|
|
328
357
|
# Reference molecule and fingerprint
|
|
329
|
-
self.refmol =
|
|
358
|
+
self.refmol = oechem.OEGraphMol(refmol)
|
|
330
359
|
self.reffp = None
|
|
331
360
|
self.fptype = None
|
|
332
361
|
|
|
333
362
|
if self.refmol.IsValid():
|
|
363
|
+
# Ensure the reference molecule has proper 2D depiction coordinates (but retain existing coordinates)
|
|
364
|
+
oedepict.OEPrepareDepiction(self.refmol, False)
|
|
334
365
|
self.reffp = self.make_fp(self.refmol)
|
|
335
366
|
self.fptype = self.reffp.GetFPTypeBase()
|
|
336
367
|
|
|
368
|
+
else:
|
|
369
|
+
log.warning("Reference molecule for fingerprint-based alignment is not valid")
|
|
370
|
+
|
|
337
371
|
def validate(self, mol: oechem.OEMolBase) -> bool:
|
|
338
372
|
if self.reffp is None:
|
|
339
373
|
return False
|
|
340
374
|
|
|
341
375
|
fp = self.make_fp(mol)
|
|
342
|
-
|
|
376
|
+
sim = oegraphsim.OETanimoto(fp, self.reffp)
|
|
377
|
+
log.debug("Fingerprint Tanimoto similarity: %.3f (threshold: %.3f)", sim, self.threshold)
|
|
378
|
+
return sim >= self.threshold
|
|
343
379
|
|
|
344
380
|
def align(self, mol: oechem.OEMolBase) -> bool:
|
|
345
381
|
if self.fptype is None:
|
|
346
382
|
return False
|
|
347
383
|
|
|
348
|
-
overlaps = oegraphsim.OEGetFPOverlap(
|
|
349
|
-
|
|
384
|
+
overlaps = oegraphsim.OEGetFPOverlap(self.refmol, mol, self.fptype)
|
|
385
|
+
result = oedepict.OEPrepareMultiAlignedDepiction(mol, self.refmol, overlaps)
|
|
350
386
|
|
|
387
|
+
log.debug("OEPrepareMultiAlignedDepiction (FP) returned: %s", result)
|
|
388
|
+
return result
|
|
351
389
|
|
|
352
|
-
|
|
390
|
+
|
|
391
|
+
# Aligners registry
|
|
353
392
|
_ALIGNERS = {
|
|
354
|
-
|
|
393
|
+
"substructure": OESubSearchAligner,
|
|
355
394
|
"fingerprint": OEFingerprintAligner,
|
|
356
|
-
|
|
395
|
+
"mcss": OEMCSSearchAligner
|
|
357
396
|
}
|
|
358
397
|
|
|
359
398
|
|
|
360
399
|
def create_aligner(
|
|
361
|
-
ref: oechem.OEMolBase,
|
|
362
|
-
method: Literal["fp", "fingerprint"] = None,
|
|
400
|
+
ref: oechem.OEMolBase | oechem.OESubSearch | oechem.OEMCSSearch | str,
|
|
401
|
+
method: Literal["substructure", "ss", "mcss", "fp", "fingerprint"] = None,
|
|
363
402
|
**kwargs
|
|
364
403
|
) -> Aligner:
|
|
365
404
|
"""
|
|
366
|
-
Create an aligner for the given reference
|
|
367
|
-
|
|
368
|
-
Note: Only fingerprint alignment is currently supported. Substructure and MCS aligners
|
|
369
|
-
are disabled due to OpenEye bugs reported on 11/30/2025.
|
|
405
|
+
Create an aligner for the given reference.
|
|
370
406
|
|
|
371
|
-
:param ref: Alignment reference molecule
|
|
372
|
-
:param method: Alignment method (
|
|
373
|
-
|
|
374
|
-
:
|
|
407
|
+
:param ref: Alignment reference - can be a molecule, substructure search, MCS search, or SMARTS string.
|
|
408
|
+
:param method: Alignment method ("substructure"/"ss", "mcss", "fingerprint"/"fp").
|
|
409
|
+
If None, the method is auto-detected based on the reference type.
|
|
410
|
+
:param kwargs: Keyword arguments passed to the aligner constructor.
|
|
411
|
+
:returns: Configured aligner instance.
|
|
375
412
|
"""
|
|
376
413
|
# Normalize the method
|
|
377
414
|
if method is not None:
|
|
378
415
|
_method = method.lower()
|
|
379
416
|
|
|
380
|
-
if _method in ("
|
|
417
|
+
if _method in ("substructure", "ss"):
|
|
418
|
+
method = "substructure"
|
|
419
|
+
elif _method in ("fingerprint", "fp"):
|
|
381
420
|
method = "fingerprint"
|
|
421
|
+
elif _method == "mcss":
|
|
422
|
+
method = "mcss"
|
|
423
|
+
else:
|
|
424
|
+
raise ValueError(f'Unknown depiction alignment method: {method}. Valid options: "substructure"/"ss", "mcss", "fingerprint"/"fp".')
|
|
425
|
+
|
|
426
|
+
# Auto-detect method based on reference type if not specified
|
|
427
|
+
if isinstance(ref, str):
|
|
428
|
+
# SMARTS string - use substructure aligner
|
|
429
|
+
log.debug("Using substructure aligner for SMARTS string alignment reference")
|
|
430
|
+
return OESubSearchAligner(ref, **kwargs)
|
|
431
|
+
|
|
432
|
+
elif isinstance(ref, oechem.OESubSearch):
|
|
433
|
+
log.debug("Using substructure aligner for oechem.OESubSearch alignment reference")
|
|
434
|
+
return OESubSearchAligner(ref, **kwargs)
|
|
435
|
+
|
|
436
|
+
elif isinstance(ref, oechem.OEMCSSearch):
|
|
437
|
+
log.debug("Using MCS aligner for oechem.OEMCSSearch alignment reference")
|
|
438
|
+
return OEMCSSearchAligner(ref, **kwargs)
|
|
439
|
+
|
|
440
|
+
elif isinstance(ref, oechem.OEMolBase):
|
|
441
|
+
# Use specified method or default to fingerprint
|
|
442
|
+
if method == "substructure":
|
|
443
|
+
log.debug("Using substructure aligner for oechem.OEMolBase alignment reference")
|
|
444
|
+
return OESubSearchAligner(ref, **kwargs)
|
|
445
|
+
elif method == "mcss":
|
|
446
|
+
log.debug("Using MCS aligner for oechem.OEMolBase alignment reference")
|
|
447
|
+
return OEMCSSearchAligner(ref, **kwargs)
|
|
382
448
|
else:
|
|
383
|
-
|
|
449
|
+
# Default to fingerprint aligner for molecules
|
|
450
|
+
log.debug("Using fingerprint aligner for oechem.OEMolBase alignment reference")
|
|
451
|
+
return OEFingerprintAligner(ref, **kwargs)
|
|
384
452
|
|
|
385
|
-
# Only fingerprint aligner is currently available
|
|
386
|
-
if isinstance(ref, oechem.OEMolBase):
|
|
387
|
-
log.debug("Using fingerprint aligner for oechem.OEMolBase alignment reference")
|
|
388
|
-
return OEFingerprintAligner(ref, **kwargs)
|
|
389
453
|
else:
|
|
390
|
-
raise TypeError(f'Unsupported alignment reference type: {type(ref)}.
|
|
454
|
+
raise TypeError(f'Unsupported alignment reference type: {type(ref)}.')
|
cnotebook/context.py
CHANGED
|
@@ -30,10 +30,18 @@ T = TypeVar('T')
|
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
class DeferredValue(Generic[T]):
|
|
33
|
+
"""A value that can be deferred to the global CNotebook context.
|
|
34
|
+
|
|
35
|
+
When a value is set to ``DEFERRED``, accessing it will look up the
|
|
36
|
+
corresponding attribute from the global context instead.
|
|
33
37
|
"""
|
|
34
|
-
|
|
35
|
-
"""
|
|
38
|
+
|
|
36
39
|
def __init__(self, name: str, value: T | _Deferred):
|
|
40
|
+
"""Create a deferred value.
|
|
41
|
+
|
|
42
|
+
:param name: Attribute name to look up in global context when deferred.
|
|
43
|
+
:param value: Initial value, or ``DEFERRED`` to use global context.
|
|
44
|
+
"""
|
|
37
45
|
self.name = name
|
|
38
46
|
self._value = value
|
|
39
47
|
self._initial_value = value
|
|
@@ -79,9 +87,14 @@ class DeferredValue(Generic[T]):
|
|
|
79
87
|
|
|
80
88
|
|
|
81
89
|
class CNotebookContext:
|
|
90
|
+
"""Context for rendering OpenEye objects in IPython/Jupyter environments.
|
|
91
|
+
|
|
92
|
+
This context controls how molecules and other OpenEye objects are rendered
|
|
93
|
+
as images. It supports deferred values that fall back to a global context.
|
|
94
|
+
|
|
95
|
+
:cvar supported_mime_types: Mapping of image formats to MIME types.
|
|
82
96
|
"""
|
|
83
|
-
|
|
84
|
-
"""
|
|
97
|
+
|
|
85
98
|
# Supported image formats and their MIME types for rendering
|
|
86
99
|
supported_mime_types = {
|
|
87
100
|
'png': 'image/png',
|
|
@@ -97,7 +110,8 @@ class CNotebookContext:
|
|
|
97
110
|
min_height: float | None | _Deferred = 200.0,
|
|
98
111
|
max_width: float | None | _Deferred = None,
|
|
99
112
|
max_height: float | None | _Deferred = None,
|
|
100
|
-
structure_scale:
|
|
113
|
+
structure_scale: float | _Deferred = oedepict.OEScale_Default * 0.6,
|
|
114
|
+
atom_label_font_scale: float | _Deferred = 1.0,
|
|
101
115
|
title_font_scale: float | _Deferred = 1.0,
|
|
102
116
|
image_format: str | _Deferred = "png",
|
|
103
117
|
bond_width_scaling: bool | _Deferred = False,
|
|
@@ -105,16 +119,24 @@ class CNotebookContext:
|
|
|
105
119
|
scope: Literal["local", "global"] = "global",
|
|
106
120
|
title: bool = True
|
|
107
121
|
):
|
|
108
|
-
"""
|
|
109
|
-
|
|
110
|
-
:param width: Image width
|
|
111
|
-
:param height: Image height
|
|
112
|
-
:param min_width: Minimum image width (prevents tiny images)
|
|
113
|
-
:param min_height: Minimum image height (prevents tiny images)
|
|
114
|
-
:param
|
|
115
|
-
:param
|
|
116
|
-
:param
|
|
117
|
-
:param
|
|
122
|
+
"""Create a rendering context.
|
|
123
|
+
|
|
124
|
+
:param width: Image width in pixels. If 0, determined by structure scale.
|
|
125
|
+
:param height: Image height in pixels. If 0, determined by structure scale.
|
|
126
|
+
:param min_width: Minimum image width in pixels (prevents tiny images).
|
|
127
|
+
:param min_height: Minimum image height in pixels (prevents tiny images).
|
|
128
|
+
:param max_width: Maximum image width in pixels, or None for no limit.
|
|
129
|
+
:param max_height: Maximum image height in pixels, or None for no limit.
|
|
130
|
+
:param structure_scale: Scale factor for structure rendering.
|
|
131
|
+
:param atom_label_font_scale: Scale factor for atom labels (0.5 to 2.0).
|
|
132
|
+
:param title_font_scale: Scale factor for title font (0.5 to 2.0).
|
|
133
|
+
:param image_format: Output image format ("png" or "svg").
|
|
134
|
+
:param bond_width_scaling: Whether to scale bond widths with structure scale.
|
|
135
|
+
:param callbacks: List of callables to invoke on OE2DMolDisplay before rendering.
|
|
136
|
+
Each callback receives the display object and can modify it.
|
|
137
|
+
:param scope: Context scope - "local" defers unset values to global context,
|
|
138
|
+
"global" uses defaults directly.
|
|
139
|
+
:param title: Whether to display molecule titles.
|
|
118
140
|
"""
|
|
119
141
|
self._width = DeferredValue[float]("width", width)
|
|
120
142
|
self._height = DeferredValue[float]("height", height)
|
|
@@ -122,7 +144,8 @@ class CNotebookContext:
|
|
|
122
144
|
self._min_width = DeferredValue[float | None]("min_width", min_width)
|
|
123
145
|
self._max_width = DeferredValue[float | None]("max_width", max_width)
|
|
124
146
|
self._max_height = DeferredValue[float | None]("max_height", max_height)
|
|
125
|
-
self._structure_scale = DeferredValue[
|
|
147
|
+
self._structure_scale = DeferredValue[float]("structure_scale", structure_scale)
|
|
148
|
+
self._atom_label_font_scale = DeferredValue[float | None]("atom_label_font_scale", atom_label_font_scale)
|
|
126
149
|
self._title_font_scale = DeferredValue[float]("title_font_scale", title_font_scale)
|
|
127
150
|
self._image_format = DeferredValue[str]("image_format", image_format)
|
|
128
151
|
self._bond_width_scaling = DeferredValue[bool]("bond_width_scaling", bond_width_scaling)
|
|
@@ -209,13 +232,21 @@ class CNotebookContext:
|
|
|
209
232
|
self._min_height.set(value)
|
|
210
233
|
|
|
211
234
|
@property
|
|
212
|
-
def structure_scale(self) ->
|
|
235
|
+
def structure_scale(self) -> float:
|
|
213
236
|
return self._structure_scale.get()
|
|
214
237
|
|
|
215
238
|
@structure_scale.setter
|
|
216
|
-
def structure_scale(self, value:
|
|
239
|
+
def structure_scale(self, value: float) -> None:
|
|
217
240
|
self._structure_scale.set(value)
|
|
218
241
|
|
|
242
|
+
@property
|
|
243
|
+
def atom_label_font_scale(self) -> float:
|
|
244
|
+
return self._atom_label_font_scale.get()
|
|
245
|
+
|
|
246
|
+
@atom_label_font_scale.setter
|
|
247
|
+
def atom_label_font_scale(self, value: float) -> None:
|
|
248
|
+
self._atom_label_font_scale.set(value)
|
|
249
|
+
|
|
219
250
|
@property
|
|
220
251
|
def title_font_scale(self) -> float:
|
|
221
252
|
return self._title_font_scale.get()
|
|
@@ -275,6 +306,7 @@ class CNotebookContext:
|
|
|
275
306
|
opts.SetScale(self.structure_scale)
|
|
276
307
|
opts.SetTitleFontScale(self.title_font_scale)
|
|
277
308
|
opts.SetBondWidthScaling(self.bond_width_scaling)
|
|
309
|
+
opts.SetAtomLabelFontScale(self.atom_label_font_scale)
|
|
278
310
|
|
|
279
311
|
if not self.title:
|
|
280
312
|
opts.SetTitleLocation(oedepict.OETitleLocation_Hidden)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Interactive molecule grid for Jupyter and Marimo notebooks."""
|
|
2
|
+
|
|
3
|
+
from cnotebook.grid.grid import MolGrid
|
|
4
|
+
from typing import Iterable, List, Optional, Union
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def molgrid(
|
|
8
|
+
mols: Iterable,
|
|
9
|
+
*,
|
|
10
|
+
title: Union[bool, str, None] = True,
|
|
11
|
+
tooltip_fields: Optional[List[str]] = None,
|
|
12
|
+
n_items_per_page: int = 24,
|
|
13
|
+
width: int = 200,
|
|
14
|
+
height: int = 200,
|
|
15
|
+
image_format: str = "svg",
|
|
16
|
+
select: bool = True,
|
|
17
|
+
information: bool = True,
|
|
18
|
+
data: Optional[Union[str, List[str]]] = None,
|
|
19
|
+
search_fields: Optional[List[str]] = None,
|
|
20
|
+
name: Optional[str] = None,
|
|
21
|
+
) -> MolGrid:
|
|
22
|
+
"""Create an interactive molecule grid.
|
|
23
|
+
|
|
24
|
+
:param mols: Iterable of OpenEye molecule objects.
|
|
25
|
+
:param title: Title display mode. True uses molecule's title, a string
|
|
26
|
+
specifies a field name, None/False hides titles.
|
|
27
|
+
:param tooltip_fields: List of fields for tooltip.
|
|
28
|
+
:param n_items_per_page: Molecules per page.
|
|
29
|
+
:param width: Image width in pixels (default 200).
|
|
30
|
+
:param height: Image height in pixels (default 200).
|
|
31
|
+
:param image_format: "svg" or "png" (default "svg").
|
|
32
|
+
:param select: Enable selection checkboxes.
|
|
33
|
+
:param information: Enable info button with hover tooltip.
|
|
34
|
+
:param data: Column(s) to display in info tooltip. If None, auto-detects
|
|
35
|
+
simple types (string, int, float) from DataFrame.
|
|
36
|
+
:param search_fields: Fields for text search.
|
|
37
|
+
:param name: Grid identifier.
|
|
38
|
+
:returns: MolGrid instance.
|
|
39
|
+
"""
|
|
40
|
+
return MolGrid(
|
|
41
|
+
mols,
|
|
42
|
+
title=title,
|
|
43
|
+
tooltip_fields=tooltip_fields,
|
|
44
|
+
n_items_per_page=n_items_per_page,
|
|
45
|
+
width=width,
|
|
46
|
+
height=height,
|
|
47
|
+
image_format=image_format,
|
|
48
|
+
select=select,
|
|
49
|
+
information=information,
|
|
50
|
+
data=data,
|
|
51
|
+
search_fields=search_fields,
|
|
52
|
+
name=name,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
__all__ = ["MolGrid", "molgrid"]
|