Skip to content

datamol.viz

Vizualize molecule in 2D or 3D

Generate an image out of a molecule or a list of molecules.

Parameters:

Name Type Description Default
mols Union[List[dm.Mol], dm.Mol]

One or a list of molecules.

required
legends Union[List[Union[str, None]], str, None]

A string or a list of string as legend for every molecules.

None
n_cols int

Number of molecules per column.

4
use_svg bool

Whether to ouput an SVG (or a PNG).

True
mol_size Union[Tuple[int, int], int]

A int or a tuple of int defining the size per molecule.

(300, 300)
highlight_atom Optional[List[List[int]]]

the atoms to highlight.

None
highlight_bond Optional[List[List[int]]]

The bonds to highlight.

None
outfile Optional[str]

Path where to save the image (local or remote path).

None
max_mols int

The maximum number of molecules to display.

32
copy bool

Whether to copy the molecules or not.

True
indices bool

Whether to draw the atom indices.

False
bond_indices bool

Whether to draw the bond indices.

False
bond_line_width int

The width of the bond lines.

2
legend_fontsize int

Font size for the legend.

16
kekulize bool

Run kekulization routine on molecules. Skipped if fails.

True
align Union[dm.Mol, str, bool]

Whether to align the 2D coordinates of the molecules. - If set to True, align all molecules with dm.align.auto_align_many(). - If set to a molecule, it is used as a template for alignment with dm.align.template_align(). - If set to False, no alignment is performed. For a more custom alignment, we suggest using directly the module dm.align instead.

False
**kwargs Any

Additional arguments to pass to the drawing function. See RDKit documentation related to MolDrawOptions for more details at https://www.rdkit.org/docs/source/rdkit.Chem.Draw.rdMolDraw2D.html.

{}
Source code in datamol/viz/_viz.py
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
def to_image(
    mols: Union[List[dm.Mol], dm.Mol],
    legends: Union[List[Union[str, None]], str, None] = None,
    n_cols: int = 4,
    use_svg: bool = True,
    mol_size: Union[Tuple[int, int], int] = (300, 300),
    highlight_atom: Optional[List[List[int]]] = None,
    highlight_bond: Optional[List[List[int]]] = None,
    outfile: Optional[str] = None,
    max_mols: int = 32,
    copy: bool = True,
    indices: bool = False,
    bond_indices: bool = False,
    bond_line_width: int = 2,
    stereo_annotations: bool = True,
    legend_fontsize: int = 16,
    kekulize: bool = True,
    align: Union[dm.Mol, str, bool] = False,
    **kwargs: Any,
):
    """Generate an image out of a molecule or a list of molecules.

    Args:
        mols: One or a list of molecules.
        legends: A string or a list of string as legend for every molecules.
        n_cols: Number of molecules per column.
        use_svg: Whether to ouput an SVG (or a PNG).
        mol_size: A int or a tuple of int defining the size per molecule.
        highlight_atom: the atoms to highlight.
        highlight_bond: The bonds to highlight.
        outfile: Path where to save the image (local or remote path).
        max_mols: The maximum number of molecules to display.
        copy: Whether to copy the molecules or not.
        indices: Whether to draw the atom indices.
        bond_indices: Whether to draw the bond indices.
        bond_line_width: The width of the bond lines.
        legend_fontsize: Font size for the legend.
        kekulize: Run kekulization routine on molecules. Skipped if fails.
        align: Whether to align the 2D coordinates of the molecules.
            - If set to True, align all molecules with `dm.align.auto_align_many()`.
            - If set to a molecule, it is used as a template for alignment with `dm.align.template_align()`.
            - If set to False, no alignment is performed.
            For a more custom alignment, we suggest using directly the module `dm.align` instead.
        **kwargs: Additional arguments to pass to the drawing function. See RDKit
            documentation related to `MolDrawOptions` for more details at
            https://www.rdkit.org/docs/source/rdkit.Chem.Draw.rdMolDraw2D.html.
    """

    if isinstance(mol_size, int):
        mol_size = (mol_size, mol_size)

    if isinstance(mols, dm.Mol):
        mols = [mols]

    if isinstance(legends, str):
        legends = [legends]

    if copy:
        mols = [dm.copy_mol(mol) for mol in mols]

    if max_mols is not None:
        mols = mols[:max_mols]

        if legends is not None:
            legends = legends[:max_mols]

    # Whether to align the molecules
    if isinstance(align, (dm.Mol, str)):
        mols = [dm.align.template_align(mol, template=align) for mol in mols]
    elif align is True:
        mols = dm.align.auto_align_many(mols)

    # Prepare molecules before drawing
    mols = [prepare_mol_for_drawing(mol, kekulize=kekulize) for mol in mols]

    _highlight_atom = highlight_atom
    if highlight_atom is not None and isinstance(highlight_atom[0], int):
        _highlight_atom = [highlight_atom]

    _highlight_bond = highlight_bond
    if highlight_bond is not None and isinstance(highlight_bond[0], int):
        _highlight_bond = [highlight_bond]

    # Don't make the image bigger than it
    if len(mols) < n_cols:
        n_cols = len(mols)

    draw_options = Draw.rdMolDraw2D.MolDrawOptions()
    draw_options.legendFontSize = legend_fontsize
    draw_options.addAtomIndices = indices
    draw_options.addBondIndices = bond_indices
    draw_options.addStereoAnnotation = stereo_annotations
    draw_options.bondLineWidth = bond_line_width

    # Add the custom drawing options.
    _kwargs = {}
    for k, v in kwargs.items():
        if hasattr(draw_options, k):
            setattr(draw_options, k, v)
        else:
            _kwargs[k] = v

    image = Draw.MolsToGridImage(
        mols,
        legends=legends,
        molsPerRow=n_cols,
        useSVG=use_svg,
        subImgSize=mol_size,
        highlightAtomLists=_highlight_atom,
        highlightBondLists=_highlight_bond,
        drawOptions=draw_options,
        **_kwargs,
    )

    if outfile is not None:
        with fsspec.open(outfile, "wb") as f:
            if use_svg:
                if isinstance(image, str):
                    # in a terminal process
                    f.write(image.encode())  # type: ignore
                else:
                    # in a jupyter kernel process
                    f.write(image.data.encode())  # type: ignore
            else:
                if isinstance(image, PIL.PngImagePlugin.PngImageFile):  # type: ignore
                    # in a terminal process
                    image.save(f)
                else:
                    # in a jupyter kernel process
                    f.write(image.data)  # type: ignore

    return image

Visualize the conformer(s) of a molecule.

Parameters:

Name Type Description Default
mol Chem.rdchem.Mol

a molecule.

required
conf_id int

The ID of the conformer to show. -1 shows the first conformer. Only works if n_confs is None.

-1
n_confs Union[int, List[int]]

Can be a number of conformers to shows or a list of conformer indices. When None, only the first conformer is displayed. When -1, show all conformers.

None
align_conf bool

Whether to align conformers together.

True
n_cols int

Number of columns. Defaults to 3.

3
sync_views bool

Wether to sync the multiple views.

True
remove_hs bool

Wether to remove the hydrogens of the conformers.

True
width str

The width of the returned view. Defaults to "auto".

'auto'
Source code in datamol/viz/_conformers.py
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
def conformers(
    mol: Chem.rdchem.Mol,
    conf_id: int = -1,
    n_confs: Union[int, List[int]] = None,
    align_conf: bool = True,
    n_cols: int = 3,
    sync_views: bool = True,
    remove_hs: bool = True,
    width: str = "auto",
):
    """Visualize the conformer(s) of a molecule.

    Args:
        mol: a molecule.
        conf_id: The ID of the conformer to show. -1 shows
            the first conformer. Only works if `n_confs` is None.
        n_confs: Can be a number of conformers
            to shows or a list of conformer indices. When None, only the first
            conformer is displayed. When -1, show all conformers.
        align_conf: Whether to align conformers together.
        n_cols: Number of columns. Defaults to 3.
        sync_views: Wether to sync the multiple views.
        remove_hs: Wether to remove the hydrogens of the conformers.
        width: The width of the returned view. Defaults to "auto".
    """

    widgets = _get_ipywidgets()
    nv = _get_nglview()

    if mol.GetNumConformers() == 0:
        raise ValueError(
            "The molecule has 0 conformers. You can generate conformers with `dm.conformers.generate(mol)`."
        )

    # Clone the molecule
    mol = copy.deepcopy(mol)

    if remove_hs:
        mol = Chem.RemoveHs(mol)  # type: ignore
    else:
        mol = Chem.AddHs(mol)  # type: ignore

    if n_confs is None:
        return nv.show_rdkit(mol, conf_id=conf_id)

    # If n_confs is int, convert to list of conformer IDs
    if n_confs == -1:
        n_confs = [conf.GetId() for conf in mol.GetConformers()]
    elif isinstance(n_confs, int):
        if n_confs > mol.GetNumConformers():
            n_confs = mol.GetNumConformers()
        n_confs = list(range(n_confs))  # type: ignore

    if align_conf:
        rdMolAlign.AlignMolConformers(mol, confIds=n_confs)

    # Get number of rows
    n_rows = len(n_confs) // n_cols
    n_rows += 1 if (len(n_confs) % n_cols) > 0 else 0

    # Create a grid
    grid = widgets.GridspecLayout(n_rows, n_cols)  # type: ignore

    # Create and add views to the grid.
    widget_coords = itertools.product(range(n_rows), range(n_cols))
    views = []
    for i, (conf_id, (x, y)) in enumerate(zip(n_confs, widget_coords)):
        view = nv.show_rdkit(mol, conf_id=conf_id)
        view.layout.width = width
        view.layout.align_self = "stretch"
        grid[x, y] = view
        views.append(view)

    # Sync views
    if sync_views:
        for view in views:
            view._set_sync_camera(views)

    return grid

Specific plotting functions

Source code in datamol/viz/_circle_grid.py
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
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
class MolsCircleGrid:
    def __init__(
        self,
        center_mol: Chem.rdchem.Mol,
        circle_mols: List[List[Chem.rdchem.Mol]],
        legend: Optional[str] = None,
        mol_size: Tuple[int, int] = (200, 200),
        circle_margin: int = 50,
        act_mapper: Optional[dict] = None,
    ):
        """Show molecules in concentric rings, with one molecule at the center

        Args:
            center_mol: Molecule at center
            circle_mols: List of molecule for each concentric circle around the center mol
            mol_size: Tuple of width and height for each molecule
            circle_margin: Margin between the circle layers
            act_mapper: Map each molecule to a dictionary of activity
        """
        self.circle_mols = circle_mols
        self.circle_count = len(self.circle_mols)
        self.legend = legend or ""
        self.margin = circle_margin
        self.center_mol = center_mol
        self.mol_size = mol_size
        size = (max(mol_size) + self.margin) * (self.circle_count + 1)
        self.size = size
        self.image = Image.new(mode="RGBA", size=(size, size), color=(255, 255, 255, 0))
        self.midpoint = size // 2
        self.draw = None
        self.act_mapper = act_mapper or {}
        self._draw()

    def show(self, crop=False):
        if crop:
            crop_img = ImageOps.crop(self.image, border=1)
        else:
            crop_img = self.image
        return crop_img.show()

    def save(self, filename):
        self.image.save(filename)

    def _draw(self):
        """Create circles and slices in-memory"""
        draw = ImageDraw.Draw(self.image)
        self.draw = draw
        all_radius = self._draw_circles(draw)
        self._draw_center_mol()
        self._draw_ring_mols(all_radius)
        font = None
        left, top, right, bottom = draw.textbbox((0, 0), self.legend)
        w, h = right - left, bottom - top
        try:
            fn = FontManager()
            fontpath = fn.findfont("Droid sans")
            font = ImageFont.truetype(fontpath, 12 * self.size // 800)
            left, top, right, bottom = font.getbbox(self.legend)
            w, h = right - left, bottom - top
        except:
            pass
        draw.text(
            ((self.size // 2 - w) - 2, self.size - 2 * h),
            self.legend,
            fill="black",
            font=font,
        )
        del draw
        self.draw = None

    def _repr_png_(self):
        bio = io.BytesIO()
        self.image.save(bio, format="PNG")
        return bio.getvalue()

    def _draw_circles(self, draw):
        if self.circle_count <= 0:
            return []
        radius_step = int(self.midpoint / (self.circle_count + 1))
        radius_list = []
        full_range = range(0, self.size // 2, radius_step)

        for i, radius in enumerate(full_range):
            radius += self.margin // 2
            bounding_box = [
                (self.midpoint - radius, self.midpoint - radius),
                (self.midpoint + radius, self.midpoint + radius),
            ]
            if radius > self.margin:
                transp = int(255 - (200 * (i - 1) / len(full_range)))
                draw.arc(bounding_box, 0, 360, fill=(190, 190, 190, transp))
            radius_list.append(radius + radius_step)
        return radius_list

    def _draw_mol_at(
        self,
        mol,
        center_x,
        center_y,
        mol_size=None,
        act_dict={},
        center=False,
        **kwargs: Any,
    ):
        img = mol
        if mol_size is None:
            mol_size = self.mol_size

        if isinstance(mol, Chem.Mol):
            img = Draw.MolToImage(mol, mol_size, kekulize=True, fitImage=True, **kwargs)

        width, height = img.size
        self.image.paste(img, (int(center_x - width / 2), int(center_y - height / 2)))
        txt = []
        for prop, propval in act_dict.items():
            if not isinstance(propval, str):
                propval = "{:.2f}".format(propval)
            txt.append(f"{prop}: {propval}")
        if txt and self.draw is not None:
            txt = "\n".join(txt)
            font = None
            w, h = self.draw.multiline_textsize(txt)
            try:
                fn = FontManager()
                fontpath = fn.findfont("Droid sans")
                font = ImageFont.truetype(fontpath, 18 + center * 8)
                w, h = self.draw.multiline_textsize(txt, font=font)
            except:
                pass

    def _draw_center_mol(self):
        self._draw_mol_at(
            self.center_mol,
            self.midpoint,
            self.midpoint,
            mol_size=[x + self.margin for x in self.mol_size],
            act_dict=self.act_mapper.get(self.center_mol, {}),
            center=True,
        )

    def _draw_ring_mols(self, radius_list):
        for i, mols in enumerate(self.circle_mols):
            radius = radius_list[i]
            ni = len(mols)
            rand_unit = random.random() * 2 * math.pi
            for k, mol in enumerate(mols):
                center_x = radius * math.cos(2 * k * math.pi / ni + rand_unit) + self.midpoint
                center_y = radius * math.sin(2 * k * math.pi / ni + rand_unit) + self.midpoint
                self._draw_mol_at(mol, center_x, center_y, act_dict=self.act_mapper.get(mol, {}))

__init__(center_mol, circle_mols, legend=None, mol_size=(200, 200), circle_margin=50, act_mapper=None)

Show molecules in concentric rings, with one molecule at the center

Parameters:

Name Type Description Default
center_mol Chem.rdchem.Mol

Molecule at center

required
circle_mols List[List[Chem.rdchem.Mol]]

List of molecule for each concentric circle around the center mol

required
mol_size Tuple[int, int]

Tuple of width and height for each molecule

(200, 200)
circle_margin int

Margin between the circle layers

50
act_mapper Optional[dict]

Map each molecule to a dictionary of activity

None
Source code in datamol/viz/_circle_grid.py
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
def __init__(
    self,
    center_mol: Chem.rdchem.Mol,
    circle_mols: List[List[Chem.rdchem.Mol]],
    legend: Optional[str] = None,
    mol_size: Tuple[int, int] = (200, 200),
    circle_margin: int = 50,
    act_mapper: Optional[dict] = None,
):
    """Show molecules in concentric rings, with one molecule at the center

    Args:
        center_mol: Molecule at center
        circle_mols: List of molecule for each concentric circle around the center mol
        mol_size: Tuple of width and height for each molecule
        circle_margin: Margin between the circle layers
        act_mapper: Map each molecule to a dictionary of activity
    """
    self.circle_mols = circle_mols
    self.circle_count = len(self.circle_mols)
    self.legend = legend or ""
    self.margin = circle_margin
    self.center_mol = center_mol
    self.mol_size = mol_size
    size = (max(mol_size) + self.margin) * (self.circle_count + 1)
    self.size = size
    self.image = Image.new(mode="RGBA", size=(size, size), color=(255, 255, 255, 0))
    self.midpoint = size // 2
    self.draw = None
    self.act_mapper = act_mapper or {}
    self._draw()

Show molecules in concentric rings, with one molecule at the center

Parameters:

Name Type Description Default
center_mol Chem.Mol

Molecule at center

required
circle_mols list of list of <Chem.Mol>

List of molecule for each concentric circle around the center mol

required
mol_size tuple

Tuple of width and height for each molecule

(200, 200)
circle_margin int

Margin between the circle layers

50
act_mapper dict

Map each molecule to a dictionary of activity

None
Source code in datamol/viz/_circle_grid.py
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
def circle_grid(
    center_mol: Chem.rdchem.Mol,
    circle_mols: List[List[Chem.rdchem.Mol]],
    legend: Optional[str] = None,
    mol_size: Tuple[int, int] = (200, 200),
    circle_margin: int = 50,
    act_mapper: Optional[dict] = None,
):
    """Show molecules in concentric rings, with one molecule at the center

    Args:
        center_mol (Chem.Mol): Molecule at center
        circle_mols (list of list of <Chem.Mol>): List of molecule for each concentric circle around the center mol
        mol_size (tuple, optional): Tuple of width and height for each molecule
        circle_margin (int, optional): Margin between the circle layers
        act_mapper (dict): Map each molecule to a dictionary of activity
    """
    return MolsCircleGrid(center_mol, circle_mols, legend, mol_size, circle_margin, act_mapper)

Vizualize 2D molecule with highlighted substructures

A generalized interface to access both highlighting options whether the input is as a smiles, smarts or mol

Parameters:

Name Type Description Default
target_molecule Union[str, dm.Mol]

The molecule to be highlighted

required
search_molecules Union[str, List[str], dm.Mol, List[dm.Mol]]

The substructure to be identified

required
mol_size Tuple[int, int]

The size of the image to be returned

(300, 300)
use_svg Optional[bool]

Whether to return an svg or png image

True
r_min float

Radius of the smallest circle around atoms. Length is relative to average bond length (1 = avg. bond len).

0.3
r_dist float

Incremental increase of radius for the next substructure.

0.13
relative_bond_width float

Distance of line to "bond" (line segment between the two atoms). Size is relative to atom_radius.

0.5
line_width int

width of drawn lines.

2
color_list Optional[List[ColorTuple]]

List of tuples with RGBA or RGB values specifying the color of the highlighting.

None
**kwargs Any

Additional arguments to pass to the drawing function. See RDKit documentation related to MolDrawOptions for more details at https://www.rdkit.org/docs/source/rdkit.Chem.Draw.rdMolDraw2D.html.

{}
Source code in datamol/viz/_lasso_highlight.py
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
def lasso_highlight_image(
    target_molecule: Union[str, dm.Mol],
    search_molecules: Union[str, List[str], dm.Mol, List[dm.Mol]],
    mol_size: Tuple[int, int] = (300, 300),
    use_svg: Optional[bool] = True,
    r_min: float = 0.3,
    r_dist: float = 0.13,
    relative_bond_width: float = 0.5,
    color_list: Optional[List[ColorTuple]] = None,
    line_width: int = 2,
    **kwargs: Any,
):
    """A generalized interface to access both highlighting options whether the
    input is as a smiles, smarts or mol

    args:
        target_molecule: The molecule to be highlighted
        search_molecules: The substructure to be identified
        mol_size: The size of the image to be returned
        use_svg: Whether to return an svg or png image
        r_min: Radius of the smallest circle around atoms. Length is relative to average bond length (1 = avg. bond len).
        r_dist: Incremental increase of radius for the next substructure.
        relative_bond_width: Distance of line to "bond" (line segment between the two atoms). Size is relative to `atom_radius`.
        line_width: width of drawn lines.
        color_list: List of tuples with RGBA or RGB values specifying the color of the highlighting.
        **kwargs: Additional arguments to pass to the drawing function. See RDKit
            documentation related to `MolDrawOptions` for more details at
            https://www.rdkit.org/docs/source/rdkit.Chem.Draw.rdMolDraw2D.html.
    """

    # check if the input is valid
    if target_molecule is None or (isinstance(target_molecule, str) and len(target_molecule) == 0):
        raise ValueError("Please enter a valid target molecule or smiles")

    if search_molecules is None or (
        isinstance(search_molecules, str) and len(search_molecules) == 0
    ):
        raise ValueError("Please enter valid search molecules or smarts")

    # less than 1 throws File parsing error: PNG header not recognized over 5,000 leads to a DecompressionBombError later on
    if mol_size[0] < 1 or mol_size[0] > 5000 or mol_size[1] < 1 or mol_size[1] > 5000:
        raise ValueError(
            "To avoid errors please choose a number between 1-5000 for the canvas width or height"
        )

    if isinstance(target_molecule, str):
        target_molecule = dm.to_mol(target_molecule)

    mol = prepare_mol_for_drawing(target_molecule, kekulize=True)

    if mol is None:
        raise ValueError("The molecule has failed to be prepared by `prepare_mol_for_drawing`.")

    if use_svg:
        d = rdMolDraw2D.MolDraw2DSVG(mol_size[0], mol_size[1])
    else:
        d = rdMolDraw2D.MolDraw2DCairo(mol_size[0], mol_size[1])

    # Setting the drawing options
    draw_options = d.drawOptions()
    for k, v in kwargs.items():
        if not hasattr(draw_options, k):
            raise ValueError(
                f"Invalid drawing option: {k}={v}. Check `rdkit.Chem.Draw.rdMolDraw2D.MolDrawOptions` for valid ones."
            )
        else:
            setattr(draw_options, k, v)

    # Setting up the coordinate system by drawing and erasing molecule
    d.DrawMolecule(mol)
    d.ClearDrawing()

    # get the atom indices for the search molecules
    atom_idx_list = []
    if isinstance(search_molecules, str):
        smart_obj = dm.to_mol(search_molecules)
        matches = mol.GetSubstructMatches(smart_obj)
        if not matches:
            logger.warning(f"no matching substructure found for {search_molecules}")
        else:
            matched_atoms = set.union(*[set(x) for x in matches])
            atom_idx_list.append(matched_atoms)

    elif isinstance(search_molecules, dm.Mol):
        matches = mol.GetSubstructMatches(search_molecules)
        if not matches:
            logger.warning(f"no matching substructure found for {dm.to_smiles(search_molecules)}")
        else:
            matched_atoms = set.union(*[set(x) for x in matches])
            atom_idx_list.append(matched_atoms)

    elif len(search_molecules) and isinstance(search_molecules[0], str):
        for smart_str in search_molecules:
            smart_obj = dm.to_mol(smart_str)
            matches = mol.GetSubstructMatches(smart_obj)
            if not matches:
                logger.warning(f"no matching substructure found for {smart_str}")
            else:
                matched_atoms = set.union(*[set(x) for x in matches])
                atom_idx_list.append(matched_atoms)

    elif len(search_molecules) and isinstance(search_molecules[0], dm.Mol):
        for smart_obj in search_molecules:
            matches = mol.GetSubstructMatches(smart_obj)
            if not matches:
                logger.warning(f"no matching substructure found for {dm.to_smiles(smart_obj)}")
            else:
                matched_atoms = set.union(*[set(x) for x in matches])
                atom_idx_list.append(matched_atoms)

    if color_list is None:
        color_list = DEFAULT_LASSO_COLORS

    if len(atom_idx_list) == 0:
        logger.warning("No matches found for the given search molecules")
    else:
        _draw_multi_matches(
            d,
            mol,
            atom_idx_list,
            r_min=r_min,
            r_dist=r_dist,
            relative_bond_width=relative_bond_width,
            line_width=line_width,
            color_list=color_list,
        )

    d.DrawMolecule(mol)
    d.FinishDrawing()

    if "ipykernel" in sys.modules:
        if use_svg:
            return IPythonConsole.SVG(d.GetDrawingText())
        else:
            return Image.open(io.BytesIO(d.GetDrawingText()))
    else:
        return d.GetDrawingText()