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/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
- Base class for a 2D molecule aligner
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
- return self.align(mol)
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
- # FIXME: Bug reported OpenEye on 11/30/2025 regarding using OESubSearch matches in OEPrepareMultiAlignedDepiction
159
- # class OESubSearchAligner(Aligner):
160
- # """
161
- # 2D molecule substructure alignment
162
- # """
163
- # def __init__(self, ref: oechem.OESubSearch | oechem.OEMolBase, **_kwargs):
164
- #
165
- # # Reference molecule with 2D coordinates
166
- # self.refmol = None
167
- #
168
- # # In the future, make these configurable
169
- # self.alignment_options = oedepict.OEAlignmentOptions()
170
- #
171
- # if isinstance(ref, (oechem.OESubSearch, str)):
172
- # self.ss = oechem.OESubSearch(ref)
173
- #
174
- # else:
175
- # self.refmol = oechem.OEGraphMol(ref)
176
- # self.ss = oechem.OESubSearch(self.refmol, oechem.OEExprOpts_DefaultAtoms, oechem.OEExprOpts_DefaultBonds)
177
- #
178
- # def validate(self, mol: oechem.OEMolBase) -> bool:
179
- # """
180
- # Validate that the molecule has a match to this substructure search
181
- # :param mol: Molecule to search
182
- # :return: True if there is a match to this substructure search
183
- # """
184
- # oechem.OEPrepareSearch(mol, self.ss)
185
- # return self.ss.SingleMatch(mol)
186
- #
187
- # def align(self, mol: oechem.OEMolBase) -> bool:
188
- # """
189
- # Align to this substructure trying the following
190
- #
191
- # 1. If we had a reference molecule, then try to maximize the alignment to that reference molecule using the
192
- # OpenEye multiple aligner (this works VERY WELL even if there are multiple matches to the reference)
193
- #
194
- # 2. Standard aligned depiction, which fails if there are multiple matches to the substructure
195
- #
196
- # :param mol: Molecule to align
197
- # :return: True if the alignment was successful
198
- # """
199
- # ok = False
200
- #
201
- # if self.refmol is not None:
202
- # oechem.OEPrepareSearch(mol, self.ss)
203
- # ok = oedepict.OEPrepareMultiAlignedDepiction(
204
- # mol,
205
- # self.refmol,
206
- # self.ss.Match(mol)
207
- # )
208
- #
209
- # if not ok:
210
- # alignres = oedepict.OEPrepareAlignedDepiction(
211
- # mol,
212
- # self.ss,
213
- # self.alignment_options
214
- # )
215
- #
216
- # ok = alignres.IsValid()
217
- #
218
- # return ok
219
- #
220
- #
221
- # FIXME: Bug reported OpenEye on 11/30/2025 regarding using OEMCSSearch matches in OEPrepareMultiAlignedDepiction
222
- # class OEMCSSearchAligner(Aligner):
223
- # """
224
- # 2D molecule MCS alignment
225
- # """
226
- # def __init__(
227
- # self,
228
- # ref: oechem.OEMCSSearch | oechem.OEMolBase,
229
- # *,
230
- # func: Literal["atoms", "bonds", "atoms_and_cycles", "bonds_and_cycles"] = "bonds_cycles",
231
- # min_atoms: int = 1,
232
- # **_kwargs
233
- # ):
234
- #
235
- # self.refmol = None
236
- #
237
- # # In the future, make these configurable
238
- # self.alignment_options = oedepict.OEAlignmentOptions()
239
- #
240
- # if isinstance(ref, oechem.OEMCSSearch):
241
- # self.mcss = oechem.OEMCSSearch(ref)
242
- #
243
- # else:
244
- # self.refmol = ref.CreateCopy()
245
- #
246
- # # Currently just using default parameters
247
- # self.mcss = oechem.OEMCSSearch(oechem.OEMCSType_Approximate)
248
- # self.mcss.Init(self.refmol, oechem.OEExprOpts_DefaultAtoms, oechem.OEExprOpts_DefaultBonds)
249
- #
250
- # if func == "atoms":
251
- # self.mcss.SetMCSFunc(oechem.OEMCSMaxAtoms())
252
- # elif func == "bonds":
253
- # self.mcss.SetMCSFunc(oechem.OEMCSMaxBonds())
254
- # elif func == "atoms_and_cycles":
255
- # self.mcss.SetMCSFunc(oechem.OEMCSMaxAtomsCompleteCycles())
256
- # elif func == "bonds_and_cycles":
257
- # self.mcss.SetMCSFunc(oechem.OEMCSMaxBondsCompleteCycles())
258
- # else:
259
- # raise ValueError(f'Unknown MCS evaluation function name: {func}')
260
- #
261
- # # Other options
262
- # self.mcss.SetMinAtoms(min_atoms)
263
- #
264
- # def validate(self, mol: oechem.OEMolBase) -> bool:
265
- # """
266
- # Validate that a maximum common substructure exists in a query molecule (within a threshold)
267
- # :param mol: Molecule to search
268
- # :return: True if the molecule contains the maximum common substructure
269
- # """
270
- # return self.mcss.SingleMatch(mol)
271
- #
272
- # def align(self, mol: oechem.OEMolBase) -> bool:
273
- # ok = False
274
- #
275
- # if self.refmol is not None:
276
- #
277
- # ok = oedepict.OEPrepareMultiAlignedDepiction(
278
- # mol,
279
- # self.refmol,
280
- # self.mcss.Match(mol)
281
- # )
282
- #
283
- # if not ok:
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
- # Simiarity threshold to apply alignment
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 = refmol.CreateCopy()
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
- return oegraphsim.OETanimoto(fp, self.reffp) >= self.threshold
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(mol, self.refmol, self.fptype)
349
- return oedepict.OEPrepareMultiAlignedDepiction(mol, self.refmol, overlaps)
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
- # Substructure aligners
390
+
391
+ # Aligners registry
353
392
  _ALIGNERS = {
354
- # "substructure": OESubSearchAligner,
393
+ "substructure": OESubSearchAligner,
355
394
  "fingerprint": OEFingerprintAligner,
356
- # "mcss": OEMCSSearchAligner
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 molecule.
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 (only "fp" or "fingerprint" supported)
373
- :param kwargs: Keyword arguments for the OEFingerprintAligner
374
- :return: Configured aligner instance
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 ("fingerprint", "fp"):
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
- raise ValueError(f'Unknown depiction alignment method: {method}. Only "fingerprint"/"fp" is currently supported.')
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)}. Only oechem.OEMolBase is currently supported.')
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
- Value that can be deferred to the global CNotebook context
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
- Context in which to render OpenEye objects within IPython
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: int | _Deferred = oedepict.OEScale_Default * 0.9,
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
- Create the render context
110
- :param width: Image width (default of None means it is determined by the structure scale)
111
- :param height: Image height (default of None means it is determined by the structure scale)
112
- :param min_width: Minimum image width (prevents tiny images)
113
- :param min_height: Minimum image height (prevents tiny images)
114
- :param structure_scale: Structure scale
115
- :param title_font_scale: Font scaling (valid is 0.5 to 2.0)
116
- :param image_format: Image format
117
- :param bond_width_scaling: Bond width scaling
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[int]("structure_scale", structure_scale)
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) -> int:
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: int) -> None:
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"]